From 4c90340ce6c492aca83ede038b523806ff9acdc4 Mon Sep 17 00:00:00 2001 From: Yeseul Jo <68415644+yeseul106@users.noreply.github.com> Date: Thu, 1 Aug 2024 08:23:31 +0900 Subject: [PATCH 01/47] =?UTF-8?q?[FEAT]=20=EB=AA=A8=EC=9E=84=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EA=B8=80=20=EB=8B=A8=EC=9D=BC=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EB=B0=8F=20=EB=AA=A8=EC=9E=84=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EA=B0=9C=EC=88=98=20=EC=A1=B0=ED=9A=8C=20V2=20API=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#275)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FEAT] 모임 게시글 단건 조회 API 마이그레이션 (#230) * [FEAT] 모임 게시글 개수 조회 API 마이그레이션 (#230) * [FEAT] 모임 게시글 개수 조회 API 테스트 코드 작성 (#230) * [CHORE] nest.js swagger 에서 deprecated 처리 (#230) --- .../crew/main/entity/like/LikeRepository.java | 5 +- .../crew/main/entity/post/PostRepository.java | 2 + .../entity/post/PostSearchRepository.java | 3 + .../entity/post/PostSearchRepositoryImpl.java | 47 +++- .../makers/crew/main/post/v2/PostV2Api.java | 71 +++--- .../crew/main/post/v2/PostV2Controller.java | 82 ++++--- .../post/v2/dto/response/PostMeetingDto.java | 6 +- .../PostV2GetPostCountResponseDto.java | 14 ++ .../main/post/v2/service/PostV2Service.java | 12 +- .../post/v2/service/PostV2ServiceImpl.java | 231 ++++++++++-------- .../v2/repository/PostRepositoryTest.java | 15 ++ server/src/post/v1/post-v1.controller.ts | 2 + 12 files changed, 315 insertions(+), 175 deletions(-) create mode 100644 main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2GetPostCountResponseDto.java diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/like/LikeRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/like/LikeRepository.java index 142f20e8..453bccbb 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/like/LikeRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/like/LikeRepository.java @@ -1,10 +1,11 @@ package org.sopt.makers.crew.main.entity.like; import java.util.List; - import org.springframework.data.jpa.repository.JpaRepository; public interface LikeRepository extends JpaRepository { - List findAllByUserIdAndPostIdNotNull(Integer userId); + List findAllByUserIdAndPostIdNotNull(Integer userId); + + boolean existsByUserIdAndPostId(Integer userId, Integer postId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostRepository.java index ef23d79b..d768747a 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostRepository.java @@ -16,4 +16,6 @@ default Post findByIdOrThrow(Integer postId) { } Optional findFirstByMeetingIdOrderByIdDesc(Integer meetingId); + + Integer countByMeetingId(Integer meetingId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepository.java index 9fbf50a6..df3f5db2 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepository.java @@ -1,10 +1,13 @@ package org.sopt.makers.crew.main.entity.post; import org.sopt.makers.crew.main.post.v2.dto.query.PostGetPostsCommand; +import org.sopt.makers.crew.main.post.v2.dto.response.PostDetailBaseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostDetailResponseDto; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; public interface PostSearchRepository { Page findPostList(PostGetPostsCommand queryCommand, Pageable pageable, Integer userId); + + PostDetailBaseDto findPost(Integer userId, Integer postId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepositoryImpl.java b/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepositoryImpl.java index befc4d88..d9781d93 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepositoryImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepositoryImpl.java @@ -41,15 +41,53 @@ public Page findPostList(PostGetPostsCommand queryCommand Integer userId) { Integer meetingId = queryCommand.getMeetingId().orElse(null); - List content = getContent(pageable, meetingId, userId); + List content = getContentList(pageable, meetingId, userId); JPAQuery countQuery = getCount(meetingId); return PageableExecutionUtils.getPage(content, PageRequest.of(pageable.getPageNumber(), pageable.getPageSize()), countQuery::fetchFirst); } - private List getContent(Pageable pageable, Integer meetingId, - Integer userId) { + @Override + public PostDetailBaseDto findPost(Integer userId, Integer postId) { + return queryFactory + .select(new QPostDetailBaseDto( + post.id, + post.title, + post.contents, + post.createdDate, + post.images, + new QPostWriterInfoDto( + post.user.id, + post.user.orgId, + post.user.name, + post.user.profileImage + ), + post.likeCount, + ExpressionUtils.as( + JPAExpressions.selectFrom(like) + .where(like.postId.eq(post.id).and(like.userId.eq(userId))) + .exists() + , "isLiked" + ), + post.viewCount, + post.commentCount, + new QPostMeetingDto( + post.meeting.id, + post.meeting.title, + post.meeting.category, + post.meeting.imageURL + ) + )) + .from(post) + .innerJoin(post.meeting, meeting) + .innerJoin(post.user, user) + .where(post.id.eq(postId)) + .fetchFirst(); + } + + private List getContentList(Pageable pageable, Integer meetingId, + Integer userId) { List responseDtos = new ArrayList<>(); List postDetailList = queryFactory @@ -77,7 +115,8 @@ private List getContent(Pageable pageable, Integer meetin new QPostMeetingDto( post.meeting.id, post.meeting.title, - post.meeting.category + post.meeting.category, + post.meeting.imageURL ) )) .from(post) diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Api.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Api.java index b300d1f8..eb928d7a 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Api.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Api.java @@ -9,48 +9,63 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; - import java.security.Principal; - import org.sopt.makers.crew.main.post.v2.dto.query.PostGetPostsCommand; import org.sopt.makers.crew.main.post.v2.dto.request.PostV2CreatePostBodyDto; import org.sopt.makers.crew.main.post.v2.dto.request.PostV2MentionUserInPostRequestDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostDetailBaseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2CreatePostResponseDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2GetPostCountResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2GetPostsResponseDto; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; @Tag(name = "게시글") public interface PostV2Api { - @Operation(summary = "모임 게시글 작성") - @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "성공"), - @ApiResponse(responseCode = "400", description = "모임이 없습니다.", content = @Content), - @ApiResponse(responseCode = "403", description = "권한이 없습니다.", content = @Content), - }) - ResponseEntity createPost( - @Valid @RequestBody PostV2CreatePostBodyDto requestBody, Principal principal); + @Operation(summary = "모임 게시글 작성") + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "성공"), + @ApiResponse(responseCode = "400", description = "모임이 없습니다.", content = @Content), + @ApiResponse(responseCode = "403", description = "권한이 없습니다.", content = @Content), + }) + ResponseEntity createPost( + @Valid @RequestBody PostV2CreatePostBodyDto requestBody, Principal principal); + + @Operation(summary = "모임 게시글 목록 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공"), + @ApiResponse(responseCode = "400", description = "모임이 없습니다.", content = @Content), + }) + @Parameters({ + @Parameter(name = "page", description = "페이지, default = 1", example = "1", schema = @Schema(type = "integer", format = "int32")), + @Parameter(name = "take", description = "가져올 데이터 개수, default = 12", example = "50", schema = @Schema(type = "integer", format = "int32")), + @Parameter(name = "meetingId", description = "모임 id", example = "0", schema = @Schema(type = "integer", format = "int32"))}) + ResponseEntity getPosts( + @ModelAttribute @Parameter(hidden = true) PostGetPostsCommand queryCommand, + Principal principal); - @Operation(summary = "모임 게시글 목록 조회") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "성공"), - @ApiResponse(responseCode = "400", description = "모임이 없습니다.", content = @Content), - }) - @Parameters({ - @Parameter(name = "page", description = "페이지, default = 1", example = "1", schema = @Schema(type = "integer", format = "int32")), - @Parameter(name = "take", description = "가져올 데이터 개수, default = 12", example = "50", schema = @Schema(type = "integer", format = "int32")), - @Parameter(name = "meetingId", description = "모임 id", example = "0", schema = @Schema(type = "integer", format = "int32"))}) - ResponseEntity getPosts( - @ModelAttribute @Parameter(hidden = true) PostGetPostsCommand queryCommand, - Principal principal); + @Operation(summary = "게시글에서 멘션하기") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공"), + }) + ResponseEntity mentionUserInPost( + @Valid @RequestBody PostV2MentionUserInPostRequestDto requestBody, Principal principal); - @Operation(summary = "게시글에서 멘션하기") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "성공"), - }) - ResponseEntity mentionUserInPost( - @Valid @RequestBody PostV2MentionUserInPostRequestDto requestBody, Principal principal); + @Operation(summary = "모임 게시글 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공"), + @ApiResponse(responseCode = "400", description = "모임이 없습니다", content = @Content) + }) + ResponseEntity getPost(@PathVariable Integer postId, Principal principal); + @Operation(summary = "모임 게시글 개수 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공"), + @ApiResponse(responseCode = "400", description = "모임이 없습니다", content = @Content) + }) + ResponseEntity getPostCount(@RequestParam Integer meetingId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Controller.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Controller.java index b35a7947..10e70bfb 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Controller.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Controller.java @@ -2,25 +2,26 @@ import io.swagger.v3.oas.annotations.Parameter; import jakarta.validation.Valid; - import java.security.Principal; - import lombok.RequiredArgsConstructor; - import org.sopt.makers.crew.main.common.util.UserUtil; import org.sopt.makers.crew.main.post.v2.dto.query.PostGetPostsCommand; import org.sopt.makers.crew.main.post.v2.dto.request.PostV2CreatePostBodyDto; import org.sopt.makers.crew.main.post.v2.dto.request.PostV2MentionUserInPostRequestDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostDetailBaseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2CreatePostResponseDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2GetPostCountResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2GetPostsResponseDto; import org.sopt.makers.crew.main.post.v2.service.PostV2Service; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; 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.ResponseStatus; import org.springframework.web.bind.annotation.RestController; @@ -29,34 +30,49 @@ @RequiredArgsConstructor public class PostV2Controller implements PostV2Api { - private final PostV2Service postV2Service; - - @Override - @PostMapping() - @ResponseStatus(HttpStatus.CREATED) - public ResponseEntity createPost( - @Valid @RequestBody PostV2CreatePostBodyDto requestBody, Principal principal) { - Integer userId = UserUtil.getUserId(principal); - return ResponseEntity.ok(postV2Service.createPost(requestBody, userId)); - } - - @Override - @GetMapping() - @ResponseStatus(HttpStatus.OK) - public ResponseEntity getPosts( - @ModelAttribute @Parameter(hidden = true) PostGetPostsCommand queryCommand, - Principal principal) { - Integer userId = UserUtil.getUserId(principal); - return ResponseEntity.ok(postV2Service.getPosts(queryCommand, userId)); - } - - @Override - @PostMapping("/mention") - @ResponseStatus(HttpStatus.OK) - public ResponseEntity mentionUserInPost( - @Valid @RequestBody PostV2MentionUserInPostRequestDto requestBody, Principal principal) { - Integer userId = UserUtil.getUserId(principal); - postV2Service.mentionUserInPost(requestBody, userId); - return ResponseEntity.status(HttpStatus.OK).build(); - } + private final PostV2Service postV2Service; + + @Override + @PostMapping() + @ResponseStatus(HttpStatus.CREATED) + public ResponseEntity createPost( + @Valid @RequestBody PostV2CreatePostBodyDto requestBody, Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.ok(postV2Service.createPost(requestBody, userId)); + } + + @Override + @GetMapping() + @ResponseStatus(HttpStatus.OK) + public ResponseEntity getPosts( + @ModelAttribute @Parameter(hidden = true) PostGetPostsCommand queryCommand, + Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.ok(postV2Service.getPosts(queryCommand, userId)); + } + + @Override + @PostMapping("/mention") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity mentionUserInPost( + @Valid @RequestBody PostV2MentionUserInPostRequestDto requestBody, Principal principal) { + Integer userId = UserUtil.getUserId(principal); + postV2Service.mentionUserInPost(requestBody, userId); + return ResponseEntity.status(HttpStatus.OK).build(); + } + + @Override + @GetMapping("/{postId}") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity getPost(@PathVariable Integer postId, Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.ok(postV2Service.getPost(userId, postId)); + } + + @Override + @GetMapping("/count") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity getPostCount(@RequestParam Integer meetingId) { + return ResponseEntity.ok(postV2Service.getPostCount(meetingId)); + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostMeetingDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostMeetingDto.java index d22cbd71..fa1819f7 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostMeetingDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostMeetingDto.java @@ -1,19 +1,23 @@ package org.sopt.makers.crew.main.post.v2.dto.response; import com.querydsl.core.annotations.QueryProjection; +import java.util.List; import lombok.Getter; import org.sopt.makers.crew.main.entity.meeting.enums.MeetingCategory; +import org.sopt.makers.crew.main.entity.meeting.vo.ImageUrlVO; @Getter public class PostMeetingDto { private final Integer id; private final String title; private final String category; + private final List imageURL; @QueryProjection - public PostMeetingDto(Integer id, String title, MeetingCategory category) { + public PostMeetingDto(Integer id, String title, MeetingCategory category, List imageURL) { this.id = id; this.title = title; this.category = category.getValue(); + this.imageURL = imageURL; } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2GetPostCountResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2GetPostCountResponseDto.java new file mode 100644 index 00000000..f29c3231 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2GetPostCountResponseDto.java @@ -0,0 +1,14 @@ +package org.sopt.makers.crew.main.post.v2.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor(staticName = "of") +public class PostV2GetPostCountResponseDto { + + /** + * 모임 게시글 개수 + */ + private Integer postCount; +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2Service.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2Service.java index 3a9ff793..75ec68a2 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2Service.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2Service.java @@ -3,14 +3,20 @@ import org.sopt.makers.crew.main.post.v2.dto.query.PostGetPostsCommand; import org.sopt.makers.crew.main.post.v2.dto.request.PostV2CreatePostBodyDto; import org.sopt.makers.crew.main.post.v2.dto.request.PostV2MentionUserInPostRequestDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostDetailBaseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2CreatePostResponseDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2GetPostCountResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2GetPostsResponseDto; public interface PostV2Service { - PostV2CreatePostResponseDto createPost(PostV2CreatePostBodyDto requestBody, Integer userId); + PostV2CreatePostResponseDto createPost(PostV2CreatePostBodyDto requestBody, Integer userId); - PostV2GetPostsResponseDto getPosts(PostGetPostsCommand queryCommand, Integer userId); + PostV2GetPostsResponseDto getPosts(PostGetPostsCommand queryCommand, Integer userId); - void mentionUserInPost(PostV2MentionUserInPostRequestDto requestBody, Integer userId); + PostDetailBaseDto getPost(Integer userId, Integer postId); + + void mentionUserInPost(PostV2MentionUserInPostRequestDto requestBody, Integer userId); + + PostV2GetPostCountResponseDto getPostCount(Integer meetingId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java index d418f840..b23ebbd1 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java @@ -7,9 +7,7 @@ import static org.sopt.makers.crew.main.internal.notification.PushNotificationEnums.PUSH_NOTIFICATION_CATEGORY; import java.util.List; - import lombok.RequiredArgsConstructor; - import org.sopt.makers.crew.main.common.exception.ForbiddenException; import org.sopt.makers.crew.main.common.pagination.dto.PageMetaDto; import org.sopt.makers.crew.main.common.pagination.dto.PageOptionsDto; @@ -27,8 +25,10 @@ import org.sopt.makers.crew.main.post.v2.dto.query.PostGetPostsCommand; import org.sopt.makers.crew.main.post.v2.dto.request.PostV2CreatePostBodyDto; import org.sopt.makers.crew.main.post.v2.dto.request.PostV2MentionUserInPostRequestDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostDetailBaseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostDetailResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2CreatePostResponseDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2GetPostCountResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2GetPostsResponseDto; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; @@ -41,106 +41,129 @@ @Transactional(readOnly = true) public class PostV2ServiceImpl implements PostV2Service { - private final MeetingRepository meetingRepository; - private final UserRepository userRepository; - private final PostRepository postRepository; - private final ApplyRepository applyRepository; - private final PushNotificationService pushNotificationService; - - @Value("${push-notification.web-url}") - private String pushWebUrl; - - /** - * 모임 게시글 작성 - * - * @throws 403 모임에 속한 유저가 아닌 경우 - * @apiNote 모임에 속한 유저만 작성 가능 - */ - @Override - @Transactional - public PostV2CreatePostResponseDto createPost(PostV2CreatePostBodyDto requestBody, - Integer userId) { - Meeting meeting = meetingRepository.findByIdOrThrow(requestBody.getMeetingId()); - User user = userRepository.findByIdOrThrow(userId); - - List applies = applyRepository.findAllByMeetingId(meeting.getId()); - - boolean isInMeeting = applies.stream() - .anyMatch(apply -> apply.getUserId().equals(userId) - && apply.getStatus().equals(EnApplyStatus.APPROVE)); - - boolean isMeetingCreator = meeting.getUserId().equals(userId); - - if (isInMeeting == false && isMeetingCreator == false) { - throw new ForbiddenException(FORBIDDEN_EXCEPTION.getErrorCode()); - } - - Post post = Post.builder() - .title(requestBody.getTitle()) - .user(user) - .contents(requestBody.getContents()) - .images(requestBody.getImages()) - .meeting(meeting) - .build(); - - Post savedPost = postRepository.save(post); - - List userIdList = applyRepository.findAllByMeetingIdAndStatus(meeting.getId(), - EnApplyStatus.APPROVE) - .stream() - .map(apply -> String.valueOf(apply.getUser().getOrgId())) - .collect(toList()); - - String[] userIds = userIdList.toArray(new String[0]); - String pushNotificationContent = String.format("[%s의 새 글] : \"%s\"", - user.getName(), post.getTitle()); - String pushNotificationWeblink = pushWebUrl + "/detail?id=" + meeting.getId(); - - PushNotificationRequestDto pushRequestDto = PushNotificationRequestDto.of(userIds, - NEW_POST_PUSH_NOTIFICATION_TITLE.getValue(), - pushNotificationContent, - PUSH_NOTIFICATION_CATEGORY.getValue(), pushNotificationWeblink); - - pushNotificationService.sendPushNotification(pushRequestDto); - - return PostV2CreatePostResponseDto.of(savedPost.getId()); - } - - @Override - @Transactional(readOnly = true) - public PostV2GetPostsResponseDto getPosts(PostGetPostsCommand queryCommand, Integer userId) { - Page meetingPostListDtos = postRepository.findPostList(queryCommand, - PageRequest.of(queryCommand.getPage() - 1, queryCommand.getTake()), userId); - - PageOptionsDto pageOptionsDto = new PageOptionsDto(queryCommand.getPage(), - queryCommand.getTake()); - PageMetaDto pageMetaDto = new PageMetaDto(pageOptionsDto, - (int)meetingPostListDtos.getTotalElements()); - - return PostV2GetPostsResponseDto.of(meetingPostListDtos.getContent(), pageMetaDto); - } - - @Override - public void mentionUserInPost(PostV2MentionUserInPostRequestDto requestBody, Integer userId) { - User user = userRepository.findByIdOrThrow(userId); - Post post = postRepository.findByIdOrThrow(requestBody.getPostId()); - - String pushNotificationContent = String.format("[%s의 글] : \"%s\"", - user.getName(), post.getTitle()); - String pushNotificationWeblink = pushWebUrl + "/post?id=" + post.getId(); - - String[] userOrgIds = requestBody.getUserIds().stream() - .map(Object::toString) - .toArray(String[]::new); - - PushNotificationRequestDto pushRequestDto = PushNotificationRequestDto.of( - userOrgIds, - NEW_POST_MENTION_PUSH_NOTIFICATION_TITLE.getValue(), - pushNotificationContent, - PUSH_NOTIFICATION_CATEGORY.getValue(), - pushNotificationWeblink - ); - - pushNotificationService.sendPushNotification(pushRequestDto); - } + private final MeetingRepository meetingRepository; + private final UserRepository userRepository; + private final PostRepository postRepository; + private final ApplyRepository applyRepository; + private final PushNotificationService pushNotificationService; + + @Value("${push-notification.web-url}") + private String pushWebUrl; + + /** + * 모임 게시글 작성 + * + * @throws 403 모임에 속한 유저가 아닌 경우 + * @apiNote 모임에 속한 유저만 작성 가능 + */ + @Override + @Transactional + public PostV2CreatePostResponseDto createPost(PostV2CreatePostBodyDto requestBody, + Integer userId) { + Meeting meeting = meetingRepository.findByIdOrThrow(requestBody.getMeetingId()); + User user = userRepository.findByIdOrThrow(userId); + + List applies = applyRepository.findAllByMeetingId(meeting.getId()); + + boolean isInMeeting = applies.stream() + .anyMatch(apply -> apply.getUserId().equals(userId) + && apply.getStatus().equals(EnApplyStatus.APPROVE)); + + boolean isMeetingCreator = meeting.getUserId().equals(userId); + + if (isInMeeting == false && isMeetingCreator == false) { + throw new ForbiddenException(FORBIDDEN_EXCEPTION.getErrorCode()); + } + + Post post = Post.builder() + .title(requestBody.getTitle()) + .user(user) + .contents(requestBody.getContents()) + .images(requestBody.getImages()) + .meeting(meeting) + .build(); + + Post savedPost = postRepository.save(post); + + List userIdList = applyRepository.findAllByMeetingIdAndStatus(meeting.getId(), + EnApplyStatus.APPROVE) + .stream() + .map(apply -> String.valueOf(apply.getUser().getOrgId())) + .collect(toList()); + + String[] userIds = userIdList.toArray(new String[0]); + String pushNotificationContent = String.format("[%s의 새 글] : \"%s\"", + user.getName(), post.getTitle()); + String pushNotificationWeblink = pushWebUrl + "/detail?id=" + meeting.getId(); + + PushNotificationRequestDto pushRequestDto = PushNotificationRequestDto.of(userIds, + NEW_POST_PUSH_NOTIFICATION_TITLE.getValue(), + pushNotificationContent, + PUSH_NOTIFICATION_CATEGORY.getValue(), pushNotificationWeblink); + + pushNotificationService.sendPushNotification(pushRequestDto); + + return PostV2CreatePostResponseDto.of(savedPost.getId()); + } + + @Override + @Transactional(readOnly = true) + public PostV2GetPostsResponseDto getPosts(PostGetPostsCommand queryCommand, Integer userId) { + Page meetingPostListDtos = postRepository.findPostList(queryCommand, + PageRequest.of(queryCommand.getPage() - 1, queryCommand.getTake()), userId); + + PageOptionsDto pageOptionsDto = new PageOptionsDto(queryCommand.getPage(), + queryCommand.getTake()); + PageMetaDto pageMetaDto = new PageMetaDto(pageOptionsDto, + (int) meetingPostListDtos.getTotalElements()); + + return PostV2GetPostsResponseDto.of(meetingPostListDtos.getContent(), pageMetaDto); + } + + /** + * 모임 게시글 단건 조회 + * + * @throws 400 + * @apiNote 모임에 속한 유저만 작성 가능 + */ + @Override + @Transactional(readOnly = true) + public PostDetailBaseDto getPost(Integer userId, Integer postId) { + return postRepository.findPost(userId, postId); + } + + @Override + public void mentionUserInPost(PostV2MentionUserInPostRequestDto requestBody, Integer userId) { + User user = userRepository.findByIdOrThrow(userId); + Post post = postRepository.findByIdOrThrow(requestBody.getPostId()); + + String pushNotificationContent = String.format("[%s의 글] : \"%s\"", + user.getName(), post.getTitle()); + String pushNotificationWeblink = pushWebUrl + "/post?id=" + post.getId(); + + String[] userOrgIds = requestBody.getUserIds().stream() + .map(Object::toString) + .toArray(String[]::new); + + PushNotificationRequestDto pushRequestDto = PushNotificationRequestDto.of( + userOrgIds, + NEW_POST_MENTION_PUSH_NOTIFICATION_TITLE.getValue(), + pushNotificationContent, + PUSH_NOTIFICATION_CATEGORY.getValue(), + pushNotificationWeblink + ); + + pushNotificationService.sendPushNotification(pushRequestDto); + } + + /** + * 모임 게시글 개수 조회 + * + * @apiNote 모든 유저가 조회 가능 + */ + @Override + @Transactional(readOnly = true) + public PostV2GetPostCountResponseDto getPostCount(Integer meetingId) { + return PostV2GetPostCountResponseDto.of(postRepository.countByMeetingId(meetingId)); + } } diff --git a/main/src/test/java/org/sopt/makers/crew/main/post/v2/repository/PostRepositoryTest.java b/main/src/test/java/org/sopt/makers/crew/main/post/v2/repository/PostRepositoryTest.java index 936d44a0..b677c9fa 100644 --- a/main/src/test/java/org/sopt/makers/crew/main/post/v2/repository/PostRepositoryTest.java +++ b/main/src/test/java/org/sopt/makers/crew/main/post/v2/repository/PostRepositoryTest.java @@ -128,4 +128,19 @@ public class PostRepositoryTest { .containsExactly(1, "스터디 구합니다1", "행사"); } + @Test + void 게시글_개수_조회() { + // given + int meetingId1 = 1; + int meetingId2 = 2; + + // when + Integer postCount1 = postRepository.countByMeetingId(meetingId1); + Integer postCount2 = postRepository.countByMeetingId(meetingId2); + + // then + assertThat(postCount1).isEqualTo(3); + assertThat(postCount2).isEqualTo(2); + } + } diff --git a/server/src/post/v1/post-v1.controller.ts b/server/src/post/v1/post-v1.controller.ts index dca431f6..a94d7dae 100644 --- a/server/src/post/v1/post-v1.controller.ts +++ b/server/src/post/v1/post-v1.controller.ts @@ -47,6 +47,7 @@ export class PostV1Controller { @ApiOperation({ summary: '모임 게시글 개수 조회', + deprecated: true, }) @ApiOkResponseCommon(PostV1GetPostCountResponseDto) @ApiResponse({ @@ -83,6 +84,7 @@ export class PostV1Controller { @ApiOperation({ summary: '모임 게시글 조회', + deprecated: true, }) @ApiOkResponseCommon(PostV1GetPostResponseDto) @ApiResponse({ From 899156b74963a3ef7df5cbb45e02efecf263688e Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Thu, 1 Aug 2024 10:18:30 +0900 Subject: [PATCH 02/47] =?UTF-8?q?[DOCS]=20=EC=8A=A4=EC=9B=A8=EA=B1=B0=20?= =?UTF-8?q?=EB=AC=B8=EC=84=9C=20=EC=9E=91=EC=84=B1=20(#277)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../query/CommentV2GetCommentsQueryDto.java | 3 +++ .../CommentV2CreateCommentBodyDto.java | 12 +++++---- ...mmentV2MentionUserInCommentRequestDto.java | 2 +- .../comment/v2/dto/response/CommentDto.java | 25 +++++++++++++++-- .../CommentV2CreateCommentResponseDto.java | 3 +++ .../CommentV2GetCommentsResponseDto.java | 5 ++++ .../CommentV2ReportCommentResponseDto.java | 3 +++ .../CommentV2UpdateCommentResponseDto.java | 5 ++++ .../v2/dto/response/CommentWriterDto.java | 10 +++++++ .../v2/service/CommentV2ServiceImpl.java | 2 +- .../MeetingV2CreateMeetingBodyDto.java | 2 ++ .../meeting/v2/dto/response/ApplicantDto.java | 15 +++++++++++ .../meeting/v2/dto/response/ApplyInfoDto.java | 13 +++++++++ .../MeetingGetApplyListResponseDto.java | 5 ++++ .../MeetingV2ApplyMeetingResponseDto.java | 4 +++ .../MeetingV2CreateMeetingResponseDto.java | 4 +++ .../MeetingV2GetAllMeetingByOrgUserDto.java | 4 +++ ...ingV2GetAllMeetingByOrgUserMeetingDto.java | 20 ++++++++++++++ .../MeetingV2GetMeetingBannerResponseDto.java | 20 ++++++++++++++ ...tingV2GetMeetingBannerResponseUserDto.java | 6 +++++ .../v2/dto/query/PostGetPostsCommand.java | 4 +++ .../v2/dto/response/PostDetailBaseDto.java | 25 +++++++++++++++++ .../dto/response/PostDetailResponseDto.java | 27 +++++++++++++++++++ .../post/v2/dto/response/PostMeetingDto.java | 10 +++++++ .../response/PostV2CreatePostResponseDto.java | 3 +++ .../response/PostV2GetPostsResponseDto.java | 5 ++++ .../v2/dto/response/PostWriterInfoDto.java | 11 ++++++++ .../UserV2GetAllMeetingByUserMeetingDto.java | 21 +++++++++++---- .../response/UserV2GetAllMentionUserDto.java | 10 +++++++ 29 files changed, 265 insertions(+), 14 deletions(-) diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/query/CommentV2GetCommentsQueryDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/query/CommentV2GetCommentsQueryDto.java index a5602c51..13eb435d 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/query/CommentV2GetCommentsQueryDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/query/CommentV2GetCommentsQueryDto.java @@ -2,14 +2,17 @@ import org.sopt.makers.crew.main.common.pagination.dto.PageOptionsDto; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; import lombok.Builder; import lombok.Getter; @Getter +@Schema(name = "CommentV2GetCommentsQueryDto", description = "댓글 목록 요청 Dto") public class CommentV2GetCommentsQueryDto extends PageOptionsDto { @NotNull + @Schema(description = "게시글 id", example = "1") private final Integer postId; @Builder diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/request/CommentV2CreateCommentBodyDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/request/CommentV2CreateCommentBodyDto.java index bdc26c45..a25e1435 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/request/CommentV2CreateCommentBodyDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/request/CommentV2CreateCommentBodyDto.java @@ -1,5 +1,7 @@ package org.sopt.makers.crew.main.comment.v2.dto.request; +import com.fasterxml.jackson.annotation.JsonProperty; + import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; @@ -11,18 +13,18 @@ @Schema(description = "댓글 생성 request body dto") public class CommentV2CreateCommentBodyDto { - @Schema(example = "1", required = true, description = "게시글 ID") + @Schema(example = "1", description = "게시글 ID") @NotNull private Integer postId; - @Schema(example = "알고보면 쓸데있는 개발 프로세스", required = true, description = "댓글 내용") + @Schema(example = "알고보면 쓸데있는 개발 프로세스", description = "댓글 내용") @NotEmpty private String contents; - @Schema(example = "댓글/대댓글 여부", required = true, description = "true") - private boolean isParent; + @Schema(example = "true", description = "댓글/대댓글 여부") + private Boolean isParent; - @Schema(example = "대댓글인 경우, 댓글의 id", required = true, description = "1") + @Schema(example = "3", description = "대댓글인 경우, 댓글의 id") private Integer parentCommentId; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/request/CommentV2MentionUserInCommentRequestDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/request/CommentV2MentionUserInCommentRequestDto.java index 5d059ef6..d59a8daa 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/request/CommentV2MentionUserInCommentRequestDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/request/CommentV2MentionUserInCommentRequestDto.java @@ -18,7 +18,7 @@ public class CommentV2MentionUserInCommentRequestDto { * 주의!! : 필드명은 userIds 이지만 실제 요청받는 값은 orgId 입니다. */ - @Schema(example = "[111, 112, 113]", required = true, description = "언급할 유저 ID") + @Schema(example = "[111, 112, 113]", required = true, description = "언급할 유저 ID, orgId 의미") @NotEmpty private List userIds; diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java index 945b5a2c..095ed77e 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java @@ -5,20 +5,41 @@ import org.sopt.makers.crew.main.entity.comment.Comment; +import com.fasterxml.jackson.annotation.JsonProperty; import com.querydsl.core.annotations.QueryProjection; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; @Getter +@Schema(name = "CommentDto", description = "댓글 객체 응답 Dto") public class CommentDto { + + @Schema(description = "댓글 id", example = "1") private final Integer id; + + @Schema(description = "댓글 내용", example = "이것은 댓글 내용입니다.") private final String contents; + + @Schema(description = "댓글 작성자 객체", example = "") private final CommentWriterDto user; + + @Schema(description = "댓글 업데이트 시간", example = "2024-07-31T15:30:00") private final LocalDateTime updatedDate; + + @Schema(description = "좋아요 갯수", example = "20") private final int likeCount; - private final boolean isLiked; - private final boolean isWriter; + + @Schema(description = "댓글 좋아요 여부", example = "true") + private final Boolean isLiked; + + @Schema(description = "댓글 작성자 여부", example = "true") + private final Boolean isWriter; + + @Schema(description = "댓글 순서", example = "2") private final int order; + + @Schema(description = "대댓글 객체 목록", example = "") private final List replies; @QueryProjection diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2CreateCommentResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2CreateCommentResponseDto.java index d40b12f8..f96ca68f 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2CreateCommentResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2CreateCommentResponseDto.java @@ -1,14 +1,17 @@ package org.sopt.makers.crew.main.comment.v2.dto.response; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor(staticName = "of") +@Schema(name = "CommentV2CreateCommentResponseDto", description = "댓글 생성 응답 Dto") public class CommentV2CreateCommentResponseDto { /** * 생성된 댓글 id */ + @Schema(description = "생성된 댓글 id", example = "1") private Integer commentId; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2GetCommentsResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2GetCommentsResponseDto.java index de6c9dfb..93d4c253 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2GetCommentsResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2GetCommentsResponseDto.java @@ -2,11 +2,16 @@ import java.util.List; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor(staticName = "of") +@Schema(name = "CommentV2GetCommentsResponseDto", description = "댓글 목록 조회 응답 Dto") public class CommentV2GetCommentsResponseDto { + + @Schema(description = "댓글 목록", example = "") private final List comments; + } diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2ReportCommentResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2ReportCommentResponseDto.java index 03340a2c..a7021905 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2ReportCommentResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2ReportCommentResponseDto.java @@ -1,14 +1,17 @@ package org.sopt.makers.crew.main.comment.v2.dto.response; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor(staticName = "of") +@Schema(name = "CommentV2ReportCommentResponseDto", description = "댓글 신고 응답 Dto") public class CommentV2ReportCommentResponseDto { /** * 생성된 신고 id */ + @Schema(description = "신고 id", example = "1") private Integer reportId; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2UpdateCommentResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2UpdateCommentResponseDto.java index fe7de072..e1a11bb0 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2UpdateCommentResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2UpdateCommentResponseDto.java @@ -1,24 +1,29 @@ package org.sopt.makers.crew.main.comment.v2.dto.response; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor(staticName = "of") +@Schema(name = "CommentV2UpdateCommentResponseDto", description = "댓글 수정 응답 Dto") public class CommentV2UpdateCommentResponseDto { /** * 생성된 댓글 id */ + @Schema(description = "수정된 댓글 id", example = "1") private Integer id; /** * 댓글 내용 */ + @Schema(description = "수정된 댓글 내용", example = "댓글내용입니다1") private String contents; /** * 업데이트 시각 */ + @Schema(description = "수정된 시간", example = "2024-07-31T15:30:00") private String updateDate; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentWriterDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentWriterDto.java index af69fa40..138952ac 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentWriterDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentWriterDto.java @@ -2,13 +2,23 @@ import com.querydsl.core.annotations.QueryProjection; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; @Getter +@Schema(name = "CommentWriterDto", description = "댓글 작성자 객체 Dto") public class CommentWriterDto { + + @Schema(description = "댓글 id", example = "1") private final Integer id; + + @Schema(description = "댓글 org id", example = "2") private final Integer orgId; + + @Schema(description = "댓글 작성자 이름", example = "홍길동") private final String name; + + @Schema(description = "댓글 작성자 프로필 사진", example = "[url] 형식") private final String profileImage; @QueryProjection diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java index 6008f8b4..d1142e84 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java @@ -79,7 +79,7 @@ public CommentV2CreateCommentResponseDto createComment( int order = 0; Integer parentId = 0; - boolean isReplyComment = !requestBody.isParent(); + boolean isReplyComment = !requestBody.getIsParent(); if (isReplyComment) { validateParentCommentId(requestBody); depth = 1; diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/request/MeetingV2CreateMeetingBodyDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/request/MeetingV2CreateMeetingBodyDto.java index dfb14240..26e53643 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/request/MeetingV2CreateMeetingBodyDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/request/MeetingV2CreateMeetingBodyDto.java @@ -9,6 +9,8 @@ import lombok.Getter; import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart; +import com.fasterxml.jackson.annotation.JsonProperty; + @Getter @AllArgsConstructor @Schema(description = "모임 생성 request body dto") diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplicantDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplicantDto.java index 4eb516a3..44e6d0db 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplicantDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplicantDto.java @@ -3,17 +3,32 @@ import com.querydsl.core.annotations.QueryProjection; import java.util.Comparator; import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import org.sopt.makers.crew.main.common.util.UserUtil; import org.sopt.makers.crew.main.entity.user.vo.UserActivityVO; @Getter +@Schema(name = "ApplicantDto", description = "모임 신청자 객체 Dto") public class ApplicantDto { + + @Schema(description = "신청 id", example = "1") private final Integer id; + + @Schema(description = "신청자 이름", example = "송민규") private final String name; + + @Schema(description = "신청자 org id", example = "1") private final Integer orgId; + + @Schema(description = "신청자 기수 정보", example = "[{\"part\": \"웹\", \"generation\": 32}]") private final UserActivityVO recentActivity; + + @Schema(description = "신청자 프로필 사진", example = "[url] 형식") private final String profileImage; + + @Schema(description = "신청자 핸드폰 번호", example = "010-1234-5678") private final String phone; @QueryProjection diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplyInfoDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplyInfoDto.java index 23148265..0300437f 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplyInfoDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplyInfoDto.java @@ -2,15 +2,28 @@ import com.querydsl.core.annotations.QueryProjection; import java.time.LocalDateTime; + +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import org.sopt.makers.crew.main.entity.apply.enums.EnApplyStatus; @Getter +@Schema(name = "ApplyInfoDto", description = "모임 신청 객체 Dto") public class ApplyInfoDto { + + @Schema(description = "신청 id", example = "1") private final Integer id; + + @Schema(description = "전하는 말", example = "저 뽑아주세요.") private final String content; + + @Schema(description = "신청 시간", example = "2024-07-30T15:30:00") private final LocalDateTime appliedDate; + + @Schema(description = "신청 상태", example = "1") private final EnApplyStatus status; + + @Schema(description = "신청자 정보", example = "") private final ApplicantDto user; @QueryProjection diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingGetApplyListResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingGetApplyListResponseDto.java index f1e85883..83911779 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingGetApplyListResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingGetApplyListResponseDto.java @@ -1,14 +1,19 @@ package org.sopt.makers.crew.main.meeting.v2.dto.response; import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; import org.sopt.makers.crew.main.common.pagination.dto.PageMetaDto; @Getter @AllArgsConstructor(staticName = "of") +@Schema(name = "MeetingGetApplyListResponseDto", description = "모임 신청 목록 응답 Dto") public class MeetingGetApplyListResponseDto { + @Schema(description = "신청 목록", example = "") private final List apply; + private final PageMetaDto meta; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2ApplyMeetingResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2ApplyMeetingResponseDto.java index 70ee5a62..4588f545 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2ApplyMeetingResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2ApplyMeetingResponseDto.java @@ -1,10 +1,14 @@ package org.sopt.makers.crew.main.meeting.v2.dto.response; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor(staticName = "of") +@Schema(name = "MeetingV2ApplyMeetingResponseDto", description = "모임 신청 응답 Dto") public class MeetingV2ApplyMeetingResponseDto { + + @Schema(description = "신청 id", example = "1") private Integer applyId; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2CreateMeetingResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2CreateMeetingResponseDto.java index 40015dee..cddc9476 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2CreateMeetingResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2CreateMeetingResponseDto.java @@ -1,10 +1,14 @@ package org.sopt.makers.crew.main.meeting.v2.dto.response; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor(staticName = "of") +@Schema(name = "MeetingV2CreateMeetingResponseDto", description = "모임 생성 응답 Dto") public class MeetingV2CreateMeetingResponseDto { + + @Schema(description = "모임 id", example = "1") private Integer meetingId; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetAllMeetingByOrgUserDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetAllMeetingByOrgUserDto.java index 17f509a2..6c7515d4 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetAllMeetingByOrgUserDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetAllMeetingByOrgUserDto.java @@ -1,14 +1,18 @@ package org.sopt.makers.crew.main.meeting.v2.dto.response; import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; import org.sopt.makers.crew.main.common.pagination.dto.PageMetaDto; @Getter @AllArgsConstructor(staticName = "of") +@Schema(name = "MeetingV2GetAllMeetingByOrgUserDto", description = "모임 조회 응답 Dto") public class MeetingV2GetAllMeetingByOrgUserDto { + @Schema(description = "모임 객체 목록", example = "") private List meetings; private PageMetaDto meta; diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetAllMeetingByOrgUserMeetingDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetAllMeetingByOrgUserMeetingDto.java index 3ea3c420..0d99c872 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetAllMeetingByOrgUserMeetingDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetAllMeetingByOrgUserMeetingDto.java @@ -1,19 +1,39 @@ package org.sopt.makers.crew.main.meeting.v2.dto.response; import java.time.LocalDateTime; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor(staticName = "of") +@Schema(name = "MeetingV2GetAllMeetingByOrgUserMeetingDto", description = "모임 객체 Dto") public class MeetingV2GetAllMeetingByOrgUserMeetingDto { + @Schema(description = "모임 id", example = "1") private Integer id; + + @Schema(description = "모임장 여부", example = "true") private Boolean isMeetingLeader; + + @Schema(description = "모임 제목", example = "모임 제목입니다1") private String title; + + @Schema(description = "모임 사진", example = "[url] 형식") private String imageUrl; + + @Schema(description = "모임 분류", example = "스터디") private String category; + + @Schema(description = "활동 시작 날짜", example = "2024-07-31T15:30:00") private LocalDateTime mStartDate; + + @Schema(description = "활동 종료 날짜", example = "2024-08-15T15:30:00") private LocalDateTime mEndDate; + + @Schema(description = "모임 활성 여부", example = "true") private Boolean isActiveMeeting; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingBannerResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingBannerResponseDto.java index 3e8d647a..bd758867 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingBannerResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingBannerResponseDto.java @@ -6,53 +6,73 @@ import org.sopt.makers.crew.main.entity.meeting.enums.MeetingCategory; import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart; import org.sopt.makers.crew.main.entity.meeting.vo.ImageUrlVO; + +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor(staticName = "of") +@Schema(name = "MeetingV2GetMeetingBannerResponseDto", description = "모임 배너 응답 Dto") public class MeetingV2GetMeetingBannerResponseDto { /** 모임 ID */ + @Schema(description = "모임 id", example = "1") private Integer id; /** 유저 Crew ID */ + @Schema(description = "유저 id", example = "1") private Integer userId; /** 모임 제목 */ + @Schema(description = "모임 제목", example = "모임 제목입니다1") private String title; /** * 모임 카테고리 * * @apiNote '스터디', '행사' */ + @Schema(description = "모임 카테고리", example = "스터디") private MeetingCategory category; /** * 썸네일 이미지 * * @apiNote 여러개여도 첫번째 이미지만 사용 */ + @Schema(description = "모임 사진", example = "[url] 형식") private List imageURL; /** 모임 활동 시작일 */ + @Schema(description = "모임 활동 시작일", example = "2024-07-31T15:30:00") private LocalDateTime mStartDate; /** 모임 활동 종료일 */ + @Schema(description = "모임 활동 종료일", example = "2024-08-25T15:30:00") private LocalDateTime mEndDate; /** 모임 모집 시작일 */ + @Schema(description = "모임 모집 시작일", example = "2024-06-11T15:30:00") private LocalDateTime startDate; /** 모임 모집 종료일 */ + @Schema(description = "모임 모집 종료일", example = "2024-06-17T15:30:00") private LocalDateTime endDate; /** 모임 인원 */ + @Schema(description = "모집 인원", example = "20") private Integer capacity; /** 최근 활동 일자 */ + @Schema(description = "최근 활동 일자", example = "2024-06-11T15:30:00") private Optional recentActivityDate; /** 모임 타겟 기수 */ + @Schema(description = "모임 타겟 기수", example = "33") private Integer targetActiveGeneration; /** 모임 타겟 파트 */ + @Schema(description = "모임 타겟 파트", example = "[\"PM\", \"SERVER\"]") private MeetingJoinablePart[] joinableParts; /** 지원자 수 */ + @Schema(description = "지원자 수", example = "50") private Integer applicantCount; /** 가입된 지원자 수 */ + @Schema(description = "가입된 지원자 수", example = "9") private Integer approvedUserCount; /** 개설자 정보 */ + @Schema(description = "모임장 정보", example = "") private MeetingV2GetMeetingBannerResponseUserDto user; /** 미팅 상태 */ + @Schema(description = "모임 상태", example = "1") private Integer status; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingBannerResponseUserDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingBannerResponseUserDto.java index 712fad0c..c1c668d5 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingBannerResponseUserDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingBannerResponseUserDto.java @@ -1,17 +1,23 @@ package org.sopt.makers.crew.main.meeting.v2.dto.response; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor(staticName = "of") +@Schema(name = "MeetingV2GetMeetingBannerResponseUserDto", description = "모임 배너 유저 Dto") public class MeetingV2GetMeetingBannerResponseUserDto { /** 개설자 crew ID */ + @Schema(description = "모임장 id", example = "1") private Integer id; /** 개설자 */ + @Schema(description = "모임장 이름", example = "홍길동") private String name; /** 개설자 playground ID */ + @Schema(description = "모임장 org id", example = "1") private Integer orgId; /** 프로필 사진 */ + @Schema(description = "모임장 프로필 사진", example = "[url] 형식") private String profileImage; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/query/PostGetPostsCommand.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/query/PostGetPostsCommand.java index e84ef927..cf516017 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/query/PostGetPostsCommand.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/query/PostGetPostsCommand.java @@ -1,12 +1,16 @@ package org.sopt.makers.crew.main.post.v2.dto.query; import java.util.Optional; + +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import org.sopt.makers.crew.main.common.pagination.dto.PageOptionsDto; @Getter +@Schema(name = "PostGetPostsCommand", description = "게시글 조회 요청 Dto") public class PostGetPostsCommand extends PageOptionsDto { + @Schema(description = "모임 id", example = "1") private Optional meetingId; public PostGetPostsCommand(Integer meetingId, Integer page, Integer take) { diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailBaseDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailBaseDto.java index 927fe64b..65f72cfe 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailBaseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailBaseDto.java @@ -1,22 +1,47 @@ package org.sopt.makers.crew.main.post.v2.dto.response; +import com.fasterxml.jackson.annotation.JsonProperty; import com.querydsl.core.annotations.QueryProjection; import java.time.LocalDateTime; + +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; @Getter +@Schema(name = "PostDetailBaseDto", description = "게시글 객체 Dto") public class PostDetailBaseDto { + + @Schema(description = "게시글 id", example = "1") private final Integer id; + + @Schema(description = "게시글 제목", example = "게시글 제목입니다.") private final String title; + + @Schema(description = "게시글 내용", example = "게시글 내용입니다.") private final String contents; + + @Schema(description = "게시글 생성일자", example = "2024-07-31T15:30:00") private final LocalDateTime createdDate; + + @Schema(description = "게시글 이미지", example = "[\"url1\", \"url2\"]") private final String[] images; + + @Schema(description = "게시글 작성자 객체", example = "") private final PostWriterInfoDto user; + + @Schema(description = "게시글 좋아요 갯수", example = "20") private final int likeCount; //* 본인이 좋아요를 눌렀는지 여부 + @Schema(description = "게시글 좋아요 여부", example = "true") private final Boolean isLiked; + + @Schema(description = "게시글 조회수", example = "30") private final int viewCount; + + @Schema(description = "게시글 댓글 수", example = "5") private final int commentCount; + + @Schema(description = "게시글에 대한 모임", example = "") private final PostMeetingDto meeting; @QueryProjection diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailResponseDto.java index 21061cc1..e3449f2a 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailResponseDto.java @@ -2,23 +2,50 @@ import java.time.LocalDateTime; import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor(staticName = "of") +@Schema(name = "PostDetailResponseDto", description = "게시글 객체 Dto") public class PostDetailResponseDto { + + @Schema(description = "게시글 id", example = "1") private final Integer id; + + @Schema(description = "게시글 제목", example = "게시글 제목입니다.") private final String title; + + @Schema(description = "게시글 내용", example = "게시글 내용입니다.") private final String contents; + + @Schema(description = "게시글 생성 일자", example = "2024-05-31T15:30:00") private final LocalDateTime createdDate; + + @Schema(description = "게시글 이미지 목록", example = "[\"url1\", \"url2\"]") private final String[] images; + + @Schema(description = "게시글 생성 유저 객체", example = "") private final PostWriterInfoDto user; + + @Schema(description = "게시글 좋아요 수", example = "20") private final int likeCount; + + @Schema(description = "게시글 좋아요 여부", example = "true") private final Boolean isLiked; + + @Schema(description = "게시글 조회수", example = "200") private final int viewCount; + + @Schema(description = "게시글 댓글 수", example = "30") private final int commentCount; + + @Schema(description = "게시글에 해당하는 모임", example = "") private final PostMeetingDto meeting; + + @Schema(description = "댓글 작성자 썸네일 목록", example = "[\"url1\", \"url2\"]") private final List commenterThumbnails; public static PostDetailResponseDto of(PostDetailBaseDto postDetail, diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostMeetingDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostMeetingDto.java index fa1819f7..ab75fc71 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostMeetingDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostMeetingDto.java @@ -1,15 +1,25 @@ package org.sopt.makers.crew.main.post.v2.dto.response; import com.querydsl.core.annotations.QueryProjection; + +import io.swagger.v3.oas.annotations.media.Schema; + import java.util.List; import lombok.Getter; import org.sopt.makers.crew.main.entity.meeting.enums.MeetingCategory; import org.sopt.makers.crew.main.entity.meeting.vo.ImageUrlVO; @Getter +@Schema(name = "PostMeetingDto", description = "게시글에 대한 모임 Dto") public class PostMeetingDto { + + @Schema(description = "모임 id", example = "1") private final Integer id; + + @Schema(description = "게시글 제목", example = "게시글 제목입니다.") private final String title; + + @Schema(description = "게시글 카테고리", example = "스터디") private final String category; private final List imageURL; diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2CreatePostResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2CreatePostResponseDto.java index 807c611c..cc2a53ec 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2CreatePostResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2CreatePostResponseDto.java @@ -1,15 +1,18 @@ package org.sopt.makers.crew.main.post.v2.dto.response; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor(staticName = "of") +@Schema(name = "PostV2CreatePostResponseDto", description = "게시글 생성 응답 Dto") public class PostV2CreatePostResponseDto { /** * 생성된 게시물 id */ + @Schema(description = "게시글 id", example = "1") private Integer postId; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2GetPostsResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2GetPostsResponseDto.java index e414007b..7b1ecae0 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2GetPostsResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2GetPostsResponseDto.java @@ -1,14 +1,19 @@ package org.sopt.makers.crew.main.post.v2.dto.response; import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; import org.sopt.makers.crew.main.common.pagination.dto.PageMetaDto; @Getter @AllArgsConstructor(staticName = "of") +@Schema(name = "PostV2GetPostsResponseDto", description = "게시글 조회 응답 Dto") public class PostV2GetPostsResponseDto { + @Schema(description = "게시글 객체", example = "") private final List posts; + private final PageMetaDto meta; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostWriterInfoDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostWriterInfoDto.java index 84a6834a..1723c5cd 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostWriterInfoDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostWriterInfoDto.java @@ -1,13 +1,24 @@ package org.sopt.makers.crew.main.post.v2.dto.response; import com.querydsl.core.annotations.QueryProjection; + +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; @Getter +@Schema(name = "PostWriterInfoDto", description = "게시글 작성자 Dto") public class PostWriterInfoDto { + + @Schema(description = "유저 id", example = "1") private final Integer id; + + @Schema(description = "유저 org id", example = "1") private final Integer orgId; + + @Schema(description = "유저 이름", example = "홍길동") private final String name; + + @Schema(description = "유저 프로필 사진", example = "[url] 형식") private final String profileImage; @QueryProjection diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetAllMeetingByUserMeetingDto.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetAllMeetingByUserMeetingDto.java index e3f79a65..2a243baa 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetAllMeetingByUserMeetingDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetAllMeetingByUserMeetingDto.java @@ -1,15 +1,26 @@ package org.sopt.makers.crew.main.user.v2.dto.response; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor(staticName = "of") +@Schema(name = "UserV2GetAllMeetingByUserMeetingDto", description = "내가 속한 모임 조회 응답 Dto") public class UserV2GetAllMeetingByUserMeetingDto { - private int id; - private String title; - private String contents; - private String imageUrl; - private String category; + @Schema(description = "모임 id", example = "1") + private int id; + + @Schema(description = "모임 제목", example = "모임 제목입니다.") + private String title; + + @Schema(description = "모임 내용", example = "모임 내용입니다.") + private String contents; + + @Schema(description = "모임 사진", example = "[url] 형식") + private String imageUrl; + + @Schema(description = "모임 카테고리", example = "스터디") + private String category; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetAllMentionUserDto.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetAllMentionUserDto.java index f25de8b2..396fc195 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetAllMentionUserDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetAllMentionUserDto.java @@ -1,19 +1,29 @@ package org.sopt.makers.crew.main.user.v2.dto.response; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor(staticName = "of") +@Schema(name = "UserV2GetAllMentionUserDto", description = "멘션 유저 조회 응답 Dto") public class UserV2GetAllMentionUserDto { /** * 주의!! : 필드명은 userId 이지만 실제 응답 해야하는 데이터는 orgId 입니다. */ + @Schema(description = "유저 id", example = "1") private final Integer userId; + @Schema(description = "유저 이름", example = "홍길") private final String userName; + + @Schema(description = "최근 파트", example = "서버") private final String recentPart; + + @Schema(description = "최근 기수", example = "33") private final int recentGeneration; + + @Schema(description = "유저 프로필 사진", example = "[url] 형식") private final String profileImageUrl; } From d3a857fd6abd83bf3af3bcb6157ee6c71f5ff382 Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Fri, 2 Aug 2024 00:06:58 +0900 Subject: [PATCH 03/47] =?UTF-8?q?fix:=20ReplyDto=20=EA=B5=AC=ED=98=84=20(#?= =?UTF-8?q?281)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/v2/dto/response/CommentDto.java | 6 +- .../comment/v2/dto/response/ReplyDto.java | 56 +++++++++++++++++++ .../v2/service/CommentV2ServiceImpl.java | 7 ++- 3 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/ReplyDto.java diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java index 095ed77e..00505392 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java @@ -40,11 +40,11 @@ public class CommentDto { private final int order; @Schema(description = "대댓글 객체 목록", example = "") - private final List replies; + private final List replies; @QueryProjection public CommentDto(Integer id, String contents, CommentWriterDto user, LocalDateTime updatedDate, int likeCount, - boolean isLiked, boolean isWriter, int order, List replies) { + boolean isLiked, boolean isWriter, int order, List replies) { this.id = id; this.contents = contents; this.user = user; @@ -56,7 +56,7 @@ public CommentDto(Integer id, String contents, CommentWriterDto user, LocalDateT this.replies = replies; } - public static CommentDto of(Comment comment, boolean isLiked, boolean isWriter, List replies) { + public static CommentDto of(Comment comment, boolean isLiked, boolean isWriter, List replies) { return new CommentDto(comment.getId(), comment.getContents(), new CommentWriterDto(comment.getUser().getId(), comment.getUser().getOrgId(), comment.getUser().getName(), comment.getUser().getProfileImage()), comment.getUpdatedDate(), comment.getLikeCount(), diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/ReplyDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/ReplyDto.java new file mode 100644 index 00000000..ce2349da --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/ReplyDto.java @@ -0,0 +1,56 @@ +package org.sopt.makers.crew.main.comment.v2.dto.response; + +import java.time.LocalDateTime; +import org.sopt.makers.crew.main.entity.comment.Comment; +import com.querydsl.core.annotations.QueryProjection; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +@Getter +@Schema(name = "ReplyDto", description = "대댓글 객체 응답 Dto") +public class ReplyDto { + + @Schema(description = "대댓글 id", example = "1") + private final Integer id; + + @Schema(description = "댓글 내용", example = "이것은 댓글 내용입니다.") + private final String contents; + + @Schema(description = "댓글 작성자 객체", example = "") + private final CommentWriterDto user; + + @Schema(description = "댓글 업데이트 시간", example = "2024-07-31T15:30:00") + private final LocalDateTime updatedDate; + + @Schema(description = "좋아요 갯수", example = "20") + private final int likeCount; + + @Schema(description = "댓글 좋아요 여부", example = "true") + private final Boolean isLiked; + + @Schema(description = "댓글 작성자 여부", example = "true") + private final Boolean isWriter; + + @Schema(description = "댓글 순서", example = "2") + private final int order; + + @QueryProjection + public ReplyDto(Integer id, String contents, CommentWriterDto user, LocalDateTime updatedDate, int likeCount, + Boolean isLiked, Boolean isWriter, int order) { + this.id = id; + this.contents = contents; + this.user = user; + this.updatedDate = updatedDate; + this.likeCount = likeCount; + this.isLiked = isLiked; + this.isWriter = isWriter; + this.order = order; + } + + public static ReplyDto of(Comment comment, boolean isLiked, boolean isWriter) { + return new ReplyDto(comment.getId(), comment.getContents(), + new CommentWriterDto(comment.getUser().getId(), comment.getUser().getOrgId(), comment.getUser().getName(), + comment.getUser().getProfileImage()), comment.getUpdatedDate(), comment.getLikeCount(), + isLiked, isWriter, comment.getOrder()); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java index d1142e84..bca27fa6 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java @@ -18,6 +18,7 @@ import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2GetCommentsResponseDto; import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2ReportCommentResponseDto; import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2UpdateCommentResponseDto; +import org.sopt.makers.crew.main.comment.v2.dto.response.ReplyDto; import org.sopt.makers.crew.main.common.exception.BadRequestException; import org.sopt.makers.crew.main.common.exception.ForbiddenException; import org.sopt.makers.crew.main.common.response.ErrorStatus; @@ -162,13 +163,13 @@ public CommentV2GetCommentsResponseDto getComments(Integer postId, Integer page, MyLikes myLikes = new MyLikes(likeRepository.findAllByUserIdAndPostIdNotNull(userId)); - Map> replyMap = new HashMap<>(); + Map> replyMap = new HashMap<>(); comments.stream() .filter(comment -> !comment.isParentComment()) .forEach( comment -> replyMap.computeIfAbsent(comment.getParentId(), k -> new ArrayList<>()) - .add(CommentDto.of(comment, myLikes.isLikeComment(comment.getId()), - comment.isWriter(userId), null))); + .add(ReplyDto.of(comment, myLikes.isLikeComment(comment.getId()), + comment.isWriter(userId)))); List commentDtos = comments.stream() .filter(Comment::isParentComment) From 4f673fc7dc94950e090f4d98e8637d9b865080c9 Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Fri, 2 Aug 2024 01:02:31 +0900 Subject: [PATCH 04/47] =?UTF-8?q?[DOCS]=20README=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 50 ++++++++------------------------------------------ 1 file changed, 8 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index db32c78d..26c7c30c 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,7 @@ # 설명 -- NestJS기반으로 운영되는 모임(Crew) 서버 +- Spring 기반으로 운영되는 모임(Crew) 서버 - [PlayGround Link](https://playground.sopt.org/group/) -- [PlayGround Dev Link](https://sopt-internal-dev.pages.dev/group/) - -## node_modules 설치 - -```bash -npm ci -``` - -## 실행하는 법 - -```bash -# development -$ npm run start - -# watch mode -$ npm run start:dev - -# production mode -$ npm run start:prod -``` - -## 테스트 - -```bash -# unit tests -$ npm run test - -# e2e tests -$ npm run test:e2e - -# test coverage -$ npm run test:cov -``` # 배경 @@ -45,15 +12,14 @@ $ npm run test:cov # 기술스택 - DB: PostgreSQL -- DB GUI tool: pgAdmin4 -- ORM: TypeORM +- ORM: jpa, TypeORM - API 문서: Swagger -- 배포: AWS EC2, Docker Compose +- 배포: AWS EC2, Docker Compose, Docker hub - 인증: JWT -- 테스트: Jest, Unit5 -- 서버 프레임워크: NestJS, Spring +- 테스트: JUnit5, Jest +- 서버 프레임워크: Spring, NestJS - 웹서버 프레임워크: Caddy -- 언어: typescript, Java +- 언어: Java, Typescript # 아키텍처 @@ -187,8 +153,8 @@ bar # 예시 모듈 ## 배포 전략 -- 현재는 수동배포를 진행중이고 Blue-Green 방식의 배포를 진행하고 있지 않는다. -- 수동배포 이후 짧은 순단(서버 재시작)이 발생하기 때문에 새벽에 배포를 하거나, 사용자가 몰리는 시간대는 피해서 배포를 진행한다. (Prod환경 기준) +- Blue-Green 방식의 배포 자동화를 구축했다. +- 그럼에도 불구하고, 사용자가 몰리는 시간대는 피해서 배포를 진행한다. (Prod환경 기준) - Prod환경의 경우는 `main` 브랜치를, Dev환경의 경우는 `develop` 브랜치를 기준으로 배포를 진행한다. ## 배포 정보 From 3254950cdea0b76ae550a07a374583fdef7b91b5 Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Fri, 2 Aug 2024 13:44:24 +0900 Subject: [PATCH 05/47] =?UTF-8?q?fix:=20=EC=8A=A4=EC=9B=A8=EA=B1=B0=20?= =?UTF-8?q?=EB=AC=B8=EC=84=9C=20=EC=88=98=EC=A0=95=20(#283)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sopt/makers/crew/main/comment/v2/CommentV2Api.java | 8 +++++++- .../makers/crew/main/comment/v2/CommentV2Controller.java | 7 ++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Api.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Api.java index a5b798f3..249c7588 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Api.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Api.java @@ -1,6 +1,8 @@ package org.sopt.makers.crew.main.comment.v2; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import jakarta.validation.Valid; @@ -71,5 +73,9 @@ ResponseEntity mentionUserInComment( @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "성공"), }) - ResponseEntity getComments(@Valid @ModelAttribute CommentV2GetCommentsQueryDto requestBody, Principal principal); + @Parameters({@Parameter(name = "page", description = "페이지, default = 1", example = "1"), + @Parameter(name = "take", description = "가져올 데이터 개수, default = 12", example = "50"), + @Parameter(name = "postId", description = "게시글 id", example = "3")}) + ResponseEntity getComments( + @Valid @ModelAttribute @Parameter(hidden = true) CommentV2GetCommentsQueryDto request, Principal principal); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Controller.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Controller.java index 06c0e59d..f5a056ea 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Controller.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Controller.java @@ -1,5 +1,6 @@ package org.sopt.makers.crew.main.comment.v2; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; @@ -89,12 +90,12 @@ public ResponseEntity mentionUserInComment( @Override @GetMapping public ResponseEntity getComments( - @Valid @ModelAttribute CommentV2GetCommentsQueryDto requestBody, + @Valid @ModelAttribute @Parameter(hidden = true) CommentV2GetCommentsQueryDto request, Principal principal) { Integer userId = UserUtil.getUserId(principal); - CommentV2GetCommentsResponseDto commentDtos = commentV2Service.getComments(requestBody.getPostId(), - requestBody.getPage(), requestBody.getTake(), userId); + CommentV2GetCommentsResponseDto commentDtos = commentV2Service.getComments(request.getPostId(), + request.getPage(), request.getTake(), userId); return ResponseEntity.status(HttpStatus.OK).body(commentDtos); } From c55d013bebe2aff3ccc327668d754cd35c983d69 Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Fri, 2 Aug 2024 17:02:38 +0900 Subject: [PATCH 06/47] =?UTF-8?q?fix:=20=EC=A2=8B=EC=95=84=EC=9A=94=20?= =?UTF-8?q?=EC=97=AC=EB=B6=80=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?(#285)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../crew/main/comment/v2/service/CommentV2ServiceImpl.java | 2 +- .../org/sopt/makers/crew/main/entity/like/LikeRepository.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java index bca27fa6..3c04d0b4 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java @@ -161,7 +161,7 @@ public CommentV2GetCommentsResponseDto getComments(Integer postId, Integer page, List comments = commentRepository.findAllByPostIdOrderByCreatedDate(postId); - MyLikes myLikes = new MyLikes(likeRepository.findAllByUserIdAndPostIdNotNull(userId)); + MyLikes myLikes = new MyLikes(likeRepository.findAllByUserIdAndCommentIdNotNull(userId)); Map> replyMap = new HashMap<>(); comments.stream() diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/like/LikeRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/like/LikeRepository.java index 453bccbb..253deaff 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/like/LikeRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/like/LikeRepository.java @@ -5,7 +5,7 @@ public interface LikeRepository extends JpaRepository { - List findAllByUserIdAndPostIdNotNull(Integer userId); + List findAllByUserIdAndCommentIdNotNull(Integer userId); boolean existsByUserIdAndPostId(Integer userId, Integer postId); } From eb408c8e3c9d85c06fc6a685c7f567f624e97f43 Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Sat, 3 Aug 2024 14:11:53 +0900 Subject: [PATCH 07/47] =?UTF-8?q?feat:=20=EC=9E=84=EC=8B=9C=20API=20?= =?UTF-8?q?=EA=B0=9C=EB=B0=9C=20(#287)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../makers/crew/main/comment/v2/CommentV2Api.java | 12 ++++++++++++ .../crew/main/comment/v2/CommentV2Controller.java | 14 ++++++++++++++ .../crew/main/common/dto/TempResponseDto.java | 14 ++++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 main/src/main/java/org/sopt/makers/crew/main/common/dto/TempResponseDto.java diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Api.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Api.java index 249c7588..35c91070 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Api.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Api.java @@ -17,6 +17,7 @@ import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2GetCommentsResponseDto; import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2ReportCommentResponseDto; import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2UpdateCommentResponseDto; +import org.sopt.makers.crew.main.common.dto.TempResponseDto; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; @@ -78,4 +79,15 @@ ResponseEntity mentionUserInComment( @Parameter(name = "postId", description = "게시글 id", example = "3")}) ResponseEntity getComments( @Valid @ModelAttribute @Parameter(hidden = true) CommentV2GetCommentsQueryDto request, Principal principal); + + @Operation(summary = "[TEMP] 모임 게시글 댓글 리스트 조회") + @ResponseStatus(HttpStatus.OK) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공"), + }) + @Parameters({@Parameter(name = "page", description = "페이지, default = 1", example = "1"), + @Parameter(name = "take", description = "가져올 데이터 개수, default = 12", example = "50"), + @Parameter(name = "postId", description = "게시글 id", example = "3")}) + ResponseEntity> getCommentsTemp( + @Valid @ModelAttribute @Parameter(hidden = true) CommentV2GetCommentsQueryDto request, Principal principal); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Controller.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Controller.java index f5a056ea..87452f71 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Controller.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Controller.java @@ -17,6 +17,7 @@ import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2ReportCommentResponseDto; import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2UpdateCommentResponseDto; import org.sopt.makers.crew.main.comment.v2.service.CommentV2Service; +import org.sopt.makers.crew.main.common.dto.TempResponseDto; import org.sopt.makers.crew.main.common.util.UserUtil; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -99,4 +100,17 @@ public ResponseEntity getComments( return ResponseEntity.status(HttpStatus.OK).body(commentDtos); } + + @Override + @GetMapping("/temp") + public ResponseEntity> getCommentsTemp( + @Valid @ModelAttribute @Parameter(hidden = true) CommentV2GetCommentsQueryDto request, + Principal principal) { + + Integer userId = UserUtil.getUserId(principal); + CommentV2GetCommentsResponseDto commentDtos = commentV2Service.getComments(request.getPostId(), + request.getPage(), request.getTake(), userId); + + return ResponseEntity.status(HttpStatus.OK).body(TempResponseDto.of(commentDtos)); + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/dto/TempResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/common/dto/TempResponseDto.java new file mode 100644 index 00000000..db94f757 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/common/dto/TempResponseDto.java @@ -0,0 +1,14 @@ +package org.sopt.makers.crew.main.common.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class TempResponseDto { + private T data; + + public static TempResponseDto of(T data) { + return new TempResponseDto(data); + } +} From c3708685550017b362f1b224d043e0efd5d7dbf4 Mon Sep 17 00:00:00 2001 From: Yeseul Jo <68415644+yeseul106@users.noreply.github.com> Date: Sun, 4 Aug 2024 17:24:19 +0900 Subject: [PATCH 08/47] =?UTF-8?q?[FEAT]=20=EB=AA=A8=EC=9E=84=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EA=B8=80=20=EC=82=AD=EC=A0=9C=20V2=20API=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#279)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FEAT] 모임 게시글 삭제 API 마이그레이션 (#278) * [FIX] 모임 게시글 단건 조회 시, 없는 게시글 id의 경우 예외처리 (#230) * [CHORE] Nest.js swagger 문서에서 v1 deprecated 처리(#278) * chore: 댓글, 좋아요 삭제 로직 추가 * fix: delete 순서 변경 --------- Co-authored-by: mikekks --- .../entity/comment/CommentRepository.java | 8 ++++++ .../crew/main/entity/like/LikeRepository.java | 13 ++++++++- .../makers/crew/main/entity/post/Post.java | 9 ++++++ .../entity/post/PostSearchRepositoryImpl.java | 10 ++++++- .../makers/crew/main/post/v2/PostV2Api.java | 8 ++++++ .../crew/main/post/v2/PostV2Controller.java | 10 +++++++ .../main/post/v2/service/PostV2Service.java | 2 ++ .../post/v2/service/PostV2ServiceImpl.java | 28 +++++++++++++++++++ server/src/post/v1/post-v1.controller.ts | 1 + 9 files changed, 87 insertions(+), 2 deletions(-) diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/comment/CommentRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/comment/CommentRepository.java index e23cb8f5..817aaead 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/comment/CommentRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/comment/CommentRepository.java @@ -6,6 +6,9 @@ import org.sopt.makers.crew.main.common.exception.BadRequestException; import org.sopt.makers.crew.main.common.response.ErrorStatus; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.transaction.annotation.Transactional; public interface CommentRepository extends JpaRepository, CommentSearchRepository { @@ -26,4 +29,9 @@ default Comment findByIdAndPostIdOrThrow(Integer id, Integer postId){ } List findAllByPostIdOrderByCreatedDate(Integer postId); + + @Modifying(clearAutomatically = true) + @Transactional + @Query("DELETE FROM Comment c WHERE c.postId = :postId") + void deleteAllByPostId(Integer postId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/like/LikeRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/like/LikeRepository.java index 253deaff..f301ad84 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/like/LikeRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/like/LikeRepository.java @@ -2,10 +2,21 @@ import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.transaction.annotation.Transactional; public interface LikeRepository extends JpaRepository { List findAllByUserIdAndCommentIdNotNull(Integer userId); - boolean existsByUserIdAndPostId(Integer userId, Integer postId); + @Modifying(clearAutomatically = true) + @Transactional + @Query("DELETE FROM Like l WHERE l.postId = :postId") + void deleteAllByPostId(Integer postId); + + @Modifying(clearAutomatically = true) + @Transactional + @Query("DELETE FROM Like l WHERE l.commentId IN :commentIds") + void deleteAllByIdsInQuery(List commentIds); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/post/Post.java b/main/src/main/java/org/sopt/makers/crew/main/entity/post/Post.java index 55fcb12c..0490445d 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/post/Post.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/post/Post.java @@ -1,5 +1,7 @@ package org.sopt.makers.crew.main.entity.post; +import static org.sopt.makers.crew.main.common.response.ErrorStatus.*; + import io.hypersistence.utils.hibernate.type.array.StringArrayType; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; @@ -21,6 +23,7 @@ import lombok.NoArgsConstructor; import org.hibernate.annotations.Type; +import org.sopt.makers.crew.main.common.exception.ForbiddenException; import org.sopt.makers.crew.main.entity.comment.Comment; import org.sopt.makers.crew.main.entity.meeting.Meeting; import org.sopt.makers.crew.main.entity.user.User; @@ -138,4 +141,10 @@ public void increaseCommentCount() { public void decreaseCommentCount() { this.commentCount--; } + + public void isWriter(Integer userId){ + if (!this.userId.equals(userId)) { + throw new ForbiddenException(FORBIDDEN_EXCEPTION.getErrorCode()); + } + } } \ No newline at end of file diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepositoryImpl.java b/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepositoryImpl.java index d9781d93..271ad6eb 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepositoryImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepositoryImpl.java @@ -1,5 +1,6 @@ package org.sopt.makers.crew.main.entity.post; +import static org.sopt.makers.crew.main.common.response.ErrorStatus.NOT_FOUND_POST; import static org.sopt.makers.crew.main.entity.comment.QComment.comment; import static org.sopt.makers.crew.main.entity.like.QLike.like; import static org.sopt.makers.crew.main.entity.meeting.QMeeting.meeting; @@ -18,6 +19,7 @@ import java.util.Map; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; +import org.sopt.makers.crew.main.common.exception.BadRequestException; import org.sopt.makers.crew.main.post.v2.dto.query.PostGetPostsCommand; import org.sopt.makers.crew.main.post.v2.dto.response.CommenterThumbnails; import org.sopt.makers.crew.main.post.v2.dto.response.PostDetailBaseDto; @@ -50,7 +52,7 @@ public Page findPostList(PostGetPostsCommand queryCommand @Override public PostDetailBaseDto findPost(Integer userId, Integer postId) { - return queryFactory + PostDetailBaseDto postDetail = queryFactory .select(new QPostDetailBaseDto( post.id, post.title, @@ -84,6 +86,12 @@ public PostDetailBaseDto findPost(Integer userId, Integer postId) { .innerJoin(post.user, user) .where(post.id.eq(postId)) .fetchFirst(); + + if (postDetail == null) { + throw new BadRequestException(NOT_FOUND_POST.getErrorCode()); + } + + return postDetail; } private List getContentList(Pageable pageable, Integer meetingId, diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Api.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Api.java index eb928d7a..e7af7756 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Api.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Api.java @@ -68,4 +68,12 @@ ResponseEntity mentionUserInPost( @ApiResponse(responseCode = "400", description = "모임이 없습니다", content = @Content) }) ResponseEntity getPostCount(@RequestParam Integer meetingId); + + @Operation(summary = "모임 게시글 삭제") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공"), + @ApiResponse(responseCode = "400", description = "모임이 없습니다.", content = @Content), + @ApiResponse(responseCode = "403", description = "권한이 없습니다.", content = @Content) + }) + ResponseEntity deletePost(@PathVariable Integer postId, Principal principal); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Controller.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Controller.java index 10e70bfb..81049c4c 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Controller.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Controller.java @@ -15,6 +15,7 @@ import org.sopt.makers.crew.main.post.v2.service.PostV2Service; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; @@ -75,4 +76,13 @@ public ResponseEntity getPost(@PathVariable Integer postId, P public ResponseEntity getPostCount(@RequestParam Integer meetingId) { return ResponseEntity.ok(postV2Service.getPostCount(meetingId)); } + + @Override + @DeleteMapping("/{postId}") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity deletePost(@PathVariable Integer postId, Principal principal) { + Integer userId = UserUtil.getUserId(principal); + postV2Service.deletePost(postId, userId); + return ResponseEntity.ok().build(); + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2Service.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2Service.java index 75ec68a2..78782f03 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2Service.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2Service.java @@ -19,4 +19,6 @@ public interface PostV2Service { void mentionUserInPost(PostV2MentionUserInPostRequestDto requestBody, Integer userId); PostV2GetPostCountResponseDto getPostCount(Integer meetingId); + + void deletePost(Integer postId, Integer userId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java index b23ebbd1..9144cf33 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java @@ -14,6 +14,9 @@ import org.sopt.makers.crew.main.entity.apply.Apply; import org.sopt.makers.crew.main.entity.apply.ApplyRepository; import org.sopt.makers.crew.main.entity.apply.enums.EnApplyStatus; +import org.sopt.makers.crew.main.entity.comment.Comment; +import org.sopt.makers.crew.main.entity.comment.CommentRepository; +import org.sopt.makers.crew.main.entity.like.LikeRepository; import org.sopt.makers.crew.main.entity.meeting.Meeting; import org.sopt.makers.crew.main.entity.meeting.MeetingRepository; import org.sopt.makers.crew.main.entity.post.Post; @@ -45,6 +48,9 @@ public class PostV2ServiceImpl implements PostV2Service { private final UserRepository userRepository; private final PostRepository postRepository; private final ApplyRepository applyRepository; + private final CommentRepository commentRepository; + private final LikeRepository likeRepository; + private final PushNotificationService pushNotificationService; @Value("${push-notification.web-url}") @@ -166,4 +172,26 @@ public void mentionUserInPost(PostV2MentionUserInPostRequestDto requestBody, Int public PostV2GetPostCountResponseDto getPostCount(Integer meetingId) { return PostV2GetPostCountResponseDto.of(postRepository.countByMeetingId(meetingId)); } + + /** + * 모임 게시글 삭제 + * + * @throws 403 글 작성자가 아닌 경우 + * @apiNote 글을 작성한 유저만 삭제 가능 + */ + @Override + @Transactional + public void deletePost(Integer postId, Integer userId) { + Post post = postRepository.findByIdOrThrow(postId); + post.isWriter(userId); + + List comments = commentRepository.findAllByPostIdOrderByCreatedDate(postId); + List commentIds = comments.stream().map(Comment::getId).toList(); + + commentRepository.deleteAllByPostId(postId); + likeRepository.deleteAllByPostId(postId); + likeRepository.deleteAllByIdsInQuery(commentIds); + + postRepository.delete(post); + } } diff --git a/server/src/post/v1/post-v1.controller.ts b/server/src/post/v1/post-v1.controller.ts index a94d7dae..55749d86 100644 --- a/server/src/post/v1/post-v1.controller.ts +++ b/server/src/post/v1/post-v1.controller.ts @@ -182,6 +182,7 @@ export class PostV1Controller { @ApiOperation({ summary: '모임 게시글 삭제', + deprecated: true, }) @ApiBearerAuth() @UseGuards(AuthGuard('jwt')) From 68b76693b9b97d9765e612f29587ac22b39ace5f Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Sun, 4 Aug 2024 17:44:04 +0900 Subject: [PATCH 09/47] =?UTF-8?q?=08[CHORE]=20=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EC=85=98=20=EB=A9=94=ED=83=80=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EC=B6=94=EA=B0=80=20(#290)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: 페이지네이션 메타 데이터 추가 * chore: Deprecated 처리 --- .../crew/main/comment/v2/CommentV2Controller.java | 2 ++ .../dto/response/CommentV2GetCommentsResponseDto.java | 5 +++++ .../main/comment/v2/service/CommentV2ServiceImpl.java | 6 +++++- server/src/notice/v1/notice-v1.controller.ts | 10 ++++++++-- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Controller.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Controller.java index 87452f71..2ec9bf05 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Controller.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Controller.java @@ -18,6 +18,8 @@ import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2UpdateCommentResponseDto; import org.sopt.makers.crew.main.comment.v2.service.CommentV2Service; import org.sopt.makers.crew.main.common.dto.TempResponseDto; +import org.sopt.makers.crew.main.common.pagination.dto.PageMetaDto; +import org.sopt.makers.crew.main.common.pagination.dto.PageOptionsDto; import org.sopt.makers.crew.main.common.util.UserUtil; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2GetCommentsResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2GetCommentsResponseDto.java index 93d4c253..bcff90a5 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2GetCommentsResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2GetCommentsResponseDto.java @@ -2,6 +2,8 @@ import java.util.List; +import org.sopt.makers.crew.main.common.pagination.dto.PageMetaDto; + import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; @@ -14,4 +16,7 @@ public class CommentV2GetCommentsResponseDto { @Schema(description = "댓글 목록", example = "") private final List comments; + @Schema(description = "페이지네이션", example = "") + private final PageMetaDto meta; + } diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java index 3c04d0b4..3e0c9a04 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java @@ -21,6 +21,8 @@ import org.sopt.makers.crew.main.comment.v2.dto.response.ReplyDto; import org.sopt.makers.crew.main.common.exception.BadRequestException; import org.sopt.makers.crew.main.common.exception.ForbiddenException; +import org.sopt.makers.crew.main.common.pagination.dto.PageMetaDto; +import org.sopt.makers.crew.main.common.pagination.dto.PageOptionsDto; import org.sopt.makers.crew.main.common.response.ErrorStatus; import org.sopt.makers.crew.main.common.util.MentionSecretStringRemover; import org.sopt.makers.crew.main.common.util.Time; @@ -178,7 +180,9 @@ public CommentV2GetCommentsResponseDto getComments(Integer postId, Integer page, replyMap.get(comment.getId()))) .toList(); - return CommentV2GetCommentsResponseDto.of(commentDtos); + PageMetaDto pageMetaDto = new PageMetaDto(new PageOptionsDto(1, 12), 30); + + return CommentV2GetCommentsResponseDto.of(commentDtos, pageMetaDto); } /** diff --git a/server/src/notice/v1/notice-v1.controller.ts b/server/src/notice/v1/notice-v1.controller.ts index f9bb02d6..793a1e0e 100644 --- a/server/src/notice/v1/notice-v1.controller.ts +++ b/server/src/notice/v1/notice-v1.controller.ts @@ -12,7 +12,10 @@ import { ApiOkResponseCommon } from 'src/common/decorator/api-ok-response-common export class NoticeV1Controller { constructor(private readonly noticeV1Service: NoticeV1Service) {} - @ApiOperation({ summary: '공지사항 조회' }) + @ApiOperation({ + summary: '공지사항 조회', + deprecated: true, + }) @ApiOkResponseCommon(NoticeV1GetNoticesResponseDto) @ApiBearerAuth() @UseGuards(AuthGuard('jwt')) @@ -23,7 +26,10 @@ export class NoticeV1Controller { return this.noticeV1Service.getNotices({ status: query.status }); } - @ApiOperation({ summary: '공지사항 작성' }) + @ApiOperation({ + summary: '공지사항 작성', + deprecated: true, + }) @ApiBearerAuth() @UseGuards(AuthGuard('jwt')) @Post() From e7690369063b098c6b66bd7939b122d4bc82775f Mon Sep 17 00:00:00 2001 From: YeongWoooo Date: Sun, 4 Aug 2024 18:37:54 +0900 Subject: [PATCH 10/47] =?UTF-8?q?[FEAT]=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EB=8C=93=EA=B8=80=20=EC=A2=8B=EC=95=84=EC=9A=94=20=ED=86=A0?= =?UTF-8?q?=EA=B8=80=20V2=20API=20=EA=B5=AC=ED=98=84=20(#288)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FEAT] 게시글 댓글 좋아요 기능 추가 * [FEAT] 게시글 댓글 좋아요 변경 시 댓글 `likeCount` 변경 * [DOCS] 게시글 댓글 좋아요 V1 API에 Deprecated 주석 추가 * add: 코드 포맷터 적용 --------- Co-authored-by: mikekks --- .../crew/main/comment/v2/CommentV2Api.java | 15 ++- .../main/comment/v2/CommentV2Controller.java | 16 ++- ...CommentV2SwitchCommentLikeResponseDto.java | 17 +++ .../comment/v2/service/CommentV2Service.java | 7 +- .../v2/service/CommentV2ServiceImpl.java | 32 +++++ .../crew/main/entity/comment/Comment.java | 13 +- .../makers/crew/main/entity/like/Like.java | 111 ++++++++++++------ .../crew/main/entity/like/LikeRepository.java | 8 ++ .../src/comment/v1/comment-v1.controller.ts | 7 ++ 9 files changed, 181 insertions(+), 45 deletions(-) create mode 100644 main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2SwitchCommentLikeResponseDto.java diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Api.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Api.java index 35c91070..5a5438ef 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Api.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Api.java @@ -16,6 +16,7 @@ import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2CreateCommentResponseDto; import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2GetCommentsResponseDto; import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2ReportCommentResponseDto; +import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2SwitchCommentLikeResponseDto; import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2UpdateCommentResponseDto; import org.sopt.makers.crew.main.common.dto.TempResponseDto; import org.springframework.http.HttpStatus; @@ -78,7 +79,8 @@ ResponseEntity mentionUserInComment( @Parameter(name = "take", description = "가져올 데이터 개수, default = 12", example = "50"), @Parameter(name = "postId", description = "게시글 id", example = "3")}) ResponseEntity getComments( - @Valid @ModelAttribute @Parameter(hidden = true) CommentV2GetCommentsQueryDto request, Principal principal); + @Valid @ModelAttribute @Parameter(hidden = true) CommentV2GetCommentsQueryDto request, + Principal principal); @Operation(summary = "[TEMP] 모임 게시글 댓글 리스트 조회") @ResponseStatus(HttpStatus.OK) @@ -89,5 +91,14 @@ ResponseEntity getComments( @Parameter(name = "take", description = "가져올 데이터 개수, default = 12", example = "50"), @Parameter(name = "postId", description = "게시글 id", example = "3")}) ResponseEntity> getCommentsTemp( - @Valid @ModelAttribute @Parameter(hidden = true) CommentV2GetCommentsQueryDto request, Principal principal); + @Valid @ModelAttribute @Parameter(hidden = true) CommentV2GetCommentsQueryDto request, + Principal principal); + + @Operation(summary = "모임 게시글 댓글 좋아요 토글") + @ResponseStatus(HttpStatus.CREATED) + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "성공"), + }) + ResponseEntity switchCommentLike(Principal principal, + @PathVariable Integer commentId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Controller.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Controller.java index 2ec9bf05..6931e347 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Controller.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Controller.java @@ -15,6 +15,7 @@ import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2CreateCommentResponseDto; import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2GetCommentsResponseDto; import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2ReportCommentResponseDto; +import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2SwitchCommentLikeResponseDto; import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2UpdateCommentResponseDto; import org.sopt.makers.crew.main.comment.v2.service.CommentV2Service; import org.sopt.makers.crew.main.common.dto.TempResponseDto; @@ -23,10 +24,10 @@ import org.sopt.makers.crew.main.common.util.UserUtil; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -115,4 +116,17 @@ public ResponseEntity> getComme return ResponseEntity.status(HttpStatus.OK).body(TempResponseDto.of(commentDtos)); } + + @Override + @PostMapping("/{commentId}/like") + public ResponseEntity switchCommentLike( + Principal principal, + @PathVariable Integer commentId) { + Integer userId = UserUtil.getUserId(principal); + + CommentV2SwitchCommentLikeResponseDto result = commentV2Service.switchCommentToggle(commentId, + userId); + + return ResponseEntity.status(HttpStatus.CREATED).body(result); + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2SwitchCommentLikeResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2SwitchCommentLikeResponseDto.java new file mode 100644 index 00000000..9b264972 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2SwitchCommentLikeResponseDto.java @@ -0,0 +1,17 @@ +package org.sopt.makers.crew.main.comment.v2.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor(staticName = "of") +@Schema(name = "CommentV2SwitchCommentLikeResponseDto", description = "댓글 좋아요 토글 응답 Dto") +public class CommentV2SwitchCommentLikeResponseDto { + + /** + * 요청 후 내가 좋아요를 누른 상태 + */ + @Schema(description = "요청 후 내가 좋아요를 누른 상태", example = "false") + private Boolean isLiked; +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2Service.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2Service.java index 39a95535..3a917969 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2Service.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2Service.java @@ -5,9 +5,9 @@ import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2CreateCommentResponseDto; import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2GetCommentsResponseDto; import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2ReportCommentResponseDto; +import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2SwitchCommentLikeResponseDto; import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2UpdateCommentResponseDto; import org.sopt.makers.crew.main.common.exception.BadRequestException; -import org.sopt.makers.crew.main.common.exception.ForbiddenException; public interface CommentV2Service { @@ -24,5 +24,8 @@ CommentV2ReportCommentResponseDto reportComment(Integer commentId, Integer userI CommentV2UpdateCommentResponseDto updateComment(Integer commentId, String contents, Integer userId); - CommentV2GetCommentsResponseDto getComments(Integer postId, Integer page, Integer take, Integer userId); + CommentV2GetCommentsResponseDto getComments(Integer postId, Integer page, Integer take, + Integer userId); + + CommentV2SwitchCommentLikeResponseDto switchCommentToggle(Integer commentId, Integer userId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java index 3e0c9a04..10a05a49 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java @@ -17,6 +17,7 @@ import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2CreateCommentResponseDto; import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2GetCommentsResponseDto; import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2ReportCommentResponseDto; +import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2SwitchCommentLikeResponseDto; import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2UpdateCommentResponseDto; import org.sopt.makers.crew.main.comment.v2.dto.response.ReplyDto; import org.sopt.makers.crew.main.common.exception.BadRequestException; @@ -29,6 +30,7 @@ import org.sopt.makers.crew.main.entity.comment.Comment; import org.sopt.makers.crew.main.entity.comment.CommentRepository; import org.sopt.makers.crew.main.entity.comment.Comments; +import org.sopt.makers.crew.main.entity.like.Like; import org.sopt.makers.crew.main.entity.like.LikeRepository; import org.sopt.makers.crew.main.entity.like.MyLikes; import org.sopt.makers.crew.main.entity.post.Post; @@ -47,6 +49,7 @@ @RequiredArgsConstructor @Transactional(readOnly = true) public class CommentV2ServiceImpl implements CommentV2Service { + private static final int IS_REPLY_COMMENT = 1; private final PostRepository postRepository; @@ -270,4 +273,33 @@ public void mentionUserInComment(CommentV2MentionUserInCommentRequestDto request pushNotificationService.sendPushNotification(pushRequestDto); } + + @Override + @Transactional + public CommentV2SwitchCommentLikeResponseDto switchCommentToggle(Integer commentId, + Integer userId) { + + Comment comment = commentRepository.findByIdOrThrow(commentId); + User user = userRepository.findByIdOrThrow(userId); + + boolean isLike = likeRepository.existsByUserIdAndCommentId(userId, commentId); + + if (isLike) { + likeRepository.deleteByUserIdAndCommentId(userId, commentId); + comment.decreaseLikeCount(); + return CommentV2SwitchCommentLikeResponseDto.of(false); + } + + Like like = Like.builder() + .user(user) + .userId(userId) + .comment(comment) + .commentId(commentId) + .build(); + + likeRepository.save(like); + comment.increaseLikeCount(); + + return CommentV2SwitchCommentLikeResponseDto.of(true); + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/comment/Comment.java b/main/src/main/java/org/sopt/makers/crew/main/entity/comment/Comment.java index 76d44d4d..346232d9 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/comment/Comment.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/comment/Comment.java @@ -38,7 +38,6 @@ public class Comment { private static final int PARENT_COMMENT = 0; private static final String DELETE_COMMENT_CONTENT = "삭제된 댓글입니다."; - /** * 댓글의 고유 식별자 */ @@ -116,6 +115,7 @@ public class Comment { private Integer parentId; public static class CommentListener { + @PostPersist public void setParentId(Comment comment) { if (comment.depth == PARENT_COMMENT) { // 댓글일 경우 @@ -125,7 +125,8 @@ public void setParentId(Comment comment) { } @Builder - public Comment(String contents, int depth, int order, User user, Integer userId, Post post, Integer postId, + public Comment(String contents, int depth, int order, User user, Integer userId, Post post, + Integer postId, int likeCount, Integer parentId) { this.contents = contents; this.depth = depth; @@ -161,4 +162,12 @@ public boolean isWriter(Integer userId) { public boolean isParentComment() { return this.depth == PARENT_COMMENT; } + + public void increaseLikeCount() { + this.likeCount++; + } + + public void decreaseLikeCount() { + this.likeCount--; + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/like/Like.java b/main/src/main/java/org/sopt/makers/crew/main/entity/like/Like.java index 345f93ff..6bba6192 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/like/Like.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/like/Like.java @@ -3,15 +3,24 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EntityListeners; +import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; + import java.time.LocalDateTime; + import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; + +import org.sopt.makers.crew.main.entity.comment.Comment; +import org.sopt.makers.crew.main.entity.post.Post; +import org.sopt.makers.crew.main.entity.user.User; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; @@ -21,43 +30,69 @@ @Table(name = "like") @EntityListeners(AuditingEntityListener.class) public class Like { - /** - * Primary key - */ - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Integer id; - - /** - * 좋아요 누른 날짜 - */ - @Column(name = "createdDate", nullable = false, columnDefinition = "TIMESTAMP") - @CreatedDate - private LocalDateTime createdDate; - - /** - * 좋아요 누른사람 id - */ - @Column(insertable = false, updatable = false) - private Integer userId; - - /** - * 게시글 id - 게시글 좋아요가 아닐 경우 null - */ - @Column(insertable = false, updatable = false) - private Integer postId; - - /** - * 댓글 id - 댓글 좋아요가 아닐 경우 null - */ - @Column(insertable = false, updatable = false) - private Integer commentId; - - @Builder - public Like(Integer userId, Integer postId, Integer commentId) { - this.userId = userId; - this.postId = postId; - this.commentId = commentId; - } + + /** + * Primary key + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private int id; + + /** + * 좋아요 누른 날짜 + */ + @Column(name = "createdDate", nullable = false, columnDefinition = "TIMESTAMP") + @CreatedDate + private LocalDateTime createdDate; + + /** + * 신고자 + */ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "userId", nullable = false) + private User user; + + /** + * 좋아요 누른사람 id + */ + @Column(insertable = false, nullable = false, updatable = false) + private int userId; + + /** + * 게시글 + */ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "postId") + private Post post; + + /** + * 게시글 id - 게시글 좋아요가 아닐 경우 null + */ + @Column(insertable = false, updatable = false) + private Integer postId; + + /** + * 댓글 + */ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "commentId") + private Comment comment; + + /** + * 댓글 id - 댓글 좋아요가 아닐 경우 null + */ + @Column(insertable = false, updatable = false) + private Integer commentId; + + @Builder + public Like(Integer userId, Integer postId, Integer commentId, User user, Post post, + Comment comment) { + this.userId = userId; + this.user = user; + this.post = post; + this.postId = postId; + this.comment = comment; + this.commentId = commentId; + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/like/LikeRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/like/LikeRepository.java index f301ad84..19442ea9 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/like/LikeRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/like/LikeRepository.java @@ -1,10 +1,14 @@ package org.sopt.makers.crew.main.entity.like; import java.util.List; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.transaction.annotation.Transactional; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.transaction.annotation.Transactional; public interface LikeRepository extends JpaRepository { @@ -19,4 +23,8 @@ public interface LikeRepository extends JpaRepository { @Transactional @Query("DELETE FROM Like l WHERE l.commentId IN :commentIds") void deleteAllByIdsInQuery(List commentIds); + + boolean existsByUserIdAndCommentId(Integer userId, Integer commentId); + + void deleteByUserIdAndCommentId(Integer userId, Integer commentId); } diff --git a/server/src/comment/v1/comment-v1.controller.ts b/server/src/comment/v1/comment-v1.controller.ts index bfb127a0..e428b46d 100644 --- a/server/src/comment/v1/comment-v1.controller.ts +++ b/server/src/comment/v1/comment-v1.controller.ts @@ -61,8 +61,12 @@ export class CommentV1Controller { return this.commentV1Service.getComments({ query, user }); } + /** + * @deprecated + */ @ApiOperation({ summary: '댓글 좋아요 토글', + deprecated: true, }) @ApiOkResponseCommon(CommentV1SwitchCommentLikeResponseDto) @ApiBearerAuth() @@ -98,6 +102,9 @@ export class CommentV1Controller { return this.commentV1Service.reportComment({ param, user }); } + /** + * @deprecated + */ @ApiOperation({ summary: '모임 게시글 댓글 작성', deprecated: true, From 898386a1624e7a426dda8fe9ed92c476003cc643 Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Tue, 6 Aug 2024 00:02:56 +0900 Subject: [PATCH 11/47] =?UTF-8?q?fix:=20=EB=8C=93=EA=B8=80=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=ED=9B=84=20=EC=A1=B0=ED=9A=8C=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20=EC=98=A4=EB=A5=98=20=ED=95=B4=EA=B2=B0=20(#292)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../crew/main/comment/v2/dto/response/CommentDto.java | 11 +++++++++-- .../sopt/makers/crew/main/entity/comment/Comment.java | 5 ++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java index 00505392..221c4db1 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java @@ -2,8 +2,10 @@ import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; import org.sopt.makers.crew.main.entity.comment.Comment; +import org.sopt.makers.crew.main.entity.user.User; import com.fasterxml.jackson.annotation.JsonProperty; import com.querydsl.core.annotations.QueryProjection; @@ -57,9 +59,14 @@ public CommentDto(Integer id, String contents, CommentWriterDto user, LocalDateT } public static CommentDto of(Comment comment, boolean isLiked, boolean isWriter, List replies) { + Integer userId = comment.getUser() == null ? null : comment.getUser().getId(); + Integer orgId = comment.getUser() == null ? null : comment.getUser().getOrgId(); + String userName = comment.getUser() == null ? null : comment.getUser().getName(); + String profileImage = comment.getUser() == null ? null : comment.getUser().getProfileImage(); + return new CommentDto(comment.getId(), comment.getContents(), - new CommentWriterDto(comment.getUser().getId(), comment.getUser().getOrgId(), comment.getUser().getName(), - comment.getUser().getProfileImage()), comment.getUpdatedDate(), comment.getLikeCount(), + new CommentWriterDto(userId, orgId, userName, + profileImage), comment.getUpdatedDate(), comment.getLikeCount(), isLiked, isWriter, comment.getOrder(), replies); } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/comment/Comment.java b/main/src/main/java/org/sopt/makers/crew/main/entity/comment/Comment.java index 346232d9..060293de 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/comment/Comment.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/comment/Comment.java @@ -156,7 +156,10 @@ public void validateWriter(Integer userId) { } public boolean isWriter(Integer userId) { - return this.userId.equals(userId); + if (this.userId == null || !this.userId.equals(userId)) { + return false; + } + return true; } public boolean isParentComment() { From 351ce1993dde90fa1b4c23e439a9d07792e1632d Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Wed, 7 Aug 2024 19:17:20 +0900 Subject: [PATCH 12/47] =?UTF-8?q?fix:=20PUT=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20(#294)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/sopt/makers/crew/main/common/config/SecurityConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/config/SecurityConfig.java b/main/src/main/java/org/sopt/makers/crew/main/common/config/SecurityConfig.java index c1b9172e..e9917c8b 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/config/SecurityConfig.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/config/SecurityConfig.java @@ -132,7 +132,7 @@ CorsConfigurationSource corsConfigurationSource() { configuration.setAllowedOrigins( Arrays.asList("https://playground.sopt.org/", "http://localhost:3000/", "https://sopt-internal-dev.pages.dev/", "https://crew.api.dev.sopt.org", "https://crew.api.prod.sopt.org")); - configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PATCH", "DELETE", "OPTIONS")); + configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PATCH", "DELETE", "PUT", "OPTIONS")); configuration.addAllowedHeader("*"); configuration.setAllowCredentials(false); From 302e1898dcbdbfa278682cfcd69f9baf2fce042d Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Fri, 9 Aug 2024 11:32:41 +0900 Subject: [PATCH 13/47] =?UTF-8?q?fix:=20=EC=8A=A4=EC=9B=A8=EA=B1=B0=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=ED=95=B4=EA=B2=B0=20(#297)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../crew/main/comment/v2/CommentV2Api.java | 67 +++++++++++++++---- .../main/comment/v2/CommentV2Controller.java | 2 - 2 files changed, 54 insertions(+), 15 deletions(-) diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Api.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Api.java index 5a5438ef..a237d785 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Api.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Api.java @@ -3,6 +3,8 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import jakarta.validation.Valid; @@ -31,7 +33,13 @@ public interface CommentV2Api { @Operation(summary = "모임 게시글 댓글 작성") @ResponseStatus(HttpStatus.CREATED) @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "성공"), + @ApiResponse( + responseCode = "201", + description = "성공", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = CommentV2CreateCommentResponseDto.class) + )), }) ResponseEntity createComment( @Valid @RequestBody CommentV2CreateCommentBodyDto requestBody, Principal principal); @@ -39,7 +47,13 @@ ResponseEntity createComment( @Operation(summary = "모임 게시글 댓글 수정") @ResponseStatus(HttpStatus.OK) @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "성공"), + @ApiResponse( + responseCode = "200", + description = "성공", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = CommentV2UpdateCommentResponseDto.class) + )), }) ResponseEntity updateComment( @PathVariable Integer commentId, @@ -49,7 +63,14 @@ ResponseEntity updateComment( @Operation(summary = "댓글 신고하기") @ResponseStatus(HttpStatus.CREATED) @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "성공"), + @ApiResponse( + responseCode = "201", + description = "성공", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = CommentV2ReportCommentResponseDto.class) + ) + ), }) ResponseEntity reportComment( @PathVariable Integer commentId, Principal principal); @@ -64,7 +85,9 @@ ResponseEntity reportComment( @Operation(summary = "댓글에서 유저 멘션") @ResponseStatus(HttpStatus.OK) @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "성공"), + @ApiResponse( + responseCode = "200", + description = "성공"), }) ResponseEntity mentionUserInComment( @Valid @RequestBody CommentV2MentionUserInCommentRequestDto requestBody, @@ -73,11 +96,20 @@ ResponseEntity mentionUserInComment( @Operation(summary = "모임 게시글 댓글 리스트 조회") @ResponseStatus(HttpStatus.OK) @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "성공"), + @ApiResponse( + responseCode = "200", + description = "성공", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = CommentV2GetCommentsResponseDto.class) + )), }) - @Parameters({@Parameter(name = "page", description = "페이지, default = 1", example = "1"), - @Parameter(name = "take", description = "가져올 데이터 개수, default = 12", example = "50"), - @Parameter(name = "postId", description = "게시글 id", example = "3")}) + @Parameters( + { + @Parameter(name = "page", description = "페이지, default = 1", example = "1", schema = @Schema(type = "integer", format = "int32")), + @Parameter(name = "take", description = "가져올 데이터 개수, default = 12", example = "50", schema = @Schema(type = "integer", format = "int32")), + @Parameter(name = "postId", description = "게시글 id", example = "3", schema = @Schema(type = "integer", format = "int32")) + }) ResponseEntity getComments( @Valid @ModelAttribute @Parameter(hidden = true) CommentV2GetCommentsQueryDto request, Principal principal); @@ -85,11 +117,14 @@ ResponseEntity getComments( @Operation(summary = "[TEMP] 모임 게시글 댓글 리스트 조회") @ResponseStatus(HttpStatus.OK) @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "성공"), + @ApiResponse( + responseCode = "200", + description = "성공"), }) - @Parameters({@Parameter(name = "page", description = "페이지, default = 1", example = "1"), - @Parameter(name = "take", description = "가져올 데이터 개수, default = 12", example = "50"), - @Parameter(name = "postId", description = "게시글 id", example = "3")}) + @Parameters({ + @Parameter(name = "page", description = "페이지, default = 1", example = "1", schema = @Schema(type = "integer", format = "int32")), + @Parameter(name = "take", description = "가져올 데이터 개수, default = 12", example = "50", schema = @Schema(type = "integer", format = "int32")), + @Parameter(name = "postId", description = "게시글 id", example = "3", schema = @Schema(type = "integer", format = "int32"))}) ResponseEntity> getCommentsTemp( @Valid @ModelAttribute @Parameter(hidden = true) CommentV2GetCommentsQueryDto request, Principal principal); @@ -97,7 +132,13 @@ ResponseEntity> getCommentsTemp @Operation(summary = "모임 게시글 댓글 좋아요 토글") @ResponseStatus(HttpStatus.CREATED) @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "성공"), + @ApiResponse( + responseCode = "201", + description = "성공", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = CommentV2SwitchCommentLikeResponseDto.class) + )), }) ResponseEntity switchCommentLike(Principal principal, @PathVariable Integer commentId); diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Controller.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Controller.java index 6931e347..132b24d9 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Controller.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Controller.java @@ -19,8 +19,6 @@ import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2UpdateCommentResponseDto; import org.sopt.makers.crew.main.comment.v2.service.CommentV2Service; import org.sopt.makers.crew.main.common.dto.TempResponseDto; -import org.sopt.makers.crew.main.common.pagination.dto.PageMetaDto; -import org.sopt.makers.crew.main.common.pagination.dto.PageOptionsDto; import org.sopt.makers.crew.main.common.util.UserUtil; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; From a0fa7c0886085d791bc95d0242c7c8629168a254 Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Sat, 10 Aug 2024 01:41:25 +0900 Subject: [PATCH 14/47] =?UTF-8?q?fix:=20=EC=8A=A4=EC=9B=A8=EA=B1=B0=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=ED=95=B4=EA=B2=B0=20(#299)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../v2/dto/response/CommentV2GetCommentsResponseDto.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2GetCommentsResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2GetCommentsResponseDto.java index bcff90a5..b88b7352 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2GetCommentsResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2GetCommentsResponseDto.java @@ -4,6 +4,7 @@ import org.sopt.makers.crew.main.common.pagination.dto.PageMetaDto; +import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; @@ -13,7 +14,9 @@ @Schema(name = "CommentV2GetCommentsResponseDto", description = "댓글 목록 조회 응답 Dto") public class CommentV2GetCommentsResponseDto { - @Schema(description = "댓글 목록", example = "") + @ArraySchema( + schema = @Schema(implementation = CommentDto.class) + ) private final List comments; @Schema(description = "페이지네이션", example = "") From 3bfdd30861259d2939ee73c2205d4ffcf90ea4e4 Mon Sep 17 00:00:00 2001 From: mikekks Date: Sat, 10 Aug 2024 22:22:04 +0900 Subject: [PATCH 15/47] =?UTF-8?q?fix:=20=EC=8A=A4=EC=9B=A8=EA=B1=B0=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sopt/makers/crew/main/comment/v2/CommentV2Api.java | 8 +++----- .../makers/crew/main/comment/v2/CommentV2Controller.java | 1 - 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Api.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Api.java index a237d785..2ce70189 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Api.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Api.java @@ -7,6 +7,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import java.security.Principal; @@ -28,6 +29,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.ResponseStatus; +@Tag(name = "댓글/대댓글") public interface CommentV2Api { @Operation(summary = "모임 게시글 댓글 작성") @@ -98,11 +100,7 @@ ResponseEntity mentionUserInComment( @ApiResponses(value = { @ApiResponse( responseCode = "200", - description = "성공", - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = CommentV2GetCommentsResponseDto.class) - )), + description = "성공"), }) @Parameters( { diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Controller.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Controller.java index 132b24d9..52c9c097 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Controller.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/CommentV2Controller.java @@ -35,7 +35,6 @@ @RestController @RequestMapping("/comment/v2") @RequiredArgsConstructor -@Tag(name = "댓글/대댓글") public class CommentV2Controller implements CommentV2Api { private final CommentV2Service commentV2Service; From 35b3962186a72d0ff9eb6e3b32f69187cf4a96f1 Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Sun, 11 Aug 2024 22:35:13 +0900 Subject: [PATCH 16/47] =?UTF-8?q?[FEAT]=20=EA=B4=91=EA=B3=A0=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20API=20=EA=B0=9C=EB=B0=9C=20(#302)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 광고 조회 dto 정의 * feat: 광고 조회 API 컨트롤러 개발 * feat: 광고 엔티티 추가 * feat: 광고 조회 서비스 로직 구현 * chore: 도커 파일 수정 --- docker-compose.yml | 8 +++ .../main/advertisement/AdvertisementApi.java | 26 +++++++++ .../AdvertisementController.java | 28 ++++++++++ .../dto/AdvertisementGetResponseDto.java | 22 ++++++++ .../service/AdvertisementService.java | 31 ++++++++++ .../entity/advertisement/Advertisement.java | 56 +++++++++++++++++++ .../AdvertisementRepository.java | 10 ++++ 7 files changed, 181 insertions(+) create mode 100644 main/src/main/java/org/sopt/makers/crew/main/advertisement/AdvertisementApi.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/advertisement/AdvertisementController.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementGetResponseDto.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/advertisement/service/AdvertisementService.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/Advertisement.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/AdvertisementRepository.java diff --git a/docker-compose.yml b/docker-compose.yml index 73adf8a1..3d7166a0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -144,6 +144,10 @@ services: caddy.route_9.reverse_proxy: "{{ upstreams 4000 }}" caddy.route_10: /notice/v2 caddy.route_10.reverse_proxy: "{{ upstreams 4000 }}" + caddy.route_11: /advertisement/v2 + caddy.route_11.reverse_proxy: "{{ upstreams 4000 }}" + caddy.route_12: /advertisement/v2/* + caddy.route_12.reverse_proxy: "{{ upstreams 4000 }}" nestjs-blue: image: makerscrew/server:latest @@ -241,6 +245,10 @@ services: caddy.route_9.reverse_proxy: "{{ upstreams 4000 }}" caddy.route_10: /notice/v2 caddy.route_10.reverse_proxy: "{{ upstreams 4000 }}" + caddy.route_11: /advertisement/v2 + caddy.route_11.reverse_proxy: "{{ upstreams 4000 }}" + caddy.route_12: /advertisement/v2/* + caddy.route_12.reverse_proxy: "{{ upstreams 4000 }}" networks: caddy: diff --git a/main/src/main/java/org/sopt/makers/crew/main/advertisement/AdvertisementApi.java b/main/src/main/java/org/sopt/makers/crew/main/advertisement/AdvertisementApi.java new file mode 100644 index 00000000..8b3733bd --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/advertisement/AdvertisementApi.java @@ -0,0 +1,26 @@ +package org.sopt.makers.crew.main.advertisement; + +import java.security.Principal; + +import org.sopt.makers.crew.main.advertisement.dto.AdvertisementGetResponseDto; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ResponseStatus; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; + +@Tag(name = "광고") +public interface AdvertisementApi { + @Operation(summary = "광고 조회") + @ResponseStatus(HttpStatus.OK) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "성공"), + }) + ResponseEntity getAdvertisement(Principal principal); + +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/advertisement/AdvertisementController.java b/main/src/main/java/org/sopt/makers/crew/main/advertisement/AdvertisementController.java new file mode 100644 index 00000000..530c3562 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/advertisement/AdvertisementController.java @@ -0,0 +1,28 @@ +package org.sopt.makers.crew.main.advertisement; + +import java.security.Principal; + +import org.sopt.makers.crew.main.advertisement.dto.AdvertisementGetResponseDto; +import org.sopt.makers.crew.main.advertisement.service.AdvertisementService; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping("/advertisement/v2") +@RequiredArgsConstructor +public class AdvertisementController implements AdvertisementApi { + + private final AdvertisementService advertisementService; + + @Override + @GetMapping + public ResponseEntity getAdvertisement(Principal principal) { + AdvertisementGetResponseDto response = advertisementService.getAdvertisement(); + + return ResponseEntity.ok().body(response); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementGetResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementGetResponseDto.java new file mode 100644 index 00000000..abbf2161 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementGetResponseDto.java @@ -0,0 +1,22 @@ +package org.sopt.makers.crew.main.advertisement.dto; + +import org.sopt.makers.crew.main.entity.advertisement.Advertisement; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(name = "AdvertisementGetResponseDto", description = "광고 구좌 조회 응답 Dto") +public record AdvertisementGetResponseDto( + @Schema(description = "광고 구좌 이미지 url", example = "[image Url]") + String advertisementImageUrl, + @Schema(description = "광고 구좌 링크", example = "https://www.naver.com") + String advertisementLink +) { + public static AdvertisementGetResponseDto of(Advertisement advertisement){ + if(advertisement == null){ + return new AdvertisementGetResponseDto(null, null); + } + + return new AdvertisementGetResponseDto(advertisement.getAdvertisementImageUrl(), + advertisement.getAdvertisementLink()); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/advertisement/service/AdvertisementService.java b/main/src/main/java/org/sopt/makers/crew/main/advertisement/service/AdvertisementService.java new file mode 100644 index 00000000..367416f9 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/advertisement/service/AdvertisementService.java @@ -0,0 +1,31 @@ +package org.sopt.makers.crew.main.advertisement.service; + +import java.util.List; +import java.util.Optional; + +import org.sopt.makers.crew.main.advertisement.dto.AdvertisementGetResponseDto; +import org.sopt.makers.crew.main.common.util.Time; +import org.sopt.makers.crew.main.entity.advertisement.Advertisement; +import org.sopt.makers.crew.main.entity.advertisement.AdvertisementRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class AdvertisementService { + private final AdvertisementRepository advertisementRepository; + + private final Time time; + + public AdvertisementGetResponseDto getAdvertisement() { + Optional advertisement = advertisementRepository.findFirstByAdvertisementEndDateBeforeAndAdvertisementStartDateAfterOrderByPriority( + time.now(), time.now()); + + return advertisement + .map(AdvertisementGetResponseDto::of) + .orElseGet(() -> AdvertisementGetResponseDto.of(null)); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/Advertisement.java b/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/Advertisement.java new file mode 100644 index 00000000..e93ccad4 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/Advertisement.java @@ -0,0 +1,56 @@ +package org.sopt.makers.crew.main.entity.advertisement; + +import static jakarta.persistence.GenerationType.*; + +import java.time.LocalDateTime; + +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import jakarta.persistence.Entity; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@EntityListeners(AuditingEntityListener.class) +@Table(name = "advertisement") +public class Advertisement { + /** + * Primary Key + */ + @Id + @GeneratedValue(strategy = IDENTITY) + private Integer id; + + @NotNull + private String advertisementImageUrl; + + @NotNull + private String advertisementLink; + + @NotNull + private Long priority; + + @NotNull + private LocalDateTime advertisementStartDate; + + @NotNull + private LocalDateTime advertisementEndDate; + + @Builder + private Advertisement(String advertisementImageUrl, String advertisementLink, LocalDateTime advertisementStartDate, + LocalDateTime advertisementEndDate) { + this.advertisementImageUrl = advertisementImageUrl; + this.advertisementLink = advertisementLink; + this.advertisementStartDate = advertisementStartDate; + this.advertisementEndDate = advertisementEndDate; + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/AdvertisementRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/AdvertisementRepository.java new file mode 100644 index 00000000..14fadcab --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/AdvertisementRepository.java @@ -0,0 +1,10 @@ +package org.sopt.makers.crew.main.entity.advertisement; + +import java.time.LocalDateTime; +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AdvertisementRepository extends JpaRepository { + Optional findFirstByAdvertisementEndDateBeforeAndAdvertisementStartDateAfterOrderByPriority(LocalDateTime now1, LocalDateTime now2); +} From 80e4e508d265ab9644559faaaa76e51c25ccecf9 Mon Sep 17 00:00:00 2001 From: mikekks Date: Mon, 12 Aug 2024 23:54:21 +0900 Subject: [PATCH 17/47] =?UTF-8?q?fix:=20=EC=8A=A4=EC=9B=A8=EA=B1=B0=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../crew/main/comment/v2/dto/response/CommentDto.java | 10 +++++----- .../dto/response/CommentV2GetCommentsResponseDto.java | 6 +++--- .../main/comment/v2/dto/response/CommentWriterDto.java | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java index 221c4db1..874006c6 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java @@ -14,19 +14,19 @@ import lombok.Getter; @Getter -@Schema(name = "CommentDto", description = "댓글 객체 응답 Dto") +@Schema(name = "CommentDto", description = "댓글 객체 응답 Dto", required = true) public class CommentDto { - @Schema(description = "댓글 id", example = "1") + @Schema(description = "댓글 id", example = "1", required = true) private final Integer id; - @Schema(description = "댓글 내용", example = "이것은 댓글 내용입니다.") + @Schema(description = "댓글 내용", example = "이것은 댓글 내용입니다.", required = true) private final String contents; - @Schema(description = "댓글 작성자 객체", example = "") + @Schema(description = "댓글 작성자 객체", example = "", required = true) private final CommentWriterDto user; - @Schema(description = "댓글 업데이트 시간", example = "2024-07-31T15:30:00") + @Schema(description = "댓글 업데이트 시간", example = "2024-07-31T15:30:00", required = true) private final LocalDateTime updatedDate; @Schema(description = "좋아요 갯수", example = "20") diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2GetCommentsResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2GetCommentsResponseDto.java index b88b7352..f15ef253 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2GetCommentsResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2GetCommentsResponseDto.java @@ -11,15 +11,15 @@ @Getter @AllArgsConstructor(staticName = "of") -@Schema(name = "CommentV2GetCommentsResponseDto", description = "댓글 목록 조회 응답 Dto") +@Schema(name = "CommentV2GetCommentsResponseDto", description = "댓글 목록 조회 응답 Dto", required = true) public class CommentV2GetCommentsResponseDto { @ArraySchema( - schema = @Schema(implementation = CommentDto.class) + schema = @Schema(implementation = CommentDto.class, required = true) ) private final List comments; - @Schema(description = "페이지네이션", example = "") + @Schema(description = "페이지네이션", example = "", required = true) private final PageMetaDto meta; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentWriterDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentWriterDto.java index 138952ac..ea624772 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentWriterDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentWriterDto.java @@ -6,7 +6,7 @@ import lombok.Getter; @Getter -@Schema(name = "CommentWriterDto", description = "댓글 작성자 객체 Dto") +@Schema(name = "CommentWriterDto", description = "댓글 작성자 객체 Dto", required = true) public class CommentWriterDto { @Schema(description = "댓글 id", example = "1") From fcd6353ab2f32cce1f6b2611b6b836abeb9b9c74 Mon Sep 17 00:00:00 2001 From: mikekks Date: Tue, 13 Aug 2024 00:12:56 +0900 Subject: [PATCH 18/47] =?UTF-8?q?fix:=20=EC=8A=A4=EC=9B=A8=EA=B1=B0=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../makers/crew/main/comment/v2/dto/response/CommentDto.java | 2 +- .../v2/dto/response/CommentV2GetCommentsResponseDto.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java index 874006c6..8dff4286 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java @@ -14,7 +14,7 @@ import lombok.Getter; @Getter -@Schema(name = "CommentDto", description = "댓글 객체 응답 Dto", required = true) +@Schema(name = "CommentDto", description = "댓글 객체 응답 Dto") public class CommentDto { @Schema(description = "댓글 id", example = "1", required = true) diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2GetCommentsResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2GetCommentsResponseDto.java index f15ef253..65cf4013 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2GetCommentsResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2GetCommentsResponseDto.java @@ -11,7 +11,7 @@ @Getter @AllArgsConstructor(staticName = "of") -@Schema(name = "CommentV2GetCommentsResponseDto", description = "댓글 목록 조회 응답 Dto", required = true) +@Schema(name = "CommentV2GetCommentsResponseDto", description = "댓글 목록 조회 응답 Dto") public class CommentV2GetCommentsResponseDto { @ArraySchema( From cda9f3dba1a6548b94c8e2a64aa13d20729d8acc Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Tue, 13 Aug 2024 13:01:54 +0900 Subject: [PATCH 19/47] =?UTF-8?q?[CHORE]=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=20->=20=EC=83=9D=EC=84=B1=EC=8B=9C=EC=A0=90=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20(#304)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: 업데이트 -> 생성시점으로 변경 * chore: 업데이트 -> 생성시점으로 변경 * chore: userId -> orgId로 변경 * [FIX] playgroundId -> orgId로 변경 * [ADD] 컬럼 설명 추가 --- .../CommentV2MentionUserInCommentRequestDto.java | 8 ++------ .../crew/main/comment/v2/dto/response/CommentDto.java | 10 +++++----- .../main/comment/v2/dto/response/CommentWriterDto.java | 4 ++-- .../crew/main/comment/v2/dto/response/ReplyDto.java | 10 +++++----- .../main/comment/v2/service/CommentV2ServiceImpl.java | 2 +- .../dto/request/PostV2MentionUserInPostRequestDto.java | 4 ++-- .../crew/main/post/v2/service/PostV2ServiceImpl.java | 2 +- .../v2/dto/response/UserV2GetAllMentionUserDto.java | 8 +++----- 8 files changed, 21 insertions(+), 27 deletions(-) diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/request/CommentV2MentionUserInCommentRequestDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/request/CommentV2MentionUserInCommentRequestDto.java index d59a8daa..6dffdb39 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/request/CommentV2MentionUserInCommentRequestDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/request/CommentV2MentionUserInCommentRequestDto.java @@ -14,13 +14,9 @@ @Schema(description = "댓글에서 유저 언급 request body dto") public class CommentV2MentionUserInCommentRequestDto { - /** - * 주의!! : 필드명은 userIds 이지만 실제 요청받는 값은 orgId 입니다. - */ - - @Schema(example = "[111, 112, 113]", required = true, description = "언급할 유저 ID, orgId 의미") + @Schema(example = "[111, 112, 113]", required = true, description = "메이커스 프로덕트에서 범용적으로 사용하는 userId") @NotEmpty - private List userIds; + private List orgIds; @Schema(example = "1", required = true, description = "게시글 ID") @NotNull diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java index 8dff4286..45bcd505 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java @@ -26,8 +26,8 @@ public class CommentDto { @Schema(description = "댓글 작성자 객체", example = "", required = true) private final CommentWriterDto user; - @Schema(description = "댓글 업데이트 시간", example = "2024-07-31T15:30:00", required = true) - private final LocalDateTime updatedDate; + @Schema(description = "댓글 생성 시점", example = "2024-07-31T15:30:00") + private final LocalDateTime createdDate; @Schema(description = "좋아요 갯수", example = "20") private final int likeCount; @@ -45,12 +45,12 @@ public class CommentDto { private final List replies; @QueryProjection - public CommentDto(Integer id, String contents, CommentWriterDto user, LocalDateTime updatedDate, int likeCount, + public CommentDto(Integer id, String contents, CommentWriterDto user, LocalDateTime createdDate, int likeCount, boolean isLiked, boolean isWriter, int order, List replies) { this.id = id; this.contents = contents; this.user = user; - this.updatedDate = updatedDate; + this.createdDate = createdDate; this.likeCount = likeCount; this.isLiked = isLiked; this.isWriter = isWriter; @@ -66,7 +66,7 @@ public static CommentDto of(Comment comment, boolean isLiked, boolean isWriter, return new CommentDto(comment.getId(), comment.getContents(), new CommentWriterDto(userId, orgId, userName, - profileImage), comment.getUpdatedDate(), comment.getLikeCount(), + profileImage), comment.getCreatedDate(), comment.getLikeCount(), isLiked, isWriter, comment.getOrder(), replies); } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentWriterDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentWriterDto.java index ea624772..96a11d65 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentWriterDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentWriterDto.java @@ -9,10 +9,10 @@ @Schema(name = "CommentWriterDto", description = "댓글 작성자 객체 Dto", required = true) public class CommentWriterDto { - @Schema(description = "댓글 id", example = "1") + @Schema(description = "크루에서 사용하는 userId", example = "1") private final Integer id; - @Schema(description = "댓글 org id", example = "2") + @Schema(description = "메이커스 프로덕트에서 범용적으로 사용하는 userId", example = "2") private final Integer orgId; @Schema(description = "댓글 작성자 이름", example = "홍길동") diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/ReplyDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/ReplyDto.java index ce2349da..ec9adb42 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/ReplyDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/ReplyDto.java @@ -19,8 +19,8 @@ public class ReplyDto { @Schema(description = "댓글 작성자 객체", example = "") private final CommentWriterDto user; - @Schema(description = "댓글 업데이트 시간", example = "2024-07-31T15:30:00") - private final LocalDateTime updatedDate; + @Schema(description = "댓글 생성 시점", example = "2024-07-31T15:30:00") + private final LocalDateTime createdDate; @Schema(description = "좋아요 갯수", example = "20") private final int likeCount; @@ -35,12 +35,12 @@ public class ReplyDto { private final int order; @QueryProjection - public ReplyDto(Integer id, String contents, CommentWriterDto user, LocalDateTime updatedDate, int likeCount, + public ReplyDto(Integer id, String contents, CommentWriterDto user, LocalDateTime createdDate, int likeCount, Boolean isLiked, Boolean isWriter, int order) { this.id = id; this.contents = contents; this.user = user; - this.updatedDate = updatedDate; + this.createdDate = createdDate; this.likeCount = likeCount; this.isLiked = isLiked; this.isWriter = isWriter; @@ -50,7 +50,7 @@ public ReplyDto(Integer id, String contents, CommentWriterDto user, LocalDateTim public static ReplyDto of(Comment comment, boolean isLiked, boolean isWriter) { return new ReplyDto(comment.getId(), comment.getContents(), new CommentWriterDto(comment.getUser().getId(), comment.getUser().getOrgId(), comment.getUser().getName(), - comment.getUser().getProfileImage()), comment.getUpdatedDate(), comment.getLikeCount(), + comment.getUser().getProfileImage()), comment.getCreatedDate(), comment.getLikeCount(), isLiked, isWriter, comment.getOrder()); } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java index 10a05a49..a3b7c9cf 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java @@ -256,7 +256,7 @@ public void mentionUserInComment(CommentV2MentionUserInCommentRequestDto request String pushNotificationContent = "\"" + requestBody.getContent() + "\""; String pushNotificationWeblink = pushWebUrl + "/post?id=" + post.getId(); - String[] userOrgIds = requestBody.getUserIds().stream() + String[] userOrgIds = requestBody.getOrgIds().stream() .map(Object::toString) .toArray(String[]::new); diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/request/PostV2MentionUserInPostRequestDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/request/PostV2MentionUserInPostRequestDto.java index 5e04e711..f43dff2b 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/request/PostV2MentionUserInPostRequestDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/request/PostV2MentionUserInPostRequestDto.java @@ -14,9 +14,9 @@ @Schema(description = "모임 게시글에서 유저 언급 request body dto") public class PostV2MentionUserInPostRequestDto { - @Schema(example = "[111, 112, 113]", required = true, description = "언급할 유저 ID") + @Schema(example = "[111, 112, 113]", required = true, description = "메이커스 프로덕트에서 범용적으로 사용하는 userId") @NotEmpty - private final List userIds; + private final List orgIds; @Schema(example = "1", required = true, description = "게시글 ID") @NotNull diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java index 9144cf33..418b49b2 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java @@ -147,7 +147,7 @@ public void mentionUserInPost(PostV2MentionUserInPostRequestDto requestBody, Int user.getName(), post.getTitle()); String pushNotificationWeblink = pushWebUrl + "/post?id=" + post.getId(); - String[] userOrgIds = requestBody.getUserIds().stream() + String[] userOrgIds = requestBody.getOrgIds().stream() .map(Object::toString) .toArray(String[]::new); diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetAllMentionUserDto.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetAllMentionUserDto.java index 396fc195..ee85c500 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetAllMentionUserDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetAllMentionUserDto.java @@ -8,11 +8,9 @@ @AllArgsConstructor(staticName = "of") @Schema(name = "UserV2GetAllMentionUserDto", description = "멘션 유저 조회 응답 Dto") public class UserV2GetAllMentionUserDto { - /** - * 주의!! : 필드명은 userId 이지만 실제 응답 해야하는 데이터는 orgId 입니다. - */ - @Schema(description = "유저 id", example = "1") - private final Integer userId; + + @Schema(description = "메이커스 프로덕트에서 범용적으로 사용하는 userId", example = "1") + private final Integer orgId; @Schema(description = "유저 이름", example = "홍길") private final String userName; From b7124c6d08fc721ec48d984efc4962f69fec9c35 Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Tue, 13 Aug 2024 16:03:58 +0900 Subject: [PATCH 20/47] =?UTF-8?q?[CHORE]=20FE=20=EC=9A=94=EC=B2=AD?= =?UTF-8?q?=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EC=88=98=EC=A0=95=20(#306)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [CHORE] response 응답에 @NotNull 추가 * [CHORE] userId, orgId 주석 추가 * [CHORE] 광고 조회 API에 category 추가 * [CHORE] Enumerate 추가 * [DOCS] 스웨거 설명 추가 --- .../main/advertisement/AdvertisementApi.java | 7 +- .../AdvertisementController.java | 9 +- .../dto/AdvertisementGetResponseDto.java | 3 + .../service/AdvertisementService.java | 7 +- .../query/CommentV2GetCommentsQueryDto.java | 2 +- .../CommentV2CreateCommentBodyDto.java | 1 + ...mmentV2MentionUserInCommentRequestDto.java | 7 +- .../CommentV2UpdateCommentBodyDto.java | 8 +- .../comment/v2/dto/response/CommentDto.java | 16 +- .../CommentV2CreateCommentResponseDto.java | 13 +- .../CommentV2GetCommentsResponseDto.java | 7 +- .../CommentV2ReportCommentResponseDto.java | 13 +- ...CommentV2SwitchCommentLikeResponseDto.java | 3 + .../CommentV2UpdateCommentResponseDto.java | 35 ++-- .../v2/dto/response/CommentWriterDto.java | 7 +- .../comment/v2/dto/response/ReplyDto.java | 9 + .../crew/main/common/dto/TempResponseDto.java | 6 + .../common/pagination/dto/PageMetaDto.java | 59 ++++--- .../entity/advertisement/Advertisement.java | 12 +- .../AdvertisementRepository.java | 4 +- .../enums/AdvertisementCategory.java | 5 + .../MeetingV2CreateMeetingBodyDto.java | 155 +++++++++--------- .../meeting/v2/dto/response/ApplicantDto.java | 52 +++--- .../meeting/v2/dto/response/ApplyInfoDto.java | 5 + .../MeetingGetApplyListResponseDto.java | 11 +- .../MeetingV2ApplyMeetingResponseDto.java | 7 +- .../MeetingV2CreateMeetingResponseDto.java | 6 +- .../MeetingV2GetAllMeetingByOrgUserDto.java | 11 +- ...ingV2GetAllMeetingByOrgUserMeetingDto.java | 41 +++-- .../MeetingV2GetMeetingBannerResponseDto.java | 152 ++++++++++------- ...tingV2GetMeetingBannerResponseUserDto.java | 32 ++-- .../dto/request/NoticeV2CreateRequestDto.java | 13 +- .../dto/response/NoticeV2GetResponseDto.java | 18 ++ .../dto/request/PostV2CreatePostBodyDto.java | 32 ++-- .../PostV2MentionUserInPostRequestDto.java | 1 + .../v2/dto/response/CommenterThumbnails.java | 5 + .../v2/dto/response/PostDetailBaseDto.java | 12 ++ .../dto/response/PostDetailResponseDto.java | 13 ++ .../post/v2/dto/response/PostMeetingDto.java | 12 +- .../response/PostV2CreatePostResponseDto.java | 12 +- .../PostV2GetPostCountResponseDto.java | 12 +- .../response/PostV2GetPostsResponseDto.java | 11 +- .../v2/dto/response/PostWriterInfoDto.java | 13 +- .../UserV2GetAllMeetingByUserMeetingDto.java | 6 + .../response/UserV2GetAllMentionUserDto.java | 25 +-- 45 files changed, 571 insertions(+), 319 deletions(-) create mode 100644 main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/enums/AdvertisementCategory.java diff --git a/main/src/main/java/org/sopt/makers/crew/main/advertisement/AdvertisementApi.java b/main/src/main/java/org/sopt/makers/crew/main/advertisement/AdvertisementApi.java index 8b3733bd..2bd9dbe9 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/advertisement/AdvertisementApi.java +++ b/main/src/main/java/org/sopt/makers/crew/main/advertisement/AdvertisementApi.java @@ -3,8 +3,10 @@ import java.security.Principal; import org.sopt.makers.crew.main.advertisement.dto.AdvertisementGetResponseDto; +import org.sopt.makers.crew.main.entity.advertisement.enums.AdvertisementCategory; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseStatus; import io.swagger.v3.oas.annotations.Operation; @@ -14,13 +16,14 @@ @Tag(name = "광고") public interface AdvertisementApi { - @Operation(summary = "광고 조회") + @Operation(summary = "광고 조회", description = "게시글 목록 페이지일 경우, ?category=POST
모임 목록 페이지일 경우, ?category=MEETING") @ResponseStatus(HttpStatus.OK) @ApiResponses(value = { @ApiResponse( responseCode = "200", description = "성공"), }) - ResponseEntity getAdvertisement(Principal principal); + ResponseEntity getAdvertisement(@RequestParam(name = "category", required = true) AdvertisementCategory category, + Principal principal); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/advertisement/AdvertisementController.java b/main/src/main/java/org/sopt/makers/crew/main/advertisement/AdvertisementController.java index 530c3562..31c5533e 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/advertisement/AdvertisementController.java +++ b/main/src/main/java/org/sopt/makers/crew/main/advertisement/AdvertisementController.java @@ -4,9 +4,11 @@ import org.sopt.makers.crew.main.advertisement.dto.AdvertisementGetResponseDto; import org.sopt.makers.crew.main.advertisement.service.AdvertisementService; +import org.sopt.makers.crew.main.entity.advertisement.enums.AdvertisementCategory; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import lombok.RequiredArgsConstructor; @@ -20,8 +22,11 @@ public class AdvertisementController implements AdvertisementApi { @Override @GetMapping - public ResponseEntity getAdvertisement(Principal principal) { - AdvertisementGetResponseDto response = advertisementService.getAdvertisement(); + public ResponseEntity getAdvertisement( + @RequestParam(name = "category") AdvertisementCategory category, + Principal principal) { + + AdvertisementGetResponseDto response = advertisementService.getAdvertisement(category); return ResponseEntity.ok().body(response); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementGetResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementGetResponseDto.java index abbf2161..226f3694 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementGetResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementGetResponseDto.java @@ -3,12 +3,15 @@ import org.sopt.makers.crew.main.entity.advertisement.Advertisement; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; @Schema(name = "AdvertisementGetResponseDto", description = "광고 구좌 조회 응답 Dto") public record AdvertisementGetResponseDto( @Schema(description = "광고 구좌 이미지 url", example = "[image Url]") + @NotNull String advertisementImageUrl, @Schema(description = "광고 구좌 링크", example = "https://www.naver.com") + @NotNull String advertisementLink ) { public static AdvertisementGetResponseDto of(Advertisement advertisement){ diff --git a/main/src/main/java/org/sopt/makers/crew/main/advertisement/service/AdvertisementService.java b/main/src/main/java/org/sopt/makers/crew/main/advertisement/service/AdvertisementService.java index 367416f9..4106fd14 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/advertisement/service/AdvertisementService.java +++ b/main/src/main/java/org/sopt/makers/crew/main/advertisement/service/AdvertisementService.java @@ -7,6 +7,7 @@ import org.sopt.makers.crew.main.common.util.Time; import org.sopt.makers.crew.main.entity.advertisement.Advertisement; import org.sopt.makers.crew.main.entity.advertisement.AdvertisementRepository; +import org.sopt.makers.crew.main.entity.advertisement.enums.AdvertisementCategory; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -20,9 +21,9 @@ public class AdvertisementService { private final Time time; - public AdvertisementGetResponseDto getAdvertisement() { - Optional advertisement = advertisementRepository.findFirstByAdvertisementEndDateBeforeAndAdvertisementStartDateAfterOrderByPriority( - time.now(), time.now()); + public AdvertisementGetResponseDto getAdvertisement(AdvertisementCategory advertisementCategory) { + Optional advertisement = advertisementRepository.findFirstByAdvertisementCategoryAndAdvertisementEndDateBeforeAndAdvertisementStartDateAfterOrderByPriority( + advertisementCategory, time.now(), time.now()); return advertisement .map(AdvertisementGetResponseDto::of) diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/query/CommentV2GetCommentsQueryDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/query/CommentV2GetCommentsQueryDto.java index 13eb435d..54a63d6a 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/query/CommentV2GetCommentsQueryDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/query/CommentV2GetCommentsQueryDto.java @@ -11,8 +11,8 @@ @Schema(name = "CommentV2GetCommentsQueryDto", description = "댓글 목록 요청 Dto") public class CommentV2GetCommentsQueryDto extends PageOptionsDto { - @NotNull @Schema(description = "게시글 id", example = "1") + @NotNull private final Integer postId; @Builder diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/request/CommentV2CreateCommentBodyDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/request/CommentV2CreateCommentBodyDto.java index a25e1435..e95b2939 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/request/CommentV2CreateCommentBodyDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/request/CommentV2CreateCommentBodyDto.java @@ -22,6 +22,7 @@ public class CommentV2CreateCommentBodyDto { private String contents; @Schema(example = "true", description = "댓글/대댓글 여부") + @NotNull private Boolean isParent; @Schema(example = "3", description = "대댓글인 경우, 댓글의 id") diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/request/CommentV2MentionUserInCommentRequestDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/request/CommentV2MentionUserInCommentRequestDto.java index 6dffdb39..e7114b41 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/request/CommentV2MentionUserInCommentRequestDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/request/CommentV2MentionUserInCommentRequestDto.java @@ -14,14 +14,15 @@ @Schema(description = "댓글에서 유저 언급 request body dto") public class CommentV2MentionUserInCommentRequestDto { - @Schema(example = "[111, 112, 113]", required = true, description = "메이커스 프로덕트에서 범용적으로 사용하는 userId") + @Schema(example = "[111, 112, 113]", description = "메이커스 프로덕트에서 범용적으로 사용하는 userId") @NotEmpty private List orgIds; - @Schema(example = "1", required = true, description = "게시글 ID") + @Schema(example = "1", description = "게시글 ID") @NotNull private Integer postId; - @Schema(example = "멘션내용~~", required = true, description = "멘션 내용") + @Schema(example = "멘션내용~~", description = "멘션 내용") + @NotNull private String content; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/request/CommentV2UpdateCommentBodyDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/request/CommentV2UpdateCommentBodyDto.java index f702ed16..8668ef69 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/request/CommentV2UpdateCommentBodyDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/request/CommentV2UpdateCommentBodyDto.java @@ -9,11 +9,11 @@ @Getter @AllArgsConstructor @NoArgsConstructor -@Schema(description = "댓글 업데이트 request body dto") +@Schema(description = "댓글 수정 request body dto") public class CommentV2UpdateCommentBodyDto { - @Schema(example = "알고보면 쓸데있는 개발 프로세스", description = "댓글 내용") - @NotEmpty - private String contents; + @Schema(example = "알고보면 쓸데있는 개발 프로세스", description = "댓글 내용") + @NotEmpty + private String contents; } \ No newline at end of file diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java index 45bcd505..7c48f1f4 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentDto.java @@ -11,37 +11,47 @@ import com.querydsl.core.annotations.QueryProjection; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Getter; @Getter @Schema(name = "CommentDto", description = "댓글 객체 응답 Dto") public class CommentDto { - @Schema(description = "댓글 id", example = "1", required = true) + @Schema(description = "댓글 id", example = "1") + @NotNull private final Integer id; - @Schema(description = "댓글 내용", example = "이것은 댓글 내용입니다.", required = true) + @Schema(description = "댓글 내용", example = "이것은 댓글 내용입니다.") + @NotNull private final String contents; - @Schema(description = "댓글 작성자 객체", example = "", required = true) + @Schema(description = "댓글 작성자 객체", example = "") + @NotNull private final CommentWriterDto user; @Schema(description = "댓글 생성 시점", example = "2024-07-31T15:30:00") + @NotNull private final LocalDateTime createdDate; @Schema(description = "좋아요 갯수", example = "20") + @NotNull private final int likeCount; @Schema(description = "댓글 좋아요 여부", example = "true") + @NotNull private final Boolean isLiked; @Schema(description = "댓글 작성자 여부", example = "true") + @NotNull private final Boolean isWriter; @Schema(description = "댓글 순서", example = "2") + @NotNull private final int order; @Schema(description = "대댓글 객체 목록", example = "") + @NotNull private final List replies; @QueryProjection diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2CreateCommentResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2CreateCommentResponseDto.java index f96ca68f..3afa311b 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2CreateCommentResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2CreateCommentResponseDto.java @@ -1,6 +1,7 @@ package org.sopt.makers.crew.main.comment.v2.dto.response; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; @@ -9,9 +10,11 @@ @Schema(name = "CommentV2CreateCommentResponseDto", description = "댓글 생성 응답 Dto") public class CommentV2CreateCommentResponseDto { - /** - * 생성된 댓글 id - */ - @Schema(description = "생성된 댓글 id", example = "1") - private Integer commentId; + /** + * 생성된 댓글 id + */ + @Schema(description = "생성된 댓글 id", example = "1") + @NotNull + private Integer commentId; + } diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2GetCommentsResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2GetCommentsResponseDto.java index 65cf4013..945ac676 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2GetCommentsResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2GetCommentsResponseDto.java @@ -6,6 +6,7 @@ import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; @@ -15,11 +16,13 @@ public class CommentV2GetCommentsResponseDto { @ArraySchema( - schema = @Schema(implementation = CommentDto.class, required = true) + schema = @Schema(implementation = CommentDto.class) ) + @NotNull private final List comments; - @Schema(description = "페이지네이션", example = "", required = true) + @Schema(description = "페이지네이션", example = "") + @NotNull private final PageMetaDto meta; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2ReportCommentResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2ReportCommentResponseDto.java index a7021905..0f5ed43a 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2ReportCommentResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2ReportCommentResponseDto.java @@ -1,6 +1,7 @@ package org.sopt.makers.crew.main.comment.v2.dto.response; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; @@ -9,9 +10,11 @@ @Schema(name = "CommentV2ReportCommentResponseDto", description = "댓글 신고 응답 Dto") public class CommentV2ReportCommentResponseDto { - /** - * 생성된 신고 id - */ - @Schema(description = "신고 id", example = "1") - private Integer reportId; + /** + * 생성된 신고 id + */ + @Schema(description = "신고 id", example = "1") + @NotNull + private Integer reportId; + } diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2SwitchCommentLikeResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2SwitchCommentLikeResponseDto.java index 9b264972..d58b648a 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2SwitchCommentLikeResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2SwitchCommentLikeResponseDto.java @@ -1,6 +1,7 @@ package org.sopt.makers.crew.main.comment.v2.dto.response; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; @@ -13,5 +14,7 @@ public class CommentV2SwitchCommentLikeResponseDto { * 요청 후 내가 좋아요를 누른 상태 */ @Schema(description = "요청 후 내가 좋아요를 누른 상태", example = "false") + @NotNull private Boolean isLiked; + } diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2UpdateCommentResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2UpdateCommentResponseDto.java index e1a11bb0..0bbc0d94 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2UpdateCommentResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentV2UpdateCommentResponseDto.java @@ -1,6 +1,7 @@ package org.sopt.makers.crew.main.comment.v2.dto.response; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; @@ -9,21 +10,25 @@ @Schema(name = "CommentV2UpdateCommentResponseDto", description = "댓글 수정 응답 Dto") public class CommentV2UpdateCommentResponseDto { - /** - * 생성된 댓글 id - */ - @Schema(description = "수정된 댓글 id", example = "1") - private Integer id; + /** + * 생성된 댓글 id + */ + @Schema(description = "수정된 댓글 id", example = "1") + @NotNull + private Integer id; - /** - * 댓글 내용 - */ - @Schema(description = "수정된 댓글 내용", example = "댓글내용입니다1") - private String contents; + /** + * 댓글 내용 + */ + @Schema(description = "수정된 댓글 내용", example = "댓글내용입니다1") + @NotNull + private String contents; + + /** + * 업데이트 시각 + */ + @Schema(description = "수정된 시간", example = "2024-07-31T15:30:00") + @NotNull + private String updateDate; - /** - * 업데이트 시각 - */ - @Schema(description = "수정된 시간", example = "2024-07-31T15:30:00") - private String updateDate; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentWriterDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentWriterDto.java index 96a11d65..905be111 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentWriterDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/CommentWriterDto.java @@ -3,22 +3,27 @@ import com.querydsl.core.annotations.QueryProjection; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Getter; @Getter -@Schema(name = "CommentWriterDto", description = "댓글 작성자 객체 Dto", required = true) +@Schema(name = "CommentWriterDto", description = "댓글 작성자 객체 Dto") public class CommentWriterDto { @Schema(description = "크루에서 사용하는 userId", example = "1") + @NotNull private final Integer id; @Schema(description = "메이커스 프로덕트에서 범용적으로 사용하는 userId", example = "2") + @NotNull private final Integer orgId; @Schema(description = "댓글 작성자 이름", example = "홍길동") + @NotNull private final String name; @Schema(description = "댓글 작성자 프로필 사진", example = "[url] 형식") + @NotNull private final String profileImage; @QueryProjection diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/ReplyDto.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/ReplyDto.java index ec9adb42..479c1986 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/ReplyDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/dto/response/ReplyDto.java @@ -4,6 +4,7 @@ import org.sopt.makers.crew.main.entity.comment.Comment; import com.querydsl.core.annotations.QueryProjection; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Getter; @Getter @@ -11,27 +12,35 @@ public class ReplyDto { @Schema(description = "대댓글 id", example = "1") + @NotNull private final Integer id; @Schema(description = "댓글 내용", example = "이것은 댓글 내용입니다.") + @NotNull private final String contents; @Schema(description = "댓글 작성자 객체", example = "") + @NotNull private final CommentWriterDto user; @Schema(description = "댓글 생성 시점", example = "2024-07-31T15:30:00") + @NotNull private final LocalDateTime createdDate; @Schema(description = "좋아요 갯수", example = "20") + @NotNull private final int likeCount; @Schema(description = "댓글 좋아요 여부", example = "true") + @NotNull private final Boolean isLiked; @Schema(description = "댓글 작성자 여부", example = "true") + @NotNull private final Boolean isWriter; @Schema(description = "댓글 순서", example = "2") + @NotNull private final int order; @QueryProjection diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/dto/TempResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/common/dto/TempResponseDto.java index db94f757..7f24aa92 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/dto/TempResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/dto/TempResponseDto.java @@ -1,11 +1,17 @@ package org.sopt.makers.crew.main.common.dto; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor +@Schema(name = "TempResponseDto", description = "임시 응답 Dto") public class TempResponseDto { + + @Schema(description = "임시 응답", example = "") + @NotNull private T data; public static TempResponseDto of(T data) { diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/pagination/dto/PageMetaDto.java b/main/src/main/java/org/sopt/makers/crew/main/common/pagination/dto/PageMetaDto.java index c987a0c9..ab8bd34e 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/pagination/dto/PageMetaDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/pagination/dto/PageMetaDto.java @@ -1,35 +1,42 @@ package org.sopt.makers.crew.main.common.pagination.dto; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Getter; @Getter public class PageMetaDto { - @Schema(description = "페이지 위치") - private int page; - - @Schema(description = "가져올 데이터 개수") - private int take; - - @Schema(description = "응답 데이터 개수") - private int itemCount; - - @Schema(description = "총 페이지 수") - private int pageCount; - - @Schema(description = "이전 페이지가 있는지 유무") - private boolean hasPreviousPage; - - @Schema(description = "다음 페이지가 있는지 유무") - private boolean hasNextPage; - - public PageMetaDto(PageOptionsDto pageOptionsDto, int itemCount) { - this.page = pageOptionsDto.getPage(); - this.take = pageOptionsDto.getTake(); - this.itemCount = itemCount; - this.pageCount = (int) Math.ceil((double) this.itemCount / this.take); - this.hasPreviousPage = this.page > 1; - this.hasNextPage = this.page < this.pageCount; - } + @Schema(description = "페이지 위치") + @NotNull + private int page; + + @Schema(description = "가져올 데이터 개수") + @NotNull + private int take; + + @Schema(description = "응답 데이터 개수") + @NotNull + private int itemCount; + + @Schema(description = "총 페이지 수") + @NotNull + private int pageCount; + + @Schema(description = "이전 페이지가 있는지 유무") + @NotNull + private boolean hasPreviousPage; + + @Schema(description = "다음 페이지가 있는지 유무") + @NotNull + private boolean hasNextPage; + + public PageMetaDto(PageOptionsDto pageOptionsDto, int itemCount) { + this.page = pageOptionsDto.getPage(); + this.take = pageOptionsDto.getTake(); + this.itemCount = itemCount; + this.pageCount = (int)Math.ceil((double)this.itemCount / this.take); + this.hasPreviousPage = this.page > 1; + this.hasNextPage = this.page < this.pageCount; + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/Advertisement.java b/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/Advertisement.java index e93ccad4..eff52dc7 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/Advertisement.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/Advertisement.java @@ -4,10 +4,13 @@ import java.time.LocalDateTime; +import org.sopt.makers.crew.main.entity.advertisement.enums.AdvertisementCategory; import org.springframework.data.jpa.domain.support.AuditingEntityListener; import jakarta.persistence.Entity; import jakarta.persistence.EntityListeners; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import jakarta.persistence.Table; @@ -36,6 +39,10 @@ public class Advertisement { @NotNull private String advertisementLink; + @NotNull + @Enumerated(EnumType.STRING) + private AdvertisementCategory advertisementCategory; + @NotNull private Long priority; @@ -46,10 +53,13 @@ public class Advertisement { private LocalDateTime advertisementEndDate; @Builder - private Advertisement(String advertisementImageUrl, String advertisementLink, LocalDateTime advertisementStartDate, + private Advertisement(String advertisementImageUrl, String advertisementLink, + AdvertisementCategory advertisementCategory, Long priority, LocalDateTime advertisementStartDate, LocalDateTime advertisementEndDate) { this.advertisementImageUrl = advertisementImageUrl; this.advertisementLink = advertisementLink; + this.advertisementCategory = advertisementCategory; + this.priority = priority; this.advertisementStartDate = advertisementStartDate; this.advertisementEndDate = advertisementEndDate; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/AdvertisementRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/AdvertisementRepository.java index 14fadcab..90858448 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/AdvertisementRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/AdvertisementRepository.java @@ -3,8 +3,10 @@ import java.time.LocalDateTime; import java.util.Optional; +import org.sopt.makers.crew.main.entity.advertisement.enums.AdvertisementCategory; import org.springframework.data.jpa.repository.JpaRepository; public interface AdvertisementRepository extends JpaRepository { - Optional findFirstByAdvertisementEndDateBeforeAndAdvertisementStartDateAfterOrderByPriority(LocalDateTime now1, LocalDateTime now2); + Optional findFirstByAdvertisementCategoryAndAdvertisementEndDateBeforeAndAdvertisementStartDateAfterOrderByPriority( + AdvertisementCategory advertisementCategory, LocalDateTime now1, LocalDateTime now2); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/enums/AdvertisementCategory.java b/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/enums/AdvertisementCategory.java new file mode 100644 index 00000000..5ae13ded --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/enums/AdvertisementCategory.java @@ -0,0 +1,5 @@ +package org.sopt.makers.crew.main.entity.advertisement.enums; + +public enum AdvertisementCategory { + POST, MEETING +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/request/MeetingV2CreateMeetingBodyDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/request/MeetingV2CreateMeetingBodyDto.java index 26e53643..04003c41 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/request/MeetingV2CreateMeetingBodyDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/request/MeetingV2CreateMeetingBodyDto.java @@ -4,9 +4,12 @@ import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; + import java.util.List; + import lombok.AllArgsConstructor; import lombok.Getter; + import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart; import com.fasterxml.jackson.annotation.JsonProperty; @@ -16,80 +19,80 @@ @Schema(description = "모임 생성 request body dto") public class MeetingV2CreateMeetingBodyDto { - @Schema(example = "알고보면 쓸데있는 개발 프로세스", description = "모임 제목") - @NotNull - private String title; - - @Schema(example = "[\n" - + " \"https://makers-web-img.s3.ap-northeast-2.amazonaws.com/meeting/2023/04/12/7bd87736-b557-4b26-a0d5-9b09f1f1d7df\"\n" - + " ]", description = "모임 이미지 리스트, 최대 6개") - @NotEmpty - @Size(max=6) - private List files; - - @Schema(example = "스터디", description = "모임 카테고리") - @NotNull - private String category; - - @Schema(example = "2022.10.08", description = "모집 기간 시작 날짜") - @NotNull - private String startDate; - - @Schema(example = "2022.10.09", description = "모집 기간 끝 날짜") - @NotNull - private String endDate; - - @Schema(example = "5", description = "모집 인원") - @NotNull - private Integer capacity; - - @Schema(example = "api 가 터졌다고? 깃이 터졌다고?", description = "모집 정보") - @NotNull - private String desc; - - @Schema(example = "소요 시간 : 1시간 예상", description = "진행 방식 소개") - @NotNull - private String processDesc; - - @Schema(example = "2022.10.29", description = "모임 활동 시작 날짜", name = "mStartDate") - @NotNull - private String mStartDate; - - @Schema(example = "2022.10.30", description = "모임 활동 종료 날짜", name = "mEndDate") - @NotNull - private String mEndDate; - - @Schema(example = "안녕하세요 기획 파트 000입니다", description = "개설자 소개") - @NotNull - private String leaderDesc; - - @Schema(example = "개발 모르는 사람도 환영", description = "모집 대상 소개") - @NotNull - private String targetDesc; - - @Schema(example = "유의할 사항", description = "유의할 사항") - private String note; - - @Schema(example = "false", description = "멘토 필요 여부") - @NotNull - private Boolean isMentorNeeded; - - @Schema(example = "false", description = "활동기수만 지원 가능 여부") - @NotNull - private Boolean canJoinOnlyActiveGeneration; - - @Schema(example = "[\n" - + " \"ANDROID\",\n" - + " \"IOS\"\n" - + " ]", description = "대상 파트 목록") - @NotNull - private MeetingJoinablePart[] joinableParts; - - public String getmStartDate() { - return mStartDate; - } - - public String getmEndDate() { - return mEndDate; - } + @Schema(example = "알고보면 쓸데있는 개발 프로세스", description = "모임 제목") + @NotNull + private String title; + + @Schema(example = "[\n" + + " \"https://makers-web-img.s3.ap-northeast-2.amazonaws.com/meeting/2023/04/12/7bd87736-b557-4b26-a0d5-9b09f1f1d7df\"\n" + + " ]", description = "모임 이미지 리스트, 최대 6개") + @NotEmpty + @Size(max = 6) + private List files; + + @Schema(example = "스터디", description = "모임 카테고리") + @NotNull + private String category; + + @Schema(example = "2022.10.08", description = "모집 기간 시작 날짜") + @NotNull + private String startDate; + + @Schema(example = "2022.10.09", description = "모집 기간 끝 날짜") + @NotNull + private String endDate; + + @Schema(example = "5", description = "모집 인원") + @NotNull + private Integer capacity; + + @Schema(example = "api 가 터졌다고? 깃이 터졌다고?", description = "모집 정보") + @NotNull + private String desc; + + @Schema(example = "소요 시간 : 1시간 예상", description = "진행 방식 소개") + @NotNull + private String processDesc; + + @Schema(example = "2022.10.29", description = "모임 활동 시작 날짜", name = "mStartDate") + @NotNull + private String mStartDate; + + @Schema(example = "2022.10.30", description = "모임 활동 종료 날짜", name = "mEndDate") + @NotNull + private String mEndDate; + + @Schema(example = "안녕하세요 기획 파트 000입니다", description = "개설자 소개") + @NotNull + private String leaderDesc; + + @Schema(example = "개발 모르는 사람도 환영", description = "모집 대상 소개") + @NotNull + private String targetDesc; + + @Schema(example = "유의할 사항", description = "유의할 사항") + private String note; + + @Schema(example = "false", description = "멘토 필요 여부") + @NotNull + private Boolean isMentorNeeded; + + @Schema(example = "false", description = "활동기수만 지원 가능 여부") + @NotNull + private Boolean canJoinOnlyActiveGeneration; + + @Schema(example = "[\n" + + " \"ANDROID\",\n" + + " \"IOS\"\n" + + " ]", description = "대상 파트 목록") + @NotNull + private MeetingJoinablePart[] joinableParts; + + public String getmStartDate() { + return mStartDate; + } + + public String getmEndDate() { + return mEndDate; + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplicantDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplicantDto.java index 44e6d0db..9a23b106 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplicantDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplicantDto.java @@ -1,11 +1,14 @@ package org.sopt.makers.crew.main.meeting.v2.dto.response; import com.querydsl.core.annotations.QueryProjection; + import java.util.Comparator; import java.util.List; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Getter; + import org.sopt.makers.crew.main.common.util.UserUtil; import org.sopt.makers.crew.main.entity.user.vo.UserActivityVO; @@ -13,33 +16,38 @@ @Schema(name = "ApplicantDto", description = "모임 신청자 객체 Dto") public class ApplicantDto { - @Schema(description = "신청 id", example = "1") - private final Integer id; + @Schema(description = "신청자 id, 크루에서 사용하는 userId", example = "1") + @NotNull + private final Integer id; - @Schema(description = "신청자 이름", example = "송민규") - private final String name; + @Schema(description = "신청자 이름", example = "송민규") + @NotNull + private final String name; - @Schema(description = "신청자 org id", example = "1") - private final Integer orgId; + @Schema(description = "신청자 org id, 메이커스 프로덕트에서 범용적으로 사용하는 userId", example = "1") + @NotNull + private final Integer orgId; - @Schema(description = "신청자 기수 정보", example = "[{\"part\": \"웹\", \"generation\": 32}]") - private final UserActivityVO recentActivity; + @Schema(description = "신청자 기수 정보", example = "[{\"part\": \"웹\", \"generation\": 32}]") + @NotNull + private final UserActivityVO recentActivity; - @Schema(description = "신청자 프로필 사진", example = "[url] 형식") - private final String profileImage; + @Schema(description = "신청자 프로필 사진", example = "[url] 형식") + private final String profileImage; - @Schema(description = "신청자 핸드폰 번호", example = "010-1234-5678") - private final String phone; + @Schema(description = "신청자 핸드폰 번호", example = "010-1234-5678") + private final String phone; - @QueryProjection - public ApplicantDto(Integer id, String name, Integer orgId, List userActivityVOs, String profileImage, - String phone) { + @QueryProjection + public ApplicantDto(Integer id, String name, Integer orgId, List userActivityVOs, + String profileImage, + String phone) { - this.id = id; - this.name = name; - this.orgId = orgId; - this.recentActivity = UserUtil.getRecentUserActivity(userActivityVOs); - this.profileImage = profileImage; - this.phone = phone; - } + this.id = id; + this.name = name; + this.orgId = orgId; + this.recentActivity = UserUtil.getRecentUserActivity(userActivityVOs); + this.profileImage = profileImage; + this.phone = phone; + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplyInfoDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplyInfoDto.java index 0300437f..4e765c50 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplyInfoDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplyInfoDto.java @@ -4,6 +4,7 @@ import java.time.LocalDateTime; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Getter; import org.sopt.makers.crew.main.entity.apply.enums.EnApplyStatus; @@ -12,18 +13,22 @@ public class ApplyInfoDto { @Schema(description = "신청 id", example = "1") + @NotNull private final Integer id; @Schema(description = "전하는 말", example = "저 뽑아주세요.") private final String content; @Schema(description = "신청 시간", example = "2024-07-30T15:30:00") + @NotNull private final LocalDateTime appliedDate; @Schema(description = "신청 상태", example = "1") + @NotNull private final EnApplyStatus status; @Schema(description = "신청자 정보", example = "") + @NotNull private final ApplicantDto user; @QueryProjection diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingGetApplyListResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingGetApplyListResponseDto.java index 83911779..87e50fca 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingGetApplyListResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingGetApplyListResponseDto.java @@ -3,8 +3,10 @@ import java.util.List; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; + import org.sopt.makers.crew.main.common.pagination.dto.PageMetaDto; @Getter @@ -12,8 +14,11 @@ @Schema(name = "MeetingGetApplyListResponseDto", description = "모임 신청 목록 응답 Dto") public class MeetingGetApplyListResponseDto { - @Schema(description = "신청 목록", example = "") - private final List apply; + @Schema(description = "신청 목록", example = "") + @NotNull + private final List apply; + + @NotNull + private final PageMetaDto meta; - private final PageMetaDto meta; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2ApplyMeetingResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2ApplyMeetingResponseDto.java index 4588f545..790a264a 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2ApplyMeetingResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2ApplyMeetingResponseDto.java @@ -1,6 +1,7 @@ package org.sopt.makers.crew.main.meeting.v2.dto.response; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; @@ -9,6 +10,8 @@ @Schema(name = "MeetingV2ApplyMeetingResponseDto", description = "모임 신청 응답 Dto") public class MeetingV2ApplyMeetingResponseDto { - @Schema(description = "신청 id", example = "1") - private Integer applyId; + @Schema(description = "신청 id", example = "1") + @NotNull + private Integer applyId; + } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2CreateMeetingResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2CreateMeetingResponseDto.java index cddc9476..031ae3f8 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2CreateMeetingResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2CreateMeetingResponseDto.java @@ -1,6 +1,7 @@ package org.sopt.makers.crew.main.meeting.v2.dto.response; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; @@ -9,6 +10,7 @@ @Schema(name = "MeetingV2CreateMeetingResponseDto", description = "모임 생성 응답 Dto") public class MeetingV2CreateMeetingResponseDto { - @Schema(description = "모임 id", example = "1") - private Integer meetingId; + @Schema(description = "모임 id", example = "1") + @NotNull + private Integer meetingId; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetAllMeetingByOrgUserDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetAllMeetingByOrgUserDto.java index 6c7515d4..2fea813b 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetAllMeetingByOrgUserDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetAllMeetingByOrgUserDto.java @@ -3,8 +3,10 @@ import java.util.List; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; + import org.sopt.makers.crew.main.common.pagination.dto.PageMetaDto; @Getter @@ -12,8 +14,11 @@ @Schema(name = "MeetingV2GetAllMeetingByOrgUserDto", description = "모임 조회 응답 Dto") public class MeetingV2GetAllMeetingByOrgUserDto { - @Schema(description = "모임 객체 목록", example = "") - private List meetings; + @Schema(description = "모임 객체 목록", example = "") + @NotNull + private List meetings; + + @NotNull + private PageMetaDto meta; - private PageMetaDto meta; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetAllMeetingByOrgUserMeetingDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetAllMeetingByOrgUserMeetingDto.java index 0d99c872..4640d7b0 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetAllMeetingByOrgUserMeetingDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetAllMeetingByOrgUserMeetingDto.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; @@ -13,27 +14,35 @@ @Schema(name = "MeetingV2GetAllMeetingByOrgUserMeetingDto", description = "모임 객체 Dto") public class MeetingV2GetAllMeetingByOrgUserMeetingDto { - @Schema(description = "모임 id", example = "1") - private Integer id; + @Schema(description = "모임 id", example = "1") + @NotNull + private Integer id; - @Schema(description = "모임장 여부", example = "true") - private Boolean isMeetingLeader; + @Schema(description = "모임장 여부", example = "true") + @NotNull + private Boolean isMeetingLeader; - @Schema(description = "모임 제목", example = "모임 제목입니다1") - private String title; + @Schema(description = "모임 제목", example = "모임 제목입니다1") + @NotNull + private String title; - @Schema(description = "모임 사진", example = "[url] 형식") - private String imageUrl; + @Schema(description = "모임 사진", example = "[url] 형식") + @NotNull + private String imageUrl; - @Schema(description = "모임 분류", example = "스터디") - private String category; + @Schema(description = "모임 분류", example = "스터디") + @NotNull + private String category; - @Schema(description = "활동 시작 날짜", example = "2024-07-31T15:30:00") - private LocalDateTime mStartDate; + @Schema(description = "활동 시작 날짜", example = "2024-07-31T15:30:00") + @NotNull + private LocalDateTime mStartDate; - @Schema(description = "활동 종료 날짜", example = "2024-08-15T15:30:00") - private LocalDateTime mEndDate; + @Schema(description = "활동 종료 날짜", example = "2024-08-15T15:30:00") + @NotNull + private LocalDateTime mEndDate; - @Schema(description = "모임 활성 여부", example = "true") - private Boolean isActiveMeeting; + @Schema(description = "모임 활성 여부", example = "true") + @NotNull + private Boolean isActiveMeeting; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingBannerResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingBannerResponseDto.java index bd758867..c543886a 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingBannerResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingBannerResponseDto.java @@ -3,11 +3,13 @@ import java.time.LocalDateTime; import java.util.List; import java.util.Optional; + import org.sopt.makers.crew.main.entity.meeting.enums.MeetingCategory; import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart; import org.sopt.makers.crew.main.entity.meeting.vo.ImageUrlVO; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; @@ -16,63 +18,95 @@ @Schema(name = "MeetingV2GetMeetingBannerResponseDto", description = "모임 배너 응답 Dto") public class MeetingV2GetMeetingBannerResponseDto { - /** 모임 ID */ - @Schema(description = "모임 id", example = "1") - private Integer id; - /** 유저 Crew ID */ - @Schema(description = "유저 id", example = "1") - private Integer userId; - /** 모임 제목 */ - @Schema(description = "모임 제목", example = "모임 제목입니다1") - private String title; - /** - * 모임 카테고리 - * - * @apiNote '스터디', '행사' - */ - @Schema(description = "모임 카테고리", example = "스터디") - private MeetingCategory category; - /** - * 썸네일 이미지 - * - * @apiNote 여러개여도 첫번째 이미지만 사용 - */ - @Schema(description = "모임 사진", example = "[url] 형식") - private List imageURL; - /** 모임 활동 시작일 */ - @Schema(description = "모임 활동 시작일", example = "2024-07-31T15:30:00") - private LocalDateTime mStartDate; - /** 모임 활동 종료일 */ - @Schema(description = "모임 활동 종료일", example = "2024-08-25T15:30:00") - private LocalDateTime mEndDate; - /** 모임 모집 시작일 */ - @Schema(description = "모임 모집 시작일", example = "2024-06-11T15:30:00") - private LocalDateTime startDate; - /** 모임 모집 종료일 */ - @Schema(description = "모임 모집 종료일", example = "2024-06-17T15:30:00") - private LocalDateTime endDate; - /** 모임 인원 */ - @Schema(description = "모집 인원", example = "20") - private Integer capacity; - /** 최근 활동 일자 */ - @Schema(description = "최근 활동 일자", example = "2024-06-11T15:30:00") - private Optional recentActivityDate; - /** 모임 타겟 기수 */ - @Schema(description = "모임 타겟 기수", example = "33") - private Integer targetActiveGeneration; - /** 모임 타겟 파트 */ - @Schema(description = "모임 타겟 파트", example = "[\"PM\", \"SERVER\"]") - private MeetingJoinablePart[] joinableParts; - /** 지원자 수 */ - @Schema(description = "지원자 수", example = "50") - private Integer applicantCount; - /** 가입된 지원자 수 */ - @Schema(description = "가입된 지원자 수", example = "9") - private Integer approvedUserCount; - /** 개설자 정보 */ - @Schema(description = "모임장 정보", example = "") - private MeetingV2GetMeetingBannerResponseUserDto user; - /** 미팅 상태 */ - @Schema(description = "모임 상태", example = "1") - private Integer status; + /** 모임 ID */ + @Schema(description = "모임 id", example = "1") + @NotNull + private Integer id; + + /** 유저 Crew ID */ + @Schema(description = "크루에서 사용하는 userId", example = "1") + @NotNull + private Integer userId; + + /** 모임 제목 */ + @Schema(description = "모임 제목", example = "모임 제목입니다1") + @NotNull + private String title; + + /** + * 모임 카테고리 + * + * @apiNote '스터디', '행사' + */ + @Schema(description = "모임 카테고리", example = "스터디") + @NotNull + private MeetingCategory category; + + /** + * 썸네일 이미지 + * + * @apiNote 여러개여도 첫번째 이미지만 사용 + */ + @Schema(description = "모임 사진", example = "[url] 형식") + @NotNull + private List imageURL; + + /** 모임 활동 시작일 */ + @Schema(description = "모임 활동 시작일", example = "2024-07-31T15:30:00") + @NotNull + private LocalDateTime mStartDate; + + /** 모임 활동 종료일 */ + @Schema(description = "모임 활동 종료일", example = "2024-08-25T15:30:00") + @NotNull + private LocalDateTime mEndDate; + + /** 모임 모집 시작일 */ + @Schema(description = "모임 모집 시작일", example = "2024-06-11T15:30:00") + @NotNull + private LocalDateTime startDate; + + /** 모임 모집 종료일 */ + @Schema(description = "모임 모집 종료일", example = "2024-06-17T15:30:00") + @NotNull + private LocalDateTime endDate; + + /** 모임 인원 */ + @Schema(description = "모집 인원", example = "20") + @NotNull + private Integer capacity; + + /** 최근 활동 일자 */ + @Schema(description = "최근 활동 일자", example = "2024-06-11T15:30:00") + private Optional recentActivityDate; + + /** 모임 타겟 기수 */ + @Schema(description = "모임 타겟 기수", example = "33") + @NotNull + private Integer targetActiveGeneration; + + /** 모임 타겟 파트 */ + @Schema(description = "모임 타겟 파트", example = "[\"PM\", \"SERVER\"]") + @NotNull + private MeetingJoinablePart[] joinableParts; + + /** 지원자 수 */ + @Schema(description = "지원자 수", example = "50") + @NotNull + private Integer applicantCount; + + /** 가입된 지원자 수 */ + @Schema(description = "가입된 지원자 수", example = "9") + @NotNull + private Integer approvedUserCount; + + /** 개설자 정보 */ + @Schema(description = "모임장 정보", example = "") + @NotNull + private MeetingV2GetMeetingBannerResponseUserDto user; + + /** 미팅 상태 */ + @Schema(description = "모임 상태", example = "1") + @NotNull + private Integer status; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingBannerResponseUserDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingBannerResponseUserDto.java index c1c668d5..c46c2ee1 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingBannerResponseUserDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingBannerResponseUserDto.java @@ -1,6 +1,7 @@ package org.sopt.makers.crew.main.meeting.v2.dto.response; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; @@ -8,16 +9,23 @@ @AllArgsConstructor(staticName = "of") @Schema(name = "MeetingV2GetMeetingBannerResponseUserDto", description = "모임 배너 유저 Dto") public class MeetingV2GetMeetingBannerResponseUserDto { - /** 개설자 crew ID */ - @Schema(description = "모임장 id", example = "1") - private Integer id; - /** 개설자 */ - @Schema(description = "모임장 이름", example = "홍길동") - private String name; - /** 개설자 playground ID */ - @Schema(description = "모임장 org id", example = "1") - private Integer orgId; - /** 프로필 사진 */ - @Schema(description = "모임장 프로필 사진", example = "[url] 형식") - private String profileImage; + + /** 개설자 crew ID */ + @Schema(description = "모임장 id, 크루에서 사용하는 userId", example = "1") + @NotNull + private Integer id; + + /** 개설자 */ + @Schema(description = "모임장 이름", example = "홍길동") + @NotNull + private String name; + + /** 개설자 playground ID */ + @Schema(description = "모임장 org id, 메이커스 프로덕트에서 범용적으로 사용하는 userId", example = "1") + @NotNull + private Integer orgId; + + /** 프로필 사진 */ + @Schema(description = "모임장 프로필 사진", example = "[url] 형식") + private String profileImage; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/notice/dto/request/NoticeV2CreateRequestDto.java b/main/src/main/java/org/sopt/makers/crew/main/notice/dto/request/NoticeV2CreateRequestDto.java index 15d95a3b..69bad0ca 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/notice/dto/request/NoticeV2CreateRequestDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/notice/dto/request/NoticeV2CreateRequestDto.java @@ -1,16 +1,17 @@ package org.sopt.makers.crew.main.notice.dto.request; import java.time.LocalDateTime; + import lombok.Getter; import lombok.RequiredArgsConstructor; @Getter @RequiredArgsConstructor public class NoticeV2CreateRequestDto { - private final String title; - private final String subTitle; - private final String contents; - private final LocalDateTime exposeStartDate; - private final LocalDateTime exposeEndDate; - private final String noticeSecretKey; + private final String title; + private final String subTitle; + private final String contents; + private final LocalDateTime exposeStartDate; + private final LocalDateTime exposeEndDate; + private final String noticeSecretKey; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/notice/dto/response/NoticeV2GetResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/notice/dto/response/NoticeV2GetResponseDto.java index 05a1615c..6b398822 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/notice/dto/response/NoticeV2GetResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/notice/dto/response/NoticeV2GetResponseDto.java @@ -1,15 +1,33 @@ package org.sopt.makers.crew.main.notice.dto.response; import java.time.LocalDateTime; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Getter; import lombok.RequiredArgsConstructor; @Getter @RequiredArgsConstructor(staticName = "of") public class NoticeV2GetResponseDto { + + @Schema(description = "공지 id", example = "1") + @NotNull private final Integer id; + + @Schema(description = "공지 제목", example = "공지 제목입니다") + @NotNull private final String title; + + @Schema(description = "공지 부제목", example = "공지 부제목입니다") + @NotNull private final String subTitle; + + @Schema(description = "공지 내용", example = "공지 내용입니다") + @NotNull private final String contents; + + @Schema(description = "공지 생성 시각", example = "2024-07-30T15:30:00") + @NotNull private final LocalDateTime createdDate; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/request/PostV2CreatePostBodyDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/request/PostV2CreatePostBodyDto.java index dbfd1d11..49fbb6fc 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/request/PostV2CreatePostBodyDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/request/PostV2CreatePostBodyDto.java @@ -11,24 +11,24 @@ @Schema(description = "게시물 생성 request body dto") public class PostV2CreatePostBodyDto { - @Schema(example = "1", required = true, description = "모임 ID") - @NotNull - private Integer meetingId; + @Schema(example = "1", required = true, description = "모임 id") + @NotNull + private Integer meetingId; - @Schema(example = "알고보면 쓸데있는 개발 프로세스", required = true, description = "모임 제목") - @NotEmpty - private String title; + @Schema(example = "알고보면 쓸데있는 개발 프로세스", required = true, description = "모임 제목") + @NotEmpty + private String title; - @Schema( - example = "[\"https://makers-web-img.s3.ap-northeast-2.amazonaws.com/meeting/2023/04/12/7bd87736-b557-4b26-a0d5-9b09f1f1d7df\"]", - required = true, - description = "게시글 이미지 리스트" - ) - @NotEmpty - private String[] images; + @Schema( + example = "[\"https://makers-web-img.s3.ap-northeast-2.amazonaws.com/meeting/2023/04/12/7bd87736-b557-4b26-a0d5-9b09f1f1d7df\"]", + required = true, + description = "게시글 이미지 리스트" + ) + @NotEmpty + private String[] images; - @Schema(example = "api 가 터졌다고? 깃이 터졌다고?", required = true, description = "게시글 내용") - @NotEmpty - private String contents; + @Schema(example = "api 가 터졌다고? 깃이 터졌다고?", required = true, description = "게시글 내용") + @NotEmpty + private String contents; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/request/PostV2MentionUserInPostRequestDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/request/PostV2MentionUserInPostRequestDto.java index f43dff2b..60065421 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/request/PostV2MentionUserInPostRequestDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/request/PostV2MentionUserInPostRequestDto.java @@ -23,5 +23,6 @@ public class PostV2MentionUserInPostRequestDto { private final Integer postId; @Schema(example = "멘션내용~~", required = true, description = "멘션 내용") + @NotEmpty private final String content; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/CommenterThumbnails.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/CommenterThumbnails.java index bff64f31..7981d28b 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/CommenterThumbnails.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/CommenterThumbnails.java @@ -1,11 +1,16 @@ package org.sopt.makers.crew.main.post.v2.dto.response; import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor @Getter public class CommenterThumbnails { + + @Schema(description = "댓글 작성자들의 프로필 이미지 목록", example = "[\"url1\", \"url2\"]") private final List commenterThumbnails; + } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailBaseDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailBaseDto.java index 65f72cfe..60e3e3b9 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailBaseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailBaseDto.java @@ -5,6 +5,7 @@ import java.time.LocalDateTime; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Getter; @Getter @@ -12,36 +13,47 @@ public class PostDetailBaseDto { @Schema(description = "게시글 id", example = "1") + @NotNull private final Integer id; @Schema(description = "게시글 제목", example = "게시글 제목입니다.") + @NotNull private final String title; @Schema(description = "게시글 내용", example = "게시글 내용입니다.") + @NotNull private final String contents; @Schema(description = "게시글 생성일자", example = "2024-07-31T15:30:00") + @NotNull private final LocalDateTime createdDate; @Schema(description = "게시글 이미지", example = "[\"url1\", \"url2\"]") + @NotNull private final String[] images; @Schema(description = "게시글 작성자 객체", example = "") + @NotNull private final PostWriterInfoDto user; @Schema(description = "게시글 좋아요 갯수", example = "20") private final int likeCount; + //* 본인이 좋아요를 눌렀는지 여부 @Schema(description = "게시글 좋아요 여부", example = "true") + @NotNull private final Boolean isLiked; @Schema(description = "게시글 조회수", example = "30") + @NotNull private final int viewCount; @Schema(description = "게시글 댓글 수", example = "5") + @NotNull private final int commentCount; @Schema(description = "게시글에 대한 모임", example = "") + @NotNull private final PostMeetingDto meeting; @QueryProjection diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailResponseDto.java index e3449f2a..a05e19e4 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostDetailResponseDto.java @@ -4,6 +4,7 @@ import java.util.List; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; @@ -13,39 +14,51 @@ public class PostDetailResponseDto { @Schema(description = "게시글 id", example = "1") + @NotNull private final Integer id; @Schema(description = "게시글 제목", example = "게시글 제목입니다.") + @NotNull private final String title; @Schema(description = "게시글 내용", example = "게시글 내용입니다.") + @NotNull private final String contents; @Schema(description = "게시글 생성 일자", example = "2024-05-31T15:30:00") + @NotNull private final LocalDateTime createdDate; @Schema(description = "게시글 이미지 목록", example = "[\"url1\", \"url2\"]") + @NotNull private final String[] images; @Schema(description = "게시글 생성 유저 객체", example = "") + @NotNull private final PostWriterInfoDto user; @Schema(description = "게시글 좋아요 수", example = "20") + @NotNull private final int likeCount; @Schema(description = "게시글 좋아요 여부", example = "true") + @NotNull private final Boolean isLiked; @Schema(description = "게시글 조회수", example = "200") + @NotNull private final int viewCount; @Schema(description = "게시글 댓글 수", example = "30") + @NotNull private final int commentCount; @Schema(description = "게시글에 해당하는 모임", example = "") + @NotNull private final PostMeetingDto meeting; @Schema(description = "댓글 작성자 썸네일 목록", example = "[\"url1\", \"url2\"]") + @NotNull private final List commenterThumbnails; public static PostDetailResponseDto of(PostDetailBaseDto postDetail, diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostMeetingDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostMeetingDto.java index ab75fc71..c833bab3 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostMeetingDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostMeetingDto.java @@ -5,6 +5,8 @@ import io.swagger.v3.oas.annotations.media.Schema; import java.util.List; + +import jakarta.validation.constraints.NotNull; import lombok.Getter; import org.sopt.makers.crew.main.entity.meeting.enums.MeetingCategory; import org.sopt.makers.crew.main.entity.meeting.vo.ImageUrlVO; @@ -14,13 +16,19 @@ public class PostMeetingDto { @Schema(description = "모임 id", example = "1") + @NotNull private final Integer id; - @Schema(description = "게시글 제목", example = "게시글 제목입니다.") + @Schema(description = "모임 제목", example = "모임 제목입니다.") + @NotNull private final String title; - @Schema(description = "게시글 카테고리", example = "스터디") + @Schema(description = "모임 카테고리", example = "스터디") + @NotNull private final String category; + + @Schema(description = "모임 이미지 url", example = "[url 형식]") + @NotNull private final List imageURL; @QueryProjection diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2CreatePostResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2CreatePostResponseDto.java index cc2a53ec..e1f1ace6 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2CreatePostResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2CreatePostResponseDto.java @@ -1,6 +1,7 @@ package org.sopt.makers.crew.main.post.v2.dto.response; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; @@ -9,10 +10,11 @@ @Schema(name = "PostV2CreatePostResponseDto", description = "게시글 생성 응답 Dto") public class PostV2CreatePostResponseDto { - /** - * 생성된 게시물 id - */ - @Schema(description = "게시글 id", example = "1") - private Integer postId; + /** + * 생성된 게시물 id + */ + @Schema(description = "게시글 id", example = "1") + @NotNull + private Integer postId; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2GetPostCountResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2GetPostCountResponseDto.java index f29c3231..0908a957 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2GetPostCountResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2GetPostCountResponseDto.java @@ -1,5 +1,7 @@ package org.sopt.makers.crew.main.post.v2.dto.response; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; @@ -7,8 +9,10 @@ @AllArgsConstructor(staticName = "of") public class PostV2GetPostCountResponseDto { - /** - * 모임 게시글 개수 - */ - private Integer postCount; + /** + * 모임 게시글 개수 + */ + @Schema(description = "게시글 갯수", example = "25") + @NotNull + private Integer postCount; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2GetPostsResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2GetPostsResponseDto.java index 7b1ecae0..dd38fa9b 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2GetPostsResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2GetPostsResponseDto.java @@ -3,8 +3,10 @@ import java.util.List; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; + import org.sopt.makers.crew.main.common.pagination.dto.PageMetaDto; @Getter @@ -12,8 +14,11 @@ @Schema(name = "PostV2GetPostsResponseDto", description = "게시글 조회 응답 Dto") public class PostV2GetPostsResponseDto { - @Schema(description = "게시글 객체", example = "") - private final List posts; + @Schema(description = "게시글 객체", example = "") + @NotNull + private final List posts; + + @NotNull + private final PageMetaDto meta; - private final PageMetaDto meta; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostWriterInfoDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostWriterInfoDto.java index 1723c5cd..35ecd812 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostWriterInfoDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostWriterInfoDto.java @@ -3,22 +3,27 @@ import com.querydsl.core.annotations.QueryProjection; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Getter; @Getter @Schema(name = "PostWriterInfoDto", description = "게시글 작성자 Dto") public class PostWriterInfoDto { - @Schema(description = "유저 id", example = "1") + @Schema(description = "작성자 id, 크루에서 사용하는 userId", example = "1") + @NotNull private final Integer id; - @Schema(description = "유저 org id", example = "1") + @Schema(description = "작성자 org id, 메이커스 프로덕트에서 범용적으로 사용하는 userId", example = "1") + @NotNull private final Integer orgId; - @Schema(description = "유저 이름", example = "홍길동") + @Schema(description = "작성자 이름", example = "홍길동") + @NotNull private final String name; - @Schema(description = "유저 프로필 사진", example = "[url] 형식") + @Schema(description = "작성자 프로필 사진", example = "[url] 형식") + @NotNull private final String profileImage; @QueryProjection diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetAllMeetingByUserMeetingDto.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetAllMeetingByUserMeetingDto.java index 2a243baa..362318a1 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetAllMeetingByUserMeetingDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetAllMeetingByUserMeetingDto.java @@ -1,6 +1,7 @@ package org.sopt.makers.crew.main.user.v2.dto.response; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; @@ -10,17 +11,22 @@ public class UserV2GetAllMeetingByUserMeetingDto { @Schema(description = "모임 id", example = "1") + @NotNull private int id; @Schema(description = "모임 제목", example = "모임 제목입니다.") + @NotNull private String title; @Schema(description = "모임 내용", example = "모임 내용입니다.") + @NotNull private String contents; @Schema(description = "모임 사진", example = "[url] 형식") + @NotNull private String imageUrl; @Schema(description = "모임 카테고리", example = "스터디") + @NotNull private String category; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetAllMentionUserDto.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetAllMentionUserDto.java index ee85c500..938b221a 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetAllMentionUserDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetAllMentionUserDto.java @@ -1,6 +1,7 @@ package org.sopt.makers.crew.main.user.v2.dto.response; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; @@ -9,19 +10,23 @@ @Schema(name = "UserV2GetAllMentionUserDto", description = "멘션 유저 조회 응답 Dto") public class UserV2GetAllMentionUserDto { - @Schema(description = "메이커스 프로덕트에서 범용적으로 사용하는 userId", example = "1") - private final Integer orgId; + @Schema(description = "메이커스 프로덕트에서 범용적으로 사용하는 userId", example = "1") + @NotNull + private final Integer orgId; - @Schema(description = "유저 이름", example = "홍길") - private final String userName; + @Schema(description = "유저 이름", example = "홍길동") + @NotNull + private final String userName; - @Schema(description = "최근 파트", example = "서버") - private final String recentPart; + @Schema(description = "최근 파트", example = "서버") + @NotNull + private final String recentPart; - @Schema(description = "최근 기수", example = "33") - private final int recentGeneration; + @Schema(description = "최근 기수", example = "33") + @NotNull + private final int recentGeneration; - @Schema(description = "유저 프로필 사진", example = "[url] 형식") - private final String profileImageUrl; + @Schema(description = "유저 프로필 사진", example = "[url] 형식") + private final String profileImageUrl; } From df4beb8461f1d104cd5db30dccc2f00f81bdb3d5 Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Mon, 19 Aug 2024 18:42:15 +0900 Subject: [PATCH 21/47] =?UTF-8?q?[FEAT]=20=EC=9C=A0=EC=A0=80=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20API=20=EB=A7=88=EC=9D=B4=EA=B7=B8=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=85=98=20(#308)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [CHORE] v2로 이름 변경 * [CHORE] 패키지 위치 수정 * [FEAT] applies 객체 추가 * [ADD] Dto 정의 * [FEAT] 내가 만든 모임 조회 및 내가 신청한 모임 조회 기능 구현 * [DOCS] 내가 만든 모임 조회 및 내가 신청한 모임 조회 스웨거 추가 --- .../main/common/config/SecurityConfig.java | 6 +- .../main/common/dto/MeetingCreatorDto.java | 24 ++++++ .../jwt/JwtAuthenticationEntryPoint.java | 2 +- .../jwt/JwtAuthenticationFilter.java | 6 +- .../{config => }/jwt/JwtExceptionType.java | 2 +- .../{config => }/jwt/JwtTokenProvider.java | 2 +- .../{config => }/jwt/UserAuthentication.java | 2 +- .../crew/main/entity/apply/Applies.java | 34 ++++++++ .../main/entity/apply/ApplyRepository.java | 5 ++ .../entity/meeting/MeetingRepository.java | 16 ++-- .../MeetingV2CreateMeetingBodyDto.java | 2 - .../makers/crew/main/user/v2/UserApi.java | 28 ------- .../makers/crew/main/user/v2/UserV2Api.java | 62 +++++++++++++++ .../crew/main/user/v2/UserV2Controller.java | 69 ++++++++++++----- ...yV2GetAppliedMeetingByUserResponseDto.java | 22 ++++++ ...gV2GetCreatedMeetingByUserResponseDto.java | 77 +++++++++++++++++++ ...rV2GetAppliedMeetingByUserResponseDto.java | 20 +++++ ...rV2GetCreatedMeetingByUserResponseDto.java | 20 +++++ .../UserV2GetUserOwnProfileResponseDto.java | 31 ++++++++ .../main/user/v2/service/UserV2Service.java | 16 +++- .../user/v2/service/UserV2ServiceImpl.java | 46 +++++++++++ 21 files changed, 425 insertions(+), 67 deletions(-) create mode 100644 main/src/main/java/org/sopt/makers/crew/main/common/dto/MeetingCreatorDto.java rename main/src/main/java/org/sopt/makers/crew/main/common/{config => }/jwt/JwtAuthenticationEntryPoint.java (93%) rename main/src/main/java/org/sopt/makers/crew/main/common/{config => }/jwt/JwtAuthenticationFilter.java (89%) rename main/src/main/java/org/sopt/makers/crew/main/common/{config => }/jwt/JwtExceptionType.java (87%) rename main/src/main/java/org/sopt/makers/crew/main/common/{config => }/jwt/JwtTokenProvider.java (98%) rename main/src/main/java/org/sopt/makers/crew/main/common/{config => }/jwt/UserAuthentication.java (91%) create mode 100644 main/src/main/java/org/sopt/makers/crew/main/entity/apply/Applies.java delete mode 100644 main/src/main/java/org/sopt/makers/crew/main/user/v2/UserApi.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/user/v2/UserV2Api.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/ApplyV2GetAppliedMeetingByUserResponseDto.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/MeetingV2GetCreatedMeetingByUserResponseDto.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetAppliedMeetingByUserResponseDto.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetCreatedMeetingByUserResponseDto.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetUserOwnProfileResponseDto.java diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/config/SecurityConfig.java b/main/src/main/java/org/sopt/makers/crew/main/common/config/SecurityConfig.java index e9917c8b..d1c8d72c 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/config/SecurityConfig.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/config/SecurityConfig.java @@ -3,9 +3,9 @@ import java.util.Arrays; import java.util.stream.Stream; import lombok.RequiredArgsConstructor; -import org.sopt.makers.crew.main.common.config.jwt.JwtAuthenticationEntryPoint; -import org.sopt.makers.crew.main.common.config.jwt.JwtAuthenticationFilter; -import org.sopt.makers.crew.main.common.config.jwt.JwtTokenProvider; +import org.sopt.makers.crew.main.common.jwt.JwtAuthenticationEntryPoint; +import org.sopt.makers.crew.main.common.jwt.JwtAuthenticationFilter; +import org.sopt.makers.crew.main.common.jwt.JwtTokenProvider; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/dto/MeetingCreatorDto.java b/main/src/main/java/org/sopt/makers/crew/main/common/dto/MeetingCreatorDto.java new file mode 100644 index 00000000..85dbebd7 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/common/dto/MeetingCreatorDto.java @@ -0,0 +1,24 @@ +package org.sopt.makers.crew.main.common.dto; + +import org.sopt.makers.crew.main.entity.user.User; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; + +public record MeetingCreatorDto( + @Schema(description = "모임장 id, 크루에서 사용하는 userId", example = "1") + @NotNull + Integer id, + @Schema(description = "모임장 이름", example = "홍길동") + @NotNull + String name, + @Schema(description = "모임장 org id, 메이커스 프로덕트에서 범용적으로 사용하는 userId", example = "1") + @NotNull + Integer orgId, + @Schema(description = "모임장 프로필 사진", example = "[url] 형식") + String profileImage +) { + public static MeetingCreatorDto of(User user){ + return new MeetingCreatorDto(user.getId(), user.getName(), user.getOrgId(), user.getProfileImage()); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/config/jwt/JwtAuthenticationEntryPoint.java b/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtAuthenticationEntryPoint.java similarity index 93% rename from main/src/main/java/org/sopt/makers/crew/main/common/config/jwt/JwtAuthenticationEntryPoint.java rename to main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtAuthenticationEntryPoint.java index fdbb56c8..79e08f90 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/config/jwt/JwtAuthenticationEntryPoint.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtAuthenticationEntryPoint.java @@ -1,4 +1,4 @@ -package org.sopt.makers.crew.main.common.config.jwt; +package org.sopt.makers.crew.main.common.jwt; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/config/jwt/JwtAuthenticationFilter.java b/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtAuthenticationFilter.java similarity index 89% rename from main/src/main/java/org/sopt/makers/crew/main/common/config/jwt/JwtAuthenticationFilter.java rename to main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtAuthenticationFilter.java index bd28aee6..afa732d9 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/config/jwt/JwtAuthenticationFilter.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtAuthenticationFilter.java @@ -1,6 +1,4 @@ -package org.sopt.makers.crew.main.common.config.jwt; - -import static org.sopt.makers.crew.main.common.config.jwt.JwtExceptionType.VALID_JWT_TOKEN; +package org.sopt.makers.crew.main.common.jwt; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; @@ -30,7 +28,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse if (accessToken != null) { // 토큰 검증 if (jwtTokenProvider.validateToken(accessToken) - == VALID_JWT_TOKEN) { // 토큰이 존재하고 유효한 토큰일 때만 + == JwtExceptionType.VALID_JWT_TOKEN) { // 토큰이 존재하고 유효한 토큰일 때만 Integer userId = jwtTokenProvider.getAccessTokenPayload(accessToken); UserAuthentication authentication = new UserAuthentication(userId, null, null); //사용자 객체 생성 diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/config/jwt/JwtExceptionType.java b/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtExceptionType.java similarity index 87% rename from main/src/main/java/org/sopt/makers/crew/main/common/config/jwt/JwtExceptionType.java rename to main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtExceptionType.java index 043887ea..aadf368f 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/config/jwt/JwtExceptionType.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtExceptionType.java @@ -1,4 +1,4 @@ -package org.sopt.makers.crew.main.common.config.jwt; +package org.sopt.makers.crew.main.common.jwt; public enum JwtExceptionType { VALID_JWT_TOKEN, // 유효한 JWT diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/config/jwt/JwtTokenProvider.java b/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtTokenProvider.java similarity index 98% rename from main/src/main/java/org/sopt/makers/crew/main/common/config/jwt/JwtTokenProvider.java rename to main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtTokenProvider.java index 837fe9ad..f47fd7bd 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/config/jwt/JwtTokenProvider.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtTokenProvider.java @@ -1,4 +1,4 @@ -package org.sopt.makers.crew.main.common.config.jwt; +package org.sopt.makers.crew.main.common.jwt; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/config/jwt/UserAuthentication.java b/main/src/main/java/org/sopt/makers/crew/main/common/jwt/UserAuthentication.java similarity index 91% rename from main/src/main/java/org/sopt/makers/crew/main/common/config/jwt/UserAuthentication.java rename to main/src/main/java/org/sopt/makers/crew/main/common/jwt/UserAuthentication.java index 1c6cea33..5bc0cb62 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/config/jwt/UserAuthentication.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/jwt/UserAuthentication.java @@ -1,4 +1,4 @@ -package org.sopt.makers.crew.main.common.config.jwt; +package org.sopt.makers.crew.main.common.jwt; import java.util.Collection; diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Applies.java b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Applies.java new file mode 100644 index 00000000..f6caebe8 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Applies.java @@ -0,0 +1,34 @@ +package org.sopt.makers.crew.main.entity.apply; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class Applies { + + /** + * Key : MeetingId + * Value : 해당 모임에 신청한 신청정보들 + * + * @note: List 내에 있는 Apply 객체는 fetch join 으로 다른 객체를 불러오지 않은 상태 + * + * */ + private final Map> appliesMap; + + public Applies(List applies) { + this.appliesMap = applies.stream() + .collect(Collectors.groupingBy(Apply::getMeetingId)); + } + + public int getAppliedCount(Integer meetingId){ + List applies = appliesMap.get(meetingId); + + if(applies == null){ + return 0; + } + return applies.size(); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/ApplyRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/ApplyRepository.java index 26ca1b56..9db319fb 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/ApplyRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/ApplyRepository.java @@ -18,10 +18,15 @@ public interface ApplyRepository extends JpaRepository, ApplySea List findAllByUserIdAndStatus(@Param("userId") Integer userId, @Param("statusValue") EnApplyStatus statusValue); + @Query("select a from Apply a join fetch a.meeting m join fetch m.user u where a.userId = :userId") + List findAllByUserId(@Param("userId") Integer userId); + List findAllByMeetingIdAndStatus(Integer meetingId, EnApplyStatus statusValue); List findAllByMeetingId(Integer meetingId); + List findAllByMeetingIdIn(List meetingIds); + boolean existsByMeetingIdAndUserId(Integer meetingId, Integer userId); @Transactional diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingRepository.java index fe05afb8..6d406010 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingRepository.java @@ -4,17 +4,21 @@ import java.util.List; import java.util.Optional; + import org.sopt.makers.crew.main.common.exception.BadRequestException; +import org.sopt.makers.crew.main.entity.user.User; import org.springframework.data.jpa.repository.JpaRepository; public interface MeetingRepository extends JpaRepository { - List findAllByUserId(int userId); + List findAllByUserId(Integer userId); + + List findAllByUser(User user); - Optional findById(Integer meetingId); + Optional findById(Integer meetingId); - default Meeting findByIdOrThrow(Integer meetingId) { - return findById(meetingId) - .orElseThrow(() -> new BadRequestException(NOT_FOUND_MEETING.getErrorCode())); - } + default Meeting findByIdOrThrow(Integer meetingId) { + return findById(meetingId) + .orElseThrow(() -> new BadRequestException(NOT_FOUND_MEETING.getErrorCode())); + } } \ No newline at end of file diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/request/MeetingV2CreateMeetingBodyDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/request/MeetingV2CreateMeetingBodyDto.java index 04003c41..8108cf84 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/request/MeetingV2CreateMeetingBodyDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/request/MeetingV2CreateMeetingBodyDto.java @@ -12,8 +12,6 @@ import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart; -import com.fasterxml.jackson.annotation.JsonProperty; - @Getter @AllArgsConstructor @Schema(description = "모임 생성 request body dto") diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/UserApi.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/UserApi.java deleted file mode 100644 index 5d17ecc3..00000000 --- a/main/src/main/java/org/sopt/makers/crew/main/user/v2/UserApi.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.sopt.makers.crew.main.user.v2; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; -import java.security.Principal; -import java.util.List; -import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAllMeetingByUserMeetingDto; -import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAllMentionUserDto; -import org.springframework.http.ResponseEntity; - -@Tag(name = "사용자") -public interface UserApi { - - @Operation(summary = "내가 속한 모임 조회") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "성공"), - @ApiResponse(responseCode = "204", description = "내가 속한 모임 리스트가 없는 경우", content = @Content), - }) - ResponseEntity> getAllMeetingByUser(Principal principal); - - @Operation(summary = "멘션 사용자 조회") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "성공")}) - ResponseEntity> getAllMentionUser(Principal principal); -} diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/UserV2Api.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/UserV2Api.java new file mode 100644 index 00000000..b7a96cb8 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/user/v2/UserV2Api.java @@ -0,0 +1,62 @@ +package org.sopt.makers.crew.main.user.v2; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import java.security.Principal; +import java.util.List; + +import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAllMeetingByUserMeetingDto; +import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAllMentionUserDto; +import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAppliedMeetingByUserResponseDto; +import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetCreatedMeetingByUserResponseDto; +import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetUserOwnProfileResponseDto; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ResponseStatus; + +@Tag(name = "사용자") +public interface UserV2Api { + + @Operation(summary = "내가 속한 모임 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공"), + @ApiResponse(responseCode = "204", description = "내가 속한 모임 리스트가 없는 경우", content = @Content), + }) + ResponseEntity> getAllMeetingByUser(Principal principal); + + @Operation(summary = "멘션 사용자 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공")}) + ResponseEntity> getAllMentionUser(Principal principal); + + @Operation(summary = "유저 본인 프로필 조회") + @ResponseStatus(HttpStatus.OK) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공"), + @ApiResponse(responseCode = "400", description = "해당 유저가 없는 경우", content = @Content), + }) + ResponseEntity getUserOwnProfile(Principal principal); + + @Operation(summary = "내가 신청한 모임 조회") + @ResponseStatus(HttpStatus.OK) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공") + }) + ResponseEntity getAppliedMeetingByUser( + Principal principal); + + @Operation(summary = "내가 만든 모임 조회") + @ResponseStatus(HttpStatus.OK) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공") + }) + ResponseEntity getCreatedMeetingByUser( + Principal principal); + + + + +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/UserV2Controller.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/UserV2Controller.java index 61f90ef1..43bfe6ed 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/user/v2/UserV2Controller.java +++ b/main/src/main/java/org/sopt/makers/crew/main/user/v2/UserV2Controller.java @@ -2,10 +2,15 @@ import java.security.Principal; import java.util.List; + import lombok.RequiredArgsConstructor; + import org.sopt.makers.crew.main.common.util.UserUtil; import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAllMeetingByUserMeetingDto; import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAllMentionUserDto; +import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAppliedMeetingByUserResponseDto; +import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetCreatedMeetingByUserResponseDto; +import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetUserOwnProfileResponseDto; import org.sopt.makers.crew.main.user.v2.service.UserV2Service; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -17,22 +22,50 @@ @RestController @RequestMapping("/user/v2") @RequiredArgsConstructor -public class UserV2Controller implements UserApi { - - private final UserV2Service userV2Service; - - @GetMapping("/meeting/all") - @ResponseStatus(HttpStatus.OK) - public ResponseEntity> getAllMeetingByUser( - Principal principal) { - Integer userId = UserUtil.getUserId(principal); - return ResponseEntity.ok(userV2Service.getAllMeetingByUser(userId)); - } - - @GetMapping("/mention") - public ResponseEntity> getAllMentionUser( - Principal principal) { - UserUtil.getUserId(principal); - return ResponseEntity.ok(userV2Service.getAllMentionUser()); - } +public class UserV2Controller implements UserV2Api { + + private final UserV2Service userV2Service; + + @GetMapping("/meeting/all") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity> getAllMeetingByUser( + Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.ok(userV2Service.getAllMeetingByUser(userId)); + } + + @Override + @GetMapping("/mention") + public ResponseEntity> getAllMentionUser( + Principal principal) { + + UserUtil.getUserId(principal); + return ResponseEntity.ok(userV2Service.getAllMentionUser()); + } + + @Override + @GetMapping("/profile/me") + public ResponseEntity getUserOwnProfile(Principal principal) { + + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.ok().body(userV2Service.getUserOwnProfile(userId)); + } + + @Override + @GetMapping("/apply") + public ResponseEntity getAppliedMeetingByUser(Principal principal) { + + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.ok().body(userV2Service.getAppliedMeetingByUser(userId)); + } + + @Override + @GetMapping("/meeting") + public ResponseEntity getCreatedMeetingByUser(Principal principal) { + + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.ok().body(userV2Service.getCreatedMeetingByUser(userId)); + } + + } diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/ApplyV2GetAppliedMeetingByUserResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/ApplyV2GetAppliedMeetingByUserResponseDto.java new file mode 100644 index 00000000..460d9d26 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/ApplyV2GetAppliedMeetingByUserResponseDto.java @@ -0,0 +1,22 @@ +package org.sopt.makers.crew.main.user.v2.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; + +@Schema(name = "ApplyV2GetAppliedMeetingByUserResponseDto", description = "내가 신청한 모임 Dto") +public record ApplyV2GetAppliedMeetingByUserResponseDto( + @Schema(description = "신청 id", example = "130") + @NotNull + Integer id, + @Schema(description = "신청 상태", example = "1") + @NotNull + Integer status, + @Schema(description = "신청 모임 정보") + @NotNull + MeetingV2GetCreatedMeetingByUserResponseDto meeting +) { + public static ApplyV2GetAppliedMeetingByUserResponseDto of(Integer id, Integer status, + MeetingV2GetCreatedMeetingByUserResponseDto meeting) { + return new ApplyV2GetAppliedMeetingByUserResponseDto(id, status, meeting); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/MeetingV2GetCreatedMeetingByUserResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/MeetingV2GetCreatedMeetingByUserResponseDto.java new file mode 100644 index 00000000..0327056a --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/MeetingV2GetCreatedMeetingByUserResponseDto.java @@ -0,0 +1,77 @@ +package org.sopt.makers.crew.main.user.v2.dto.response; + +import java.time.LocalDateTime; +import java.util.List; + +import org.sopt.makers.crew.main.common.dto.MeetingCreatorDto; +import org.sopt.makers.crew.main.entity.meeting.Meeting; +import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart; +import org.sopt.makers.crew.main.entity.meeting.vo.ImageUrlVO; +import org.sopt.makers.crew.main.entity.user.User; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; + +@Schema(name = "MeetingV2GetCreatedMeetingByUserResponseDto", description = "모임 Dto") +public record MeetingV2GetCreatedMeetingByUserResponseDto( + @Schema(description = "모임 id", example = "1") + @NotNull + Integer id, + @Schema(description = "모임 제목", example = "모임 제목입니다") + @NotNull + String title, + @Schema(description = "대상 기수", example = "33") + @NotNull + Integer targetActiveGeneration, + @Schema(example = "[\n" + + " \"ANDROID\",\n" + + " \"IOS\"\n" + + " ]", description = "대상 파트 목록") + @NotNull + MeetingJoinablePart[] joinableParts, + @Schema(example = "스터디", description = "모임 카테고리") + @NotNull + String category, + @Schema(example = "false", description = "활동기수만 지원 가능 여부") + @NotNull + Boolean canJoinOnlyActiveGeneration, + @Schema(example = "2", description = "모임 활동 상태") + @NotNull + Integer status, + /** + * 썸네일 이미지 + * + * @apiNote 여러개여도 첫번째 이미지만 사용 + */ + @Schema(description = "모임 사진", example = "[url] 형식") + @NotNull + List imageURL, + @Schema(example = "false", description = "멘토 필요 여부") + @NotNull + Boolean isMentorNeeded, + @Schema(description = "모임 활동 시작일", example = "2024-07-31T15:30:00") + @NotNull + LocalDateTime mStartDate, + @Schema(description = "모임 활동 종료일", example = "2024-08-25T15:30:00") + @NotNull + LocalDateTime mEndDate, + @Schema(description = "모집 인원", example = "20") + @NotNull + int capacity, + @Schema(description = "모임장 정보", example = "") + @NotNull + MeetingCreatorDto user, + @Schema(description = "신청 수", example = "7") + @NotNull + int appliedCount +) { + public static MeetingV2GetCreatedMeetingByUserResponseDto of(Meeting meeting, User meetingCreator, int appliedCount) { + MeetingCreatorDto creatorDto = MeetingCreatorDto.of(meetingCreator); + + return new MeetingV2GetCreatedMeetingByUserResponseDto(meeting.getId(), meeting.getTitle(), + meeting.getTargetActiveGeneration(), meeting.getJoinableParts(), meeting.getCategory().getValue(), + meeting.getCanJoinOnlyActiveGeneration(), meeting.getMeetingStatus(), meeting.getImageURL(), + meeting.getIsMentorNeeded(), meeting.getMStartDate(), meeting.getMEndDate(), meeting.getCapacity(), creatorDto, appliedCount); + } + +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetAppliedMeetingByUserResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetAppliedMeetingByUserResponseDto.java new file mode 100644 index 00000000..50bc741d --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetAppliedMeetingByUserResponseDto.java @@ -0,0 +1,20 @@ +package org.sopt.makers.crew.main.user.v2.dto.response; + +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; + +@Schema(name = "UserV2GetAppliedMeetingByUserResponseDto", description = "내가 신청한 모임 조회 Dto") +public record UserV2GetAppliedMeetingByUserResponseDto( + @Schema(description = "내가 신청한 모임 정보") + @NotNull + List apply, + @Schema(description = "내가 신청한 모임 갯수") + @NotNull + Integer count +) { + public static UserV2GetAppliedMeetingByUserResponseDto of(List apply){ + return new UserV2GetAppliedMeetingByUserResponseDto(apply, apply.size()); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetCreatedMeetingByUserResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetCreatedMeetingByUserResponseDto.java new file mode 100644 index 00000000..63c0d515 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetCreatedMeetingByUserResponseDto.java @@ -0,0 +1,20 @@ +package org.sopt.makers.crew.main.user.v2.dto.response; + +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; + +@Schema(name = "UserV2GetCreatedMeetingByUserResponseDto", description = "내가 생성한 모임 조회 Dto") +public record UserV2GetCreatedMeetingByUserResponseDto( + @Schema(description = "내가 생성한 모임 정보") + @NotNull + List meetings, + @Schema(description = "내가 신청한 모임 갯수") + @NotNull + Integer count +) { + public static UserV2GetCreatedMeetingByUserResponseDto of(List meetings){ + return new UserV2GetCreatedMeetingByUserResponseDto(meetings, meetings.size()); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetUserOwnProfileResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetUserOwnProfileResponseDto.java new file mode 100644 index 00000000..22986134 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/UserV2GetUserOwnProfileResponseDto.java @@ -0,0 +1,31 @@ +package org.sopt.makers.crew.main.user.v2.dto.response; + +import org.sopt.makers.crew.main.entity.user.User; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; + +@Schema(name = "UserV2GetUserOwnProfileResponseDto", description = "유저 본인 프로필 조회 응답 Dto") +public record UserV2GetUserOwnProfileResponseDto( + @Schema(description = "유저 id, 크루에서 사용하는 userId", example = "1") + @NotNull + Integer id, + @Schema(description = "org id, 메이커스 프로덕트에서 범용적으로 사용하는 userId", example = "1") + @NotNull + Integer orgId, + @Schema(description = "유저 이름", example = "홍길동") + @NotNull + String name, + @Schema(description = "유저 프로필 이미지", example = "[url 형식]") + String profileImage, + @Schema(description = "유저 활동 가지고 있는지 여부, 유저 활동이 없으면 false", example = "1") + @NotNull + Boolean hasActivities +) { + public static UserV2GetUserOwnProfileResponseDto of(User user){ + boolean hasActivities = !user.getActivities().isEmpty(); + + return new UserV2GetUserOwnProfileResponseDto(user.getId(), user.getOrgId(), user.getName(), + user.getProfileImage(), hasActivities); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2Service.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2Service.java index a6710c9d..1b71514d 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2Service.java +++ b/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2Service.java @@ -1,12 +1,24 @@ package org.sopt.makers.crew.main.user.v2.service; import java.util.List; + import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAllMeetingByUserMeetingDto; import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAllMentionUserDto; +import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAppliedMeetingByUserResponseDto; +import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetCreatedMeetingByUserResponseDto; +import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetUserOwnProfileResponseDto; public interface UserV2Service { - List getAllMeetingByUser(Integer userId); + List getAllMeetingByUser(Integer userId); + + List getAllMentionUser(); + + UserV2GetUserOwnProfileResponseDto getUserOwnProfile(Integer userId); + + UserV2GetAppliedMeetingByUserResponseDto getAppliedMeetingByUser(Integer userId); + + UserV2GetCreatedMeetingByUserResponseDto getCreatedMeetingByUser(Integer userId); + - List getAllMentionUser(); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2ServiceImpl.java index cba42120..ee2ad829 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2ServiceImpl.java @@ -8,14 +8,21 @@ import lombok.RequiredArgsConstructor; import org.sopt.makers.crew.main.common.exception.BaseException; +import org.sopt.makers.crew.main.entity.apply.Applies; +import org.sopt.makers.crew.main.entity.apply.Apply; import org.sopt.makers.crew.main.entity.apply.ApplyRepository; import org.sopt.makers.crew.main.entity.apply.enums.EnApplyStatus; import org.sopt.makers.crew.main.entity.meeting.Meeting; import org.sopt.makers.crew.main.entity.meeting.MeetingRepository; import org.sopt.makers.crew.main.entity.user.User; import org.sopt.makers.crew.main.entity.user.UserRepository; +import org.sopt.makers.crew.main.user.v2.dto.response.ApplyV2GetAppliedMeetingByUserResponseDto; +import org.sopt.makers.crew.main.user.v2.dto.response.MeetingV2GetCreatedMeetingByUserResponseDto; import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAllMeetingByUserMeetingDto; import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAllMentionUserDto; +import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetAppliedMeetingByUserResponseDto; +import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetCreatedMeetingByUserResponseDto; +import org.sopt.makers.crew.main.user.v2.dto.response.UserV2GetUserOwnProfileResponseDto; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -69,4 +76,43 @@ public List getAllMentionUser() { user.getProfileImage())) .toList(); } + + @Override + public UserV2GetUserOwnProfileResponseDto getUserOwnProfile(Integer userId) { + User user = userRepository.findByIdOrThrow(userId); + return UserV2GetUserOwnProfileResponseDto.of(user); + } + + @Override + public UserV2GetCreatedMeetingByUserResponseDto getCreatedMeetingByUser(Integer userId) { + User meetingCreator = userRepository.findByIdOrThrow(userId); + + List meetings = meetingRepository.findAllByUser(meetingCreator); + List meetingIds = meetings.stream().map(Meeting::getId).toList(); + Applies applies = new Applies(applyRepository.findAllByMeetingIdIn(meetingIds)); + + List meetingByUserDtos = meetings.stream() + .map(meeting -> MeetingV2GetCreatedMeetingByUserResponseDto.of(meeting, meetingCreator, + applies.getAppliedCount(meeting.getId()))) + .toList(); + + return UserV2GetCreatedMeetingByUserResponseDto.of(meetingByUserDtos); + } + + @Override + public UserV2GetAppliedMeetingByUserResponseDto getAppliedMeetingByUser(Integer userId) { + List myApplies = applyRepository.findAllByUserId(userId); + List meetingIds = myApplies.stream().map(Apply::getMeetingId).toList(); + + Applies allApplies = new Applies(applyRepository.findAllByMeetingIdIn(meetingIds)); + + List appliedMeetingByUserDtos = myApplies.stream() + .map(apply -> ApplyV2GetAppliedMeetingByUserResponseDto.of( + apply.getId(), apply.getStatus().getValue(), + MeetingV2GetCreatedMeetingByUserResponseDto.of(apply.getMeeting(), apply.getMeeting().getUser(), + allApplies.getAppliedCount(apply.getMeetingId())) + )).toList(); + + return UserV2GetAppliedMeetingByUserResponseDto.of(appliedMeetingByUserDtos); + } } From 0367583e2922d547fe7c45290a2c20e3834ceb85 Mon Sep 17 00:00:00 2001 From: mikekks Date: Mon, 19 Aug 2024 18:47:19 +0900 Subject: [PATCH 22/47] =?UTF-8?q?[CHORE]=20deprecated=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/user/v0/user-v0.controller.ts | 3 +++ server/src/user/v1/user-v1.controller.ts | 1 + 2 files changed, 4 insertions(+) diff --git a/server/src/user/v0/user-v0.controller.ts b/server/src/user/v0/user-v0.controller.ts index fb1bbcfc..29894828 100644 --- a/server/src/user/v0/user-v0.controller.ts +++ b/server/src/user/v0/user-v0.controller.ts @@ -27,6 +27,7 @@ export class UserV0Controller { @ApiOperation({ summary: '내가 만든 모임 조회', description: '내가 만든 모임 조회', + deprecated: true, }) @ApiOkResponseCommon(UserV0GetMeetingByUserDto) @ApiBearerAuth() @@ -41,6 +42,7 @@ export class UserV0Controller { @ApiOperation({ summary: '내가 신청한 모임 조회', description: '내가 신청한 모임 조회', + deprecated: true, }) @ApiOkResponseCommon(UserV0GetApplyByUserDto) @ApiBearerAuth() @@ -55,6 +57,7 @@ export class UserV0Controller { @ApiOperation({ summary: '유저 상세 조회', description: '유저 상세 조회', + deprecated: true, }) @ApiOkResponseCommon(User) @ApiParam({ name: 'id', required: true, description: '유저 id' }) diff --git a/server/src/user/v1/user-v1.controller.ts b/server/src/user/v1/user-v1.controller.ts index d1f54504..6f8d50e0 100644 --- a/server/src/user/v1/user-v1.controller.ts +++ b/server/src/user/v1/user-v1.controller.ts @@ -15,6 +15,7 @@ export class UserV1Controller { @ApiOperation({ summary: '유저 본인 프로필 조회', description: '유저 본인 프로필 조회', + deprecated: true, }) @ApiOkResponseCommon(UserV1GetUserOwnProfileResponseDto) @ApiBearerAuth() From e3386dd94ef62239b34846085aad7dabb8006f54 Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Wed, 21 Aug 2024 22:56:36 +0900 Subject: [PATCH 23/47] =?UTF-8?q?feat:=20=EC=A0=84=EC=B2=B4=EB=AA=A8?= =?UTF-8?q?=EC=9E=84=20=EA=B2=80=EC=83=89=20=EB=B0=8F=20=ED=95=84=ED=84=B0?= =?UTF-8?q?=EB=A7=81=20=EB=A7=88=EC=9D=B4=EA=B7=B8=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=85=98=20(#310)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: deprecated 처리 * add: Dto 정의 * add: Dto 정의 * feat: 모임 검색 필터링 로직 구현 * docs: 스웨거 추가 * feat: 전체 모임 검색 및 필터링 서비스 로직 구현 * fix: 타임 객체 의존성 수정 --- .../main/common/dto/MeetingCreatorDto.java | 28 ++- .../main/common/dto/MeetingResponseDto.java | 114 ++++++++++++ .../entity/meeting/MeetingRepository.java | 2 +- .../meeting/MeetingSearchRepository.java | 10 ++ .../meeting/MeetingSearchRepositoryImpl.java | 170 ++++++++++++++++++ .../crew/main/meeting/v2/MeetingV2Api.java | 33 ++++ .../main/meeting/v2/MeetingV2Controller.java | 21 +++ .../query/MeetingV2GetAllMeetingQueryDto.java | 30 ++++ .../response/MeetingV2GetAllMeetingDto.java | 23 +++ .../meeting/v2/service/MeetingV2Service.java | 4 + .../v2/service/MeetingV2ServiceImpl.java | 27 +++ ...gV2GetCreatedMeetingByUserResponseDto.java | 5 +- .../src/meeting/v0/meeting-v0.controller.ts | 1 + 13 files changed, 459 insertions(+), 9 deletions(-) create mode 100644 main/src/main/java/org/sopt/makers/crew/main/common/dto/MeetingResponseDto.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingSearchRepository.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingSearchRepositoryImpl.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/query/MeetingV2GetAllMeetingQueryDto.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetAllMeetingDto.java diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/dto/MeetingCreatorDto.java b/main/src/main/java/org/sopt/makers/crew/main/common/dto/MeetingCreatorDto.java index 85dbebd7..9aaf21ac 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/dto/MeetingCreatorDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/dto/MeetingCreatorDto.java @@ -2,23 +2,37 @@ import org.sopt.makers.crew.main.entity.user.User; +import com.querydsl.core.annotations.QueryProjection; + import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; +import lombok.Getter; -public record MeetingCreatorDto( +@Schema(name = "MeetingCreatorDto", description = "모임 개설자 Dto") +@Getter +public class MeetingCreatorDto { @Schema(description = "모임장 id, 크루에서 사용하는 userId", example = "1") @NotNull - Integer id, + Integer id; @Schema(description = "모임장 이름", example = "홍길동") @NotNull - String name, + String name; @Schema(description = "모임장 org id, 메이커스 프로덕트에서 범용적으로 사용하는 userId", example = "1") @NotNull - Integer orgId, + Integer orgId; @Schema(description = "모임장 프로필 사진", example = "[url] 형식") - String profileImage -) { - public static MeetingCreatorDto of(User user){ + String profileImage; + + @QueryProjection + public MeetingCreatorDto(Integer id, String name, Integer orgId, String profileImag) { + this.id = id; + this.name = name; + this.orgId = orgId; + this.profileImage = profileImag; + } + + public static MeetingCreatorDto of(User user) { return new MeetingCreatorDto(user.getId(), user.getName(), user.getOrgId(), user.getProfileImage()); } + } diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/dto/MeetingResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/common/dto/MeetingResponseDto.java new file mode 100644 index 00000000..dbd46cdc --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/common/dto/MeetingResponseDto.java @@ -0,0 +1,114 @@ +package org.sopt.makers.crew.main.common.dto; + +import java.time.LocalDateTime; + +import java.util.List; + +import org.sopt.makers.crew.main.common.constant.CrewConst; +import org.sopt.makers.crew.main.entity.meeting.Meeting; +import org.sopt.makers.crew.main.entity.meeting.enums.MeetingCategory; +import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart; +import org.sopt.makers.crew.main.entity.meeting.vo.ImageUrlVO; +import org.sopt.makers.crew.main.entity.user.User; + +import com.querydsl.core.annotations.QueryProjection; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Schema(name = "MeetingResponseDto", description = "모임 Dto") +@RequiredArgsConstructor +@Getter +public class MeetingResponseDto { + + @Schema(description = "모임 id", example = "1") + @NotNull + private final Integer id; + @Schema(description = "모임 제목", example = "모임 제목입니다") + @NotNull + String title; + @Schema(description = "대상 기수", example = "33") + @NotNull + private final Integer targetActiveGeneration; + @Schema(example = "[\n" + + " \"ANDROID\",\n" + + " \"IOS\"\n" + + " ]", description = "대상 파트 목록") + @NotNull + private final MeetingJoinablePart[] joinableParts; + @Schema(example = "스터디", description = "모임 카테고리") + @NotNull + private final String category; + @Schema(example = "false", description = "활동기수만 지원 가능 여부") + @NotNull + private final Boolean canJoinOnlyActiveGeneration; + @Schema(example = "2", description = "모임 활동 상태") + @NotNull + private final Integer status; + /** + * 썸네일 이미지 + * + * @apiNote 여러개여도 첫번째 이미지만 사용 + */ + @Schema(description = "모임 사진", example = "[url] 형식") + @NotNull + private final List imageURL; + @Schema(example = "false", description = "멘토 필요 여부") + @NotNull + private final Boolean isMentorNeeded; + @Schema(description = "모임 활동 시작일", example = "2024-07-31T15:30:00") + @NotNull + private final LocalDateTime mStartDate; + @Schema(description = "모임 활동 종료일", example = "2024-08-25T15:30:00") + @NotNull + private final LocalDateTime mEndDate; + @Schema(description = "모집 인원", example = "20") + @NotNull + private final int capacity; + @Schema(description = "모임장 정보", example = "") + @NotNull + private final MeetingCreatorDto user; + @Schema(description = "신청 수", example = "7") + @NotNull + private final int appliedCount; + + @QueryProjection + public MeetingResponseDto(Integer id, String title, Integer targetActiveGeneration, + @NotNull MeetingJoinablePart[] joinableParts, MeetingCategory category, Boolean canJoinOnlyActiveGeneration, + Integer status, + List imageURL, Boolean isMentorNeeded, LocalDateTime mStartDate, LocalDateTime mEndDate, + int capacity, + MeetingCreatorDto user, int appliedCount) { + + boolean processedCanJoinOnlyActiveGeneration = canJoinOnlyActiveGeneration + && (CrewConst.ACTIVE_GENERATION.equals(targetActiveGeneration)); + + this.id = id; + this.title = title; + this.targetActiveGeneration = targetActiveGeneration; + this.joinableParts = joinableParts; + this.category = category.getValue(); + this.canJoinOnlyActiveGeneration = processedCanJoinOnlyActiveGeneration; + this.status = status; + this.imageURL = imageURL; + this.isMentorNeeded = isMentorNeeded; + this.mStartDate = mStartDate; + this.mEndDate = mEndDate; + this.capacity = capacity; + this.user = user; + this.appliedCount = appliedCount; + } + + public static MeetingResponseDto of(Meeting meeting, User meetingCreator, int appliedCount){ + MeetingCreatorDto creatorDto = MeetingCreatorDto.of(meetingCreator); + boolean canJoinOnlyActiveGeneration = meeting.getTargetActiveGeneration() == CrewConst.ACTIVE_GENERATION + && meeting.getCanJoinOnlyActiveGeneration(); + + return new MeetingResponseDto(meeting.getId(), meeting.getTitle(), + meeting.getTargetActiveGeneration(), meeting.getJoinableParts(), meeting.getCategory(), + canJoinOnlyActiveGeneration, meeting.getMeetingStatus(), meeting.getImageURL(), + meeting.getIsMentorNeeded(), meeting.getMStartDate(), meeting.getMEndDate(), meeting.getCapacity(), creatorDto, appliedCount); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingRepository.java index 6d406010..423189ab 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingRepository.java @@ -9,7 +9,7 @@ import org.sopt.makers.crew.main.entity.user.User; import org.springframework.data.jpa.repository.JpaRepository; -public interface MeetingRepository extends JpaRepository { +public interface MeetingRepository extends JpaRepository, MeetingSearchRepository { List findAllByUserId(Integer userId); diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingSearchRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingSearchRepository.java new file mode 100644 index 00000000..37757b15 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingSearchRepository.java @@ -0,0 +1,10 @@ +package org.sopt.makers.crew.main.entity.meeting; + +import org.sopt.makers.crew.main.common.util.Time; +import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingQueryDto; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface MeetingSearchRepository { + Page findAllByQuery(MeetingV2GetAllMeetingQueryDto queryCommand, Pageable pageable, Time time); +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingSearchRepositoryImpl.java b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingSearchRepositoryImpl.java new file mode 100644 index 00000000..ca888976 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingSearchRepositoryImpl.java @@ -0,0 +1,170 @@ +package org.sopt.makers.crew.main.entity.meeting; + +import static org.sopt.makers.crew.main.entity.meeting.QMeeting.*; +import static org.sopt.makers.crew.main.entity.user.QUser.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import org.sopt.makers.crew.main.common.constant.CrewConst; +import org.sopt.makers.crew.main.common.util.Time; +import org.sopt.makers.crew.main.entity.meeting.enums.EnMeetingStatus; +import org.sopt.makers.crew.main.entity.meeting.enums.MeetingCategory; +import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart; +import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingQueryDto; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.support.PageableExecutionUtils; +import org.springframework.stereotype.Repository; + +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.jpa.impl.JPAQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class MeetingSearchRepositoryImpl implements MeetingSearchRepository { + private final JPAQueryFactory queryFactory; + //private final Time time; + + /** + * @note: canJoinOnlyActiveGeneration 처리 유의 + * @note: status 처리 유의 + * */ + + @Override + public Page findAllByQuery(MeetingV2GetAllMeetingQueryDto queryCommand, Pageable pageable, Time time) { + + List meetings = getMeetings(queryCommand, pageable, time); + JPAQuery countQuery = getCount(queryCommand, time); + + return PageableExecutionUtils.getPage(meetings, + PageRequest.of(pageable.getPageNumber(), pageable.getPageSize()), countQuery::fetchFirst); + } + + private List getMeetings(MeetingV2GetAllMeetingQueryDto queryCommand, Pageable pageable, Time time) { + return queryFactory + .selectFrom(meeting) + .where( + eqCategory(queryCommand.getCategory()), + eqStatus(queryCommand.getStatus(), time), + isOnlyActiveGeneration(queryCommand.getIsOnlyActiveGeneration()), + eqJoinableParts(queryCommand.getJoinableParts()), + eqQuery(queryCommand.getQuery()) + ) + .innerJoin(meeting.user, user) + .fetchJoin() + .orderBy(meeting.id.desc()) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + } + + private JPAQuery getCount(MeetingV2GetAllMeetingQueryDto queryCommand, Time time) { + return queryFactory + .select(meeting.count()) + .from(meeting) + .where( + eqCategory(queryCommand.getCategory()), + eqStatus(queryCommand.getStatus(), time), + isOnlyActiveGeneration(queryCommand.getIsOnlyActiveGeneration()), + eqJoinableParts(queryCommand.getJoinableParts()), + eqQuery(queryCommand.getQuery()) + ); + } + + private BooleanExpression eqCategory(List categories) { + + if (categories == null || categories.isEmpty()) { + return null; + } + + List categoryList = categories.stream() + .map(MeetingCategory::ofValue) + .collect(Collectors.toList()); + + return meeting.category.in(categoryList); + } + + private BooleanExpression eqStatus(List statues, Time time) { + + if (statues == null || statues.isEmpty()) { + return null; + } + + List conditions = new ArrayList<>(); + + List statuesInt = statues.stream() + .map(Integer::parseInt) + .toList(); + + for (Integer status : statuesInt) { + if (status == EnMeetingStatus.BEFORE_START.getValue()) { + BooleanExpression condition = meeting.startDate.after(time.now()); + conditions.add(condition); + } else if (status == EnMeetingStatus.APPLY_ABLE.getValue()) { + BooleanExpression afterStartDate = meeting.startDate.before(time.now()); + BooleanExpression beforeEndDate = meeting.endDate.after(time.now()); + BooleanExpression condition = afterStartDate.and(beforeEndDate); + conditions.add(condition); + } else if (status == EnMeetingStatus.RECRUITMENT_COMPLETE.getValue()) { + BooleanExpression condition = meeting.endDate.before(time.now()); + conditions.add(condition); + } + } + + if (conditions.isEmpty()) { + return null; // No valid conditions + } + + BooleanExpression combinedCondition = conditions.get(0); + for (int i = 1; i < conditions.size(); i++) { + combinedCondition = combinedCondition.or(conditions.get(i)); + } + + return combinedCondition; + } + + private BooleanExpression isOnlyActiveGeneration(boolean isOnlyActiveGeneration) { + + if (isOnlyActiveGeneration) { + return meeting.canJoinOnlyActiveGeneration.eq(true) + .and(meeting.targetActiveGeneration.eq(CrewConst.ACTIVE_GENERATION)); + } + + return null; + } + + private BooleanExpression eqJoinableParts(MeetingJoinablePart[] joinableParts) { + + if (joinableParts == null || joinableParts.length == 0) { + return null; + } + + String joinablePartsToString = Arrays.stream(joinableParts) + .map(Enum::name) // 각 요소를 큰따옴표로 감쌉니다. + .collect(Collectors.joining(",", "'{", "}'")); // 요소들을 쉼표로 연결하고 중괄호로 감쌉니다. + + // SQL 템플릿을 사용하여 BooleanExpression 생성 + return Expressions.booleanTemplate( + "arraycontains({0}, "+ joinablePartsToString + ") || '' = 'true'", + meeting.joinableParts, + joinablePartsToString + ); + } + + private BooleanExpression eqQuery(String query) { + if (query == null) { + return null; + } + + return meeting.title.contains(query); + } + +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java index 4331585e..039e3c25 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java @@ -5,20 +5,25 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import java.security.Principal; import java.util.List; + +import org.sopt.makers.crew.main.common.dto.TempResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingGetAppliesQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingByOrgUserQueryDto; +import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2ApplyMeetingDto; import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2CreateMeetingBodyDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingGetApplyListResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2ApplyMeetingResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2CreateMeetingResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetAllMeetingByOrgUserDto; +import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetAllMeetingDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetMeetingBannerResponseDto; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; @@ -71,4 +76,32 @@ ResponseEntity applyMeetingCancel(@PathVariable Integer meetingId, ResponseEntity findApplyList(@PathVariable Integer meetingId, @ModelAttribute MeetingGetAppliesQueryDto queryCommand, Principal principal); + + @Operation(summary = "모임 전체 조회/검색/필터링", description = "모임 전체 조회/검색/필터링\n") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "모임 지원자/참여자 조회 성공")}) + @Parameters({ + @Parameter(name = "page", description = "페이지, default = 1", example = "1", schema = @Schema(type = "integer", format = "int32")), + @Parameter(name = "take", description = "가져올 데이터 개수, default = 12", example = "50", schema = @Schema(type = "integer", format = "int32")), + @Parameter(name = "category", description = "카테고리", example = "스터디,번개", schema = @Schema(type = "string", format = "string")), + @Parameter(name = "status", description = "모임 모집 상태", example = "0,1", schema = @Schema(type = "string", format = "string")), + @Parameter(name = "isOnlyActiveGeneration", description = "활동기수만 참여여부", example = "true", schema = @Schema(type = "boolean", format = "boolean")), + @Parameter(name = "joinableParts", description = "검색할 활동 파트 다중 선택. OR 조건으로 검색됨
Available values : PM, DESIGN, IOS, ANDROID, SERVER, WEB", example = "PM,DESIGN,IOS,ANDROID,SERVER,WEB", schema = @Schema(type = "array[string]", format = "array[string]")), + @Parameter(name = "query", description = "검색 내용", example = "고수스터디 검색", schema = @Schema(type = "string", format = "string")), + }) + ResponseEntity getMeetings(@ModelAttribute MeetingV2GetAllMeetingQueryDto queryCommand, + Principal principal); + + @Operation(summary = "[TEMP] 모임 전체 조회/검색/필터링", description = "모임 전체 조회/검색/필터링\n") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "모임 지원자/참여자 조회 성공")}) + @Parameters({ + @Parameter(name = "page", description = "페이지, default = 1", example = "1", schema = @Schema(type = "integer", format = "int32")), + @Parameter(name = "take", description = "가져올 데이터 개수, default = 12", example = "50", schema = @Schema(type = "integer", format = "int32")), + @Parameter(name = "category", description = "카테고리", example = "스터디,번개", schema = @Schema(type = "string", format = "string")), + @Parameter(name = "status", description = "모임 모집 상태", example = "0,1", schema = @Schema(type = "string", format = "string")), + @Parameter(name = "isOnlyActiveGeneration", description = "활동기수만 참여여부", example = "true", schema = @Schema(type = "boolean", format = "boolean")), + @Parameter(name = "joinableParts", description = "검색할 활동 파트 다중 선택. OR 조건으로 검색됨
Available values : PM, DESIGN, IOS, ANDROID, SERVER, WEB", example = "PM,DESIGN,IOS,ANDROID,SERVER,WEB", schema = @Schema(type = "array[string]", format = "array[string]")), + @Parameter(name = "query", description = "검색 내용", example = "고수스터디 검색", schema = @Schema(type = "string", format = "string")), + }) + ResponseEntity> getMeetingsTemp(@ModelAttribute MeetingV2GetAllMeetingQueryDto queryCommand, + Principal principal); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java index e05957c8..b89bdea9 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java @@ -5,15 +5,19 @@ import java.security.Principal; import java.util.List; import lombok.RequiredArgsConstructor; + +import org.sopt.makers.crew.main.common.dto.TempResponseDto; import org.sopt.makers.crew.main.common.util.UserUtil; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingGetAppliesQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingByOrgUserQueryDto; +import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2ApplyMeetingDto; import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2CreateMeetingBodyDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingGetApplyListResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2ApplyMeetingResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2CreateMeetingResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetAllMeetingByOrgUserDto; +import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetAllMeetingDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetMeetingBannerResponseDto; import org.sopt.makers.crew.main.meeting.v2.service.MeetingV2Service; import org.springframework.http.HttpStatus; @@ -91,4 +95,21 @@ public ResponseEntity findApplyList(@PathVariabl return ResponseEntity.status(HttpStatus.OK) .body(meetingV2Service.findApplyList(queryCommand, meetingId, userId)); } + + @Override + @GetMapping + public ResponseEntity getMeetings(@ModelAttribute @Valid MeetingV2GetAllMeetingQueryDto queryCommand, + Principal principal) { + + MeetingV2GetAllMeetingDto meetings = meetingV2Service.getMeetings(queryCommand); + return ResponseEntity.ok().body(meetings); + } + + @Override + @GetMapping("/temp") + public ResponseEntity> getMeetingsTemp( + MeetingV2GetAllMeetingQueryDto queryCommand, Principal principal) { + MeetingV2GetAllMeetingDto meetings = meetingV2Service.getMeetings(queryCommand); + return ResponseEntity.ok().body(TempResponseDto.of(meetings)); + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/query/MeetingV2GetAllMeetingQueryDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/query/MeetingV2GetAllMeetingQueryDto.java new file mode 100644 index 00000000..26b84e08 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/query/MeetingV2GetAllMeetingQueryDto.java @@ -0,0 +1,30 @@ +package org.sopt.makers.crew.main.meeting.v2.dto.query; + +import java.util.List; + +import org.sopt.makers.crew.main.common.pagination.dto.PageOptionsDto; +import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart; + +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class MeetingV2GetAllMeetingQueryDto extends PageOptionsDto { + + + List category; + + List status; + @NotNull + Boolean isOnlyActiveGeneration; + @NotNull + MeetingJoinablePart[] joinableParts; + @NotNull + String query; + + public MeetingV2GetAllMeetingQueryDto(Integer page, Integer take) { + super(page, take); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetAllMeetingDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetAllMeetingDto.java new file mode 100644 index 00000000..5d17f6d9 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetAllMeetingDto.java @@ -0,0 +1,23 @@ +package org.sopt.makers.crew.main.meeting.v2.dto.response; + +import java.util.List; + +import org.sopt.makers.crew.main.common.dto.MeetingResponseDto; +import org.sopt.makers.crew.main.common.pagination.dto.PageMetaDto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; + +@Schema(name = "MeetingV2GetAllMeetingDto", description = "모임 조회 응답 Dto") +public record MeetingV2GetAllMeetingDto( + @Schema(description = "모임 객체 목록", example = "") + @NotNull + List meetings, + @Schema(description = "페이지네이션 객체", example = "") + @NotNull + PageMetaDto meta +) { + public static MeetingV2GetAllMeetingDto of(List meetings, PageMetaDto meta){ + return new MeetingV2GetAllMeetingDto(meetings, meta); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java index 5a1362bf..8e8645d0 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java @@ -3,12 +3,14 @@ import java.util.List; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingGetAppliesQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingByOrgUserQueryDto; +import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2ApplyMeetingDto; import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2CreateMeetingBodyDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingGetApplyListResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2ApplyMeetingResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2CreateMeetingResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetAllMeetingByOrgUserDto; +import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetAllMeetingDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetMeetingBannerResponseDto; public interface MeetingV2Service { @@ -26,4 +28,6 @@ MeetingV2GetAllMeetingByOrgUserDto getAllMeetingByOrgUser( MeetingGetApplyListResponseDto findApplyList(MeetingGetAppliesQueryDto queryCommand, Integer meetingId, Integer userId); + + MeetingV2GetAllMeetingDto getMeetings(MeetingV2GetAllMeetingQueryDto queryCommand); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java index bf613ef8..d65f6f39 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java @@ -21,10 +21,13 @@ import lombok.RequiredArgsConstructor; +import org.sopt.makers.crew.main.common.dto.MeetingResponseDto; import org.sopt.makers.crew.main.common.exception.BadRequestException; import org.sopt.makers.crew.main.common.pagination.dto.PageMetaDto; import org.sopt.makers.crew.main.common.pagination.dto.PageOptionsDto; +import org.sopt.makers.crew.main.common.util.Time; import org.sopt.makers.crew.main.common.util.UserPartUtil; +import org.sopt.makers.crew.main.entity.apply.Applies; import org.sopt.makers.crew.main.entity.apply.Apply; import org.sopt.makers.crew.main.entity.apply.ApplyRepository; import org.sopt.makers.crew.main.entity.apply.enums.EnApplyStatus; @@ -42,6 +45,7 @@ import org.sopt.makers.crew.main.meeting.v2.dto.MeetingMapper; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingGetAppliesQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingByOrgUserQueryDto; +import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2ApplyMeetingDto; import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2CreateMeetingBodyDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.ApplyInfoDto; @@ -50,6 +54,7 @@ import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2CreateMeetingResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetAllMeetingByOrgUserDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetAllMeetingByOrgUserMeetingDto; +import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetAllMeetingDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetMeetingBannerResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetMeetingBannerResponseUserDto; import org.springframework.data.domain.Page; @@ -72,6 +77,8 @@ public class MeetingV2ServiceImpl implements MeetingV2Service { private final MeetingMapper meetingMapper; private final ApplyMapper applyMapper; + private final Time time; + @Override public MeetingV2GetAllMeetingByOrgUserDto getAllMeetingByOrgUser( MeetingV2GetAllMeetingByOrgUserQueryDto queryDto) { @@ -204,6 +211,26 @@ public MeetingGetApplyListResponseDto findApplyList(MeetingGetAppliesQueryDto qu return MeetingGetApplyListResponseDto.of(applyInfoDtos.getContent(), pageMetaDto); } + @Override + public MeetingV2GetAllMeetingDto getMeetings(MeetingV2GetAllMeetingQueryDto queryCommand) { + + Page meetings = meetingRepository.findAllByQuery(queryCommand, + PageRequest.of(queryCommand.getPage() - 1, queryCommand.getTake()), time); + List meetingIds = meetings.stream().map(Meeting::getId).toList(); + + Applies allApplies = new Applies(applyRepository.findAllByMeetingIdIn(meetingIds)); + + List meetingResponseDtos = meetings.getContent().stream() + .map(meeting -> MeetingResponseDto.of(meeting, meeting.getUser(), + allApplies.getAppliedCount(meeting.getId()))) + .toList(); + + PageOptionsDto pageOptionsDto = new PageOptionsDto(queryCommand.getPage(), queryCommand.getTake()); + PageMetaDto pageMetaDto = new PageMetaDto(pageOptionsDto, (int)meetings.getTotalElements()); + + return MeetingV2GetAllMeetingDto.of(meetingResponseDtos, pageMetaDto); + } + private Boolean checkMeetingLeader(Meeting meeting, Integer userId) { return meeting.getUserId().equals(userId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/MeetingV2GetCreatedMeetingByUserResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/MeetingV2GetCreatedMeetingByUserResponseDto.java index 0327056a..6daf7120 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/MeetingV2GetCreatedMeetingByUserResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/MeetingV2GetCreatedMeetingByUserResponseDto.java @@ -3,6 +3,7 @@ import java.time.LocalDateTime; import java.util.List; +import org.sopt.makers.crew.main.common.constant.CrewConst; import org.sopt.makers.crew.main.common.dto.MeetingCreatorDto; import org.sopt.makers.crew.main.entity.meeting.Meeting; import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart; @@ -67,10 +68,12 @@ public record MeetingV2GetCreatedMeetingByUserResponseDto( ) { public static MeetingV2GetCreatedMeetingByUserResponseDto of(Meeting meeting, User meetingCreator, int appliedCount) { MeetingCreatorDto creatorDto = MeetingCreatorDto.of(meetingCreator); + boolean canJoinOnlyActiveGeneration = meeting.getTargetActiveGeneration() == CrewConst.ACTIVE_GENERATION + && meeting.getCanJoinOnlyActiveGeneration(); return new MeetingV2GetCreatedMeetingByUserResponseDto(meeting.getId(), meeting.getTitle(), meeting.getTargetActiveGeneration(), meeting.getJoinableParts(), meeting.getCategory().getValue(), - meeting.getCanJoinOnlyActiveGeneration(), meeting.getMeetingStatus(), meeting.getImageURL(), + canJoinOnlyActiveGeneration, meeting.getMeetingStatus(), meeting.getImageURL(), meeting.getIsMentorNeeded(), meeting.getMStartDate(), meeting.getMEndDate(), meeting.getCapacity(), creatorDto, appliedCount); } diff --git a/server/src/meeting/v0/meeting-v0.controller.ts b/server/src/meeting/v0/meeting-v0.controller.ts index b4fb5be2..63be7486 100644 --- a/server/src/meeting/v0/meeting-v0.controller.ts +++ b/server/src/meeting/v0/meeting-v0.controller.ts @@ -136,6 +136,7 @@ export class MeetingV0Controller { @ApiOperation({ summary: '모임 전체 조회/검색/필터링', description: '모임 전체 조회/검색/필터링', + deprecated: true }) @ApiOkResponseCommon(MeetingV0GetAllMeetingsResponseDto) @Get('/') From c8faa2a4a0496827c200e5291ec67def12744188 Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Thu, 22 Aug 2024 10:49:18 +0900 Subject: [PATCH 24/47] =?UTF-8?q?[FEAT]=20=EB=A1=9C=EA=B7=B8=EC=9D=B8/?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20=EB=A7=88=EC=9D=B4?= =?UTF-8?q?=EA=B7=B8=EB=A0=88=EC=9D=B4=EC=85=98=20(#311)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: deprecated 처리 * add: dto 추가 * feat: 플레이그라운드 서버와의 통신 구현 * feat: 로그인/회원가입 로직 구현 * feat: 로그인/회원가입 엔티티 로직 구현 * chore: jwt 로직 포맷팅 및 클레임 put 로직 수정 * chore: 플레이그라운드 관련 데이터 추가 * chore: ci-cd 시크릿값 통일 * chore: 프록시 루트 변경 --- .github/workflows/cd-dev.yml | 2 +- .github/workflows/cd-prod.yml | 2 +- .github/workflows/ci.yml | 2 +- docker-compose.yml | 8 + .../makers/crew/main/MainApplication.java | 2 + .../makers/crew/main/auth/v2/AuthV2Api.java | 26 ++++ .../crew/main/auth/v2/AuthV2Controller.java | 29 ++++ .../auth/v2/dto/request/AuthV2RequestDto.java | 13 ++ .../v2/dto/response/AuthV2ResponseDto.java | 15 ++ .../main/auth/v2/service/AuthV2Service.java | 8 + .../auth/v2/service/AuthV2ServiceImpl.java | 63 ++++++++ .../main/common/config/FeignClientConfig.java | 10 -- .../main/common/config/SecurityConfig.java | 4 +- .../jwt/JwtAuthenticationEntryPoint.java | 21 +-- .../common/jwt/JwtAuthenticationFilter.java | 57 +++---- .../main/common/jwt/JwtExceptionType.java | 12 +- .../main/common/jwt/JwtTokenProvider.java | 144 +++++++++--------- .../main/common/jwt/UserAuthentication.java | 12 +- .../main/common/response/ErrorStatus.java | 1 + .../makers/crew/main/entity/user/User.java | 10 ++ .../crew/main/entity/user/UserRepository.java | 14 +- .../external/playground/PlaygroundServer.java | 14 ++ .../playground/PlaygroundService.java | 33 ++++ .../dto/request/PlaygroundUserRequestDto.java | 11 ++ .../PlaygroundUserActivityResponseDto.java | 9 ++ .../response/PlaygroundUserResponseDto.java | 44 ++++++ main/src/main/resources/application-dev.yml | 7 +- main/src/main/resources/application-prod.yml | 7 +- main/src/main/resources/application-test.yml | 7 +- server/src/auth/v0/auth-v0.controller.ts | 1 + 30 files changed, 441 insertions(+), 147 deletions(-) create mode 100644 main/src/main/java/org/sopt/makers/crew/main/auth/v2/AuthV2Api.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/auth/v2/AuthV2Controller.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/auth/v2/dto/request/AuthV2RequestDto.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/auth/v2/dto/response/AuthV2ResponseDto.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/auth/v2/service/AuthV2Service.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/auth/v2/service/AuthV2ServiceImpl.java delete mode 100644 main/src/main/java/org/sopt/makers/crew/main/common/config/FeignClientConfig.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/external/playground/PlaygroundServer.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/external/playground/PlaygroundService.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/external/playground/dto/request/PlaygroundUserRequestDto.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/external/playground/dto/response/PlaygroundUserActivityResponseDto.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/external/playground/dto/response/PlaygroundUserResponseDto.java diff --git a/.github/workflows/cd-dev.yml b/.github/workflows/cd-dev.yml index c1f2cb23..d4840b74 100644 --- a/.github/workflows/cd-dev.yml +++ b/.github/workflows/cd-dev.yml @@ -21,7 +21,7 @@ jobs: - name: Create application.properties from secret run: | - echo "${{ secrets.APPLICATION_SECRET_SPRING_DEV }}" > ./main/src/main/resources/application-secret.properties + echo "${{ secrets.APPLICATION_SECRET_SPRING }}" > ./main/src/main/resources/application-secret.properties shell: bash - name: Docker hub에 로그인 diff --git a/.github/workflows/cd-prod.yml b/.github/workflows/cd-prod.yml index 557ebe72..04fb1e23 100644 --- a/.github/workflows/cd-prod.yml +++ b/.github/workflows/cd-prod.yml @@ -21,7 +21,7 @@ jobs: - name: Create application.properties from secret run: | - echo "${{ secrets.APPLICATION_SECRET_SPRING_PROD }}" > ./main/src/main/resources/application-secret.properties + echo "${{ secrets.APPLICATION_SECRET_SPRING }}" > ./main/src/main/resources/application-secret.properties shell: bash - name: Docker hub에 로그인 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bf2f5864..9f35178e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: - name: Create application.properties from secret run: | - echo "${{ secrets.APPLICATION_SECRET_SPRING_DEV }}" > ./main/src/main/resources/application-secret.properties + echo "${{ secrets.APPLICATION_SECRET_SPRING }}" > ./main/src/main/resources/application-secret.properties shell: bash - name: Build and analyze (SpringBoot) diff --git a/docker-compose.yml b/docker-compose.yml index 3d7166a0..30df9779 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -148,6 +148,10 @@ services: caddy.route_11.reverse_proxy: "{{ upstreams 4000 }}" caddy.route_12: /advertisement/v2/* caddy.route_12.reverse_proxy: "{{ upstreams 4000 }}" + caddy.route_13: /auth/v2 + caddy.route_13.reverse_proxy: "{{ upstreams 4000 }}" + caddy.route_14: /auth/v2/* + caddy.route_14.reverse_proxy: "{{ upstreams 4000 }}" nestjs-blue: image: makerscrew/server:latest @@ -249,6 +253,10 @@ services: caddy.route_11.reverse_proxy: "{{ upstreams 4000 }}" caddy.route_12: /advertisement/v2/* caddy.route_12.reverse_proxy: "{{ upstreams 4000 }}" + caddy.route_13: /auth/v2 + caddy.route_13.reverse_proxy: "{{ upstreams 4000 }}" + caddy.route_14: /auth/v2/* + caddy.route_14.reverse_proxy: "{{ upstreams 4000 }}" networks: caddy: diff --git a/main/src/main/java/org/sopt/makers/crew/main/MainApplication.java b/main/src/main/java/org/sopt/makers/crew/main/MainApplication.java index 6d45a179..16a2ab9f 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/MainApplication.java +++ b/main/src/main/java/org/sopt/makers/crew/main/MainApplication.java @@ -2,10 +2,12 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.EnableAspectJAutoProxy; @SpringBootApplication @EnableAspectJAutoProxy +@EnableFeignClients public class MainApplication { public static void main(String[] args) { diff --git a/main/src/main/java/org/sopt/makers/crew/main/auth/v2/AuthV2Api.java b/main/src/main/java/org/sopt/makers/crew/main/auth/v2/AuthV2Api.java new file mode 100644 index 00000000..bde202fb --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/auth/v2/AuthV2Api.java @@ -0,0 +1,26 @@ +package org.sopt.makers.crew.main.auth.v2; + + +import org.sopt.makers.crew.main.auth.v2.dto.request.AuthV2RequestDto; +import org.sopt.makers.crew.main.auth.v2.dto.response.AuthV2ResponseDto; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; + +@Tag(name = "인증") +public interface AuthV2Api { + @Operation(summary = "로그인/회원가입") + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "성공"), + @ApiResponse(responseCode = "401", description = "유효하지 않는 토큰입니다.", content = @Content), + @ApiResponse(responseCode = "500", description = "크루 서버 또는 플레이그라운드 서버 오류입니다.", content = @Content), + }) + ResponseEntity loginUser( + @RequestBody @Valid AuthV2RequestDto requestDto); +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/auth/v2/AuthV2Controller.java b/main/src/main/java/org/sopt/makers/crew/main/auth/v2/AuthV2Controller.java new file mode 100644 index 00000000..a3066df6 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/auth/v2/AuthV2Controller.java @@ -0,0 +1,29 @@ +package org.sopt.makers.crew.main.auth.v2; + +import org.sopt.makers.crew.main.auth.v2.dto.request.AuthV2RequestDto; +import org.sopt.makers.crew.main.auth.v2.dto.response.AuthV2ResponseDto; +import org.sopt.makers.crew.main.auth.v2.service.AuthV2Service; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping("/auth/v2") +@RequiredArgsConstructor +public class AuthV2Controller implements AuthV2Api { + + private final AuthV2Service authV2Service; + + @Override + @PostMapping + public ResponseEntity loginUser(@RequestBody @Valid AuthV2RequestDto requestDto) { + AuthV2ResponseDto responseDto = authV2Service.loginUser(requestDto); + + return ResponseEntity.ok(responseDto); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/auth/v2/dto/request/AuthV2RequestDto.java b/main/src/main/java/org/sopt/makers/crew/main/auth/v2/dto/request/AuthV2RequestDto.java new file mode 100644 index 00000000..c760d74c --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/auth/v2/dto/request/AuthV2RequestDto.java @@ -0,0 +1,13 @@ +package org.sopt.makers.crew.main.auth.v2.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; + + +@Schema(description = "인증 관련 request body dto") +public record AuthV2RequestDto( + @Schema(example = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxOCIsImV4cCI6MTY3OTYwOTk3OH0.9D_Tc14J3S0VDmQgT5lUJ5i3KJZob3NKVmSS3fPjHAo", required = true, description = "플레이그라운드 토큰") + @NotNull + String authToken +) { +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/auth/v2/dto/response/AuthV2ResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/auth/v2/dto/response/AuthV2ResponseDto.java new file mode 100644 index 00000000..f866f671 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/auth/v2/dto/response/AuthV2ResponseDto.java @@ -0,0 +1,15 @@ +package org.sopt.makers.crew.main.auth.v2.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; + +@Schema(description = "인증 관련 response dto") +public record AuthV2ResponseDto( + @Schema(example = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoi7Iah66-86recIiwiaWQiOjI4MywiaWF0IjoxNzI0MjYxMDg3LCJleHAiOjE3NjAyNjEwODd9.r2ScFqhSdt6pyl7gUvx0qFXHIknhtrXQVGjJavbAVRY", required = true, description = "크루 토큰") + @NotNull + String accessToken +) { + public static AuthV2ResponseDto of(String accessToken){ + return new AuthV2ResponseDto(accessToken); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/auth/v2/service/AuthV2Service.java b/main/src/main/java/org/sopt/makers/crew/main/auth/v2/service/AuthV2Service.java new file mode 100644 index 00000000..75f5bf88 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/auth/v2/service/AuthV2Service.java @@ -0,0 +1,8 @@ +package org.sopt.makers.crew.main.auth.v2.service; + +import org.sopt.makers.crew.main.auth.v2.dto.request.AuthV2RequestDto; +import org.sopt.makers.crew.main.auth.v2.dto.response.AuthV2ResponseDto; + +public interface AuthV2Service { + AuthV2ResponseDto loginUser(AuthV2RequestDto requestDto); +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/auth/v2/service/AuthV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/auth/v2/service/AuthV2ServiceImpl.java new file mode 100644 index 00000000..ba6357a8 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/auth/v2/service/AuthV2ServiceImpl.java @@ -0,0 +1,63 @@ +package org.sopt.makers.crew.main.auth.v2.service; + +import java.util.Optional; + +import org.sopt.makers.crew.main.auth.v2.dto.request.AuthV2RequestDto; +import org.sopt.makers.crew.main.auth.v2.dto.response.AuthV2ResponseDto; +import org.sopt.makers.crew.main.common.jwt.JwtTokenProvider; +import org.sopt.makers.crew.main.entity.user.User; +import org.sopt.makers.crew.main.entity.user.UserRepository; +import org.sopt.makers.crew.main.external.playground.PlaygroundService; +import org.sopt.makers.crew.main.external.playground.dto.request.PlaygroundUserRequestDto; +import org.sopt.makers.crew.main.external.playground.dto.response.PlaygroundUserResponseDto; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Slf4j +public class AuthV2ServiceImpl implements AuthV2Service { + + private final UserRepository userRepository; + + private final PlaygroundService playgroundService; + private final JwtTokenProvider jwtTokenProvider; + + @Override + public AuthV2ResponseDto loginUser(AuthV2RequestDto requestDto) { + + // 플그 서버로의 요청 + PlaygroundUserResponseDto responseDto = playgroundService.getUser(PlaygroundUserRequestDto.of(requestDto.authToken())); + Optional user = userRepository.findByOrgId(responseDto.getId()); + + /** + * @note: 회원가입 경우 + * + * */ + if (user.isEmpty()) { + User newUser = responseDto.toEntity(); + userRepository.save(newUser); + + log.info("new user signup : {} {}", newUser.getId(), newUser.getName()); + String accessToken = jwtTokenProvider.generateAccessToken(newUser.getId(), newUser.getName()); + return AuthV2ResponseDto.of(accessToken); + } + + /** + * @note: 로그인 경우 : 기존 정보에서 변화있는 부분은 업데이트 한다. + * + * */ + User curUser = user.get(); + curUser.updateUser(responseDto.getName(), responseDto.getId(), responseDto.getUserActivities(), + responseDto.getProfileImage(), responseDto.getPhone()); + + String accessToken = jwtTokenProvider.generateAccessToken(curUser.getId(), curUser.getName()); + log.info("accessToken : {}", accessToken); + + return AuthV2ResponseDto.of(accessToken); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/config/FeignClientConfig.java b/main/src/main/java/org/sopt/makers/crew/main/common/config/FeignClientConfig.java deleted file mode 100644 index 963ec155..00000000 --- a/main/src/main/java/org/sopt/makers/crew/main/common/config/FeignClientConfig.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.sopt.makers.crew.main.common.config; - -import org.springframework.cloud.openfeign.EnableFeignClients; -import org.springframework.context.annotation.Configuration; - -@EnableFeignClients(basePackages = "org.sopt.makers.crew.main.internal.notification") -@Configuration -public class FeignClientConfig { - -} diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/config/SecurityConfig.java b/main/src/main/java/org/sopt/makers/crew/main/common/config/SecurityConfig.java index d1c8d72c..b3121b7d 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/config/SecurityConfig.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/config/SecurityConfig.java @@ -42,7 +42,9 @@ public class SecurityConfig { private static final String[] AUTH_WHITELIST = { "/health", "/health/v2", - "/meeting/v2/org-user/**" + "/meeting/v2/org-user/**", + "/auth/v2", + "/auth/v2/**" }; @Bean diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtAuthenticationEntryPoint.java b/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtAuthenticationEntryPoint.java index 79e08f90..02daa42c 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtAuthenticationEntryPoint.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtAuthenticationEntryPoint.java @@ -2,7 +2,9 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; + import java.io.IOException; + import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; @@ -10,16 +12,15 @@ @Component public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { - @Override - public void commence(HttpServletRequest request, HttpServletResponse response, - AuthenticationException authException) throws IOException { - setResponse(response); - } - + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, + AuthenticationException authException) throws IOException { + setResponse(response); + } - public void setResponse(HttpServletResponse response) throws IOException { - response.setContentType("application/json;charset=UTF-8"); - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - } + public void setResponse(HttpServletResponse response) throws IOException { + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtAuthenticationFilter.java b/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtAuthenticationFilter.java index afa732d9..b5399be2 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtAuthenticationFilter.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtAuthenticationFilter.java @@ -4,8 +4,11 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; + import java.io.IOException; + import lombok.RequiredArgsConstructor; + import org.sopt.makers.crew.main.common.response.ErrorStatus; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContextHolder; @@ -17,31 +20,31 @@ @RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { - private final JwtTokenProvider jwtTokenProvider; - private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, - FilterChain chain) throws ServletException, IOException { - String accessToken = jwtTokenProvider.resolveToken(request); - - if (accessToken != null) { - // 토큰 검증 - if (jwtTokenProvider.validateToken(accessToken) - == JwtExceptionType.VALID_JWT_TOKEN) { // 토큰이 존재하고 유효한 토큰일 때만 - Integer userId = jwtTokenProvider.getAccessTokenPayload(accessToken); - UserAuthentication authentication = new UserAuthentication(userId, null, - null); //사용자 객체 생성 - authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails( - request)); // request 정보로 사용자 객체 디테일 설정 - SecurityContextHolder.getContext().setAuthentication(authentication); - } else { - jwtAuthenticationEntryPoint.commence(request, response, - new AuthenticationException(ErrorStatus.UNAUTHORIZED_TOKEN.getErrorCode()) { - }); - return; - } - } - chain.doFilter(request, response); - } + private final JwtTokenProvider jwtTokenProvider; + private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain chain) throws ServletException, IOException { + String accessToken = jwtTokenProvider.resolveToken(request); + + if (accessToken != null) { + // 토큰 검증 + if (jwtTokenProvider.validateToken(accessToken) + == JwtExceptionType.VALID_JWT_TOKEN) { // 토큰이 존재하고 유효한 토큰일 때만 + Integer userId = jwtTokenProvider.getAccessTokenPayload(accessToken); + UserAuthentication authentication = new UserAuthentication(userId, null, + null); //사용자 객체 생성 + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails( + request)); // request 정보로 사용자 객체 디테일 설정 + SecurityContextHolder.getContext().setAuthentication(authentication); + } else { + jwtAuthenticationEntryPoint.commence(request, response, + new AuthenticationException(ErrorStatus.UNAUTHORIZED_TOKEN.getErrorCode()) { + }); + return; + } + } + chain.doFilter(request, response); + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtExceptionType.java b/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtExceptionType.java index aadf368f..3834205a 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtExceptionType.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtExceptionType.java @@ -1,10 +1,10 @@ package org.sopt.makers.crew.main.common.jwt; public enum JwtExceptionType { - VALID_JWT_TOKEN, // 유효한 JWT - INVALID_JWT_SIGNATURE, // 유효하지 않은 서명 - INVALID_JWT_TOKEN, // 유효하지 않은 토큰 - EXPIRED_JWT_TOKEN, // 만료된 토큰 - UNSUPPORTED_JWT_TOKEN, // 지원하지 않는 형식의 토큰 - EMPTY_JWT // 빈 JWT + VALID_JWT_TOKEN, // 유효한 JWT + INVALID_JWT_SIGNATURE, // 유효하지 않은 서명 + INVALID_JWT_TOKEN, // 유효하지 않은 토큰 + EXPIRED_JWT_TOKEN, // 만료된 토큰 + UNSUPPORTED_JWT_TOKEN, // 지원하지 않는 형식의 토큰 + EMPTY_JWT // 빈 JWT } diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtTokenProvider.java b/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtTokenProvider.java index f47fd7bd..1ed5b3d6 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtTokenProvider.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtTokenProvider.java @@ -8,14 +8,17 @@ import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.UnsupportedJwtException; import jakarta.servlet.http.HttpServletRequest; + import java.nio.charset.StandardCharsets; import java.security.Key; import java.util.Date; + import javax.crypto.spec.SecretKeySpec; + import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; + import org.springframework.beans.factory.annotation.Value; -import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; @Slf4j @@ -23,73 +26,74 @@ @RequiredArgsConstructor public class JwtTokenProvider { - @Value("${jwt.secret}") - private String secretKey; - - @Value("${jwt.access-token.expire-length}") - private Long accessTokenExpireLength; - - private static final String AUTHORIZATION_HEADER = "Authorization"; - - public String generateAccessToken(Authentication authentication) { - Date now = new Date(); - Date expiration = new Date(now.getTime() + accessTokenExpireLength); - - final Claims claims = Jwts.claims() - .setIssuedAt(now) - .setExpiration(expiration); - - claims.put("id", authentication.getPrincipal()); - - return Jwts.builder() - .setHeaderParam(Header.TYPE, Header.JWT_TYPE) - .setClaims(claims) - .signWith(getSignKey(), SignatureAlgorithm.HS256) - .compact(); - } - - public Integer getAccessTokenPayload(String token) { - return Integer.parseInt( - Jwts.parserBuilder().setSigningKey(getSignKey()).build().parseClaimsJws(token) - .getBody().get("id").toString()); - } - - public String resolveToken(HttpServletRequest request) { - - String header = request.getHeader(AUTHORIZATION_HEADER); - - if (header == null || !header.startsWith("Bearer ")) { - return null; - } else { - return header.split(" ")[1]; - } - } - - public JwtExceptionType validateToken(String token) { - try { - Jwts.parserBuilder().setSigningKey(getSignKey()).build().parseClaimsJws(token) - .getBody(); - return JwtExceptionType.VALID_JWT_TOKEN; - } catch (io.jsonwebtoken.security.SignatureException exception) { - log.error("잘못된 JWT 서명을 가진 토큰입니다."); - return JwtExceptionType.INVALID_JWT_SIGNATURE; - } catch (MalformedJwtException exception) { - log.error("잘못된 JWT 토큰입니다."); - return JwtExceptionType.INVALID_JWT_TOKEN; - } catch (ExpiredJwtException exception) { - log.error("만료된 JWT 토큰입니다."); - return JwtExceptionType.EXPIRED_JWT_TOKEN; - } catch (UnsupportedJwtException exception) { - log.error("지원하지 않는 JWT 토큰입니다."); - return JwtExceptionType.UNSUPPORTED_JWT_TOKEN; - } catch (IllegalArgumentException exception) { - log.error("JWT Claims가 비어있습니다."); - return JwtExceptionType.EMPTY_JWT; - } - } - - private Key getSignKey() { - byte[] keyBytes = secretKey.getBytes(StandardCharsets.UTF_8); - return new SecretKeySpec(keyBytes, "HmacSHA256"); - } + @Value("${jwt.secret}") + private String secretKey; + + @Value("${jwt.access-token.expire-length}") + private Long accessTokenExpireLength; + + private static final String AUTHORIZATION_HEADER = "Authorization"; + + public String generateAccessToken(Integer userId, String userName) { + Date now = new Date(); + Date expiration = new Date(now.getTime() + accessTokenExpireLength); + + final Claims claims = Jwts.claims() + .setIssuedAt(now) + .setExpiration(expiration); + + claims.put("id", userId); + claims.put("name", userName); + + return Jwts.builder() + .setHeaderParam(Header.TYPE, Header.JWT_TYPE) + .setClaims(claims) + .signWith(getSignKey(), SignatureAlgorithm.HS256) + .compact(); + } + + public Integer getAccessTokenPayload(String token) { + return Integer.parseInt( + Jwts.parserBuilder().setSigningKey(getSignKey()).build().parseClaimsJws(token) + .getBody().get("id").toString()); + } + + public String resolveToken(HttpServletRequest request) { + + String header = request.getHeader(AUTHORIZATION_HEADER); + + if (header == null || !header.startsWith("Bearer ")) { + return null; + } else { + return header.split(" ")[1]; + } + } + + public JwtExceptionType validateToken(String token) { + try { + Jwts.parserBuilder().setSigningKey(getSignKey()).build().parseClaimsJws(token) + .getBody(); + return JwtExceptionType.VALID_JWT_TOKEN; + } catch (io.jsonwebtoken.security.SignatureException exception) { + log.error("잘못된 JWT 서명을 가진 토큰입니다."); + return JwtExceptionType.INVALID_JWT_SIGNATURE; + } catch (MalformedJwtException exception) { + log.error("잘못된 JWT 토큰입니다."); + return JwtExceptionType.INVALID_JWT_TOKEN; + } catch (ExpiredJwtException exception) { + log.error("만료된 JWT 토큰입니다."); + return JwtExceptionType.EXPIRED_JWT_TOKEN; + } catch (UnsupportedJwtException exception) { + log.error("지원하지 않는 JWT 토큰입니다."); + return JwtExceptionType.UNSUPPORTED_JWT_TOKEN; + } catch (IllegalArgumentException exception) { + log.error("JWT Claims가 비어있습니다."); + return JwtExceptionType.EMPTY_JWT; + } + } + + private Key getSignKey() { + byte[] keyBytes = secretKey.getBytes(StandardCharsets.UTF_8); + return new SecretKeySpec(keyBytes, "HmacSHA256"); + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/jwt/UserAuthentication.java b/main/src/main/java/org/sopt/makers/crew/main/common/jwt/UserAuthentication.java index 5bc0cb62..9a2dda09 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/jwt/UserAuthentication.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/jwt/UserAuthentication.java @@ -1,16 +1,16 @@ package org.sopt.makers.crew.main.common.jwt; - import java.util.Collection; + import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.GrantedAuthority; // UsernamePasswordAuthenticationToken: 사용자의 인증 정보 저장하고 전달 public class UserAuthentication extends UsernamePasswordAuthenticationToken { - // 사용자 인증 객체 생성 - public UserAuthentication(Object principal, Object credentials, - Collection authorities) { - super(principal, credentials, authorities); - } + // 사용자 인증 객체 생성 + public UserAuthentication(Object principal, Object credentials, + Collection authorities) { + super(principal, credentials, authorities); + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/response/ErrorStatus.java b/main/src/main/java/org/sopt/makers/crew/main/common/response/ErrorStatus.java index 130043d8..ab9a66be 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/response/ErrorStatus.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/response/ErrorStatus.java @@ -33,6 +33,7 @@ public enum ErrorStatus { * 401 UNAUTHORIZED */ UNAUTHORIZED_TOKEN("유효하지 않은 토큰입니다."), + UNAUTHORIZED_USER("존재하지 않거나 유효하지 않은 유저입니다."), /** * 403 FORBIDDEN diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/user/User.java b/main/src/main/java/org/sopt/makers/crew/main/entity/user/User.java index e13ecae5..4a37d3bc 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/user/User.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/user/User.java @@ -84,4 +84,14 @@ public UserActivityVO getRecentActivityVO(){ .max(Comparator.comparingInt(UserActivityVO::getGeneration)) .orElseThrow(() -> new ServerException(INTERNAL_SERVER_ERROR.getErrorCode())); } + + public void updateUser(String name, Integer orgId, List activities, String profileImage, + String phone){ + + this.name = name; + this.orgId = orgId; + this.activities = activities; + this.profileImage = profileImage; + this.phone = phone; + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/user/UserRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/user/UserRepository.java index 0f390f1c..84960ddb 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/user/UserRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/user/UserRepository.java @@ -1,11 +1,9 @@ package org.sopt.makers.crew.main.entity.user; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.NO_CONTENT_EXCEPTION; +import static org.sopt.makers.crew.main.common.response.ErrorStatus.*; -import java.util.List; import java.util.Optional; -import org.sopt.makers.crew.main.common.exception.NoContentException; import org.sopt.makers.crew.main.common.exception.UnAuthorizedException; import org.springframework.data.jpa.repository.JpaRepository; @@ -14,14 +12,8 @@ public interface UserRepository extends JpaRepository { Optional findByOrgId(Integer orgId); default User findByIdOrThrow(Integer userId) { - return findById(userId).orElseThrow(() -> new UnAuthorizedException()); + return findById(userId) + .orElseThrow(() -> new UnAuthorizedException(UNAUTHORIZED_USER.getErrorCode())); } - default User findByOrgIdOrThrow(Integer orgUserId) { - return findByOrgId(orgUserId).orElseThrow( - () -> new NoContentException( - NO_CONTENT_EXCEPTION.getErrorCode())); //유저가 아직 모임 서비스를 이용 전이기 때문에 - } - - List findByIdIn(List userIds); } \ No newline at end of file diff --git a/main/src/main/java/org/sopt/makers/crew/main/external/playground/PlaygroundServer.java b/main/src/main/java/org/sopt/makers/crew/main/external/playground/PlaygroundServer.java new file mode 100644 index 00000000..752dd200 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/external/playground/PlaygroundServer.java @@ -0,0 +1,14 @@ +package org.sopt.makers.crew.main.external.playground; + + +import org.sopt.makers.crew.main.external.playground.dto.response.PlaygroundUserResponseDto; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; + +@FeignClient(name = "playgroundServer", url = "${playground.server.url}") +public interface PlaygroundServer { + @GetMapping("${playground.server.endpoint}") + PlaygroundUserResponseDto getUser(@RequestHeader("Authorization") String accessToken); + +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/external/playground/PlaygroundService.java b/main/src/main/java/org/sopt/makers/crew/main/external/playground/PlaygroundService.java new file mode 100644 index 00000000..e518a1df --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/external/playground/PlaygroundService.java @@ -0,0 +1,33 @@ +package org.sopt.makers.crew.main.external.playground; + +import java.util.HashMap; +import java.util.Map; + +import org.sopt.makers.crew.main.external.playground.dto.request.PlaygroundUserRequestDto; +import org.sopt.makers.crew.main.external.playground.dto.response.PlaygroundUserResponseDto; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class PlaygroundService { + private final PlaygroundServer playgroundServer; + + public PlaygroundUserResponseDto getUser(PlaygroundUserRequestDto requestDto){ + return playgroundServer.getUser(requestDto.accessToken()); + } + + + private Map createAuthorizationHeader(String accessToken) { + Map headers = createDefaultHeader(); + headers.put(HttpHeaders.AUTHORIZATION, accessToken); + return headers; + } + + private Map createDefaultHeader() { + return new HashMap<>(Map.of(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/external/playground/dto/request/PlaygroundUserRequestDto.java b/main/src/main/java/org/sopt/makers/crew/main/external/playground/dto/request/PlaygroundUserRequestDto.java new file mode 100644 index 00000000..2e366c52 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/external/playground/dto/request/PlaygroundUserRequestDto.java @@ -0,0 +1,11 @@ +package org.sopt.makers.crew.main.external.playground.dto.request; + + +public record PlaygroundUserRequestDto( + String accessToken + +) { + public static PlaygroundUserRequestDto of(String accessToken){ + return new PlaygroundUserRequestDto(accessToken); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/external/playground/dto/response/PlaygroundUserActivityResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/external/playground/dto/response/PlaygroundUserActivityResponseDto.java new file mode 100644 index 00000000..54dff740 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/external/playground/dto/response/PlaygroundUserActivityResponseDto.java @@ -0,0 +1,9 @@ +package org.sopt.makers.crew.main.external.playground.dto.response; + +public record PlaygroundUserActivityResponseDto( + Long id, + int generation, + String part, + String team +) { +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/external/playground/dto/response/PlaygroundUserResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/external/playground/dto/response/PlaygroundUserResponseDto.java new file mode 100644 index 00000000..62cac8d1 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/external/playground/dto/response/PlaygroundUserResponseDto.java @@ -0,0 +1,44 @@ +package org.sopt.makers.crew.main.external.playground.dto.response; + +import java.util.List; + +import org.sopt.makers.crew.main.entity.user.User; +import org.sopt.makers.crew.main.entity.user.vo.UserActivityVO; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * @note: id는 orgId 의미 + * + * */ +@RequiredArgsConstructor +@Getter +public class PlaygroundUserResponseDto { + + private final Integer id; + private final String name; + private final String profileImage; + private final String phone; + private final List activities; + + public User toEntity() { + List userActivityVOs = activities.stream() + .map(a -> new UserActivityVO(a.part(), a.generation())) + .toList(); + + return User.builder() + .orgId(this.id) + .name(name) + .profileImage(profileImage) + .phone(phone) + .activities(userActivityVOs) + .build(); + } + + public List getUserActivities(){ + return activities.stream() + .map(a -> new UserActivityVO(a.part(), a.generation())) + .toList(); + } +} diff --git a/main/src/main/resources/application-dev.yml b/main/src/main/resources/application-dev.yml index b4013a68..80bbca3a 100644 --- a/main/src/main/resources/application-dev.yml +++ b/main/src/main/resources/application-dev.yml @@ -54,4 +54,9 @@ push-notification: push-server-url: ${DEV_PUSH_SERVER_URL} notice: - secret-key : ${NOTICE_SECRET_KEY} \ No newline at end of file + secret-key : ${NOTICE_SECRET_KEY} + +playground: + server: + url: ${DEV_PLAYGROUND_URL} + endpoint: ${PLAYGROUND_ENDPOINT} \ No newline at end of file diff --git a/main/src/main/resources/application-prod.yml b/main/src/main/resources/application-prod.yml index 4110fa7b..b4316fd5 100644 --- a/main/src/main/resources/application-prod.yml +++ b/main/src/main/resources/application-prod.yml @@ -53,4 +53,9 @@ push-notification: push-server-url: ${PROD_PUSH_SERVER_URL} notice: - secret-key : ${NOTICE_SECRET_KEY} \ No newline at end of file + secret-key : ${NOTICE_SECRET_KEY} + +playground: + server: + url: ${PROD_PLAYGROUND_URL} + endpoint: ${PLAYGROUND_ENDPOINT} \ No newline at end of file diff --git a/main/src/main/resources/application-test.yml b/main/src/main/resources/application-test.yml index d245c43c..f6d1649a 100644 --- a/main/src/main/resources/application-test.yml +++ b/main/src/main/resources/application-test.yml @@ -57,4 +57,9 @@ push-notification: push-server-url: ${DEV_PUSH_SERVER_URL} notice: - secret-key : ${NOTICE_SECRET_KEY} \ No newline at end of file + secret-key : ${NOTICE_SECRET_KEY} + +playground: + server: + url: ${DEV_PLAYGROUND_URL} + endpoint: ${PLAYGROUND_ENDPOINT} \ No newline at end of file diff --git a/server/src/auth/v0/auth-v0.controller.ts b/server/src/auth/v0/auth-v0.controller.ts index 467c83e9..6df710a3 100644 --- a/server/src/auth/v0/auth-v0.controller.ts +++ b/server/src/auth/v0/auth-v0.controller.ts @@ -18,6 +18,7 @@ export class AuthV0Controller { @ApiOperation({ summary: '로그인/회원가입', description: '로그인/회원가입', + deprecated: true, }) @ApiResponse({ status: HttpStatus.UNAUTHORIZED, From b2cc9f3822af873db8875d65ffe3cb5504a11240 Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Thu, 22 Aug 2024 19:14:51 +0900 Subject: [PATCH 25/47] =?UTF-8?q?[FEAT]=20=EB=AA=A8=EC=9E=84=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20API=20=EA=B5=AC=ED=98=84=20(#313)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: deprecated 처리 * chore: 패키지 구조 변경 * feat: 모임 삭제 로직 구현 * feat: 모임장 확인 로직 구현 * docs: 모임 삭제 스웨거 추가 * chore: 패키지 구조로 인한 변경 * docs: 주석 수정 --- .../v2/service/CommentV2ServiceImpl.java | 2 +- .../advice/ControllerExceptionAdvice.java | 4 +- .../CommonResponseDto.java | 2 +- .../{response => exception}/ErrorStatus.java | 2 +- .../common/jwt/JwtAuthenticationFilter.java | 2 +- .../main/entity/apply/ApplyRepository.java | 7 ++- .../crew/main/entity/comment/Comment.java | 2 +- .../entity/comment/CommentRepository.java | 9 +++- .../crew/main/entity/like/LikeRepository.java | 14 ++++-- .../crew/main/entity/meeting/Meeting.java | 10 +++- .../entity/meeting/MeetingRepository.java | 2 +- .../makers/crew/main/entity/post/Post.java | 4 +- .../crew/main/entity/post/PostRepository.java | 13 ++++- .../entity/post/PostSearchRepositoryImpl.java | 2 +- .../makers/crew/main/entity/user/User.java | 2 +- .../crew/main/entity/user/UserRepository.java | 2 +- .../notification/PushNotificationService.java | 2 +- .../crew/main/meeting/v2/MeetingV2Api.java | 3 ++ .../main/meeting/v2/MeetingV2Controller.java | 9 ++++ .../meeting/v2/service/MeetingV2Service.java | 2 + .../v2/service/MeetingV2ServiceImpl.java | 50 ++++++++++++++++--- .../main/notice/service/NoticeV2Service.java | 4 +- .../post/v2/service/PostV2ServiceImpl.java | 4 +- .../v2/service/MeetingV2ServiceTest.java | 4 +- .../src/meeting/v0/meeting-v0.controller.ts | 1 + 25 files changed, 121 insertions(+), 37 deletions(-) rename main/src/main/java/org/sopt/makers/crew/main/common/{response => exception}/CommonResponseDto.java (88%) rename main/src/main/java/org/sopt/makers/crew/main/common/{response => exception}/ErrorStatus.java (97%) diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java index a3b7c9cf..5ea912b4 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java @@ -24,7 +24,7 @@ import org.sopt.makers.crew.main.common.exception.ForbiddenException; import org.sopt.makers.crew.main.common.pagination.dto.PageMetaDto; import org.sopt.makers.crew.main.common.pagination.dto.PageOptionsDto; -import org.sopt.makers.crew.main.common.response.ErrorStatus; +import org.sopt.makers.crew.main.common.exception.ErrorStatus; import org.sopt.makers.crew.main.common.util.MentionSecretStringRemover; import org.sopt.makers.crew.main.common.util.Time; import org.sopt.makers.crew.main.entity.comment.Comment; diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/advice/ControllerExceptionAdvice.java b/main/src/main/java/org/sopt/makers/crew/main/common/advice/ControllerExceptionAdvice.java index 76b05c84..84375352 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/advice/ControllerExceptionAdvice.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/advice/ControllerExceptionAdvice.java @@ -1,8 +1,8 @@ package org.sopt.makers.crew.main.common.advice; import org.sopt.makers.crew.main.common.exception.BaseException; -import org.sopt.makers.crew.main.common.response.CommonResponseDto; -import org.sopt.makers.crew.main.common.response.ErrorStatus; +import org.sopt.makers.crew.main.common.exception.CommonResponseDto; +import org.sopt.makers.crew.main.common.exception.ErrorStatus; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MissingServletRequestParameterException; diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/response/CommonResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/common/exception/CommonResponseDto.java similarity index 88% rename from main/src/main/java/org/sopt/makers/crew/main/common/response/CommonResponseDto.java rename to main/src/main/java/org/sopt/makers/crew/main/common/exception/CommonResponseDto.java index eca1b6f2..4d6cbfc9 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/response/CommonResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/exception/CommonResponseDto.java @@ -1,4 +1,4 @@ -package org.sopt.makers.crew.main.common.response; +package org.sopt.makers.crew.main.common.exception; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Builder; diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/response/ErrorStatus.java b/main/src/main/java/org/sopt/makers/crew/main/common/exception/ErrorStatus.java similarity index 97% rename from main/src/main/java/org/sopt/makers/crew/main/common/response/ErrorStatus.java rename to main/src/main/java/org/sopt/makers/crew/main/common/exception/ErrorStatus.java index ab9a66be..3d477b72 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/response/ErrorStatus.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/exception/ErrorStatus.java @@ -1,4 +1,4 @@ -package org.sopt.makers.crew.main.common.response; +package org.sopt.makers.crew.main.common.exception; import lombok.AccessLevel; import lombok.Getter; diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtAuthenticationFilter.java b/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtAuthenticationFilter.java index b5399be2..0f1d45a2 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtAuthenticationFilter.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtAuthenticationFilter.java @@ -9,7 +9,7 @@ import lombok.RequiredArgsConstructor; -import org.sopt.makers.crew.main.common.response.ErrorStatus; +import org.sopt.makers.crew.main.common.exception.ErrorStatus; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/ApplyRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/ApplyRepository.java index 9db319fb..fcd22845 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/ApplyRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/ApplyRepository.java @@ -1,6 +1,6 @@ package org.sopt.makers.crew.main.entity.apply; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.NOT_FOUND_APPLY; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.NOT_FOUND_APPLY; import java.util.List; import java.util.Optional; @@ -41,4 +41,9 @@ default Apply findByIdOrThrow(Integer applyId) { .orElseThrow(() -> new BadRequestException(NOT_FOUND_APPLY.getErrorCode())); } + @Modifying(clearAutomatically = true) + @Transactional + @Query("DELETE FROM Apply a WHERE a.meetingId = :meetingId") + void deleteAllByMeetingIdQuery(Integer meetingId); + } \ No newline at end of file diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/comment/Comment.java b/main/src/main/java/org/sopt/makers/crew/main/entity/comment/Comment.java index 060293de..039c006b 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/comment/Comment.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/comment/Comment.java @@ -1,6 +1,6 @@ package org.sopt.makers.crew.main.entity.comment; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.*; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.*; import jakarta.persistence.Column; import jakarta.persistence.Entity; diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/comment/CommentRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/comment/CommentRepository.java index 817aaead..f41879e0 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/comment/CommentRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/comment/CommentRepository.java @@ -4,7 +4,7 @@ import java.util.Optional; import org.sopt.makers.crew.main.common.exception.BadRequestException; -import org.sopt.makers.crew.main.common.response.ErrorStatus; +import org.sopt.makers.crew.main.common.exception.ErrorStatus; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; @@ -30,8 +30,15 @@ default Comment findByIdAndPostIdOrThrow(Integer id, Integer postId){ List findAllByPostIdOrderByCreatedDate(Integer postId); + List findAllByPostIdIsIn(List postIds); + @Modifying(clearAutomatically = true) @Transactional @Query("DELETE FROM Comment c WHERE c.postId = :postId") void deleteAllByPostId(Integer postId); + + @Modifying(clearAutomatically = true) + @Transactional + @Query("DELETE FROM Comment c WHERE c.postId IN :postIds") + void deleteAllByPostIdsInQuery(List postIds); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/like/LikeRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/like/LikeRepository.java index 19442ea9..775a9e21 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/like/LikeRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/like/LikeRepository.java @@ -6,9 +6,6 @@ import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.transaction.annotation.Transactional; -import org.springframework.data.jpa.repository.Modifying; -import org.springframework.data.jpa.repository.Query; -import org.springframework.transaction.annotation.Transactional; public interface LikeRepository extends JpaRepository { @@ -19,12 +16,21 @@ public interface LikeRepository extends JpaRepository { @Query("DELETE FROM Like l WHERE l.postId = :postId") void deleteAllByPostId(Integer postId); + @Modifying(clearAutomatically = true) + @Transactional + @Query("DELETE FROM Like l WHERE l.postId IN :postIds") + void deleteAllByPostIdsInQuery(List postIds); + @Modifying(clearAutomatically = true) @Transactional @Query("DELETE FROM Like l WHERE l.commentId IN :commentIds") - void deleteAllByIdsInQuery(List commentIds); + void deleteAllByCommentIdsInQuery(List commentIds); boolean existsByUserIdAndCommentId(Integer userId, Integer commentId); void deleteByUserIdAndCommentId(Integer userId, Integer commentId); + + List findAllByPostIdIsIn(List postIds); + List findAllByCommentIdIsIn(List commentIds); + } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/Meeting.java b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/Meeting.java index b2968c1d..41c409a3 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/Meeting.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/Meeting.java @@ -1,9 +1,10 @@ package org.sopt.makers.crew.main.entity.meeting; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.*; + import io.hypersistence.utils.hibernate.type.array.EnumArrayType; import io.hypersistence.utils.hibernate.type.array.internal.AbstractArrayType; import io.hypersistence.utils.hibernate.type.json.JsonBinaryType; -import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Convert; import jakarta.persistence.Entity; @@ -26,6 +27,7 @@ import org.hibernate.annotations.Parameter; import org.hibernate.annotations.Type; +import org.sopt.makers.crew.main.common.exception.ForbiddenException; import org.sopt.makers.crew.main.entity.apply.Apply; import org.sopt.makers.crew.main.entity.meeting.converter.MeetingCategoryConverter; import org.sopt.makers.crew.main.entity.meeting.enums.EnMeetingStatus; @@ -217,4 +219,10 @@ public Integer getMeetingStatus() { return EnMeetingStatus.RECRUITMENT_COMPLETE.getValue(); } } + + public void validateMeetingCreator(Integer requestUserId){ + if(!this.userId.equals(requestUserId)){ + throw new ForbiddenException(FORBIDDEN_EXCEPTION.getErrorCode()); + } + } } \ No newline at end of file diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingRepository.java index 423189ab..776578ba 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingRepository.java @@ -1,6 +1,6 @@ package org.sopt.makers.crew.main.entity.meeting; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.NOT_FOUND_MEETING; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.NOT_FOUND_MEETING; import java.util.List; import java.util.Optional; diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/post/Post.java b/main/src/main/java/org/sopt/makers/crew/main/entity/post/Post.java index 0490445d..9d273625 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/post/Post.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/post/Post.java @@ -1,9 +1,8 @@ package org.sopt.makers.crew.main.entity.post; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.*; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.*; import io.hypersistence.utils.hibernate.type.array.StringArrayType; -import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EntityListeners; @@ -24,7 +23,6 @@ import org.hibernate.annotations.Type; import org.sopt.makers.crew.main.common.exception.ForbiddenException; -import org.sopt.makers.crew.main.entity.comment.Comment; import org.sopt.makers.crew.main.entity.meeting.Meeting; import org.sopt.makers.crew.main.entity.user.User; import org.springframework.data.annotation.CreatedDate; diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostRepository.java index d768747a..3dccb412 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostRepository.java @@ -1,10 +1,14 @@ package org.sopt.makers.crew.main.entity.post; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.NOT_FOUND_POST; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.NOT_FOUND_POST; +import java.util.List; import java.util.Optional; import org.sopt.makers.crew.main.common.exception.BadRequestException; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.transaction.annotation.Transactional; public interface PostRepository extends JpaRepository, PostSearchRepository { @@ -18,4 +22,11 @@ default Post findByIdOrThrow(Integer postId) { Optional findFirstByMeetingIdOrderByIdDesc(Integer meetingId); Integer countByMeetingId(Integer meetingId); + + List findAllByMeetingId(Integer meetingId); + + @Modifying(clearAutomatically = true) + @Transactional + @Query("DELETE FROM Post p WHERE p.meetingId = :meetingId") + void deleteAllByMeetingIdQuery(Integer meetingId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepositoryImpl.java b/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepositoryImpl.java index 271ad6eb..f255113a 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepositoryImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostSearchRepositoryImpl.java @@ -1,6 +1,6 @@ package org.sopt.makers.crew.main.entity.post; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.NOT_FOUND_POST; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.NOT_FOUND_POST; import static org.sopt.makers.crew.main.entity.comment.QComment.comment; import static org.sopt.makers.crew.main.entity.like.QLike.like; import static org.sopt.makers.crew.main.entity.meeting.QMeeting.meeting; diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/user/User.java b/main/src/main/java/org/sopt/makers/crew/main/entity/user/User.java index 4a37d3bc..edb0bb6f 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/user/User.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/user/User.java @@ -1,6 +1,6 @@ package org.sopt.makers.crew.main.entity.user; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.*; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.*; import io.hypersistence.utils.hibernate.type.json.JsonBinaryType; import jakarta.persistence.Column; diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/user/UserRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/user/UserRepository.java index 84960ddb..1531436c 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/user/UserRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/user/UserRepository.java @@ -1,6 +1,6 @@ package org.sopt.makers.crew.main.entity.user; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.*; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.*; import java.util.Optional; diff --git a/main/src/main/java/org/sopt/makers/crew/main/internal/notification/PushNotificationService.java b/main/src/main/java/org/sopt/makers/crew/main/internal/notification/PushNotificationService.java index 947b9b06..830da233 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/internal/notification/PushNotificationService.java +++ b/main/src/main/java/org/sopt/makers/crew/main/internal/notification/PushNotificationService.java @@ -1,6 +1,6 @@ package org.sopt.makers.crew.main.internal.notification; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.*; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.*; import static org.sopt.makers.crew.main.internal.notification.PushNotificationEnums.PUSH_NOTIFICATION_ACTION; import java.util.UUID; diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java index 039e3c25..33d93ecb 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java @@ -104,4 +104,7 @@ ResponseEntity getMeetings(@ModelAttribute MeetingV2G }) ResponseEntity> getMeetingsTemp(@ModelAttribute MeetingV2GetAllMeetingQueryDto queryCommand, Principal principal); + + @Operation(summary = "모임 삭제", description = "모임 삭제합니다.") + ResponseEntity deleteMeeting(@PathVariable("id") Integer meetingId, Principal principal); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java index b89bdea9..482fa566 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java @@ -112,4 +112,13 @@ public ResponseEntity> getMeetingsTem MeetingV2GetAllMeetingDto meetings = meetingV2Service.getMeetings(queryCommand); return ResponseEntity.ok().body(TempResponseDto.of(meetings)); } + + @Override + @DeleteMapping("/{meetingId}") + public ResponseEntity deleteMeeting(@PathVariable("meetingId") Integer meetingId, Principal principal) { + Integer userId = UserUtil.getUserId(principal); + meetingV2Service.deleteMeeting(meetingId, userId); + + return ResponseEntity.ok().build(); + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java index 8e8645d0..98047934 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java @@ -30,4 +30,6 @@ MeetingGetApplyListResponseDto findApplyList(MeetingGetAppliesQueryDto queryComm Integer userId); MeetingV2GetAllMeetingDto getMeetings(MeetingV2GetAllMeetingQueryDto queryCommand); + + void deleteMeeting(Integer meetingId, Integer userId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java index d65f6f39..8365f0d9 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java @@ -1,14 +1,14 @@ package org.sopt.makers.crew.main.meeting.v2.service; import static org.sopt.makers.crew.main.common.constant.CrewConst.ACTIVE_GENERATION; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.ALREADY_APPLIED_MEETING; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.FULL_MEETING_CAPACITY; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.MISSING_GENERATION_PART; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.NOT_ACTIVE_GENERATION; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.NOT_FOUND_APPLY; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.NOT_IN_APPLY_PERIOD; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.NOT_TARGET_PART; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.VALIDATION_EXCEPTION; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.ALREADY_APPLIED_MEETING; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.FULL_MEETING_CAPACITY; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.MISSING_GENERATION_PART; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.NOT_ACTIVE_GENERATION; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.NOT_FOUND_APPLY; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.NOT_IN_APPLY_PERIOD; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.NOT_TARGET_PART; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.VALIDATION_EXCEPTION; import java.time.LocalDateTime; import java.util.ArrayList; @@ -32,6 +32,10 @@ import org.sopt.makers.crew.main.entity.apply.ApplyRepository; import org.sopt.makers.crew.main.entity.apply.enums.EnApplyStatus; import org.sopt.makers.crew.main.entity.apply.enums.EnApplyType; +import org.sopt.makers.crew.main.entity.comment.Comment; +import org.sopt.makers.crew.main.entity.comment.CommentRepository; +import org.sopt.makers.crew.main.entity.like.Like; +import org.sopt.makers.crew.main.entity.like.LikeRepository; import org.sopt.makers.crew.main.entity.meeting.Meeting; import org.sopt.makers.crew.main.entity.meeting.MeetingRepository; import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart; @@ -73,6 +77,8 @@ public class MeetingV2ServiceImpl implements MeetingV2Service { private final ApplyRepository applyRepository; private final MeetingRepository meetingRepository; private final PostRepository postRepository; + private final CommentRepository commentRepository; + private final LikeRepository likeRepository; private final MeetingMapper meetingMapper; private final ApplyMapper applyMapper; @@ -231,6 +237,34 @@ public MeetingV2GetAllMeetingDto getMeetings(MeetingV2GetAllMeetingQueryDto quer return MeetingV2GetAllMeetingDto.of(meetingResponseDtos, pageMetaDto); } + /** + * @note: 1. like(Comment, post 관련) -> comment -> post 순으로 삭제 + * 2. apply 삭제 + * 3. meeting 삭제 + * */ + + @Override + @Transactional + public void deleteMeeting(Integer meetingId, Integer userId) { + Meeting meeting = meetingRepository.findByIdOrThrow(meetingId); + meeting.validateMeetingCreator(userId); + + List posts = postRepository.findAllByMeetingId(meetingId); + List postIds = posts.stream().map(Post::getId).toList(); + + List comments = commentRepository.findAllByPostIdIsIn(postIds); + List commentIds = comments.stream().map(Comment::getId).toList(); + + likeRepository.deleteAllByPostIdsInQuery(postIds); + likeRepository.deleteAllByCommentIdsInQuery(commentIds); + + commentRepository.deleteAllByPostIdsInQuery(postIds); + postRepository.deleteAllByMeetingIdQuery(meetingId); + applyRepository.deleteAllByMeetingIdQuery(meetingId); + + meetingRepository.delete(meeting); + } + private Boolean checkMeetingLeader(Meeting meeting, Integer userId) { return meeting.getUserId().equals(userId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/notice/service/NoticeV2Service.java b/main/src/main/java/org/sopt/makers/crew/main/notice/service/NoticeV2Service.java index 96cc326b..31610539 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/notice/service/NoticeV2Service.java +++ b/main/src/main/java/org/sopt/makers/crew/main/notice/service/NoticeV2Service.java @@ -1,11 +1,11 @@ package org.sopt.makers.crew.main.notice.service; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.FORBIDDEN_EXCEPTION; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.FORBIDDEN_EXCEPTION; import java.time.LocalDateTime; import java.util.List; import lombok.RequiredArgsConstructor; -import org.sopt.makers.crew.main.common.exception.BadRequestException; + import org.sopt.makers.crew.main.common.exception.ForbiddenException; import org.sopt.makers.crew.main.entity.notice.Notice; import org.sopt.makers.crew.main.entity.notice.NoticeRepository; diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java index 418b49b2..466bfbe1 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java @@ -1,7 +1,7 @@ package org.sopt.makers.crew.main.post.v2.service; import static java.util.stream.Collectors.toList; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.FORBIDDEN_EXCEPTION; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.FORBIDDEN_EXCEPTION; import static org.sopt.makers.crew.main.internal.notification.PushNotificationEnums.NEW_POST_MENTION_PUSH_NOTIFICATION_TITLE; import static org.sopt.makers.crew.main.internal.notification.PushNotificationEnums.NEW_POST_PUSH_NOTIFICATION_TITLE; import static org.sopt.makers.crew.main.internal.notification.PushNotificationEnums.PUSH_NOTIFICATION_CATEGORY; @@ -190,7 +190,7 @@ public void deletePost(Integer postId, Integer userId) { commentRepository.deleteAllByPostId(postId); likeRepository.deleteAllByPostId(postId); - likeRepository.deleteAllByIdsInQuery(commentIds); + likeRepository.deleteAllByCommentIdsInQuery(commentIds); postRepository.delete(post); } diff --git a/main/src/test/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceTest.java b/main/src/test/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceTest.java index 95173295..77b2ab4f 100644 --- a/main/src/test/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceTest.java +++ b/main/src/test/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceTest.java @@ -4,8 +4,8 @@ import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.FULL_MEETING_CAPACITY; -import static org.sopt.makers.crew.main.common.response.ErrorStatus.NOT_IN_APPLY_PERIOD; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.FULL_MEETING_CAPACITY; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.NOT_IN_APPLY_PERIOD; import java.time.LocalDateTime; import java.time.Month; diff --git a/server/src/meeting/v0/meeting-v0.controller.ts b/server/src/meeting/v0/meeting-v0.controller.ts index 63be7486..bc10dbf4 100644 --- a/server/src/meeting/v0/meeting-v0.controller.ts +++ b/server/src/meeting/v0/meeting-v0.controller.ts @@ -149,6 +149,7 @@ export class MeetingV0Controller { @ApiOperation({ summary: '모임 삭제', description: '모임 삭제', + deprecated: true, }) @ApiResponse({ status: HttpStatus.OK, From dd411e8d90fc82e71081f1453a5f98072e91b1a8 Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Thu, 22 Aug 2024 19:24:18 +0900 Subject: [PATCH 26/47] =?UTF-8?q?chore:=20deprecated=20=EC=B2=98=EB=A6=AC?= =?UTF-8?q?=20(#315)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/meeting/v0/meeting-v0.controller.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/meeting/v0/meeting-v0.controller.ts b/server/src/meeting/v0/meeting-v0.controller.ts index bc10dbf4..3884f4d2 100644 --- a/server/src/meeting/v0/meeting-v0.controller.ts +++ b/server/src/meeting/v0/meeting-v0.controller.ts @@ -68,6 +68,7 @@ export class MeetingV0Controller { summary: '모임 지원자/참여자 조회', description: '모임 지원자/참여자 조회 (모임장이면 지원자, 아니면 참여자 조회)', + deprecated: true, }) @ApiOkResponseCommon(MeetingV0GetApplyListByMeetingResponseDto) @ApiResponse({ From 4a2267354f545330b6565a1622a4c2578843e8a3 Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Thu, 22 Aug 2024 20:35:34 +0900 Subject: [PATCH 27/47] =?UTF-8?q?[FEAT]=20=EB=AA=A8=EC=9E=84=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20API=20=EB=A7=88=EC=9D=B4=EA=B7=B8=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=85=98=20(#317)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: 스웨거 추가 * feat: 모임 수정 로직 구현 * chore: 가독성을 위한 수정 * test: 테스트 코드 수정 * chore: deprecated 처리 --- .../crew/main/entity/meeting/Meeting.java | 33 +++- .../crew/main/meeting/v2/MeetingV2Api.java | 5 +- .../main/meeting/v2/MeetingV2Controller.java | 185 ++++++++++-------- .../MeetingV2CreateMeetingBodyDto.java | 2 +- .../meeting/v2/service/MeetingV2Service.java | 2 + .../v2/service/MeetingV2ServiceImpl.java | 26 ++- .../v2/service/MeetingV2ServiceTest.java | 2 - .../src/meeting/v1/meeting-v1.controller.ts | 1 + 8 files changed, 157 insertions(+), 99 deletions(-) diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/Meeting.java b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/Meeting.java index 41c409a3..11dad9d3 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/Meeting.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/Meeting.java @@ -35,6 +35,7 @@ import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart; import org.sopt.makers.crew.main.entity.meeting.vo.ImageUrlVO; import org.sopt.makers.crew.main.entity.user.User; +import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2CreateMeetingBodyDto; import org.springframework.data.jpa.domain.support.AuditingEntityListener; @Entity @@ -176,7 +177,7 @@ public class Meeting { private MeetingJoinablePart[] joinableParts; @Builder - public Meeting(User user, Integer userId, List appliedInfo, String title, MeetingCategory category, + public Meeting(User user, Integer userId, String title, MeetingCategory category, List imageURL, LocalDateTime startDate, LocalDateTime endDate, Integer capacity, String desc, String processDesc, LocalDateTime mStartDate, LocalDateTime mEndDate, String leaderDesc, String targetDesc, String note, Boolean isMentorNeeded, @@ -220,9 +221,35 @@ public Integer getMeetingStatus() { } } - public void validateMeetingCreator(Integer requestUserId){ - if(!this.userId.equals(requestUserId)){ + public void validateMeetingCreator(Integer requestUserId) { + if (Boolean.FALSE.equals(checkMeetingLeader(requestUserId))) { throw new ForbiddenException(FORBIDDEN_EXCEPTION.getErrorCode()); } } + + public Boolean checkMeetingLeader(Integer userId) { + return this.userId.equals(userId); + } + + public void updateMeeting(Meeting updateMeeting) { + + this.title = updateMeeting.getTitle(); + this.category = updateMeeting.getCategory(); + this.imageURL = updateMeeting.getImageURL(); + this.startDate = updateMeeting.getStartDate(); + this.endDate = updateMeeting.getEndDate(); + this.capacity = updateMeeting.getCapacity(); + this.desc = updateMeeting.getDesc(); + this.processDesc = updateMeeting.getProcessDesc(); + this.mStartDate = updateMeeting.mStartDate; + this.mEndDate = updateMeeting.getMEndDate(); + this.leaderDesc = updateMeeting.getLeaderDesc(); + this.targetDesc = updateMeeting.getTargetDesc(); + this.note = updateMeeting.getNote(); + this.isMentorNeeded = updateMeeting.getIsMentorNeeded(); + this.canJoinOnlyActiveGeneration = updateMeeting.getCanJoinOnlyActiveGeneration(); + this.targetActiveGeneration = updateMeeting.getTargetActiveGeneration(); + this.joinableParts = updateMeeting.getJoinableParts(); + } + } \ No newline at end of file diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java index 33d93ecb..99d604db 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java @@ -106,5 +106,8 @@ ResponseEntity> getMeetingsTemp(@Mode Principal principal); @Operation(summary = "모임 삭제", description = "모임 삭제합니다.") - ResponseEntity deleteMeeting(@PathVariable("id") Integer meetingId, Principal principal); + ResponseEntity deleteMeeting(@PathVariable("meetingId") Integer meetingId, Principal principal); + + @Operation(summary = "모임 수정", description = "모임 내용을 수정합니다.") + ResponseEntity updateMeeting(@PathVariable Integer meetingId, @RequestBody @Valid MeetingV2CreateMeetingBodyDto requestBody ,Principal principal); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java index 482fa566..4a7660a9 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java @@ -2,8 +2,10 @@ import io.swagger.v3.oas.annotations.Parameter; import jakarta.validation.Valid; + import java.security.Principal; import java.util.List; + import lombok.RequiredArgsConstructor; import org.sopt.makers.crew.main.common.dto.TempResponseDto; @@ -27,6 +29,7 @@ import org.springframework.web.bind.annotation.ModelAttribute; 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.ResponseStatus; @@ -37,88 +40,102 @@ @RequiredArgsConstructor public class MeetingV2Controller implements MeetingV2Api { - private final MeetingV2Service meetingV2Service; - - @Override - @GetMapping("/org-user") - @ResponseStatus(HttpStatus.OK) - public ResponseEntity getAllMeetingByOrgUser( - @ModelAttribute @Parameter(hidden = true) MeetingV2GetAllMeetingByOrgUserQueryDto queryDto) { - return ResponseEntity.ok(meetingV2Service.getAllMeetingByOrgUser(queryDto)); - } - - @Override - @GetMapping("/banner") - @ResponseStatus(HttpStatus.OK) - public ResponseEntity> getMeetingBanner( - Principal principal) { - UserUtil.getUserId(principal); - return ResponseEntity.ok(meetingV2Service.getMeetingBanner()); - } - - @Override - @PostMapping - public ResponseEntity createMeeting( - @Valid @RequestBody MeetingV2CreateMeetingBodyDto requestBody, - Principal principal) { - Integer userId = UserUtil.getUserId(principal); - return ResponseEntity.status(HttpStatus.CREATED).body(meetingV2Service.createMeeting(requestBody, userId)); - } - - @Override - @PostMapping("/apply") - public ResponseEntity applyMeeting( - @Valid @RequestBody MeetingV2ApplyMeetingDto requestBody, - Principal principal) { - Integer userId = UserUtil.getUserId(principal); - return ResponseEntity.status(HttpStatus.CREATED).body(meetingV2Service.applyMeeting(requestBody, userId)); - } - - @Override - @DeleteMapping("/{meetingId}/apply") - @ResponseStatus(HttpStatus.OK) - public ResponseEntity applyMeetingCancel(@PathVariable Integer meetingId, - Principal principal) { - Integer userId = UserUtil.getUserId(principal); - meetingV2Service.applyMeetingCancel(meetingId, userId); - return ResponseEntity.status(HttpStatus.OK).build(); - } - - @Override - @GetMapping("/{meetingId}/list") - public ResponseEntity findApplyList(@PathVariable Integer meetingId, - @ModelAttribute MeetingGetAppliesQueryDto queryCommand, - Principal principal) { - - Integer userId = UserUtil.getUserId(principal); - - return ResponseEntity.status(HttpStatus.OK) - .body(meetingV2Service.findApplyList(queryCommand, meetingId, userId)); - } - - @Override - @GetMapping - public ResponseEntity getMeetings(@ModelAttribute @Valid MeetingV2GetAllMeetingQueryDto queryCommand, - Principal principal) { - - MeetingV2GetAllMeetingDto meetings = meetingV2Service.getMeetings(queryCommand); - return ResponseEntity.ok().body(meetings); - } - - @Override - @GetMapping("/temp") - public ResponseEntity> getMeetingsTemp( - MeetingV2GetAllMeetingQueryDto queryCommand, Principal principal) { - MeetingV2GetAllMeetingDto meetings = meetingV2Service.getMeetings(queryCommand); - return ResponseEntity.ok().body(TempResponseDto.of(meetings)); - } - - @Override - @DeleteMapping("/{meetingId}") - public ResponseEntity deleteMeeting(@PathVariable("meetingId") Integer meetingId, Principal principal) { - Integer userId = UserUtil.getUserId(principal); - meetingV2Service.deleteMeeting(meetingId, userId); - - return ResponseEntity.ok().build(); - } + private final MeetingV2Service meetingV2Service; + + @Override + @GetMapping("/org-user") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity getAllMeetingByOrgUser( + @ModelAttribute @Parameter(hidden = true) MeetingV2GetAllMeetingByOrgUserQueryDto queryDto) { + return ResponseEntity.ok(meetingV2Service.getAllMeetingByOrgUser(queryDto)); + } + + @Override + @GetMapping("/banner") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity> getMeetingBanner( + Principal principal) { + UserUtil.getUserId(principal); + return ResponseEntity.ok(meetingV2Service.getMeetingBanner()); + } + + @Override + @PostMapping + public ResponseEntity createMeeting( + @Valid @RequestBody MeetingV2CreateMeetingBodyDto requestBody, + Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.status(HttpStatus.CREATED).body(meetingV2Service.createMeeting(requestBody, userId)); + } + + @Override + @PostMapping("/apply") + public ResponseEntity applyMeeting( + @Valid @RequestBody MeetingV2ApplyMeetingDto requestBody, + Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.status(HttpStatus.CREATED).body(meetingV2Service.applyMeeting(requestBody, userId)); + } + + @Override + @DeleteMapping("/{meetingId}/apply") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity applyMeetingCancel(@PathVariable Integer meetingId, + Principal principal) { + Integer userId = UserUtil.getUserId(principal); + meetingV2Service.applyMeetingCancel(meetingId, userId); + return ResponseEntity.status(HttpStatus.OK).build(); + } + + @Override + @GetMapping("/{meetingId}/list") + public ResponseEntity findApplyList(@PathVariable Integer meetingId, + @ModelAttribute MeetingGetAppliesQueryDto queryCommand, + Principal principal) { + + Integer userId = UserUtil.getUserId(principal); + + return ResponseEntity.status(HttpStatus.OK) + .body(meetingV2Service.findApplyList(queryCommand, meetingId, userId)); + } + + @Override + @GetMapping + public ResponseEntity getMeetings( + @ModelAttribute @Valid MeetingV2GetAllMeetingQueryDto queryCommand, + Principal principal) { + + MeetingV2GetAllMeetingDto meetings = meetingV2Service.getMeetings(queryCommand); + return ResponseEntity.ok().body(meetings); + } + + @Override + @GetMapping("/temp") + public ResponseEntity> getMeetingsTemp( + MeetingV2GetAllMeetingQueryDto queryCommand, Principal principal) { + MeetingV2GetAllMeetingDto meetings = meetingV2Service.getMeetings(queryCommand); + return ResponseEntity.ok().body(TempResponseDto.of(meetings)); + } + + @Override + @DeleteMapping("/{meetingId}") + public ResponseEntity deleteMeeting(@PathVariable("meetingId") Integer meetingId, Principal principal) { + Integer userId = UserUtil.getUserId(principal); + meetingV2Service.deleteMeeting(meetingId, userId); + + return ResponseEntity.ok().build(); + } + + @Override + @PutMapping("/{meetingId}") + public ResponseEntity updateMeeting( + @PathVariable Integer meetingId, + @RequestBody @Valid MeetingV2CreateMeetingBodyDto requestBody, + Principal principal) { + + Integer userId = UserUtil.getUserId(principal); + meetingV2Service.updateMeeting(meetingId, requestBody, userId); + + return ResponseEntity.ok().build(); + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/request/MeetingV2CreateMeetingBodyDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/request/MeetingV2CreateMeetingBodyDto.java index 8108cf84..b46fc5d7 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/request/MeetingV2CreateMeetingBodyDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/request/MeetingV2CreateMeetingBodyDto.java @@ -14,7 +14,7 @@ @Getter @AllArgsConstructor -@Schema(description = "모임 생성 request body dto") +@Schema(description = "모임 생성 및 수정 request body dto") public class MeetingV2CreateMeetingBodyDto { @Schema(example = "알고보면 쓸데있는 개발 프로세스", description = "모임 제목") diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java index 98047934..c0883606 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java @@ -32,4 +32,6 @@ MeetingGetApplyListResponseDto findApplyList(MeetingGetAppliesQueryDto queryComm MeetingV2GetAllMeetingDto getMeetings(MeetingV2GetAllMeetingQueryDto queryCommand); void deleteMeeting(Integer meetingId, Integer userId); + + void updateMeeting(Integer meetingId, MeetingV2CreateMeetingBodyDto requestBody, Integer userId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java index 8365f0d9..ed15235e 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java @@ -34,7 +34,6 @@ import org.sopt.makers.crew.main.entity.apply.enums.EnApplyType; import org.sopt.makers.crew.main.entity.comment.Comment; import org.sopt.makers.crew.main.entity.comment.CommentRepository; -import org.sopt.makers.crew.main.entity.like.Like; import org.sopt.makers.crew.main.entity.like.LikeRepository; import org.sopt.makers.crew.main.entity.meeting.Meeting; import org.sopt.makers.crew.main.entity.meeting.MeetingRepository; @@ -101,9 +100,9 @@ public MeetingV2GetAllMeetingByOrgUserDto getAllMeetingByOrgUser( userJoinedList = Stream .concat(myMeetings.stream(), applyRepository.findAllByUserIdAndStatus(existUser.getId(), EnApplyStatus.APPROVE) - .stream().map(apply -> apply.getMeeting())) + .stream().map(Apply::getMeeting)) .map(meeting -> MeetingV2GetAllMeetingByOrgUserMeetingDto.of(meeting.getId(), - checkMeetingLeader(meeting, existUser.getId()), meeting.getTitle(), + meeting.checkMeetingLeader(existUser.getId()), meeting.getTitle(), meeting.getImageURL().get(0).getUrl(), meeting.getCategory().getValue(), meeting.getMStartDate(), meeting.getMEndDate(), checkActivityStatus(meeting))) .sorted(Comparator.comparing(MeetingV2GetAllMeetingByOrgUserMeetingDto::getId).reversed()) @@ -163,7 +162,7 @@ public MeetingV2CreateMeetingResponseDto createMeeting(MeetingV2CreateMeetingBod } Meeting meeting = meetingMapper.toMeetingEntity(requestBody, - getTargetActiveGeneration(requestBody.getCanJoinOnlyActiveGeneration()), ACTIVE_GENERATION, user, + createTargetActiveGeneration(requestBody.getCanJoinOnlyActiveGeneration()), ACTIVE_GENERATION, user, user.getId()); Meeting savedMeeting = meetingRepository.save(meeting); @@ -265,8 +264,19 @@ public void deleteMeeting(Integer meetingId, Integer userId) { meetingRepository.delete(meeting); } - private Boolean checkMeetingLeader(Meeting meeting, Integer userId) { - return meeting.getUserId().equals(userId); + @Override + @Transactional + public void updateMeeting(Integer meetingId, MeetingV2CreateMeetingBodyDto requestBody, Integer userId) { + User user = userRepository.findByIdOrThrow(userId); + + Meeting meeting = meetingRepository.findByIdOrThrow(meetingId); + meeting.validateMeetingCreator(userId); + + Meeting updatedMeeting = meetingMapper.toMeetingEntity(requestBody, + createTargetActiveGeneration(requestBody.getCanJoinOnlyActiveGeneration()), ACTIVE_GENERATION, user, + user.getId()); + + meeting.updateMeeting(updatedMeeting); } private Boolean checkActivityStatus(Meeting meeting) { @@ -276,8 +286,8 @@ private Boolean checkActivityStatus(Meeting meeting) { return now.isEqual(mStartDate) || (now.isAfter(mStartDate) && now.isBefore(mEndDate)); } - private Integer getTargetActiveGeneration(Boolean canJoinOnlyActiveGeneration) { - return canJoinOnlyActiveGeneration ? ACTIVE_GENERATION : null; + private Integer createTargetActiveGeneration(Boolean canJoinOnlyActiveGeneration) { + return Boolean.TRUE.equals(canJoinOnlyActiveGeneration) ? ACTIVE_GENERATION : null; } private List filterUserActivities(User user, Meeting meeting) { diff --git a/main/src/test/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceTest.java b/main/src/test/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceTest.java index 77b2ab4f..475c75bd 100644 --- a/main/src/test/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceTest.java +++ b/main/src/test/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceTest.java @@ -104,7 +104,6 @@ void init() { .createdGeneration(33) .targetActiveGeneration(33) .joinableParts(MeetingJoinablePart.values()) - .appliedInfo(new ArrayList<>()) .build(); Apply apply = Apply.builder() @@ -206,7 +205,6 @@ void init() { .createdGeneration(33) .targetActiveGeneration(33) .joinableParts(MeetingJoinablePart.values()) - .appliedInfo(new ArrayList<>()) .build(); MeetingV2ApplyMeetingDto requestBody = new MeetingV2ApplyMeetingDto(meeting.getId(), "열심히 하겠습니다."); diff --git a/server/src/meeting/v1/meeting-v1.controller.ts b/server/src/meeting/v1/meeting-v1.controller.ts index 7d992975..dbb41ffc 100644 --- a/server/src/meeting/v1/meeting-v1.controller.ts +++ b/server/src/meeting/v1/meeting-v1.controller.ts @@ -106,6 +106,7 @@ export class MeetingV1Controller { @ApiOperation({ summary: '모임 수정', description: '모임 수정', + deprecated: true, }) @ApiResponse({ status: HttpStatus.BAD_REQUEST, From 9507d0724f80f9f0952b3ef55226af9d7155d2ef Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Thu, 22 Aug 2024 21:24:31 +0900 Subject: [PATCH 28/47] =?UTF-8?q?[FEAT]=20=EB=AA=A8=EC=9E=84=20=EC=A7=80?= =?UTF-8?q?=EC=9B=90=EC=9E=90=20=EC=83=81=ED=83=9C=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?API=20=EB=A7=88=EC=9D=B4=EA=B7=B8=EB=A0=88=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?(#320)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: deprecated 처리 * docs: 스웨거 추가 * feat: 모임 지원자 상태 변경 로직 구현 * feat: 이미 처리된 경우 검증 로직 추가 * feat: 정원 초과 검증 로직 추가 --- .../main/common/exception/ErrorStatus.java | 1 + .../makers/crew/main/entity/apply/Apply.java | 9 +++++ .../crew/main/entity/meeting/Meeting.java | 10 ++++-- .../crew/main/meeting/v2/MeetingV2Api.java | 6 +++- .../main/meeting/v2/MeetingV2Controller.java | 16 ++++++++- .../request/ApplyV2UpdateStatusBodyDto.java | 20 +++++++++++ .../meeting/v2/service/MeetingV2Service.java | 3 ++ .../v2/service/MeetingV2ServiceImpl.java | 35 +++++++++++++------ .../src/meeting/v0/meeting-v0.controller.ts | 1 + 9 files changed, 85 insertions(+), 16 deletions(-) create mode 100644 main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/request/ApplyV2UpdateStatusBodyDto.java diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/exception/ErrorStatus.java b/main/src/main/java/org/sopt/makers/crew/main/common/exception/ErrorStatus.java index 3d477b72..4c67a8cd 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/exception/ErrorStatus.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/exception/ErrorStatus.java @@ -28,6 +28,7 @@ public enum ErrorStatus { NOT_ACTIVE_GENERATION("활동 기수가 아닙니다."), NOT_TARGET_PART("지원 가능한 파트가 아닙니다."), NOT_FOUND_APPLY("신청상태가 아닌 모임입니다."), + ALREADY_PROCESSED_APPLY("이미 해당 상태로 처리된 신청 정보입니다."), /** * 401 UNAUTHORIZED diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Apply.java b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Apply.java index 92fe4795..08bc368b 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Apply.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Apply.java @@ -1,6 +1,7 @@ package org.sopt.makers.crew.main.entity.apply; import static jakarta.persistence.GenerationType.IDENTITY; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.*; import jakarta.persistence.Column; import jakarta.persistence.Convert; @@ -17,6 +18,8 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; + +import org.sopt.makers.crew.main.common.exception.BadRequestException; import org.sopt.makers.crew.main.entity.apply.enums.ApplyStatusConverter; import org.sopt.makers.crew.main.entity.apply.enums.ApplyTypeConverter; import org.sopt.makers.crew.main.entity.apply.enums.EnApplyStatus; @@ -109,4 +112,10 @@ public Apply(EnApplyType type, Meeting meeting, Integer meetingId, User user, In public void updateApplyStatus(EnApplyStatus status) { this.status = status; } + + public void validateDuplicateUpdateApplyStatus(EnApplyStatus updatedApplyStatus){ + if(updatedApplyStatus.equals(this.getStatus())){ + throw new BadRequestException(ALREADY_PROCESSED_APPLY.getErrorCode()); + } + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/Meeting.java b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/Meeting.java index 11dad9d3..c4aa4148 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/Meeting.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/Meeting.java @@ -27,15 +27,14 @@ import org.hibernate.annotations.Parameter; import org.hibernate.annotations.Type; +import org.sopt.makers.crew.main.common.exception.BadRequestException; import org.sopt.makers.crew.main.common.exception.ForbiddenException; -import org.sopt.makers.crew.main.entity.apply.Apply; import org.sopt.makers.crew.main.entity.meeting.converter.MeetingCategoryConverter; import org.sopt.makers.crew.main.entity.meeting.enums.EnMeetingStatus; import org.sopt.makers.crew.main.entity.meeting.enums.MeetingCategory; import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart; import org.sopt.makers.crew.main.entity.meeting.vo.ImageUrlVO; import org.sopt.makers.crew.main.entity.user.User; -import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2CreateMeetingBodyDto; import org.springframework.data.jpa.domain.support.AuditingEntityListener; @Entity @@ -80,7 +79,6 @@ public class Meeting { */ @Column(name = "imageURL", columnDefinition = "jsonb") @Type(JsonBinaryType.class) - //@JdbcTypeCode(SqlTypes.JSON) private List imageURL; /** @@ -231,6 +229,12 @@ public Boolean checkMeetingLeader(Integer userId) { return this.userId.equals(userId); } + public void validateCapacity(int approvedCount) { + if (approvedCount >= this.capacity) { + throw new BadRequestException(FULL_MEETING_CAPACITY.getErrorCode()); + } + } + public void updateMeeting(Meeting updateMeeting) { this.title = updateMeeting.getTitle(); diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java index 99d604db..328ad758 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java @@ -17,6 +17,7 @@ import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingGetAppliesQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingByOrgUserQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingQueryDto; +import org.sopt.makers.crew.main.meeting.v2.dto.request.ApplyV2UpdateStatusBodyDto; import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2ApplyMeetingDto; import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2CreateMeetingBodyDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingGetApplyListResponseDto; @@ -106,8 +107,11 @@ ResponseEntity> getMeetingsTemp(@Mode Principal principal); @Operation(summary = "모임 삭제", description = "모임 삭제합니다.") - ResponseEntity deleteMeeting(@PathVariable("meetingId") Integer meetingId, Principal principal); + ResponseEntity deleteMeeting(@PathVariable Integer meetingId, Principal principal); @Operation(summary = "모임 수정", description = "모임 내용을 수정합니다.") ResponseEntity updateMeeting(@PathVariable Integer meetingId, @RequestBody @Valid MeetingV2CreateMeetingBodyDto requestBody ,Principal principal); + + @Operation(summary = "모임 지원자 상태 변경", description = "모임 지원자의 지원 상태를 변경합니다.") + ResponseEntity updateApplyStatus(@PathVariable Integer meetingId, @RequestBody @Valid ApplyV2UpdateStatusBodyDto requestBody ,Principal principal); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java index 4a7660a9..35700230 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java @@ -13,6 +13,7 @@ import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingGetAppliesQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingByOrgUserQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingQueryDto; +import org.sopt.makers.crew.main.meeting.v2.dto.request.ApplyV2UpdateStatusBodyDto; import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2ApplyMeetingDto; import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2CreateMeetingBodyDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingGetApplyListResponseDto; @@ -119,7 +120,7 @@ public ResponseEntity> getMeetingsTem @Override @DeleteMapping("/{meetingId}") - public ResponseEntity deleteMeeting(@PathVariable("meetingId") Integer meetingId, Principal principal) { + public ResponseEntity deleteMeeting(@PathVariable Integer meetingId, Principal principal) { Integer userId = UserUtil.getUserId(principal); meetingV2Service.deleteMeeting(meetingId, userId); @@ -138,4 +139,17 @@ public ResponseEntity updateMeeting( return ResponseEntity.ok().build(); } + + @Override + @PutMapping("/{meetingId}/apply/status") + public ResponseEntity updateApplyStatus( + @PathVariable Integer meetingId, + @RequestBody @Valid ApplyV2UpdateStatusBodyDto requestBody, + Principal principal) { + + Integer userId = UserUtil.getUserId(principal); + meetingV2Service.updateApplyStatus(meetingId, requestBody, userId); + + return ResponseEntity.ok().build(); + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/request/ApplyV2UpdateStatusBodyDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/request/ApplyV2UpdateStatusBodyDto.java new file mode 100644 index 00000000..3bf6e289 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/request/ApplyV2UpdateStatusBodyDto.java @@ -0,0 +1,20 @@ +package org.sopt.makers.crew.main.meeting.v2.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Schema(description = "모임 지원자 상태 변경 request body dto") +public class ApplyV2UpdateStatusBodyDto { + @Schema(example = "1", description = "신청/지원 id") + @NotNull + private final Integer applyId; + + @Schema(example = "0", description = "0: 대기, 1: 승인, 2: 거절") + @NotNull + private final Integer status; + +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java index c0883606..984d5cbb 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java @@ -4,6 +4,7 @@ import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingGetAppliesQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingByOrgUserQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingQueryDto; +import org.sopt.makers.crew.main.meeting.v2.dto.request.ApplyV2UpdateStatusBodyDto; import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2ApplyMeetingDto; import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2CreateMeetingBodyDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingGetApplyListResponseDto; @@ -34,4 +35,6 @@ MeetingGetApplyListResponseDto findApplyList(MeetingGetAppliesQueryDto queryComm void deleteMeeting(Integer meetingId, Integer userId); void updateMeeting(Integer meetingId, MeetingV2CreateMeetingBodyDto requestBody, Integer userId); + + void updateApplyStatus(Integer meetingId, ApplyV2UpdateStatusBodyDto requestBody, Integer userId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java index ed15235e..61dc7d1a 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java @@ -1,14 +1,7 @@ package org.sopt.makers.crew.main.meeting.v2.service; import static org.sopt.makers.crew.main.common.constant.CrewConst.ACTIVE_GENERATION; -import static org.sopt.makers.crew.main.common.exception.ErrorStatus.ALREADY_APPLIED_MEETING; -import static org.sopt.makers.crew.main.common.exception.ErrorStatus.FULL_MEETING_CAPACITY; -import static org.sopt.makers.crew.main.common.exception.ErrorStatus.MISSING_GENERATION_PART; -import static org.sopt.makers.crew.main.common.exception.ErrorStatus.NOT_ACTIVE_GENERATION; -import static org.sopt.makers.crew.main.common.exception.ErrorStatus.NOT_FOUND_APPLY; -import static org.sopt.makers.crew.main.common.exception.ErrorStatus.NOT_IN_APPLY_PERIOD; -import static org.sopt.makers.crew.main.common.exception.ErrorStatus.NOT_TARGET_PART; -import static org.sopt.makers.crew.main.common.exception.ErrorStatus.VALIDATION_EXCEPTION; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.*; import java.time.LocalDateTime; import java.util.ArrayList; @@ -49,6 +42,7 @@ import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingGetAppliesQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingByOrgUserQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingQueryDto; +import org.sopt.makers.crew.main.meeting.v2.dto.request.ApplyV2UpdateStatusBodyDto; import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2ApplyMeetingDto; import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2CreateMeetingBodyDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.ApplyInfoDto; @@ -279,6 +273,27 @@ public void updateMeeting(Integer meetingId, MeetingV2CreateMeetingBodyDto reque meeting.updateMeeting(updatedMeeting); } + @Override + @Transactional + public void updateApplyStatus(Integer meetingId, ApplyV2UpdateStatusBodyDto requestBody, Integer userId) { + Meeting meeting = meetingRepository.findByIdOrThrow(meetingId); + meeting.validateMeetingCreator(userId); + + Apply apply = applyRepository.findByIdOrThrow(requestBody.getApplyId()); + EnApplyStatus updatedApplyStatus = EnApplyStatus.ofValue(requestBody.getStatus()); + apply.validateDuplicateUpdateApplyStatus(updatedApplyStatus); + + if (updatedApplyStatus.equals(EnApplyStatus.APPROVE)) { + List applies = applyRepository.findAllByMeetingIdAndStatus(meetingId, + EnApplyStatus.APPROVE); + + meeting.validateCapacity(applies.size()); + } + + apply.updateApplyStatus(updatedApplyStatus); + + } + private Boolean checkActivityStatus(Meeting meeting) { LocalDateTime now = LocalDateTime.now(); LocalDateTime mStartDate = meeting.getMStartDate(); @@ -312,9 +327,7 @@ private void validateMeetingCapacity(Meeting meeting, List applies) { .filter(apply -> EnApplyStatus.APPROVE.equals(apply.getStatus())) .toList(); - if (approvedApplies.size() >= meeting.getCapacity()) { - throw new BadRequestException(FULL_MEETING_CAPACITY.getErrorCode()); - } + meeting.validateCapacity(approvedApplies.size()); } private void validateUserAlreadyApplied(Integer userId, List applies) { diff --git a/server/src/meeting/v0/meeting-v0.controller.ts b/server/src/meeting/v0/meeting-v0.controller.ts index 3884f4d2..4704b7c9 100644 --- a/server/src/meeting/v0/meeting-v0.controller.ts +++ b/server/src/meeting/v0/meeting-v0.controller.ts @@ -47,6 +47,7 @@ export class MeetingV0Controller { @ApiOperation({ summary: '모임 지원자 상태 변경', description: '모임 지원자 상태 변경', + deprecated: true, }) @ApiBearerAuth() @UseGuards(AuthGuard('jwt')) From dcbf660eb643b5fcc1e4c2de86cbcfd420744740 Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Sat, 24 Aug 2024 00:02:09 +0900 Subject: [PATCH 29/47] =?UTF-8?q?[CHORE]=20=EA=B4=91=EA=B3=A0=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20API=20=EC=88=98=EC=A0=95=20(#323)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: 광고 조회 단건 -> 리스트로 수정 * docs: 스웨거 문서 수정 --- .../dto/AdvertisementGetResponseDto.java | 15 +++---- .../dto/AdvertisementImageDto.java | 26 ++++++++++++ .../service/AdvertisementService.java | 19 +++++++-- .../advertisement/AdvertisementImage.java | 42 +++++++++++++++++++ .../AdvertisementImageRepository.java | 9 ++++ 5 files changed, 99 insertions(+), 12 deletions(-) create mode 100644 main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementImageDto.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/AdvertisementImage.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/AdvertisementImageRepository.java diff --git a/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementGetResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementGetResponseDto.java index 226f3694..bd091e13 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementGetResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementGetResponseDto.java @@ -1,25 +1,22 @@ package org.sopt.makers.crew.main.advertisement.dto; -import org.sopt.makers.crew.main.entity.advertisement.Advertisement; +import java.util.List; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; @Schema(name = "AdvertisementGetResponseDto", description = "광고 구좌 조회 응답 Dto") public record AdvertisementGetResponseDto( - @Schema(description = "광고 구좌 이미지 url", example = "[image Url]") + @Schema(description = "광고 구좌 이미지 객체", example = "") @NotNull - String advertisementImageUrl, + List advertisementImages, @Schema(description = "광고 구좌 링크", example = "https://www.naver.com") @NotNull String advertisementLink ) { - public static AdvertisementGetResponseDto of(Advertisement advertisement){ - if(advertisement == null){ - return new AdvertisementGetResponseDto(null, null); - } + public static AdvertisementGetResponseDto of(List advertisementImages, + String advertisementLink) { - return new AdvertisementGetResponseDto(advertisement.getAdvertisementImageUrl(), - advertisement.getAdvertisementLink()); + return new AdvertisementGetResponseDto(advertisementImages, advertisementLink); } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementImageDto.java b/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementImageDto.java new file mode 100644 index 00000000..621c848c --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementImageDto.java @@ -0,0 +1,26 @@ +package org.sopt.makers.crew.main.advertisement.dto; + +import org.sopt.makers.crew.main.entity.advertisement.AdvertisementImage; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +@Schema(name = "AdvertisementImageDto", description = "광고 구좌 이미지 Dto") +public class AdvertisementImageDto { + + @Schema(description = "광고 구좌 이미지 url", example = "[url 형식]") + @NotNull + private final String imageUrl; + + @Schema(description = "광고 이미지 순서, 서버에서 정렬해서 전달한다.
프론트에서 확인용으로 사용하기 위함", example = "2") + @NotNull + private final int imageOrder; + + public static AdvertisementImageDto of(AdvertisementImage advertisementImage){ + return new AdvertisementImageDto(advertisementImage.getAdvertisementImageUrl(), advertisementImage.getImageOrder()); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/advertisement/service/AdvertisementService.java b/main/src/main/java/org/sopt/makers/crew/main/advertisement/service/AdvertisementService.java index 4106fd14..eef81cdd 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/advertisement/service/AdvertisementService.java +++ b/main/src/main/java/org/sopt/makers/crew/main/advertisement/service/AdvertisementService.java @@ -4,8 +4,11 @@ import java.util.Optional; import org.sopt.makers.crew.main.advertisement.dto.AdvertisementGetResponseDto; +import org.sopt.makers.crew.main.advertisement.dto.AdvertisementImageDto; import org.sopt.makers.crew.main.common.util.Time; import org.sopt.makers.crew.main.entity.advertisement.Advertisement; +import org.sopt.makers.crew.main.entity.advertisement.AdvertisementImage; +import org.sopt.makers.crew.main.entity.advertisement.AdvertisementImageRepository; import org.sopt.makers.crew.main.entity.advertisement.AdvertisementRepository; import org.sopt.makers.crew.main.entity.advertisement.enums.AdvertisementCategory; import org.springframework.stereotype.Service; @@ -18,6 +21,7 @@ @Transactional(readOnly = true) public class AdvertisementService { private final AdvertisementRepository advertisementRepository; + private final AdvertisementImageRepository advertisementImageRepository; private final Time time; @@ -25,8 +29,17 @@ public AdvertisementGetResponseDto getAdvertisement(AdvertisementCategory advert Optional advertisement = advertisementRepository.findFirstByAdvertisementCategoryAndAdvertisementEndDateBeforeAndAdvertisementStartDateAfterOrderByPriority( advertisementCategory, time.now(), time.now()); - return advertisement - .map(AdvertisementGetResponseDto::of) - .orElseGet(() -> AdvertisementGetResponseDto.of(null)); + if (advertisement.isEmpty()) { + return AdvertisementGetResponseDto.of(null, null); + } + + List advertisementImages = advertisementImageRepository.findAllByAdvertisementOrderByImageOrder( + advertisement.get()); + + List imageDtos = advertisementImages.stream() + .map(AdvertisementImageDto::of) + .toList(); + + return AdvertisementGetResponseDto.of(imageDtos, advertisement.get().getAdvertisementLink()); } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/AdvertisementImage.java b/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/AdvertisementImage.java new file mode 100644 index 00000000..b7356f6d --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/AdvertisementImage.java @@ -0,0 +1,42 @@ +package org.sopt.makers.crew.main.entity.advertisement; + +import static jakarta.persistence.GenerationType.*; + +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import jakarta.persistence.Entity; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@EntityListeners(AuditingEntityListener.class) +@Table(name = "advertisement_image") +public class AdvertisementImage { + + @Id + @GeneratedValue(strategy = IDENTITY) + private Integer id; + + @NotNull + private String advertisementImageUrl; + + @NotNull + private Integer imageOrder; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "advertisementId") + @NotNull + private Advertisement advertisement; + +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/AdvertisementImageRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/AdvertisementImageRepository.java new file mode 100644 index 00000000..205a03c9 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/AdvertisementImageRepository.java @@ -0,0 +1,9 @@ +package org.sopt.makers.crew.main.entity.advertisement; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AdvertisementImageRepository extends JpaRepository { + List findAllByAdvertisementOrderByImageOrder(Advertisement advertisement); +} From 42398f170b1f96f4614c4317b73803b47a294190 Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Sat, 24 Aug 2024 15:27:14 +0900 Subject: [PATCH 30/47] =?UTF-8?q?[FEAT]=20presignedUrl=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84=20(#324)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: yml 데이터 추가 * feat: pre-signed-url 생성 로직 구현 * chore: deprecated 처리 * add: csv 관련 dto 추가 --- main/build.gradle | 3 + .../main/common/exception/ErrorStatus.java | 1 + .../external/s3/config/AwsProperties.java | 21 ++ .../s3/config/AwsPropertiesConfiguration.java | 9 + .../main/external/s3/config/S3Config.java | 44 ++++ .../main/external/s3/service/S3Service.java | 194 ++++++++++++++++++ .../crew/main/meeting/v2/MeetingV2Api.java | 15 ++ .../main/meeting/v2/MeetingV2Controller.java | 28 +++ .../AppliesCsvFileUrlResponseDto.java | 10 + .../PreSignedUrlFieldResponseDto.java | 43 ++++ .../dto/response/PreSignedUrlResponseDto.java | 16 ++ main/src/main/resources/application-dev.yml | 5 + main/src/main/resources/application-prod.yml | 5 + main/src/main/resources/application-test.yml | 5 + .../src/meeting/v1/meeting-v1.controller.ts | 1 + 15 files changed, 400 insertions(+) create mode 100644 main/src/main/java/org/sopt/makers/crew/main/external/s3/config/AwsProperties.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/external/s3/config/AwsPropertiesConfiguration.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/external/s3/config/S3Config.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/external/s3/service/S3Service.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/AppliesCsvFileUrlResponseDto.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/PreSignedUrlFieldResponseDto.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/PreSignedUrlResponseDto.java diff --git a/main/build.gradle b/main/build.gradle index b2d70c5b..7818e2f2 100644 --- a/main/build.gradle +++ b/main/build.gradle @@ -71,6 +71,9 @@ dependencies { annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta" annotationProcessor "jakarta.annotation:jakarta.annotation-api" annotationProcessor "jakarta.persistence:jakarta.persistence-api" + + // AWS SDK for S3 + implementation "software.amazon.awssdk:s3:2.27.0" } tasks.named('test') { diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/exception/ErrorStatus.java b/main/src/main/java/org/sopt/makers/crew/main/common/exception/ErrorStatus.java index 4c67a8cd..541508d1 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/exception/ErrorStatus.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/exception/ErrorStatus.java @@ -45,6 +45,7 @@ public enum ErrorStatus { * 500 SERVER_ERROR */ NOTIFICATION_SERVER_ERROR("알림 서버에 에러가 발생했습니다."), + S3_STORAGE_ERROR("s3 스토리지에 에러가 발생했습니다."), INTERNAL_SERVER_ERROR("예상치 못한 서버 에러가 발생했습니다."); private final String errorCode; diff --git a/main/src/main/java/org/sopt/makers/crew/main/external/s3/config/AwsProperties.java b/main/src/main/java/org/sopt/makers/crew/main/external/s3/config/AwsProperties.java new file mode 100644 index 00000000..57b640aa --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/external/s3/config/AwsProperties.java @@ -0,0 +1,21 @@ +package org.sopt.makers.crew.main.external.s3.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +@ConfigurationProperties("aws-property") +public final class AwsProperties { + private final String awsRegion; + private final String s3BucketName; + private final String accessKey; + private final String secretKey; + private final long fileMinSize; + private final long fileMaxSize; + private final String algorithm; + private final String contentType; + private final String requestType; +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/external/s3/config/AwsPropertiesConfiguration.java b/main/src/main/java/org/sopt/makers/crew/main/external/s3/config/AwsPropertiesConfiguration.java new file mode 100644 index 00000000..72805a8f --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/external/s3/config/AwsPropertiesConfiguration.java @@ -0,0 +1,9 @@ +package org.sopt.makers.crew.main.external.s3.config; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableConfigurationProperties(value = {AwsProperties.class}) +public class AwsPropertiesConfiguration { +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/external/s3/config/S3Config.java b/main/src/main/java/org/sopt/makers/crew/main/external/s3/config/S3Config.java new file mode 100644 index 00000000..dd06a63f --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/external/s3/config/S3Config.java @@ -0,0 +1,44 @@ +package org.sopt.makers.crew.main.external.s3.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import lombok.RequiredArgsConstructor; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.presigner.S3Presigner; + +@Configuration +@RequiredArgsConstructor +public class S3Config { + + private final AwsProperties awsProperties; + + @Bean + public S3Client s3Client() { + AwsBasicCredentials awsBasicCredentials = getAwsBasicCredentials(); + + return S3Client.builder() + .region(Region.of(awsProperties.getAwsRegion())) + .credentialsProvider(StaticCredentialsProvider.create(awsBasicCredentials)) + .build(); + } + + @Bean + public S3Presigner s3Presigner() { + AwsBasicCredentials awsBasicCredentials = getAwsBasicCredentials(); + + return S3Presigner.builder() + .region(Region.of(awsProperties.getAwsRegion())) + .credentialsProvider(StaticCredentialsProvider.create(awsBasicCredentials)) + .build(); + } + + private AwsBasicCredentials getAwsBasicCredentials() { + return AwsBasicCredentials.create( + awsProperties.getAccessKey(), + awsProperties.getSecretKey()); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/external/s3/service/S3Service.java b/main/src/main/java/org/sopt/makers/crew/main/external/s3/service/S3Service.java new file mode 100644 index 00000000..0176ad22 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/external/s3/service/S3Service.java @@ -0,0 +1,194 @@ +package org.sopt.makers.crew.main.external.s3.service; + +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.*; + +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Base64; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.SimpleTimeZone; +import java.util.UUID; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +import org.sopt.makers.crew.main.common.exception.ServerException; +import org.sopt.makers.crew.main.external.s3.config.AwsProperties; +import org.sopt.makers.crew.main.meeting.v2.dto.response.PreSignedUrlFieldResponseDto; +import org.sopt.makers.crew.main.meeting.v2.dto.response.PreSignedUrlResponseDto; +import org.springframework.stereotype.Service; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import lombok.RequiredArgsConstructor; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.utils.BinaryUtils; + +@Service +@RequiredArgsConstructor +public class S3Service { + private static final String MEETING_PATH = "meeting"; + private static final String S3_SERVCIE = "s3"; + private static final String PRE_SIGNED_URL_PREFIX = "https://s3.ap-northeast-2.amazonaws.com"; + + private final S3Client s3Client; + + private final AwsProperties awsProperties; + + public PreSignedUrlResponseDto generatePreSignedUrl(String contentType) { + SimpleDateFormat dateTimeFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"); + dateTimeFormat.setTimeZone(new SimpleTimeZone(0, "UTC")); + SimpleDateFormat dateStampFormat = new SimpleDateFormat("yyyyMMdd"); + dateStampFormat.setTimeZone(new SimpleTimeZone(0, "UTC")); + + Date now = new Date(); + String dateTimeStamp = dateTimeFormat.format(now); + String dateStamp = dateStampFormat.format(now); + + Calendar calendar = Calendar.getInstance(); + calendar.setTime(now); + calendar.add(Calendar.MINUTE, 10); + Date expirationDate = calendar.getTime(); + + String objectKey = getObjectKey(contentType); + String encodedPolicy = generateEncodedPolicy(awsProperties.getS3BucketName(), objectKey, dateTimeStamp, + dateStamp, expirationDate); + + String signature = sign(encodedPolicy, dateStamp, S3_SERVCIE); + String credentials = getCredentials(dateStamp); + + PreSignedUrlFieldResponseDto fieldResponseDto = new PreSignedUrlFieldResponseDto(awsProperties.getContentType(), + objectKey, awsProperties.getS3BucketName(), awsProperties.getAlgorithm(), credentials, dateTimeStamp, + encodedPolicy, + signature); + + return PreSignedUrlResponseDto.of(PRE_SIGNED_URL_PREFIX + "/" + awsProperties.getS3BucketName(), + fieldResponseDto); + } + + /** + * 파일의 경로명을 랜덤으로 생성 + * @author @mikekks + * @param contentType 파일의 컨텐츠 타입 + * @returns 파일의 경로명 + */ + private String getObjectKey(String contentType) { + UUID uuid = UUID.randomUUID(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd"); + String curDate = LocalDateTime.now().format(formatter); + + return MEETING_PATH + "/" + curDate + "/" + uuid + "." + contentType; + } + + /** + * PreSignedUrl 업로드 정책 생성 + * @author @mikekks + * @returns base64로 인코딩된 String + */ + public String generateEncodedPolicy(String bucketName, String objectKey, String dateTimeStamp, String dateStamp, + Date expirationDate) { + List conditions = generateConditions(bucketName, objectKey, dateTimeStamp, + dateStamp); + + SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + simpleDateFormat.setTimeZone(new SimpleTimeZone(0, "UTC")); + + Map policy = Map.of( + "expiration", simpleDateFormat.format(expirationDate), + "conditions", conditions + ); + + return encodePolicy(policy); + } + + private List generateConditions(String bucketName, String objectKey, String dateTimeStamp, String dateStamp) { + List contentLengthRange = List.of("content-length-range", awsProperties.getFileMinSize(), + awsProperties.getFileMaxSize()); + + List conditions = new LinkedList<>(); + conditions.add(contentLengthRange); + + Map params = new HashMap<>(); + String credentials = getCredentials(dateStamp); + params.put("Content-Type", awsProperties.getContentType()); + params.put("key", objectKey); + params.put("bucket", bucketName); + params.put("X-Amz-Algorithm", awsProperties.getAlgorithm()); + params.put("X-Amz-Credential", credentials); + params.put("X-Amz-Date", dateTimeStamp); + + Map awsDefaults = generateAwsDefaultParams(bucketName, objectKey, dateTimeStamp, dateStamp); + + conditions.addAll(awsDefaults.entrySet()); + + return conditions; + } + + private Map generateAwsDefaultParams(String bucketName, String objectKey, String dateTimeStamp, + String dateStamp) { + + String credentials = getCredentials(dateStamp); + Map params = new HashMap<>(); + params.put("Content-Type", awsProperties.getContentType()); + params.put("key", objectKey); + params.put("bucket", bucketName); + params.put("X-Amz-Algorithm", awsProperties.getAlgorithm()); + params.put("X-Amz-Credential", credentials); + params.put("X-Amz-Date", dateTimeStamp); + + return params; + } + + private String getCredentials(String dateStamp) { + return String.format("%s/%s/%s/%s/%s", awsProperties.getAccessKey(), dateStamp, awsProperties.getAwsRegion(), + S3_SERVCIE, + awsProperties.getRequestType()); + } + + private String encodePolicy(Map policy) { + try { + ObjectMapper objectMapper = new ObjectMapper(); + String jsonPolicy = objectMapper.writeValueAsString(policy); + + return Base64.getEncoder().encodeToString(jsonPolicy.getBytes()); + } catch (Exception e) { + throw new ServerException(S3_STORAGE_ERROR.getErrorCode()); + } + } + + public String sign(String toSign, String dateStamp, String service) { + try { + String awsSecretKey = awsProperties.getSecretKey(); + + byte[] kSecret = ("AWS4" + awsSecretKey).getBytes(StandardCharsets.UTF_8); + byte[] kDate = hmacSHA256(dateStamp, kSecret); + byte[] kRegion = hmacSHA256("ap-northeast-2", kDate); + byte[] kService = hmacSHA256(service, kRegion); + byte[] kSigning = hmacSHA256("aws4_request", kService); + byte[] signature = hmacSHA256(toSign, kSigning); + + return BinaryUtils.toHex(signature); + } catch (Exception e) { + throw new ServerException(S3_STORAGE_ERROR.getErrorCode()); + } + } + + private byte[] hmacSHA256(String data, byte[] key) { + try { + String algorithm = "HmacSHA256"; + Mac mac = Mac.getInstance(algorithm); + mac.init(new SecretKeySpec(key, algorithm)); + return mac.doFinal(data.getBytes(StandardCharsets.UTF_8)); + } catch (Exception e) { + throw new ServerException(S3_STORAGE_ERROR.getErrorCode()); + } + } + +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java index 328ad758..3075b694 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java @@ -10,6 +10,8 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; +import jakarta.websocket.server.PathParam; + import java.security.Principal; import java.util.List; @@ -20,12 +22,14 @@ import org.sopt.makers.crew.main.meeting.v2.dto.request.ApplyV2UpdateStatusBodyDto; import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2ApplyMeetingDto; import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2CreateMeetingBodyDto; +import org.sopt.makers.crew.main.meeting.v2.dto.response.AppliesCsvFileUrlResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingGetApplyListResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2ApplyMeetingResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2CreateMeetingResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetAllMeetingByOrgUserDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetAllMeetingDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetMeetingBannerResponseDto; +import org.sopt.makers.crew.main.meeting.v2.dto.response.PreSignedUrlResponseDto; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; @@ -114,4 +118,15 @@ ResponseEntity> getMeetingsTemp(@Mode @Operation(summary = "모임 지원자 상태 변경", description = "모임 지원자의 지원 상태를 변경합니다.") ResponseEntity updateApplyStatus(@PathVariable Integer meetingId, @RequestBody @Valid ApplyV2UpdateStatusBodyDto requestBody ,Principal principal); + + @Operation(summary = "Meeting 썸네일 업로드용 Pre-Signed URL 발급", description = "Meeting 썸네일 업로드용 Pre-Signed URL 발급합니다.") + ResponseEntity createPreSignedUrl(@PathParam("contentType") String contentType ,Principal principal); + + @Operation(summary = "모임 지원자 목록 csv 파일 다운로드", description = "모임 지원자 목록 csv 파일 다운로드") + ResponseEntity getAppliesCsvFileUrl( + @PathVariable Integer meetingId, + @PathParam("status") Integer status, + @PathParam("type") Integer type, + @PathParam("order") String order, + Principal principal); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java index 35700230..6c8ed187 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java @@ -6,22 +6,26 @@ import java.security.Principal; import java.util.List; +import jakarta.websocket.server.PathParam; import lombok.RequiredArgsConstructor; import org.sopt.makers.crew.main.common.dto.TempResponseDto; import org.sopt.makers.crew.main.common.util.UserUtil; +import org.sopt.makers.crew.main.external.s3.service.S3Service; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingGetAppliesQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingByOrgUserQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.request.ApplyV2UpdateStatusBodyDto; import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2ApplyMeetingDto; import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2CreateMeetingBodyDto; +import org.sopt.makers.crew.main.meeting.v2.dto.response.AppliesCsvFileUrlResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingGetApplyListResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2ApplyMeetingResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2CreateMeetingResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetAllMeetingByOrgUserDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetAllMeetingDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetMeetingBannerResponseDto; +import org.sopt.makers.crew.main.meeting.v2.dto.response.PreSignedUrlResponseDto; import org.sopt.makers.crew.main.meeting.v2.service.MeetingV2Service; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -43,6 +47,8 @@ public class MeetingV2Controller implements MeetingV2Api { private final MeetingV2Service meetingV2Service; + private final S3Service s3Service; + @Override @GetMapping("/org-user") @ResponseStatus(HttpStatus.OK) @@ -152,4 +158,26 @@ public ResponseEntity updateApplyStatus( return ResponseEntity.ok().build(); } + + @Override + @GetMapping("/presigned-url") + public ResponseEntity createPreSignedUrl( + @PathParam("contentType") String contentType, Principal principal) { + PreSignedUrlResponseDto responseDto = s3Service.generatePreSignedUrl(contentType); + + return ResponseEntity.ok(responseDto); + } + + @Override + @GetMapping("/{meetingId}/list/csv") + public ResponseEntity getAppliesCsvFileUrl( + @PathVariable Integer meetingId, + @PathParam("status") Integer status, + @PathParam("type") Integer type, + @PathParam("order") String order, + Principal principal) { + + + return null; + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/AppliesCsvFileUrlResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/AppliesCsvFileUrlResponseDto.java new file mode 100644 index 00000000..7a8ba882 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/AppliesCsvFileUrlResponseDto.java @@ -0,0 +1,10 @@ +package org.sopt.makers.crew.main.meeting.v2.dto.response; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class AppliesCsvFileUrlResponseDto { + private final String url; +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/PreSignedUrlFieldResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/PreSignedUrlFieldResponseDto.java new file mode 100644 index 00000000..6c016a8d --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/PreSignedUrlFieldResponseDto.java @@ -0,0 +1,43 @@ +package org.sopt.makers.crew.main.meeting.v2.dto.response; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class PreSignedUrlFieldResponseDto { + + @JsonProperty("Content-Type") + private final String contentType; + + @JsonProperty("key") + private final String key; + + @JsonProperty("bucket") + private final String bucket; + + @JsonProperty("X-Amz-Algorithm") + private final String algorithm; + + @JsonProperty("X-Amz-Credential") + private final String credential; + + @JsonProperty("X-Amz-Date") + private final String date; + + @JsonProperty("Policy") + private final String policy; + + @JsonProperty("X-Amz-Signature") + private final String signature; + + public static PreSignedUrlFieldResponseDto of(String contentType, String key, String bucket, String algorithm, + String credential, + String date, String policy, String signature) { + + return new PreSignedUrlFieldResponseDto(contentType, key, bucket, algorithm, credential, date, policy, + signature); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/PreSignedUrlResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/PreSignedUrlResponseDto.java new file mode 100644 index 00000000..19ddad50 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/PreSignedUrlResponseDto.java @@ -0,0 +1,16 @@ +package org.sopt.makers.crew.main.meeting.v2.dto.response; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class PreSignedUrlResponseDto { + private final String url; + private final PreSignedUrlFieldResponseDto fields; + + public static PreSignedUrlResponseDto of(String url, PreSignedUrlFieldResponseDto fields){ + + return new PreSignedUrlResponseDto(url, fields); + } +} diff --git a/main/src/main/resources/application-dev.yml b/main/src/main/resources/application-dev.yml index 80bbca3a..b640100a 100644 --- a/main/src/main/resources/application-dev.yml +++ b/main/src/main/resources/application-dev.yml @@ -34,6 +34,11 @@ aws-property: s3-bucket-name: ${AWS_S3_BUCKET_NAME} access-key: ${AWS_ACCESS_KEY_ID} secret-key: ${AWS_SECRET_ACCESS_KEY} + file-min-size: ${AWS_FILE_MIN_SIZE} + file-max-size: ${AWS_FILE_MAX_SIZE} + algorithm: ${AWS_ALGORITHM} + content-type: ${AWS_CONTENT_TYPE} + request-type: ${AWS_REQUEST_TYPE} springdoc: packages-to-scan: org.sopt.makers.crew diff --git a/main/src/main/resources/application-prod.yml b/main/src/main/resources/application-prod.yml index b4316fd5..0e2bfed6 100644 --- a/main/src/main/resources/application-prod.yml +++ b/main/src/main/resources/application-prod.yml @@ -33,6 +33,11 @@ aws-property: s3-bucket-name: ${AWS_S3_BUCKET_NAME} access-key: ${AWS_ACCESS_KEY_ID} secret-key: ${AWS_SECRET_ACCESS_KEY} + file-min-size: ${AWS_FILE_MIN_SIZE} + file-max-size: ${AWS_FILE_MAX_SIZE} + algorithm: ${AWS_ALGORITHM} + content-type: ${AWS_CONTENT_TYPE} + request-type: ${AWS_REQUEST_TYPE} springdoc: packages-to-scan: org.sopt.makers.crew diff --git a/main/src/main/resources/application-test.yml b/main/src/main/resources/application-test.yml index f6d1649a..6b6889db 100644 --- a/main/src/main/resources/application-test.yml +++ b/main/src/main/resources/application-test.yml @@ -37,6 +37,11 @@ aws-property: s3-bucket-name: ${AWS_S3_BUCKET_NAME} access-key: ${AWS_ACCESS_KEY_ID} secret-key: ${AWS_SECRET_ACCESS_KEY} + file-min-size: ${AWS_FILE_MIN_SIZE} + file-max-size: ${AWS_FILE_MAX_SIZE} + algorithm: ${AWS_ALGORITHM} + content-type: ${AWS_CONTENT_TYPE} + request-type: ${AWS_REQUEST_TYPE} springdoc: packages-to-scan: org.sopt.makers.crew diff --git a/server/src/meeting/v1/meeting-v1.controller.ts b/server/src/meeting/v1/meeting-v1.controller.ts index dbb41ffc..ac81c7c5 100644 --- a/server/src/meeting/v1/meeting-v1.controller.ts +++ b/server/src/meeting/v1/meeting-v1.controller.ts @@ -65,6 +65,7 @@ export class MeetingV1Controller { @ApiOperation({ summary: 'Meeting 썸네일 업로드용 Pre-Signed URL 발급', + deprecated: true, }) @ApiOkResponseCommon(MeetingV1GetPresignedUrlResponseDto) @ApiResponse({ From 1ef56cccddbafaec8fa2e1ea4d92902f3a892e35 Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Sat, 24 Aug 2024 17:52:04 +0900 Subject: [PATCH 31/47] =?UTF-8?q?[FEAT]=20csv=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=EB=B0=8F=20url=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84=20(#325)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add: notnull 추가 * feat: csv 파일 업로드 및 url 반환 로직 구현 * chore: deprecated 처리 * docs: presignedUrl 관련 스웨거 추가 * feat: 신청자 검색 쿼리 구현 --- main/build.gradle | 3 + .../main/common/exception/ErrorStatus.java | 1 + .../main/entity/apply/ApplyRepository.java | 9 ++- .../external/s3/config/AwsProperties.java | 1 + .../main/external/s3/service/S3Service.java | 28 ++++++++ .../crew/main/meeting/v2/MeetingV2Api.java | 8 +-- .../main/meeting/v2/MeetingV2Controller.java | 13 ++-- .../AppliesCsvFileUrlResponseDto.java | 10 +++ .../PreSignedUrlFieldResponseDto.java | 19 ++++++ .../dto/response/PreSignedUrlResponseDto.java | 9 +++ .../meeting/v2/service/MeetingV2Service.java | 3 + .../v2/service/MeetingV2ServiceImpl.java | 67 ++++++++++++++++++- main/src/main/resources/application-dev.yml | 2 + main/src/main/resources/application-prod.yml | 2 + main/src/main/resources/application-test.yml | 2 + .../src/meeting/v1/meeting-v1.controller.ts | 1 + 16 files changed, 166 insertions(+), 12 deletions(-) diff --git a/main/build.gradle b/main/build.gradle index 7818e2f2..7c0f7b78 100644 --- a/main/build.gradle +++ b/main/build.gradle @@ -74,6 +74,9 @@ dependencies { // AWS SDK for S3 implementation "software.amazon.awssdk:s3:2.27.0" + + // csv 관련 + implementation 'com.opencsv:opencsv:5.5.2' } tasks.named('test') { diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/exception/ErrorStatus.java b/main/src/main/java/org/sopt/makers/crew/main/common/exception/ErrorStatus.java index 541508d1..2cfc08c8 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/exception/ErrorStatus.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/exception/ErrorStatus.java @@ -45,6 +45,7 @@ public enum ErrorStatus { * 500 SERVER_ERROR */ NOTIFICATION_SERVER_ERROR("알림 서버에 에러가 발생했습니다."), + CSV_ERROR("csv 처리 과정에 에러가 발생했습니다."), S3_STORAGE_ERROR("s3 스토리지에 에러가 발생했습니다."), INTERNAL_SERVER_ERROR("예상치 못한 서버 에러가 발생했습니다."); diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/ApplyRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/ApplyRepository.java index fcd22845..f9caa7d1 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/ApplyRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/ApplyRepository.java @@ -21,6 +21,13 @@ List findAllByUserIdAndStatus(@Param("userId") Integer userId, @Query("select a from Apply a join fetch a.meeting m join fetch m.user u where a.userId = :userId") List findAllByUserId(@Param("userId") Integer userId); + @Query("select a " + + "from Apply a " + + "join fetch a.user u " + + "where a.meetingId = :meetingId " + + "and a.status in :statuses order by :order") + List findAllByMeetingIdWithUser(@Param("meetingId") Integer meetingId, @Param("statuses") List statuses, @Param("order") String order); + List findAllByMeetingIdAndStatus(Integer meetingId, EnApplyStatus statusValue); List findAllByMeetingId(Integer meetingId); @@ -34,8 +41,6 @@ List findAllByUserIdAndStatus(@Param("userId") Integer userId, @Query("delete from Apply a where a.meeting.id = :meetingId and a.userId = :userId") void deleteByMeetingIdAndUserId(@Param("meetingId") Integer meetingId, @Param("userId") Integer userId); - Optional findById(Integer applyId); - default Apply findByIdOrThrow(Integer applyId) { return findById(applyId) .orElseThrow(() -> new BadRequestException(NOT_FOUND_APPLY.getErrorCode())); diff --git a/main/src/main/java/org/sopt/makers/crew/main/external/s3/config/AwsProperties.java b/main/src/main/java/org/sopt/makers/crew/main/external/s3/config/AwsProperties.java index 57b640aa..a30afbbd 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/external/s3/config/AwsProperties.java +++ b/main/src/main/java/org/sopt/makers/crew/main/external/s3/config/AwsProperties.java @@ -18,4 +18,5 @@ public final class AwsProperties { private final String algorithm; private final String contentType; private final String requestType; + private final String objectUrl; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/external/s3/service/S3Service.java b/main/src/main/java/org/sopt/makers/crew/main/external/s3/service/S3Service.java index 0176ad22..762a3604 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/external/s3/service/S3Service.java +++ b/main/src/main/java/org/sopt/makers/crew/main/external/s3/service/S3Service.java @@ -2,6 +2,7 @@ import static org.sopt.makers.crew.main.common.exception.ErrorStatus.*; +import java.io.File; import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.time.LocalDateTime; @@ -28,11 +29,15 @@ import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import software.amazon.awssdk.core.sync.RequestBody; import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; import software.amazon.awssdk.utils.BinaryUtils; @Service @RequiredArgsConstructor +@Slf4j public class S3Service { private static final String MEETING_PATH = "meeting"; private static final String S3_SERVCIE = "s3"; @@ -42,6 +47,29 @@ public class S3Service { private final AwsProperties awsProperties; + public String uploadCSVFile(String fileName) { + try { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd"); + String curDate = LocalDateTime.now().format(formatter); + + String objectKey = MEETING_PATH + "/" + curDate + "/" + fileName; + + File file = new File(fileName); + PutObjectRequest putObjectRequest = PutObjectRequest.builder() + .bucket(awsProperties.getS3BucketName()) + .key(objectKey) + .build(); + + s3Client.putObject(putObjectRequest, RequestBody.fromFile(file)); + log.info("File uploaded successfully to S3: {}", fileName); + + return awsProperties.getObjectUrl() + objectKey; + } catch (Exception e) { + throw new ServerException(S3_STORAGE_ERROR.getErrorCode()); + } + + } + public PreSignedUrlResponseDto generatePreSignedUrl(String contentType) { SimpleDateFormat dateTimeFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"); dateTimeFormat.setTimeZone(new SimpleTimeZone(0, "UTC")); diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java index 3075b694..ba2a3d9d 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java @@ -34,7 +34,7 @@ import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; - +import org.springframework.web.bind.annotation.RequestParam; @Tag(name = "모임") public interface MeetingV2Api { @@ -125,8 +125,8 @@ ResponseEntity> getMeetingsTemp(@Mode @Operation(summary = "모임 지원자 목록 csv 파일 다운로드", description = "모임 지원자 목록 csv 파일 다운로드") ResponseEntity getAppliesCsvFileUrl( @PathVariable Integer meetingId, - @PathParam("status") Integer status, - @PathParam("type") Integer type, - @PathParam("order") String order, + @RequestParam(name = "status") List status, + @RequestParam(name = "type") List type, + @RequestParam(name = "order", required = false, defaultValue = "desc") String order, Principal principal); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java index 6c8ed187..f11362b5 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java @@ -37,6 +37,7 @@ 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.ResponseStatus; import org.springframework.web.bind.annotation.RestController; @@ -162,7 +163,7 @@ public ResponseEntity updateApplyStatus( @Override @GetMapping("/presigned-url") public ResponseEntity createPreSignedUrl( - @PathParam("contentType") String contentType, Principal principal) { + @RequestParam("contentType") String contentType, Principal principal) { PreSignedUrlResponseDto responseDto = s3Service.generatePreSignedUrl(contentType); return ResponseEntity.ok(responseDto); @@ -172,12 +173,14 @@ public ResponseEntity createPreSignedUrl( @GetMapping("/{meetingId}/list/csv") public ResponseEntity getAppliesCsvFileUrl( @PathVariable Integer meetingId, - @PathParam("status") Integer status, - @PathParam("type") Integer type, - @PathParam("order") String order, + @RequestParam(name = "status") List status, + @RequestParam(name = "type") List type, + @RequestParam(name = "order", required = false, defaultValue = "desc") String order, Principal principal) { + Integer userId = UserUtil.getUserId(principal); + AppliesCsvFileUrlResponseDto responseDto = meetingV2Service.getAppliesCsvFileUrl(meetingId, status, order, userId); - return null; + return ResponseEntity.ok(responseDto); } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/AppliesCsvFileUrlResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/AppliesCsvFileUrlResponseDto.java index 7a8ba882..cd4437be 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/AppliesCsvFileUrlResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/AppliesCsvFileUrlResponseDto.java @@ -1,10 +1,20 @@ package org.sopt.makers.crew.main.meeting.v2.dto.response; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Getter; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor @Getter +@Schema(name = "AppliesCsvFileUrlResponseDto", description = "csv url Dto") public class AppliesCsvFileUrlResponseDto { + + @NotNull + @Schema(description = "csv 파일 url", example = "[url] 형식") private final String url; + + public static AppliesCsvFileUrlResponseDto of(String url){ + return new AppliesCsvFileUrlResponseDto(url); + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/PreSignedUrlFieldResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/PreSignedUrlFieldResponseDto.java index 6c016a8d..c46b501b 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/PreSignedUrlFieldResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/PreSignedUrlFieldResponseDto.java @@ -2,35 +2,54 @@ import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Getter; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor @Getter +@Schema(name = "PreSignedUrlFieldResponseDto", description = "presigned 필드 Dto") public class PreSignedUrlFieldResponseDto { @JsonProperty("Content-Type") + @NotNull + @Schema(description = "contentType", example = "image/jpeg") private final String contentType; @JsonProperty("key") + @NotNull + @Schema(description = "key", example = "key 값") private final String key; @JsonProperty("bucket") + @NotNull + @Schema(description = "bucket", example = "bucket 값") private final String bucket; @JsonProperty("X-Amz-Algorithm") + @NotNull + @Schema(description = "algorithm", example = "algorithm 값") private final String algorithm; @JsonProperty("X-Amz-Credential") + @NotNull + @Schema(description = "credential", example = "credential 값") private final String credential; @JsonProperty("X-Amz-Date") + @NotNull + @Schema(description = "X-Amz-Date", example = "X-Amz-Date 값") private final String date; @JsonProperty("Policy") + @NotNull + @Schema(description = "policy", example = "policy 값") private final String policy; @JsonProperty("X-Amz-Signature") + @NotNull + @Schema(description = "X-Amz-Signature", example = "X-Amz-Signature 값") private final String signature; public static PreSignedUrlFieldResponseDto of(String contentType, String key, String bucket, String algorithm, diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/PreSignedUrlResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/PreSignedUrlResponseDto.java index 19ddad50..77df7e88 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/PreSignedUrlResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/PreSignedUrlResponseDto.java @@ -1,12 +1,21 @@ package org.sopt.makers.crew.main.meeting.v2.dto.response; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Getter; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor @Getter +@Schema(name = "PreSignedUrlResponseDto", description = "presigned url Dto") public class PreSignedUrlResponseDto { + + @NotNull + @Schema(description = "presignedUrl", example = "[url] 형식") private final String url; + + @NotNull + @Schema(description = "field", example = "") private final PreSignedUrlFieldResponseDto fields; public static PreSignedUrlResponseDto of(String url, PreSignedUrlFieldResponseDto fields){ diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java index 984d5cbb..5c44eed0 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java @@ -7,6 +7,7 @@ import org.sopt.makers.crew.main.meeting.v2.dto.request.ApplyV2UpdateStatusBodyDto; import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2ApplyMeetingDto; import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2CreateMeetingBodyDto; +import org.sopt.makers.crew.main.meeting.v2.dto.response.AppliesCsvFileUrlResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingGetApplyListResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2ApplyMeetingResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2CreateMeetingResponseDto; @@ -37,4 +38,6 @@ MeetingGetApplyListResponseDto findApplyList(MeetingGetAppliesQueryDto queryComm void updateMeeting(Integer meetingId, MeetingV2CreateMeetingBodyDto requestBody, Integer userId); void updateApplyStatus(Integer meetingId, ApplyV2UpdateStatusBodyDto requestBody, Integer userId); + + AppliesCsvFileUrlResponseDto getAppliesCsvFileUrl(Integer meetingId, List status, String order, Integer userId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java index 61dc7d1a..032ec6dd 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java @@ -3,12 +3,18 @@ import static org.sopt.makers.crew.main.common.constant.CrewConst.ACTIVE_GENERATION; import static org.sopt.makers.crew.main.common.exception.ErrorStatus.*; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.Optional; +import java.util.UUID; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -16,6 +22,7 @@ import org.sopt.makers.crew.main.common.dto.MeetingResponseDto; import org.sopt.makers.crew.main.common.exception.BadRequestException; +import org.sopt.makers.crew.main.common.exception.ServerException; import org.sopt.makers.crew.main.common.pagination.dto.PageMetaDto; import org.sopt.makers.crew.main.common.pagination.dto.PageOptionsDto; import org.sopt.makers.crew.main.common.util.Time; @@ -37,6 +44,7 @@ import org.sopt.makers.crew.main.entity.user.UserRepository; import org.sopt.makers.crew.main.entity.user.enums.UserPart; import org.sopt.makers.crew.main.entity.user.vo.UserActivityVO; +import org.sopt.makers.crew.main.external.s3.service.S3Service; import org.sopt.makers.crew.main.meeting.v2.dto.ApplyMapper; import org.sopt.makers.crew.main.meeting.v2.dto.MeetingMapper; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingGetAppliesQueryDto; @@ -45,6 +53,7 @@ import org.sopt.makers.crew.main.meeting.v2.dto.request.ApplyV2UpdateStatusBodyDto; import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2ApplyMeetingDto; import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2CreateMeetingBodyDto; +import org.sopt.makers.crew.main.meeting.v2.dto.response.AppliesCsvFileUrlResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.ApplyInfoDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingGetApplyListResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2ApplyMeetingResponseDto; @@ -59,6 +68,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import com.opencsv.CSVWriter; + @Service @RequiredArgsConstructor @Transactional(readOnly = true) @@ -73,6 +84,8 @@ public class MeetingV2ServiceImpl implements MeetingV2Service { private final CommentRepository commentRepository; private final LikeRepository likeRepository; + private final S3Service s3Service; + private final MeetingMapper meetingMapper; private final ApplyMapper applyMapper; @@ -196,7 +209,6 @@ public void applyMeetingCancel(Integer meetingId, Integer userId) { } @Override - @Transactional(readOnly = true) public MeetingGetApplyListResponseDto findApplyList(MeetingGetAppliesQueryDto queryCommand, Integer meetingId, Integer userId) { @@ -294,6 +306,59 @@ public void updateApplyStatus(Integer meetingId, ApplyV2UpdateStatusBodyDto requ } + @Override + public AppliesCsvFileUrlResponseDto getAppliesCsvFileUrl(Integer meetingId, List status, String order, Integer userId) { + Meeting meeting = meetingRepository.findByIdOrThrow(meetingId); + meeting.validateMeetingCreator(userId); + + List statuses = status.stream().map(EnApplyStatus::ofValue).toList(); + List applies = applyRepository.findAllByMeetingIdWithUser(meetingId, statuses, order); + + String csvFilePath = createCsvFile(applies); + String csvFileUrl = s3Service.uploadCSVFile(csvFilePath); + deleteCsvFile(csvFilePath); + + return AppliesCsvFileUrlResponseDto.of(csvFileUrl); + } + + private void deleteCsvFile(String filePath) { + try { + Files.deleteIfExists(Paths.get(filePath)); + } catch (IOException e) { + throw new ServerException(CSV_ERROR.getErrorCode()); + } + } + + private String createCsvFile(List applies) { + String filePath = UUID.randomUUID() + ".csv"; + + try (CSVWriter writer = new CSVWriter(new FileWriter(filePath))) { + // CSV 파일의 헤더 정의 + String[] header = {"이름", "최근 활동 파트", "최근 활동 기수", "전화번호", "신청 날짜 및 시간", "신청 내용"}; + writer.writeNext(header); + + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + for (Apply apply : applies) { + User user = apply.getUser(); + UserActivityVO activity = user.getRecentActivityVO(); + + String[] data = { + user.getName(), + activity.getPart(), + String.valueOf(activity.getGeneration()), + String.format("\"%s\"", user.getPhone()), + apply.getAppliedDate().format(formatter), + apply.getContent() + }; + writer.writeNext(data); + } + } catch (Exception e) { + throw new ServerException(CSV_ERROR.getErrorCode()); + } + + return filePath; + } + private Boolean checkActivityStatus(Meeting meeting) { LocalDateTime now = LocalDateTime.now(); LocalDateTime mStartDate = meeting.getMStartDate(); diff --git a/main/src/main/resources/application-dev.yml b/main/src/main/resources/application-dev.yml index b640100a..b406236b 100644 --- a/main/src/main/resources/application-dev.yml +++ b/main/src/main/resources/application-dev.yml @@ -12,6 +12,7 @@ spring: username: ${DEV_DB_USERNAME} password: ${DEV_DB_PASSWORD} jpa: + open-in-view: false hibernate: naming: physical-strategy: org.sopt.makers.crew.main.common.config.CamelCaseNamingStrategy @@ -39,6 +40,7 @@ aws-property: algorithm: ${AWS_ALGORITHM} content-type: ${AWS_CONTENT_TYPE} request-type: ${AWS_REQUEST_TYPE} + object-url: ${AWS_OBJECT_URL} springdoc: packages-to-scan: org.sopt.makers.crew diff --git a/main/src/main/resources/application-prod.yml b/main/src/main/resources/application-prod.yml index 0e2bfed6..7b593c9c 100644 --- a/main/src/main/resources/application-prod.yml +++ b/main/src/main/resources/application-prod.yml @@ -12,6 +12,7 @@ spring: username: ${PROD_DB_USERNAME} password: ${PROD_DB_PASSWORD} jpa: + open-in-view: false hibernate: naming: physical-strategy: org.sopt.makers.crew.main.common.config.CamelCaseNamingStrategy @@ -38,6 +39,7 @@ aws-property: algorithm: ${AWS_ALGORITHM} content-type: ${AWS_CONTENT_TYPE} request-type: ${AWS_REQUEST_TYPE} + object-url: ${AWS_OBJECT_URL} springdoc: packages-to-scan: org.sopt.makers.crew diff --git a/main/src/main/resources/application-test.yml b/main/src/main/resources/application-test.yml index 6b6889db..5e4abe55 100644 --- a/main/src/main/resources/application-test.yml +++ b/main/src/main/resources/application-test.yml @@ -14,6 +14,7 @@ spring: # username: root # password: jpa: + open-in-view: false hibernate: naming: physical-strategy: org.sopt.makers.crew.main.common.config.CamelCaseNamingStrategy @@ -42,6 +43,7 @@ aws-property: algorithm: ${AWS_ALGORITHM} content-type: ${AWS_CONTENT_TYPE} request-type: ${AWS_REQUEST_TYPE} + object-url: ${AWS_OBJECT_URL} springdoc: packages-to-scan: org.sopt.makers.crew diff --git a/server/src/meeting/v1/meeting-v1.controller.ts b/server/src/meeting/v1/meeting-v1.controller.ts index ac81c7c5..c3df1858 100644 --- a/server/src/meeting/v1/meeting-v1.controller.ts +++ b/server/src/meeting/v1/meeting-v1.controller.ts @@ -40,6 +40,7 @@ export class MeetingV1Controller { @ApiOperation({ summary: '모임 지원자 목록 csv 파일 다운로드', description: '모임장일때만 지원자 목록 csv 파일 다운로드 가능', + deprecated: true, }) @ApiOkResponseCommon(MeetingV1GetApplyListByMeetingCsvFileUrlResponseDto) @ApiResponse({ From de41110c46872423915e4f1520e9345827deafff Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Sat, 24 Aug 2024 22:05:18 +0900 Subject: [PATCH 32/47] =?UTF-8?q?[FEAT]=20=EB=AA=A8=EC=9E=84=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=EC=A1=B0=ED=9A=8C=20API=20=EB=A7=88=EC=9D=B4?= =?UTF-8?q?=EA=B7=B8=EB=A0=88=EC=9D=B4=EC=85=98=20(#326)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: 매직 리터럴 -> 상수로 변경 * chore: deprecated 처리 * add: dto 추가 * feat: 모임 상세 조회 구현 * refactor: Time DI를 통한 의존성 최소화 * test: 엔티티 테스트 코드 작성 * fix: 이전 버전과 동일한 응답값으로 변경 --- .../crew/main/common/constant/CrewConst.java | 14 +- .../main/common/dto/MeetingCreatorDto.java | 39 ++-- .../main/common/dto/MeetingResponseDto.java | 4 +- .../crew/main/entity/apply/Applies.java | 21 ++ .../makers/crew/main/entity/apply/Apply.java | 8 + .../apply/ApplySearchRepositoryImpl.java | 3 +- .../crew/main/entity/meeting/Meeting.java | 4 +- .../main/entity/user/vo/UserActivityVO.java | 11 +- .../crew/main/meeting/v2/MeetingV2Api.java | 6 + .../main/meeting/v2/MeetingV2Controller.java | 9 + .../dto/response/ApplicantByMeetingDto.java | 44 ++++ .../v2/dto/response/ApplyWholeInfoDto.java | 58 +++++ .../MeetingV2GetMeetingByIdResponseDto.java | 161 +++++++++++++ .../meeting/v2/service/MeetingV2Service.java | 3 + .../v2/service/MeetingV2ServiceImpl.java | 35 ++- ...gV2GetCreatedMeetingByUserResponseDto.java | 4 +- .../user/v2/service/UserV2ServiceImpl.java | 7 +- .../crew/main/entity/apply/AppliesTest.java | 218 ++++++++++++++++++ .../crew/main/entity/meeting/MeetingTest.java | 108 +++++++++ .../src/meeting/v0/meeting-v0.controller.ts | 1 + 20 files changed, 722 insertions(+), 36 deletions(-) create mode 100644 main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplicantByMeetingDto.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplyWholeInfoDto.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingByIdResponseDto.java create mode 100644 main/src/test/java/org/sopt/makers/crew/main/entity/apply/AppliesTest.java create mode 100644 main/src/test/java/org/sopt/makers/crew/main/entity/meeting/MeetingTest.java diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/constant/CrewConst.java b/main/src/main/java/org/sopt/makers/crew/main/common/constant/CrewConst.java index 895501fb..67805790 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/constant/CrewConst.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/constant/CrewConst.java @@ -5,11 +5,15 @@ public abstract class CrewConst { /** * 매 기수 시작하기 전에 수정 필요 * */ - public final static Integer ACTIVE_GENERATION = 34; + public static final Integer ACTIVE_GENERATION = 34; - public final static String DAY_START_TIME = " 00:00:00"; - public final static String DAY_END_TIME = " 23:59:59"; + public static final String DAY_START_TIME = " 00:00:00"; + public static final String DAY_END_TIME = " 23:59:59"; + + public static final String DAY_FORMAT = "yyyy.MM.dd"; + public static final String DAY_TIME_FORMAT = "yyyy.MM.dd HH:mm:ss"; + + public static final String ORDER_ASC = "asc"; + public static final String ORDER_DESC = "desc"; - public final static String DAY_FORMAT = "yyyy.MM.dd"; - public final static String DAY_TIME_FORMAT = "yyyy.MM.dd HH:mm:ss"; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/dto/MeetingCreatorDto.java b/main/src/main/java/org/sopt/makers/crew/main/common/dto/MeetingCreatorDto.java index 9aaf21ac..d1fcf8b5 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/dto/MeetingCreatorDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/dto/MeetingCreatorDto.java @@ -1,38 +1,45 @@ package org.sopt.makers.crew.main.common.dto; -import org.sopt.makers.crew.main.entity.user.User; +import java.util.List; -import com.querydsl.core.annotations.QueryProjection; +import org.sopt.makers.crew.main.entity.user.User; +import org.sopt.makers.crew.main.entity.user.vo.UserActivityVO; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; import lombok.Getter; +import lombok.RequiredArgsConstructor; -@Schema(name = "MeetingCreatorDto", description = "모임 개설자 Dto") @Getter +@RequiredArgsConstructor +@Schema(name = "MeetingCreatorDto", description = "모임 개설자 Dto") public class MeetingCreatorDto { @Schema(description = "모임장 id, 크루에서 사용하는 userId", example = "1") @NotNull - Integer id; + private final Integer id; + @Schema(description = "모임장 이름", example = "홍길동") @NotNull - String name; + private final String name; + @Schema(description = "모임장 org id, 메이커스 프로덕트에서 범용적으로 사용하는 userId", example = "1") @NotNull - Integer orgId; + private final Integer orgId; + @Schema(description = "모임장 프로필 사진", example = "[url] 형식") - String profileImage; - - @QueryProjection - public MeetingCreatorDto(Integer id, String name, Integer orgId, String profileImag) { - this.id = id; - this.name = name; - this.orgId = orgId; - this.profileImage = profileImag; - } + private final String profileImage; + + @Schema(description = "활동 기수", example = "") + @NotNull + private final List activities; + + @Schema(description = "전화번호", example = "01094726796") + @NotNull + private final String phone; public static MeetingCreatorDto of(User user) { - return new MeetingCreatorDto(user.getId(), user.getName(), user.getOrgId(), user.getProfileImage()); + return new MeetingCreatorDto(user.getId(), user.getName(), user.getOrgId(), user.getProfileImage(), + user.getActivities(), user.getPhone()); } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/dto/MeetingResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/common/dto/MeetingResponseDto.java index dbd46cdc..8477102d 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/dto/MeetingResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/dto/MeetingResponseDto.java @@ -101,14 +101,14 @@ public MeetingResponseDto(Integer id, String title, Integer targetActiveGenerati this.appliedCount = appliedCount; } - public static MeetingResponseDto of(Meeting meeting, User meetingCreator, int appliedCount){ + public static MeetingResponseDto of(Meeting meeting, User meetingCreator, int appliedCount, LocalDateTime now){ MeetingCreatorDto creatorDto = MeetingCreatorDto.of(meetingCreator); boolean canJoinOnlyActiveGeneration = meeting.getTargetActiveGeneration() == CrewConst.ACTIVE_GENERATION && meeting.getCanJoinOnlyActiveGeneration(); return new MeetingResponseDto(meeting.getId(), meeting.getTitle(), meeting.getTargetActiveGeneration(), meeting.getJoinableParts(), meeting.getCategory(), - canJoinOnlyActiveGeneration, meeting.getMeetingStatus(), meeting.getImageURL(), + canJoinOnlyActiveGeneration, meeting.getMeetingStatus(now), meeting.getImageURL(), meeting.getIsMentorNeeded(), meeting.getMStartDate(), meeting.getMEndDate(), meeting.getCapacity(), creatorDto, appliedCount); } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Applies.java b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Applies.java index f6caebe8..d59b6806 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Applies.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Applies.java @@ -4,9 +4,13 @@ import java.util.Map; import java.util.stream.Collectors; +import org.sopt.makers.crew.main.entity.apply.enums.EnApplyStatus; + +import lombok.Getter; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor +@Getter public class Applies { /** @@ -31,4 +35,21 @@ public int getAppliedCount(Integer meetingId){ } return applies.size(); } + + public long getApprovedCount(Integer meetingId) { + return appliesMap.get(meetingId).stream() + .filter(apply -> apply.getStatus().equals(EnApplyStatus.APPROVE)) + .count(); + } + + public Boolean isApply(Integer meetingId, Integer userId) { + return appliesMap.get(meetingId).stream() + .anyMatch(apply -> apply.getUserId().equals(userId)); + } + + public Boolean isApproved(Integer meetingId, Integer userId) { + return appliesMap.get(meetingId).stream() + .anyMatch(apply -> apply.getUserId().equals(userId) + && apply.getStatus().equals(EnApplyStatus.APPROVE)); + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Apply.java b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Apply.java index 08bc368b..ea4deb97 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Apply.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Apply.java @@ -118,4 +118,12 @@ public void validateDuplicateUpdateApplyStatus(EnApplyStatus updatedApplyStatus) throw new BadRequestException(ALREADY_PROCESSED_APPLY.getErrorCode()); } } + + public String getContent(Integer requestUserId){ + if(!this.userId.equals(requestUserId)){ + return ""; + } + + return this.content; + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/ApplySearchRepositoryImpl.java b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/ApplySearchRepositoryImpl.java index f47765ae..ea0303de 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/ApplySearchRepositoryImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/ApplySearchRepositoryImpl.java @@ -1,5 +1,6 @@ package org.sopt.makers.crew.main.entity.apply; +import static org.sopt.makers.crew.main.common.constant.CrewConst.*; import static org.sopt.makers.crew.main.entity.apply.QApply.apply; import static org.sopt.makers.crew.main.entity.user.QUser.user; @@ -47,7 +48,7 @@ private List getContent(MeetingGetAppliesQueryDto queryCommand, Pa apply.meetingId.eq(meetingId), apply.status.in(queryCommand.getStatus()) ) - .orderBy(queryCommand.getDate().equals("desc") ? apply.appliedDate.desc() : apply.appliedDate.asc()) + .orderBy(queryCommand.getDate().equals(ORDER_DESC) ? apply.appliedDate.desc() : apply.appliedDate.asc()) .offset(pageable.getOffset()) .limit(pageable.getPageSize()) .fetch(); diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/Meeting.java b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/Meeting.java index c4aa4148..fead16d4 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/Meeting.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/Meeting.java @@ -208,8 +208,8 @@ public Meeting(User user, Integer userId, String title, MeetingCategory category * * @return 모임 모집상태 */ - public Integer getMeetingStatus() { - LocalDateTime now = LocalDateTime.now(); + public Integer getMeetingStatus(LocalDateTime now) { + if (now.isBefore(startDate)) { return EnMeetingStatus.BEFORE_START.getValue(); } else if (now.isBefore(endDate)) { diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/user/vo/UserActivityVO.java b/main/src/main/java/org/sopt/makers/crew/main/entity/user/vo/UserActivityVO.java index 4a06ab06..c2f96ab6 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/user/vo/UserActivityVO.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/user/vo/UserActivityVO.java @@ -1,5 +1,7 @@ package org.sopt.makers.crew.main.entity.user.vo; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.ToString; @@ -9,7 +11,12 @@ @ToString public class UserActivityVO { - private final String part; - private final int generation; + @Schema(description = "파트", example = "서버") + @NotNull + private final String part; + + @Schema(description = "기수", example = "36") + @NotNull + private final int generation; } \ No newline at end of file diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java index ba2a3d9d..f44d5d2b 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java @@ -29,6 +29,7 @@ import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetAllMeetingByOrgUserDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetAllMeetingDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetMeetingBannerResponseDto; +import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetMeetingByIdResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.PreSignedUrlResponseDto; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; @@ -129,4 +130,9 @@ ResponseEntity getAppliesCsvFileUrl( @RequestParam(name = "type") List type, @RequestParam(name = "order", required = false, defaultValue = "desc") String order, Principal principal); + + @Operation(summary = "모임 상세 조회", description = "모임 상세 조회") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "모임 상세 조회 성공"), + @ApiResponse(responseCode = "400", description = "모임이 없습니다.", content = @Content),}) + ResponseEntity getMeetingById(@PathVariable Integer meetingId, Principal principal); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java index f11362b5..5c908ca6 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java @@ -25,6 +25,7 @@ import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetAllMeetingByOrgUserDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetAllMeetingDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetMeetingBannerResponseDto; +import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetMeetingByIdResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.PreSignedUrlResponseDto; import org.sopt.makers.crew.main.meeting.v2.service.MeetingV2Service; import org.springframework.http.HttpStatus; @@ -183,4 +184,12 @@ public ResponseEntity getAppliesCsvFileUrl( return ResponseEntity.ok(responseDto); } + + @Override + @GetMapping("/{meetingId}") + public ResponseEntity getMeetingById(@PathVariable Integer meetingId, Principal principal) { + Integer userId = UserUtil.getUserId(principal); + + return ResponseEntity.ok(meetingV2Service.getMeetingById(meetingId, userId)); + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplicantByMeetingDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplicantByMeetingDto.java new file mode 100644 index 00000000..971f5007 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplicantByMeetingDto.java @@ -0,0 +1,44 @@ +package org.sopt.makers.crew.main.meeting.v2.dto.response; + +import java.util.List; + +import org.sopt.makers.crew.main.entity.user.User; +import org.sopt.makers.crew.main.entity.user.vo.UserActivityVO; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +@Schema(name = "ApplicantByMeetingDto", description = "모임 신청자 객체 Dto") +public class ApplicantByMeetingDto { + + @Schema(description = "신청자 id, 크루에서 사용하는 userId", example = "1") + @NotNull + private final Integer id; + + @Schema(description = "신청자 이름", example = "송민규") + @NotNull + private final String name; + + @Schema(description = "신청자 org id, 메이커스 프로덕트에서 범용적으로 사용하는 userId", example = "1") + @NotNull + private final Integer orgId; + + @Schema(description = "신청자 기수 정보", example = "[{\"part\": \"웹\", \"generation\": 32}]") + @NotNull + private final List activities; + + @Schema(description = "신청자 프로필 사진", example = "[url] 형식") + private final String profileImage; + + @Schema(description = "신청자 핸드폰 번호", example = "010-1234-5678") + private final String phone; + + public static ApplicantByMeetingDto of(User user){ + return new ApplicantByMeetingDto(user.getId(), user.getName(), user.getOrgId(), user.getActivities(), + user.getProfileImage(), user.getPhone()); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplyWholeInfoDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplyWholeInfoDto.java new file mode 100644 index 00000000..a57e508d --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/ApplyWholeInfoDto.java @@ -0,0 +1,58 @@ +package org.sopt.makers.crew.main.meeting.v2.dto.response; + +import java.time.LocalDateTime; + +import org.sopt.makers.crew.main.entity.apply.Apply; +import org.sopt.makers.crew.main.entity.user.User; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +@Schema(description = "신청 정보 조회 dto") +public class ApplyWholeInfoDto { + + @Schema(description = "신청 id", example = "3") + @NotNull + private final Integer id; + + @Schema(description = "신청 타입", example = "0") + @NotNull + private final Integer type; + + @Schema(description = "모임 id", example = "13") + @NotNull + private final Integer meetingId; + + @Schema(description = "신청자 id", example = "184") + @NotNull + private final Integer userId; + + @Schema(description = "신청 내용", example = "모임장에게 전하는 말입니다.") + @NotNull + private final String content; + + @Schema(description = "신청 날짜 및 시간", example = "2024-10-13T23:59:59") + @NotNull + private final LocalDateTime appliedDate; + + @Schema(description = "신청 상태", example = "1") + @NotNull + private final Integer status; + + @Schema(description = "신청자 객체", example = "") + @NotNull + private final ApplicantByMeetingDto user; + + public static ApplyWholeInfoDto of(Apply apply, User user, Integer requestUserId) { + + ApplicantByMeetingDto applicantByMeetingDto = ApplicantByMeetingDto.of(user); + + return new ApplyWholeInfoDto(apply.getId(), apply.getType().getValue(), apply.getMeetingId(), apply.getUserId(), + apply.getContent(requestUserId), apply.getAppliedDate(), apply.getStatus().getValue(), + applicantByMeetingDto); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingByIdResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingByIdResponseDto.java new file mode 100644 index 00000000..2be1f924 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingByIdResponseDto.java @@ -0,0 +1,161 @@ +package org.sopt.makers.crew.main.meeting.v2.dto.response; + +import java.time.LocalDateTime; +import java.util.List; + +import org.sopt.makers.crew.main.common.dto.MeetingCreatorDto; +import org.sopt.makers.crew.main.entity.meeting.Meeting; +import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart; +import org.sopt.makers.crew.main.entity.meeting.vo.ImageUrlVO; +import org.sopt.makers.crew.main.entity.user.User; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +@Schema(description = "모임 상세 조회 dto") +public class MeetingV2GetMeetingByIdResponseDto { + + @Schema(description = "모임 id", example = "2") + @NotNull + private final Integer id; + + @Schema(description = "모임장 id", example = "184") + @NotNull + private final Integer userId; + + @Schema(description = "모임 제목", example = "모임 제목입니다.") + @NotNull + private final String title; + + @Schema(description = "모임 카테고리", example = "스터디") + @NotNull + private final String category; + + @Schema(description = "모임 이미지", example = "[url 형식]") + @NotNull + private final List imageURL; + + @Schema(description = "모임 신청 시작 시간", example = "2024-07-10T15:30:00") + @NotNull + private final LocalDateTime startDate; + + @Schema(description = "모임 신청 종료 시간", example = "2024-07-30T23:59:59") + @NotNull + private final LocalDateTime endDate; + + @Schema(description = "모집 인원", example = "23") + @NotNull + private final int capacity; + + @Schema(description = "모임 소개", example = "모임 소개 입니다.") + @NotNull + private final String desc; + + @Schema(description = "진행방식 소개", example = "진행방식 설명입니다.") + @NotNull + private final String processDesc; + + @Schema(description = "모임 활동 시작 시간", example = "2024-08-13T15:30:00") + @NotNull + private final LocalDateTime mStartDate; + + @Schema(description = "모임 활동 종료 시간", example = "2024-10-13T23:59:59") + @NotNull + private final LocalDateTime mEndDate; + + @Schema(description = "개설자 소개", example = "개설자 소개 입니다.") + @NotNull + private final String leaderDesc; + + @Schema(description = "모집 대상 소개", example = "모집 대상 소개입니다.") + @NotNull + private final String targetDesc; + + @Schema(description = "유의사항", example = "유의사항입니다.") + @NotNull + private final String note; + + @Schema(description = "멘토 필요 여부", example = "true") + @NotNull + private final Boolean isMentorNeeded; + + @Schema(description = "활동 기수만 신청가능한 여부", example = "false") + @NotNull + private final Boolean canJoinOnlyActiveGeneration; + + @Schema(description = "개설 기수", example = "36") + @NotNull + private final Integer createdGeneration; + + @Schema(description = "모집 대상 기수", example = "36") + @NotNull + private final Integer targetActiveGeneration; + + @Schema(description = "모집 대상 파트", example = "[\n" + + " \"PM\",\n" + + " \"DESIGN\",\n" + + " \"WEB\",\n" + + " \"ANDROID\",\n" + + " \"IOS\",\n" + + " \"SERVER\"\n" + + " ]") + @NotNull + private final MeetingJoinablePart[] joinableParts; + + @Schema(description = "모임 상태, 0: 모집전, 1: 모집중, 2: 모집종료", example = "1") + @NotNull + private final Integer status; + + @Schema(description = "승인된 신청 수", example = "7") + @NotNull + private final long approvedApplyCount; + + @Schema(description = "모임 개설자 여부", example = "true") + @NotNull + private final Boolean host; + + @Schema(description = "모임 신청 여부", example = "false") + @NotNull + private final Boolean apply; + + @Schema(description = "모임 승인 여부", example = "false") + @NotNull + private final Boolean approved; + + @Schema(description = "모임장 객체", example = "") + @NotNull + private final MeetingCreatorDto user; + + @Schema(description = "신청 목록", example = "") + @NotNull + private final List appliedInfo; + + public static MeetingV2GetMeetingByIdResponseDto of(Meeting meeting, long approvedCount, Boolean isHost, Boolean isApply, + Boolean isApproved, User meetingCreator, + List appliedInfo, LocalDateTime now) { + + MeetingCreatorDto meetingCreatorDto = MeetingCreatorDto.of(meetingCreator); + + Integer meetingStatus = meeting.getMeetingStatus(now); + + return new MeetingV2GetMeetingByIdResponseDto(meeting.getId(), meeting.getUserId(), meeting.getTitle(), + meeting.getCategory().getValue(), meeting.getImageURL(), meeting.getStartDate(), meeting.getEndDate(), + meeting.getCapacity(), meeting.getDesc(), meeting.getProcessDesc(), meeting.getMStartDate(), + meeting.getMEndDate(), meeting.getLeaderDesc(), meeting.getTargetDesc(), meeting.getNote(), + meeting.getIsMentorNeeded(), meeting.getCanJoinOnlyActiveGeneration(), meeting.getCreatedGeneration(), + meeting.getTargetActiveGeneration(), meeting.getJoinableParts(), meetingStatus, + approvedCount, isHost, isApply, isApproved, meetingCreatorDto, appliedInfo); + } + + public LocalDateTime getmStartDate() { + return mStartDate; + } + + public LocalDateTime getmEndDate() { + return mEndDate; + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java index 5c44eed0..d668a0b1 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java @@ -14,6 +14,7 @@ import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetAllMeetingByOrgUserDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetAllMeetingDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetMeetingBannerResponseDto; +import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetMeetingByIdResponseDto; public interface MeetingV2Service { @@ -40,4 +41,6 @@ MeetingGetApplyListResponseDto findApplyList(MeetingGetAppliesQueryDto queryComm void updateApplyStatus(Integer meetingId, ApplyV2UpdateStatusBodyDto requestBody, Integer userId); AppliesCsvFileUrlResponseDto getAppliesCsvFileUrl(Integer meetingId, List status, String order, Integer userId); + + MeetingV2GetMeetingByIdResponseDto getMeetingById(Integer meetingId, Integer userId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java index 032ec6dd..a1f78363 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java @@ -1,7 +1,8 @@ package org.sopt.makers.crew.main.meeting.v2.service; -import static org.sopt.makers.crew.main.common.constant.CrewConst.ACTIVE_GENERATION; +import static org.sopt.makers.crew.main.common.constant.CrewConst.*; import static org.sopt.makers.crew.main.common.exception.ErrorStatus.*; +import static org.sopt.makers.crew.main.entity.apply.enums.EnApplyStatus.*; import java.io.FileWriter; import java.io.IOException; @@ -55,6 +56,7 @@ import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2CreateMeetingBodyDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.AppliesCsvFileUrlResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.ApplyInfoDto; +import org.sopt.makers.crew.main.meeting.v2.dto.response.ApplyWholeInfoDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingGetApplyListResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2ApplyMeetingResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2CreateMeetingResponseDto; @@ -63,6 +65,7 @@ import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetAllMeetingDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetMeetingBannerResponseDto; import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetMeetingBannerResponseUserDto; +import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetMeetingByIdResponseDto; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; @@ -151,7 +154,7 @@ public List getMeetingBanner() { meeting.getEndDate(), meeting.getCapacity(), recentActivityDate, meeting.getTargetActiveGeneration(), meeting.getJoinableParts(), applicantCount, appliedUserCount, meetingLeaderDto, - meeting.getMeetingStatus()); + meeting.getMeetingStatus(time.now())); }).toList(); } @@ -233,7 +236,7 @@ public MeetingV2GetAllMeetingDto getMeetings(MeetingV2GetAllMeetingQueryDto quer List meetingResponseDtos = meetings.getContent().stream() .map(meeting -> MeetingResponseDto.of(meeting, meeting.getUser(), - allApplies.getAppliedCount(meeting.getId()))) + allApplies.getAppliedCount(meeting.getId()), time.now())) .toList(); PageOptionsDto pageOptionsDto = new PageOptionsDto(queryCommand.getPage(), queryCommand.getTake()); @@ -307,7 +310,8 @@ public void updateApplyStatus(Integer meetingId, ApplyV2UpdateStatusBodyDto requ } @Override - public AppliesCsvFileUrlResponseDto getAppliesCsvFileUrl(Integer meetingId, List status, String order, Integer userId) { + public AppliesCsvFileUrlResponseDto getAppliesCsvFileUrl(Integer meetingId, List status, String order, + Integer userId) { Meeting meeting = meetingRepository.findByIdOrThrow(meetingId); meeting.validateMeetingCreator(userId); @@ -321,6 +325,29 @@ public AppliesCsvFileUrlResponseDto getAppliesCsvFileUrl(Integer meetingId, List return AppliesCsvFileUrlResponseDto.of(csvFileUrl); } + @Override + public MeetingV2GetMeetingByIdResponseDto getMeetingById(Integer meetingId, Integer userId) { + User user = userRepository.findByIdOrThrow(userId); + + Meeting meeting = meetingRepository.findByIdOrThrow(meetingId); + User meetingCreator = userRepository.findByIdOrThrow(meeting.getUserId()); + + Applies applies = new Applies(applyRepository.findAllByMeetingIdWithUser(meetingId, List.of(WAITING, APPROVE, REJECT), ORDER_ASC)); + + Boolean isHost = meeting.checkMeetingLeader(user.getId()); + Boolean isApply = applies.isApply(meetingId, user.getId()); + Boolean isApproved = applies.isApproved(meetingId, user.getId()); + long approvedCount = applies.getApprovedCount(meetingId); + + List applyWholeInfoDtos = applies.getAppliesMap().get(meetingId).stream() + .map(apply -> ApplyWholeInfoDto.of(apply, apply.getUser(), userId)) + .toList(); + + + return MeetingV2GetMeetingByIdResponseDto.of(meeting, approvedCount, isHost, isApply, isApproved, + meetingCreator, applyWholeInfoDtos, time.now()); + } + private void deleteCsvFile(String filePath) { try { Files.deleteIfExists(Paths.get(filePath)); diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/MeetingV2GetCreatedMeetingByUserResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/MeetingV2GetCreatedMeetingByUserResponseDto.java index 6daf7120..781b9d5e 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/MeetingV2GetCreatedMeetingByUserResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/user/v2/dto/response/MeetingV2GetCreatedMeetingByUserResponseDto.java @@ -66,14 +66,14 @@ public record MeetingV2GetCreatedMeetingByUserResponseDto( @NotNull int appliedCount ) { - public static MeetingV2GetCreatedMeetingByUserResponseDto of(Meeting meeting, User meetingCreator, int appliedCount) { + public static MeetingV2GetCreatedMeetingByUserResponseDto of(Meeting meeting, User meetingCreator, int appliedCount, LocalDateTime now) { MeetingCreatorDto creatorDto = MeetingCreatorDto.of(meetingCreator); boolean canJoinOnlyActiveGeneration = meeting.getTargetActiveGeneration() == CrewConst.ACTIVE_GENERATION && meeting.getCanJoinOnlyActiveGeneration(); return new MeetingV2GetCreatedMeetingByUserResponseDto(meeting.getId(), meeting.getTitle(), meeting.getTargetActiveGeneration(), meeting.getJoinableParts(), meeting.getCategory().getValue(), - canJoinOnlyActiveGeneration, meeting.getMeetingStatus(), meeting.getImageURL(), + canJoinOnlyActiveGeneration, meeting.getMeetingStatus(now), meeting.getImageURL(), meeting.getIsMentorNeeded(), meeting.getMStartDate(), meeting.getMEndDate(), meeting.getCapacity(), creatorDto, appliedCount); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2ServiceImpl.java index ee2ad829..742842f9 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/user/v2/service/UserV2ServiceImpl.java @@ -8,6 +8,7 @@ import lombok.RequiredArgsConstructor; import org.sopt.makers.crew.main.common.exception.BaseException; +import org.sopt.makers.crew.main.common.util.Time; import org.sopt.makers.crew.main.entity.apply.Applies; import org.sopt.makers.crew.main.entity.apply.Apply; import org.sopt.makers.crew.main.entity.apply.ApplyRepository; @@ -36,6 +37,8 @@ public class UserV2ServiceImpl implements UserV2Service { private final ApplyRepository applyRepository; private final MeetingRepository meetingRepository; + private final Time time; + @Override public List getAllMeetingByUser(Integer userId) { User user = userRepository.findByIdOrThrow(userId); @@ -93,7 +96,7 @@ public UserV2GetCreatedMeetingByUserResponseDto getCreatedMeetingByUser(Integer List meetingByUserDtos = meetings.stream() .map(meeting -> MeetingV2GetCreatedMeetingByUserResponseDto.of(meeting, meetingCreator, - applies.getAppliedCount(meeting.getId()))) + applies.getAppliedCount(meeting.getId()), time.now())) .toList(); return UserV2GetCreatedMeetingByUserResponseDto.of(meetingByUserDtos); @@ -110,7 +113,7 @@ public UserV2GetAppliedMeetingByUserResponseDto getAppliedMeetingByUser(Integer .map(apply -> ApplyV2GetAppliedMeetingByUserResponseDto.of( apply.getId(), apply.getStatus().getValue(), MeetingV2GetCreatedMeetingByUserResponseDto.of(apply.getMeeting(), apply.getMeeting().getUser(), - allApplies.getAppliedCount(apply.getMeetingId())) + allApplies.getAppliedCount(apply.getMeetingId()), time.now()) )).toList(); return UserV2GetAppliedMeetingByUserResponseDto.of(appliedMeetingByUserDtos); diff --git a/main/src/test/java/org/sopt/makers/crew/main/entity/apply/AppliesTest.java b/main/src/test/java/org/sopt/makers/crew/main/entity/apply/AppliesTest.java new file mode 100644 index 00000000..f903b22a --- /dev/null +++ b/main/src/test/java/org/sopt/makers/crew/main/entity/apply/AppliesTest.java @@ -0,0 +1,218 @@ +package org.sopt.makers.crew.main.entity.apply; + +import java.time.LocalDateTime; +import java.util.List; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.sopt.makers.crew.main.entity.apply.enums.EnApplyStatus; +import org.sopt.makers.crew.main.entity.apply.enums.EnApplyType; +import org.sopt.makers.crew.main.entity.meeting.Meeting; +import org.sopt.makers.crew.main.entity.meeting.enums.MeetingCategory; +import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart; +import org.sopt.makers.crew.main.entity.user.User; +import org.sopt.makers.crew.main.entity.user.vo.UserActivityVO; + +public class AppliesTest { + + @Test + void 승인된_신청자_수_조회(){ + // given + User host = User.builder().name("김철수") + .orgId(1) + .activities(List.of(new UserActivityVO("웹", 31))) + .profileImage(null) + .phone("010-2222-2222") + .build(); + + Meeting meeting = Meeting.builder() + .user(host) + .title("모임 제목입니다") + .category(MeetingCategory.EVENT) + .startDate(LocalDateTime.of(2024,4,24,0,0,0)) + .endDate(LocalDateTime.of(2024,4,29,23,59,59)) + .capacity(20) + .desc("모임 소개입니다") + .processDesc("진행방식 소개입니다") + .mStartDate(LocalDateTime.of(2024,5,24,0,0,0)) + .mEndDate(LocalDateTime.of(2024,6,24,0,0,0)) + .leaderDesc("스장 소개입니다") + .targetDesc("모집 대상입니다") + .isMentorNeeded(true) + .canJoinOnlyActiveGeneration(false) + .createdGeneration(34) + .targetActiveGeneration(null) + .joinableParts(MeetingJoinablePart.values()) + .build(); + + User applicant1 = User.builder().name("홍길동") + .orgId(2) + .activities(List.of(new UserActivityVO("서버", 33))) + .profileImage(null) + .phone("010-1234-5678") + .build(); + + User applicant2 = User.builder().name("김삼순") + .orgId(3) + .activities(List.of(new UserActivityVO("디자인", 34))) + .profileImage(null) + .phone("010-1111-1111") + .build(); + + Apply apply1 = Apply.builder() + .type(EnApplyType.APPLY) + .user(applicant1) + .userId(2) + .content("지원동기입니다1") + .meeting(meeting) + .meetingId(1) + .build(); + + Apply apply2 = Apply.builder() + .type(EnApplyType.APPLY) + .user(applicant2) + .userId(3) + .content("지원동기입니다2") + .meeting(meeting) + .meetingId(1) + .build(); + apply2.updateApplyStatus(EnApplyStatus.APPROVE); + + Applies applyGroups = new Applies(List.of(apply1, apply2)); + + // when + long approvedCount = applyGroups.getApprovedCount(1); + + // then + Assertions.assertThat(approvedCount).isEqualTo(1L); + } + + @Test + void 특정_사용자가_신청했는지_조회(){ + // given + User host = User.builder().name("김철수") + .orgId(1) + .activities(List.of(new UserActivityVO("웹", 31))) + .profileImage(null) + .phone("010-2222-2222") + .build(); + + Meeting meeting = Meeting.builder() + .user(host) + .title("모임 제목입니다") + .category(MeetingCategory.EVENT) + .startDate(LocalDateTime.of(2024,4,24,0,0,0)) + .endDate(LocalDateTime.of(2024,4,29,23,59,59)) + .capacity(20) + .desc("모임 소개입니다") + .processDesc("진행방식 소개입니다") + .mStartDate(LocalDateTime.of(2024,5,24,0,0,0)) + .mEndDate(LocalDateTime.of(2024,6,24,0,0,0)) + .leaderDesc("스장 소개입니다") + .targetDesc("모집 대상입니다") + .isMentorNeeded(true) + .canJoinOnlyActiveGeneration(false) + .createdGeneration(34) + .targetActiveGeneration(null) + .joinableParts(MeetingJoinablePart.values()) + .build(); + + User applicant1 = User.builder().name("홍길동") + .orgId(2) + .activities(List.of(new UserActivityVO("서버", 33))) + .profileImage(null) + .phone("010-1234-5678") + .build(); + + Apply apply1 = Apply.builder() + .type(EnApplyType.APPLY) + .user(applicant1) + .userId(2) + .content("지원동기입니다1") + .meeting(meeting) + .meetingId(1) + .build(); + + Applies applies = new Applies(List.of(apply1)); + + // when + Boolean isApply = applies.isApply(1,2); + + // then + Assertions.assertThat(isApply).isEqualTo(true); + } + + @Test + void 특정_사용자가_승인되었는지_조회(){ + // given + User host = User.builder().name("김철수") + .orgId(1) + .activities(List.of(new UserActivityVO("웹", 31))) + .profileImage(null) + .phone("010-2222-2222") + .build(); + + Meeting meeting = Meeting.builder() + .user(host) + .title("모임 제목입니다") + .category(MeetingCategory.EVENT) + .startDate(LocalDateTime.of(2024,4,24,0,0,0)) + .endDate(LocalDateTime.of(2024,4,29,23,59,59)) + .capacity(20) + .desc("모임 소개입니다") + .processDesc("진행방식 소개입니다") + .mStartDate(LocalDateTime.of(2024,5,24,0,0,0)) + .mEndDate(LocalDateTime.of(2024,6,24,0,0,0)) + .leaderDesc("스장 소개입니다") + .targetDesc("모집 대상입니다") + .isMentorNeeded(true) + .canJoinOnlyActiveGeneration(false) + .createdGeneration(34) + .targetActiveGeneration(null) + .joinableParts(MeetingJoinablePart.values()) + .build(); + + User applicant1 = User.builder().name("홍길동") + .orgId(2) + .activities(List.of(new UserActivityVO("서버", 33))) + .profileImage(null) + .phone("010-1234-5678") + .build(); + + User applicant2 = User.builder().name("김삼순") + .orgId(3) + .activities(List.of(new UserActivityVO("디자인", 34))) + .profileImage(null) + .phone("010-1111-1111") + .build(); + + Apply apply1 = Apply.builder() + .type(EnApplyType.APPLY) + .user(applicant1) + .userId(2) + .content("지원동기입니다1") + .meeting(meeting) + .meetingId(1) + .build(); + + Apply apply2 = Apply.builder() + .type(EnApplyType.APPLY) + .user(applicant2) + .userId(3) + .content("지원동기입니다2") + .meeting(meeting) + .meetingId(1) + .build(); + apply2.updateApplyStatus(EnApplyStatus.APPROVE); + + Applies applies = new Applies(List.of(apply1, apply2)); + + // when + Boolean isApproved1 = applies.isApproved(1,2); + Boolean isApproved2 = applies.isApproved(1,3); + + // then + Assertions.assertThat(isApproved1).isEqualTo(false); + Assertions.assertThat(isApproved2).isEqualTo(true); + } +} diff --git a/main/src/test/java/org/sopt/makers/crew/main/entity/meeting/MeetingTest.java b/main/src/test/java/org/sopt/makers/crew/main/entity/meeting/MeetingTest.java new file mode 100644 index 00000000..eb0bff31 --- /dev/null +++ b/main/src/test/java/org/sopt/makers/crew/main/entity/meeting/MeetingTest.java @@ -0,0 +1,108 @@ +package org.sopt.makers.crew.main.entity.meeting; + +import java.time.LocalDateTime; +import java.util.List; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.sopt.makers.crew.main.entity.meeting.enums.EnMeetingStatus; +import org.sopt.makers.crew.main.entity.meeting.enums.MeetingCategory; +import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart; +import org.sopt.makers.crew.main.entity.user.User; +import org.sopt.makers.crew.main.entity.user.vo.UserActivityVO; + +public class MeetingTest { + + @Test + void 스터디장인지_확인(){ + // given + User host = User.builder().name("김철수") + .orgId(1) + .activities(List.of(new UserActivityVO("웹", 31))) + .profileImage(null) + .phone("010-2222-2222") + .build(); + + Meeting meeting = Meeting.builder() + .user(host) + .userId(1) + .title("모임 제목입니다") + .category(MeetingCategory.EVENT) + .startDate(LocalDateTime.of(2024,4,24,0,0,0)) + .endDate(LocalDateTime.of(2024,4,29,23,59,59)) + .capacity(20) + .desc("모임 소개입니다") + .processDesc("진행방식 소개입니다") + .mStartDate(LocalDateTime.of(2024,5,24,0,0,0)) + .mEndDate(LocalDateTime.of(2024,6,24,0,0,0)) + .leaderDesc("스장 소개입니다") + .targetDesc("모집 대상입니다") + .isMentorNeeded(true) + .canJoinOnlyActiveGeneration(false) + .createdGeneration(34) + .targetActiveGeneration(null) + .joinableParts(MeetingJoinablePart.values()) + .build(); + + // when + Boolean isHost1 = meeting.checkMeetingLeader(1); + Boolean isHost2 = meeting.checkMeetingLeader(2); + + // then + Assertions.assertThat(isHost1).isEqualTo(true); + Assertions.assertThat(isHost2).isEqualTo(false); + } + + @Test + void 신청기간_이전에_모임_모집상태_확인(){ + // given + User hostFixture = createHostFixture(); + + Meeting meeting = Meeting.builder() + .user(hostFixture) + .userId(1) + .title("모임 제목입니다") + .category(MeetingCategory.EVENT) + .startDate(LocalDateTime.of(2024,4,24,0,0,0)) + .endDate(LocalDateTime.of(2024,4,29,23,59,59)) + .capacity(20) + .desc("모임 소개입니다") + .processDesc("진행방식 소개입니다") + .mStartDate(LocalDateTime.of(2024,5,24,0,0,0)) + .mEndDate(LocalDateTime.of(2024,6,24,0,0,0)) + .leaderDesc("스장 소개입니다") + .targetDesc("모집 대상입니다") + .isMentorNeeded(true) + .canJoinOnlyActiveGeneration(false) + .createdGeneration(34) + .targetActiveGeneration(null) + .joinableParts(MeetingJoinablePart.values()) + .build(); + + // when + Integer beforeRecruitment = meeting.getMeetingStatus(LocalDateTime.of(2024, 4, 23, 23, 59, 59)); + Integer recruiting = meeting.getMeetingStatus(LocalDateTime.of(2024, 4, 24, 0, 0, 0)); + Integer closeRecruitment = meeting.getMeetingStatus(LocalDateTime.of(2024, 4, 30, 0, 0, 0)); + Integer active = meeting.getMeetingStatus(LocalDateTime.of(2024, 5, 24, 0, 0, 0)); + Integer activityEnd = meeting.getMeetingStatus(LocalDateTime.of(2024, 6, 24, 0, 0, 1)); + + + // then + Assertions.assertThat(beforeRecruitment).isEqualTo(EnMeetingStatus.BEFORE_START.getValue()); + Assertions.assertThat(recruiting).isEqualTo(EnMeetingStatus.APPLY_ABLE.getValue()); + Assertions.assertThat(closeRecruitment).isEqualTo(EnMeetingStatus.RECRUITMENT_COMPLETE.getValue()); + Assertions.assertThat(active).isEqualTo(EnMeetingStatus.RECRUITMENT_COMPLETE.getValue()); + Assertions.assertThat(activityEnd).isEqualTo(EnMeetingStatus.RECRUITMENT_COMPLETE.getValue()); + + } + + private User createHostFixture(){ + return User.builder().name("김철수") + .orgId(1) + .activities(List.of(new UserActivityVO("웹", 31))) + .profileImage(null) + .phone("010-2222-2222") + .build(); + } + +} diff --git a/server/src/meeting/v0/meeting-v0.controller.ts b/server/src/meeting/v0/meeting-v0.controller.ts index 4704b7c9..27d48964 100644 --- a/server/src/meeting/v0/meeting-v0.controller.ts +++ b/server/src/meeting/v0/meeting-v0.controller.ts @@ -117,6 +117,7 @@ export class MeetingV0Controller { @ApiOperation({ summary: '모임 상세 조회', description: '모임 상세 조회', + deprecated: true, }) @ApiOkResponseCommon(MeetingV0GetMeetingByIdResponseDto) @ApiResponse({ From 206be51f2e7ebfcaba90efbbb9b7228596eb328c Mon Sep 17 00:00:00 2001 From: Yeseul Jo <68415644+yeseul106@users.noreply.github.com> Date: Sun, 25 Aug 2024 12:29:03 +0900 Subject: [PATCH 33/47] =?UTF-8?q?[FEAT]=20=EB=AA=A8=EC=9E=84=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EA=B8=80=20=EC=88=98=EC=A0=95=20V2=20API=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#327)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FEAT] controller 코드 구현 * [FEAT] 엔티티 내 update 메서드 구현 * [ADD] 관련 dto 추가 * [FEAT] service 단 코드 구현 * [TEST] 테스트 코드 작성 * [CHORE] .gitignore 파일 수정 * [CHORE] nest.js 서버 스웨거 deprecated 처리 * [TEST] 댓글 관련 테스트 코드에서 누락된 meeting 정보 추가 --- main/.gitignore | 2 + .../main/common/exception/ErrorStatus.java | 3 +- .../makers/crew/main/entity/post/Post.java | 231 ++++++------- .../makers/crew/main/post/v2/PostV2Api.java | 12 + .../crew/main/post/v2/PostV2Controller.java | 13 + .../dto/request/PostV2UpdatePostBodyDto.java | 24 ++ .../response/PostV2UpdatePostResponseDto.java | 31 ++ .../main/post/v2/service/PostV2Service.java | 4 + .../post/v2/service/PostV2ServiceImpl.java | 32 ++ .../v2/service/CommentV2ServiceTest.java | 307 ++++++++++-------- .../post/v2/service/PostV2ServiceTest.java | 125 +++++++ server/src/post/v1/post-v1.controller.ts | 1 + 12 files changed, 530 insertions(+), 255 deletions(-) create mode 100644 main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/request/PostV2UpdatePostBodyDto.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2UpdatePostResponseDto.java create mode 100644 main/src/test/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceTest.java diff --git a/main/.gitignore b/main/.gitignore index 9f8add33..a8f16721 100644 --- a/main/.gitignore +++ b/main/.gitignore @@ -38,3 +38,5 @@ out/ .vscode/ application-secret.properties + +.DS_Store \ No newline at end of file diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/exception/ErrorStatus.java b/main/src/main/java/org/sopt/makers/crew/main/common/exception/ErrorStatus.java index 2cfc08c8..6574f4f6 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/exception/ErrorStatus.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/exception/ErrorStatus.java @@ -29,7 +29,8 @@ public enum ErrorStatus { NOT_TARGET_PART("지원 가능한 파트가 아닙니다."), NOT_FOUND_APPLY("신청상태가 아닌 모임입니다."), ALREADY_PROCESSED_APPLY("이미 해당 상태로 처리된 신청 정보입니다."), - + MAX_IMAGE_UPLOAD_EXCEEDED("이미지는 최대 10개까지만 업로드 가능합니다."), + /** * 401 UNAUTHORIZED */ diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/post/Post.java b/main/src/main/java/org/sopt/makers/crew/main/entity/post/Post.java index 9d273625..d08f1391 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/post/Post.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/post/Post.java @@ -1,6 +1,6 @@ package org.sopt.makers.crew.main.entity.post; -import static org.sopt.makers.crew.main.common.exception.ErrorStatus.*; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.FORBIDDEN_EXCEPTION; import io.hypersistence.utils.hibernate.type.array.StringArrayType; import jakarta.persistence.Column; @@ -13,14 +13,11 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; - import java.time.LocalDateTime; - import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; - import org.hibernate.annotations.Type; import org.sopt.makers.crew.main.common.exception.ForbiddenException; import org.sopt.makers.crew.main.entity.meeting.Meeting; @@ -36,113 +33,121 @@ @Table(name = "post") public class Post { - /** - * 게시글의 고유 식별자 - */ - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Integer id; - - /** - * 게시글 제목 - */ - @Column(nullable = false) - private String title; - - /** - * 게시글 내용 - */ - @Column(nullable = false, columnDefinition = "TEXT") - private String contents; - - /** - * 게시글 작성일 - */ - @Column(name = "createdDate", nullable = false, columnDefinition = "TIMESTAMP") - @CreatedDate - private LocalDateTime createdDate; - - /** - * 게시글 수정일 - */ - @Column(name = "updatedDate", nullable = false, columnDefinition = "TIMESTAMP") - @LastModifiedDate - private LocalDateTime updatedDate; - - /** - * 조회수 - */ - @Column(nullable = false, columnDefinition = "int default 0") - private int viewCount; - - /** - * 이미지 리스트 - */ - @Column(name = "images", columnDefinition = "text[]") - @Type(StringArrayType.class) - private String[] images; - - /** - * 작성자 정보 - */ - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "userId", nullable = false) - private User user; - - /** - * 작성자의 고유 식별자 - */ - @Column(insertable = false, updatable = false) - private Integer userId; - - /** - * 게시글이 속한 미팅 정보 - */ - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "meetingId", nullable = false) - private Meeting meeting; - - /** - * 게시글이 속한 미팅의 고유 식별자 - */ - @Column(insertable = false, updatable = false) - private Integer meetingId; - - /** - * 게시글에 달린 댓글 수 - */ - @Column(nullable = false, columnDefinition = "int default 0") - private int commentCount; - - /** - * 게시글에 대한 좋아요 수 - */ - @Column(nullable = false, columnDefinition = "int default 0") - private int likeCount; - - @Builder - public Post(String title, String contents, String[] images, User user, Meeting meeting) { - this.title = title; - this.contents = contents; - this.viewCount = 0; - this.images = images; - this.user = user; - this.meeting = meeting; - this.commentCount = 0; - this.likeCount = 0; - } - - public void increaseCommentCount() { - this.commentCount++; - } - - public void decreaseCommentCount() { - this.commentCount--; - } - - public void isWriter(Integer userId){ - if (!this.userId.equals(userId)) { - throw new ForbiddenException(FORBIDDEN_EXCEPTION.getErrorCode()); - } - } + /** + * 게시글의 고유 식별자 + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + /** + * 게시글 제목 + */ + @Column(nullable = false) + private String title; + + /** + * 게시글 내용 + */ + @Column(nullable = false, columnDefinition = "TEXT") + private String contents; + + /** + * 게시글 작성일 + */ + @Column(name = "createdDate", nullable = false, columnDefinition = "TIMESTAMP") + @CreatedDate + private LocalDateTime createdDate; + + /** + * 게시글 수정일 + */ + @Column(name = "updatedDate", nullable = false, columnDefinition = "TIMESTAMP") + @LastModifiedDate + private LocalDateTime updatedDate; + + /** + * 조회수 + */ + @Column(nullable = false, columnDefinition = "int default 0") + private int viewCount; + + /** + * 이미지 리스트 + */ + @Column(name = "images", columnDefinition = "text[]") + @Type(StringArrayType.class) + private String[] images; + + /** + * 작성자 정보 + */ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "userId", nullable = false) + private User user; + + /** + * 작성자의 고유 식별자 + */ + @Column(insertable = false, updatable = false) + private Integer userId; + + /** + * 게시글이 속한 미팅 정보 + */ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "meetingId", nullable = false) + private Meeting meeting; + + /** + * 게시글이 속한 미팅의 고유 식별자 + */ + @Column(insertable = false, updatable = false) + private Integer meetingId; + + /** + * 게시글에 달린 댓글 수 + */ + @Column(nullable = false, columnDefinition = "int default 0") + private int commentCount; + + /** + * 게시글에 대한 좋아요 수 + */ + @Column(nullable = false, columnDefinition = "int default 0") + private int likeCount; + + @Builder + public Post(String title, String contents, String[] images, User user, Meeting meeting) { + this.title = title; + this.contents = contents; + this.viewCount = 0; + this.images = images; + this.user = user; + this.userId = user.getId(); + this.meeting = meeting; + this.meetingId = meeting.getId(); + this.commentCount = 0; + this.likeCount = 0; + } + + public void increaseCommentCount() { + this.commentCount++; + } + + public void decreaseCommentCount() { + this.commentCount--; + } + + public void isWriter(Integer userId) { + if (!this.userId.equals(userId)) { + throw new ForbiddenException(FORBIDDEN_EXCEPTION.getErrorCode()); + } + } + + public void updatePost(String title, String contents, String[] images) { + this.title = title; + this.contents = contents; + this.images = images; + } } \ No newline at end of file diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Api.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Api.java index e7af7756..d7290e94 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Api.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Api.java @@ -13,10 +13,12 @@ import org.sopt.makers.crew.main.post.v2.dto.query.PostGetPostsCommand; import org.sopt.makers.crew.main.post.v2.dto.request.PostV2CreatePostBodyDto; import org.sopt.makers.crew.main.post.v2.dto.request.PostV2MentionUserInPostRequestDto; +import org.sopt.makers.crew.main.post.v2.dto.request.PostV2UpdatePostBodyDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostDetailBaseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2CreatePostResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2GetPostCountResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2GetPostsResponseDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2UpdatePostResponseDto; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; @@ -76,4 +78,14 @@ ResponseEntity mentionUserInPost( @ApiResponse(responseCode = "403", description = "권한이 없습니다.", content = @Content) }) ResponseEntity deletePost(@PathVariable Integer postId, Principal principal); + + @Operation(summary = "모임 게시글 수정") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "성공"), + @ApiResponse(responseCode = "400", description = "\"게시글이 없습니다.\" or \"이미지는 최대 10개까지만 업로드 가능합니다.\""), + @ApiResponse(responseCode = "403", description = "권한이 없습니다.") + }) + ResponseEntity updatePost(@PathVariable Integer postId, + @RequestBody PostV2UpdatePostBodyDto requestBody, + Principal principal); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Controller.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Controller.java index 81049c4c..71072b99 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Controller.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Controller.java @@ -8,10 +8,12 @@ import org.sopt.makers.crew.main.post.v2.dto.query.PostGetPostsCommand; import org.sopt.makers.crew.main.post.v2.dto.request.PostV2CreatePostBodyDto; import org.sopt.makers.crew.main.post.v2.dto.request.PostV2MentionUserInPostRequestDto; +import org.sopt.makers.crew.main.post.v2.dto.request.PostV2UpdatePostBodyDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostDetailBaseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2CreatePostResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2GetPostCountResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2GetPostsResponseDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2UpdatePostResponseDto; import org.sopt.makers.crew.main.post.v2.service.PostV2Service; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -20,6 +22,7 @@ import org.springframework.web.bind.annotation.ModelAttribute; 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; @@ -85,4 +88,14 @@ public ResponseEntity deletePost(@PathVariable Integer postId, Principal p postV2Service.deletePost(postId, userId); return ResponseEntity.ok().build(); } + + @Override + @PutMapping("/{postId}") + @ResponseStatus(HttpStatus.OK) + public ResponseEntity updatePost(@PathVariable Integer postId, + @RequestBody PostV2UpdatePostBodyDto requestBody, + Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.ok(postV2Service.updatePost(postId, requestBody, userId)); + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/request/PostV2UpdatePostBodyDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/request/PostV2UpdatePostBodyDto.java new file mode 100644 index 00000000..d32c3af1 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/request/PostV2UpdatePostBodyDto.java @@ -0,0 +1,24 @@ +package org.sopt.makers.crew.main.post.v2.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Schema(description = "게시물 수정 request body dto") +public class PostV2UpdatePostBodyDto { + + @Schema(description = "모임 게시글 제목", example = "알고보면 쓸데있는 개발 프로세스") + @NotNull + private final String title; + + @Schema(description = "모임 게시글 내용", example = "api가 터졌다고 ? 깃이 터졌다고?") + @NotNull + private final String contents; + + @Schema(description = "모임 게시글 이미지 리스트", example = "[\"url1\", \"url2\"]") + @NotNull + private final String[] images; +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2UpdatePostResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2UpdatePostResponseDto.java new file mode 100644 index 00000000..8a311a03 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2UpdatePostResponseDto.java @@ -0,0 +1,31 @@ +package org.sopt.makers.crew.main.post.v2.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor(staticName = "of") +@Schema(name = "PostV2UpdatePostResponseDto", description = "게시글 수정 응답 Dto") +public class PostV2UpdatePostResponseDto { + + @Schema(description = "모임 게시글", example = "1") + @NotNull + private final Integer id; + + @Schema(description = "모임 게시글 제목", example = "알고보면 쓸데있는 개발 프로세스") + @NotNull + private final String title; + + @Schema(description = "모임 게시글 내용", example = "api가 터졌다고 ? 깃이 터졌다고?") + @NotNull + private final String contents; + + @Schema(description = "모임 게시글 수정 날짜", example = "2024-08-25T15:30:00") + @NotNull + private final String updatedDate; + + @Schema(description = "모임 게시글 이미지 리스트", example = "[\"url1\", \"url2\"]") + private final String[] images; +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2Service.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2Service.java index 78782f03..0bbfce19 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2Service.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2Service.java @@ -3,10 +3,12 @@ import org.sopt.makers.crew.main.post.v2.dto.query.PostGetPostsCommand; import org.sopt.makers.crew.main.post.v2.dto.request.PostV2CreatePostBodyDto; import org.sopt.makers.crew.main.post.v2.dto.request.PostV2MentionUserInPostRequestDto; +import org.sopt.makers.crew.main.post.v2.dto.request.PostV2UpdatePostBodyDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostDetailBaseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2CreatePostResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2GetPostCountResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2GetPostsResponseDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2UpdatePostResponseDto; public interface PostV2Service { @@ -21,4 +23,6 @@ public interface PostV2Service { PostV2GetPostCountResponseDto getPostCount(Integer meetingId); void deletePost(Integer postId, Integer userId); + + PostV2UpdatePostResponseDto updatePost(Integer postId, PostV2UpdatePostBodyDto requestBody, Integer userId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java index 466bfbe1..25fc134a 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java @@ -2,15 +2,18 @@ import static java.util.stream.Collectors.toList; import static org.sopt.makers.crew.main.common.exception.ErrorStatus.FORBIDDEN_EXCEPTION; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.MAX_IMAGE_UPLOAD_EXCEEDED; import static org.sopt.makers.crew.main.internal.notification.PushNotificationEnums.NEW_POST_MENTION_PUSH_NOTIFICATION_TITLE; import static org.sopt.makers.crew.main.internal.notification.PushNotificationEnums.NEW_POST_PUSH_NOTIFICATION_TITLE; import static org.sopt.makers.crew.main.internal.notification.PushNotificationEnums.PUSH_NOTIFICATION_CATEGORY; import java.util.List; import lombok.RequiredArgsConstructor; +import org.sopt.makers.crew.main.common.exception.BadRequestException; import org.sopt.makers.crew.main.common.exception.ForbiddenException; import org.sopt.makers.crew.main.common.pagination.dto.PageMetaDto; import org.sopt.makers.crew.main.common.pagination.dto.PageOptionsDto; +import org.sopt.makers.crew.main.common.util.Time; import org.sopt.makers.crew.main.entity.apply.Apply; import org.sopt.makers.crew.main.entity.apply.ApplyRepository; import org.sopt.makers.crew.main.entity.apply.enums.EnApplyStatus; @@ -28,11 +31,13 @@ import org.sopt.makers.crew.main.post.v2.dto.query.PostGetPostsCommand; import org.sopt.makers.crew.main.post.v2.dto.request.PostV2CreatePostBodyDto; import org.sopt.makers.crew.main.post.v2.dto.request.PostV2MentionUserInPostRequestDto; +import org.sopt.makers.crew.main.post.v2.dto.request.PostV2UpdatePostBodyDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostDetailBaseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostDetailResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2CreatePostResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2GetPostCountResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2GetPostsResponseDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2UpdatePostResponseDto; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -56,6 +61,8 @@ public class PostV2ServiceImpl implements PostV2Service { @Value("${push-notification.web-url}") private String pushWebUrl; + private final Time time; + /** * 모임 게시글 작성 * @@ -194,4 +201,29 @@ public void deletePost(Integer postId, Integer userId) { postRepository.delete(post); } + + /** + * 모임 게시글 수정 + * + * @throws 400 존재하지 않는 게시글인 경우 + * @throws 400 업로드하려는 이미지가 10개 초과인 경우 + * @throws 403 글 작성자가 아닌 경우 + * @apiNote 글을 작성한 유저만 수정 가능 + */ + @Override + @Transactional + public PostV2UpdatePostResponseDto updatePost(Integer postId, PostV2UpdatePostBodyDto requestBody, Integer userId) { + Post post = postRepository.findByIdOrThrow(postId); + post.isWriter(userId); + + if (requestBody.getImages().length > 10) { + throw new BadRequestException(MAX_IMAGE_UPLOAD_EXCEEDED.getErrorCode()); + } + + post.updatePost(requestBody.getTitle(), requestBody.getContents(), requestBody.getImages()); + + return PostV2UpdatePostResponseDto.of(post.getId(), post.getTitle(), post.getContents(), + String.valueOf(time.now()), + post.getImages()); + } } diff --git a/main/src/test/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceTest.java b/main/src/test/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceTest.java index c970c079..0c70e918 100644 --- a/main/src/test/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceTest.java +++ b/main/src/test/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceTest.java @@ -5,9 +5,8 @@ import static org.mockito.Mockito.doReturn; import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; +import java.time.Month; import java.util.Optional; - import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; @@ -15,7 +14,6 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2ReportCommentResponseDto; import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2UpdateCommentResponseDto; @@ -24,6 +22,9 @@ import org.sopt.makers.crew.main.common.util.Time; import org.sopt.makers.crew.main.entity.comment.Comment; import org.sopt.makers.crew.main.entity.comment.CommentRepository; +import org.sopt.makers.crew.main.entity.meeting.Meeting; +import org.sopt.makers.crew.main.entity.meeting.enums.MeetingCategory; +import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart; import org.sopt.makers.crew.main.entity.post.Post; import org.sopt.makers.crew.main.entity.post.PostRepository; import org.sopt.makers.crew.main.entity.report.Report; @@ -35,142 +36,166 @@ @ExtendWith(MockitoExtension.class) public class CommentV2ServiceTest { - @InjectMocks - private CommentV2ServiceImpl commentV2Service; - @Mock - private CommentRepository commentRepository; - @Mock - private PostRepository postRepository; - @Mock - private ReportRepository reportRepository; - @Mock - private UserRepository userRepository; - @Mock - private Time time; - - private Comment comment; - - private Post post; - - private User user; - - private Report report; - - @BeforeEach - void init() { - user = UserFixture.createStaticUser(); - user.setUserIdForTest(1); - - String[] images = {"image1", "image2", "image3"}; - this.post = Post.builder().user(user).title("title").contents("contents").images(images) - .build(); - this.comment = Comment.builder() - .contents("contents") - .post(post) - .postId(1) - .user(user) - .userId(1).build(); - - this.report = Report.builder().comment(comment).user(user).build(); - } - - @Nested - class 댓글_수정 { - - @Test - void 성공() { - // given - String updatedContents = "updatedContents"; - LocalDateTime expectedUpdatedDate = time.now(); - - doReturn(comment).when(commentRepository).findByIdOrThrow(any()); - doReturn(LocalDateTime.of(2024, 4, 24, 23, 59)).when(time).now(); - - // when - CommentV2UpdateCommentResponseDto result = commentV2Service.updateComment(comment.getId(), - updatedContents, user.getId()); - - // then - Assertions.assertThat(result.getId()).isEqualTo(comment.getId()); - Assertions.assertThat(result.getContents()).isEqualTo(updatedContents); - Assertions.assertThat(LocalDateTime.parse(result.getUpdateDate())) - .isEqualTo(LocalDateTime.of(2024, 4, 24, 23, 59)); - } - - @Test - void 실패_본인_작성_댓글_아님() { - // given - doReturn(comment).when(commentRepository).findByIdOrThrow(any()); - - // when & then - assertThrows(ForbiddenException.class, () -> { - commentV2Service.updateComment(comment.getId(), "updatedContents", - comment.getUser().getId() + 1); - }); - } - } - - @Nested - class 댓글_삭제 { - - @Test - void 성공() { - // given - int initialCommentCount = post.getCommentCount(); - doReturn(comment).when(commentRepository).findByIdOrThrow(any()); - doReturn(post).when(postRepository).findByIdOrThrow((any())); - - // when - commentV2Service.deleteComment(comment.getId(), user.getId()); - - // then - Assertions.assertThat(commentRepository.findById(comment.getId())) - .isEqualTo(Optional.empty()); - Assertions.assertThat(post.getCommentCount()).isEqualTo(initialCommentCount - 1); - } - - @Test - void 실패_본인_작성_댓글_아님() { - // given - doReturn(comment).when(commentRepository).findByIdOrThrow(any()); - Integer id = comment.getUser().getId(); - // when & then - assertThrows(ForbiddenException.class, () -> { - commentV2Service.deleteComment(0, comment.getUser().getId() + 1); - }); - } - } - - @Nested - class 댓글_신고 { - - @Test - void 댓글_신고_성공() { - // given - doReturn(comment).when(commentRepository).findByIdOrThrow(any()); - doReturn(user).when(userRepository).findByIdOrThrow(any()); - doReturn(Optional.empty()).when(reportRepository).findByCommentAndUser(any(), any()); - doReturn(report).when(reportRepository).save(any()); - - // when - CommentV2ReportCommentResponseDto result = commentV2Service.reportComment(comment.getId(), - user.getId()); - - // then - Assertions.assertThat(result.getReportId()).isEqualTo(report.getId()); - } - - @Test - void 댓글_신고_실패_이미_신고한_댓글() { - // given - doReturn(comment).when(commentRepository).findByIdOrThrow(any()); - doReturn(user).when(userRepository).findByIdOrThrow(any()); - doReturn(Optional.of(report)).when(reportRepository).findByCommentAndUser(any(), any()); - - // when & then - assertThrows(BadRequestException.class, () -> { - commentV2Service.reportComment(comment.getId(), user.getId()); - }); - } - } + @InjectMocks + private CommentV2ServiceImpl commentV2Service; + @Mock + private CommentRepository commentRepository; + @Mock + private PostRepository postRepository; + @Mock + private ReportRepository reportRepository; + @Mock + private UserRepository userRepository; + @Mock + private Time time; + + private Comment comment; + + private Post post; + + private User user; + + private Report report; + + private Meeting meeting; + + @BeforeEach + void init() { + user = UserFixture.createStaticUser(); + user.setUserIdForTest(1); + + meeting = Meeting.builder() + .user(user) + .userId(user.getId()) + .title("사람 구해요") + .category(MeetingCategory.STUDY) + .startDate(LocalDateTime.of(2024, Month.MARCH, 17, 0, 0)) + .endDate(LocalDateTime.of(2024, Month.MARCH, 20, 23, 59)) + .capacity(10) + .desc("열정 많은 사람 구해요") + .processDesc("이렇게 할거에여") + .mStartDate(LocalDateTime.of(2024, Month.APRIL, 1, 0, 0)) + .mEndDate(LocalDateTime.of(2030, Month.APRIL, 20, 0, 0)) + .leaderDesc("저는 이런 사람이에요.") + .targetDesc("이런 사람이 왔으면 좋겠어요") + .note("유의사항은 이거에요") + .isMentorNeeded(true) + .canJoinOnlyActiveGeneration(true) + .createdGeneration(33) + .targetActiveGeneration(33) + .joinableParts(MeetingJoinablePart.values()) + .build(); + + String[] images = {"image1", "image2", "image3"}; + this.post = Post.builder().user(user).title("title").contents("contents").images(images).meeting(meeting) + .build(); + this.comment = Comment.builder() + .contents("contents") + .post(post) + .postId(1) + .user(user) + .userId(1).build(); + + this.report = Report.builder().comment(comment).user(user).build(); + } + + @Nested + class 댓글_수정 { + + @Test + void 성공() { + // given + String updatedContents = "updatedContents"; + LocalDateTime expectedUpdatedDate = time.now(); + + doReturn(comment).when(commentRepository).findByIdOrThrow(any()); + doReturn(LocalDateTime.of(2024, 4, 24, 23, 59)).when(time).now(); + + // when + CommentV2UpdateCommentResponseDto result = commentV2Service.updateComment(comment.getId(), + updatedContents, user.getId()); + + // then + Assertions.assertThat(result.getId()).isEqualTo(comment.getId()); + Assertions.assertThat(result.getContents()).isEqualTo(updatedContents); + Assertions.assertThat(LocalDateTime.parse(result.getUpdateDate())) + .isEqualTo(LocalDateTime.of(2024, 4, 24, 23, 59)); + } + + @Test + void 실패_본인_작성_댓글_아님() { + // given + doReturn(comment).when(commentRepository).findByIdOrThrow(any()); + + // when & then + assertThrows(ForbiddenException.class, () -> { + commentV2Service.updateComment(comment.getId(), "updatedContents", + comment.getUser().getId() + 1); + }); + } + } + + @Nested + class 댓글_삭제 { + + @Test + void 성공() { + // given + int initialCommentCount = post.getCommentCount(); + doReturn(comment).when(commentRepository).findByIdOrThrow(any()); + doReturn(post).when(postRepository).findByIdOrThrow((any())); + + // when + commentV2Service.deleteComment(comment.getId(), user.getId()); + + // then + Assertions.assertThat(commentRepository.findById(comment.getId())) + .isEqualTo(Optional.empty()); + Assertions.assertThat(post.getCommentCount()).isEqualTo(initialCommentCount - 1); + } + + @Test + void 실패_본인_작성_댓글_아님() { + // given + doReturn(comment).when(commentRepository).findByIdOrThrow(any()); + Integer id = comment.getUser().getId(); + // when & then + assertThrows(ForbiddenException.class, () -> { + commentV2Service.deleteComment(0, comment.getUser().getId() + 1); + }); + } + } + + @Nested + class 댓글_신고 { + + @Test + void 댓글_신고_성공() { + // given + doReturn(comment).when(commentRepository).findByIdOrThrow(any()); + doReturn(user).when(userRepository).findByIdOrThrow(any()); + doReturn(Optional.empty()).when(reportRepository).findByCommentAndUser(any(), any()); + doReturn(report).when(reportRepository).save(any()); + + // when + CommentV2ReportCommentResponseDto result = commentV2Service.reportComment(comment.getId(), + user.getId()); + + // then + Assertions.assertThat(result.getReportId()).isEqualTo(report.getId()); + } + + @Test + void 댓글_신고_실패_이미_신고한_댓글() { + // given + doReturn(comment).when(commentRepository).findByIdOrThrow(any()); + doReturn(user).when(userRepository).findByIdOrThrow(any()); + doReturn(Optional.of(report)).when(reportRepository).findByCommentAndUser(any(), any()); + + // when & then + assertThrows(BadRequestException.class, () -> { + commentV2Service.reportComment(comment.getId(), user.getId()); + }); + } + } } diff --git a/main/src/test/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceTest.java b/main/src/test/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceTest.java new file mode 100644 index 00000000..9a93d301 --- /dev/null +++ b/main/src/test/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceTest.java @@ -0,0 +1,125 @@ +package org.sopt.makers.crew.main.post.v2.service; + +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; + +import java.time.LocalDateTime; +import java.time.Month; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.sopt.makers.crew.main.common.exception.ForbiddenException; +import org.sopt.makers.crew.main.common.util.Time; +import org.sopt.makers.crew.main.entity.comment.CommentRepository; +import org.sopt.makers.crew.main.entity.meeting.Meeting; +import org.sopt.makers.crew.main.entity.meeting.MeetingRepository; +import org.sopt.makers.crew.main.entity.meeting.enums.MeetingCategory; +import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart; +import org.sopt.makers.crew.main.entity.post.Post; +import org.sopt.makers.crew.main.entity.post.PostRepository; +import org.sopt.makers.crew.main.entity.user.User; +import org.sopt.makers.crew.main.entity.user.UserFixture; +import org.sopt.makers.crew.main.entity.user.UserRepository; +import org.sopt.makers.crew.main.post.v2.dto.request.PostV2UpdatePostBodyDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2UpdatePostResponseDto; + +@ExtendWith(MockitoExtension.class) +public class PostV2ServiceTest { + + @InjectMocks + private PostV2ServiceImpl postV2Service; + @Mock + private MeetingRepository meetingRepository; + @Mock + private UserRepository userRepository; + @Mock + private PostRepository postRepository; + @Mock + private CommentRepository commentRepository; + + @Mock + private Time time; + + private User user; + + private Meeting meeting; + + private Post post; + + @BeforeEach + void init() { + user = UserFixture.createStaticUser(); + user.setUserIdForTest(1); + + meeting = Meeting.builder() + .user(user) + .userId(user.getId()) + .title("사람 구해요") + .category(MeetingCategory.STUDY) + .startDate(LocalDateTime.of(2024, Month.MARCH, 17, 0, 0)) + .endDate(LocalDateTime.of(2024, Month.MARCH, 20, 23, 59)) + .capacity(10) + .desc("열정 많은 사람 구해요") + .processDesc("이렇게 할거에여") + .mStartDate(LocalDateTime.of(2024, Month.APRIL, 1, 0, 0)) + .mEndDate(LocalDateTime.of(2030, Month.APRIL, 20, 0, 0)) + .leaderDesc("저는 이런 사람이에요.") + .targetDesc("이런 사람이 왔으면 좋겠어요") + .note("유의사항은 이거에요") + .isMentorNeeded(true) + .canJoinOnlyActiveGeneration(true) + .createdGeneration(33) + .targetActiveGeneration(33) + .joinableParts(MeetingJoinablePart.values()) + .build(); + + String[] images = {"image1", "image2", "image3"}; + post = Post.builder().user(user).title("title").contents("contents").images(images).meeting(meeting).build(); + + } + + @Nested + class 게시글_수정 { + + @Test + void 성공() { + // given + String[] images = {"image4", "image5"}; + PostV2UpdatePostBodyDto requestDto = new PostV2UpdatePostBodyDto("글 제목 수정", "글 내용 수정", images); + + doReturn(post).when(postRepository).findByIdOrThrow(any()); + doReturn(LocalDateTime.of(2024, 4, 24, 23, 59)).when(time).now(); + + // when + PostV2UpdatePostResponseDto result = postV2Service.updatePost(post.getId(), + requestDto, user.getId()); + + // then + Assertions.assertThat(result.getId()).isEqualTo(post.getId()); + Assertions.assertThat(result.getContents()).isEqualTo(requestDto.getContents()); + Assertions.assertThat(LocalDateTime.parse(result.getUpdatedDate())) + .isEqualTo(LocalDateTime.of(2024, 4, 24, 23, 59)); + } + + @Test + void 실패_본인_작성_게시글_아님() { + // given + doReturn(post).when(postRepository).findByIdOrThrow(any()); + String[] images = {"image4", "image5"}; + PostV2UpdatePostBodyDto requestDto = new PostV2UpdatePostBodyDto("글 제목 수정", "글 내용 수정", images); + + // when & then + assertThrows(ForbiddenException.class, () -> { + postV2Service.updatePost(post.getId(), requestDto, + post.getUser().getId() + 1); + }); + } + } + +} diff --git a/server/src/post/v1/post-v1.controller.ts b/server/src/post/v1/post-v1.controller.ts index 55749d86..234c7221 100644 --- a/server/src/post/v1/post-v1.controller.ts +++ b/server/src/post/v1/post-v1.controller.ts @@ -157,6 +157,7 @@ export class PostV1Controller { @ApiOperation({ summary: '모임 게시글 수정', + deprecated: true, }) @ApiOkResponseCommon(PostV1UpdatePostResponseDto) @ApiResponse({ From a47cfb638b75abdbe12423718af0d384d1fe61fa Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Sun, 25 Aug 2024 21:32:24 +0900 Subject: [PATCH 34/47] =?UTF-8?q?[CHORE]=20csv=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EB=8B=A4=EC=9A=B4=EB=A1=9C=EB=93=9C=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#330)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: 모임 상태 설명 추가 * chore: 협업을 위한 스웨거 수정 * chore: 신청 상태 컬럼 추가 * chore: temp API 추가 --- .../entity/apply/enums/EnApplyStatus.java | 22 +++++-------- .../crew/main/meeting/v2/MeetingV2Api.java | 19 +++++++++-- .../main/meeting/v2/MeetingV2Controller.java | 33 +++++++++++++++---- .../query/MeetingGetAppliesCsvQueryDto.java | 32 ++++++++++++++++++ .../v2/service/MeetingV2ServiceImpl.java | 5 +-- 5 files changed, 87 insertions(+), 24 deletions(-) create mode 100644 main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/query/MeetingGetAppliesCsvQueryDto.java diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/enums/EnApplyStatus.java b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/enums/EnApplyStatus.java index 97452538..804aa500 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/enums/EnApplyStatus.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/enums/EnApplyStatus.java @@ -2,25 +2,25 @@ import java.util.Arrays; import org.sopt.makers.crew.main.common.exception.BadRequestException; -import org.sopt.makers.crew.main.common.exception.BaseException; -import org.springframework.http.HttpStatus; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; /** 신청 상태 */ +@RequiredArgsConstructor +@Getter public enum EnApplyStatus { /** 대기 */ - WAITING(0), + WAITING(0, "대기"), /** 승인된 신청자 */ - APPROVE(1), + APPROVE(1, "승인"), /** 거절된 신청자 */ - REJECT(2); + REJECT(2, "거절"); private final int value; - - EnApplyStatus(int value) { - this.value = value; - } + private final String description; public static EnApplyStatus ofValue(int dbData) { return Arrays.stream(EnApplyStatus.values()) @@ -29,8 +29,4 @@ public static EnApplyStatus ofValue(int dbData) { .orElseThrow(() -> new BadRequestException( String.format("EnApplyStatus 클래스에 value = [%s] 값을 가진 enum 객체가 없습니다.", dbData))); } - - public int getValue() { - return value; - } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java index f44d5d2b..cd424f5b 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java @@ -16,6 +16,7 @@ import java.util.List; import org.sopt.makers.crew.main.common.dto.TempResponseDto; +import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingGetAppliesCsvQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingGetAppliesQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingByOrgUserQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingQueryDto; @@ -124,11 +125,23 @@ ResponseEntity> getMeetingsTemp(@Mode ResponseEntity createPreSignedUrl(@PathParam("contentType") String contentType ,Principal principal); @Operation(summary = "모임 지원자 목록 csv 파일 다운로드", description = "모임 지원자 목록 csv 파일 다운로드") + @Parameters({ + @Parameter(name = "status", description = "0: 대기, 1: 승인된 신청자, 2: 거절된 신청자", example = "[0,1]", required = true, schema = @Schema(type = "array[Integer]", format = "array[Integer]")), + @Parameter(name = "type", description = "0: 지원, 1: 초대", example = "[0,1]", required = true, schema = @Schema(type = "array[Integer]", format = "array[Integer]")), + @Parameter(name = "order", description = "정렬순", example = "desc", schema = @Schema(type = "string", format = "string"))}) ResponseEntity getAppliesCsvFileUrl( @PathVariable Integer meetingId, - @RequestParam(name = "status") List status, - @RequestParam(name = "type") List type, - @RequestParam(name = "order", required = false, defaultValue = "desc") String order, + @ModelAttribute @Valid @Parameter(hidden = true) MeetingGetAppliesCsvQueryDto queryCommand, + Principal principal); + + @Operation(summary = "[TEMP] 모임 지원자 목록 csv 파일 다운로드", description = "모임 지원자 목록 csv 파일 다운로드") + @Parameters({ + @Parameter(name = "status", description = "0: 대기, 1: 승인된 신청자, 2: 거절된 신청자", example = "[0,1]", required = true, schema = @Schema(type = "array[Integer]", format = "array[Integer]")), + @Parameter(name = "type", description = "0: 지원, 1: 초대", example = "[0,1]", required = true, schema = @Schema(type = "array[Integer]", format = "array[Integer]")), + @Parameter(name = "order", description = "정렬순", example = "desc", schema = @Schema(type = "string", format = "string"))}) + ResponseEntity> getAppliesCsvFileUrlTemp( + @PathVariable Integer meetingId, + @ModelAttribute @Valid @Parameter(hidden = true) MeetingGetAppliesCsvQueryDto queryCommand, Principal principal); @Operation(summary = "모임 상세 조회", description = "모임 상세 조회") diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java index 5c908ca6..f4817628 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java @@ -6,12 +6,12 @@ import java.security.Principal; import java.util.List; -import jakarta.websocket.server.PathParam; import lombok.RequiredArgsConstructor; import org.sopt.makers.crew.main.common.dto.TempResponseDto; import org.sopt.makers.crew.main.common.util.UserUtil; import org.sopt.makers.crew.main.external.s3.service.S3Service; +import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingGetAppliesCsvQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingGetAppliesQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingByOrgUserQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingQueryDto; @@ -174,20 +174,41 @@ public ResponseEntity createPreSignedUrl( @GetMapping("/{meetingId}/list/csv") public ResponseEntity getAppliesCsvFileUrl( @PathVariable Integer meetingId, - @RequestParam(name = "status") List status, - @RequestParam(name = "type") List type, - @RequestParam(name = "order", required = false, defaultValue = "desc") String order, + @ModelAttribute @Valid MeetingGetAppliesCsvQueryDto queryCommand, Principal principal) { Integer userId = UserUtil.getUserId(principal); - AppliesCsvFileUrlResponseDto responseDto = meetingV2Service.getAppliesCsvFileUrl(meetingId, status, order, userId); + + // TODO: FE 에서 request 값 변경하도록 요청 필요 + List statuses = List.of(0, 1, 2); + + AppliesCsvFileUrlResponseDto responseDto = meetingV2Service.getAppliesCsvFileUrl(meetingId, statuses, + queryCommand.getOrder(), userId); return ResponseEntity.ok(responseDto); } + @Override + @GetMapping("/{meetingId}/list/csv/temp") + public ResponseEntity> getAppliesCsvFileUrlTemp( + @PathVariable Integer meetingId, + @ModelAttribute @Valid MeetingGetAppliesCsvQueryDto queryCommand, + Principal principal) { + Integer userId = UserUtil.getUserId(principal); + + // TODO: FE 에서 request 값 변경하도록 요청 필요 + List statuses = List.of(0, 1, 2); + + AppliesCsvFileUrlResponseDto responseDto = meetingV2Service.getAppliesCsvFileUrl(meetingId, statuses, + queryCommand.getOrder(), userId); + + return ResponseEntity.ok(TempResponseDto.of(responseDto)); + } + @Override @GetMapping("/{meetingId}") - public ResponseEntity getMeetingById(@PathVariable Integer meetingId, Principal principal) { + public ResponseEntity getMeetingById(@PathVariable Integer meetingId, + Principal principal) { Integer userId = UserUtil.getUserId(principal); return ResponseEntity.ok(meetingV2Service.getMeetingById(meetingId, userId)); diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/query/MeetingGetAppliesCsvQueryDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/query/MeetingGetAppliesCsvQueryDto.java new file mode 100644 index 00000000..1374f688 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/query/MeetingGetAppliesCsvQueryDto.java @@ -0,0 +1,32 @@ +package org.sopt.makers.crew.main.meeting.v2.dto.query; + +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Schema(description = "모임 지원자 csv 파일 요청 dto") +public class MeetingGetAppliesCsvQueryDto { + + @NotNull + @Schema(example = "[0,1]", description = "0: 대기, 1: 승인된 신청자, 2: 거절된 신청자") + private List status; + + @NotNull + @Schema(example = "0", description = "0: 지원, 1: 초대") + private List type; + + @NotNull + @Schema(example = "asc", description = "정렬순") + private String order; + + public MeetingGetAppliesCsvQueryDto(List status, List type, String order) { + this.status = status; + this.type = type; + this.order = order; + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java index a1f78363..97becf4d 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java @@ -361,7 +361,7 @@ private String createCsvFile(List applies) { try (CSVWriter writer = new CSVWriter(new FileWriter(filePath))) { // CSV 파일의 헤더 정의 - String[] header = {"이름", "최근 활동 파트", "최근 활동 기수", "전화번호", "신청 날짜 및 시간", "신청 내용"}; + String[] header = {"이름", "최근 활동 파트", "최근 활동 기수", "전화번호", "신청 날짜 및 시간", "신청 내용", "신청 상태"}; writer.writeNext(header); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); @@ -375,7 +375,8 @@ private String createCsvFile(List applies) { String.valueOf(activity.getGeneration()), String.format("\"%s\"", user.getPhone()), apply.getAppliedDate().format(formatter), - apply.getContent() + apply.getContent(), + apply.getStatus().getDescription() }; writer.writeNext(data); } From e75f7c17ada09ac23bc5fcc87ec1fec5326d6688 Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Sun, 25 Aug 2024 21:39:23 +0900 Subject: [PATCH 35/47] =?UTF-8?q?docs:=20=EC=8A=A4=EC=9B=A8=EA=B1=B0=20?= =?UTF-8?q?=EA=B8=B0=EC=A1=B4=EA=B3=BC=20=EB=8F=99=EC=9D=BC=ED=95=98?= =?UTF-8?q?=EA=B2=8C=20=EC=88=98=EC=A0=95=20(#331)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sopt/makers/crew/main/meeting/v2/MeetingV2Api.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java index cd424f5b..e507c64a 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java @@ -126,8 +126,8 @@ ResponseEntity> getMeetingsTemp(@Mode @Operation(summary = "모임 지원자 목록 csv 파일 다운로드", description = "모임 지원자 목록 csv 파일 다운로드") @Parameters({ - @Parameter(name = "status", description = "0: 대기, 1: 승인된 신청자, 2: 거절된 신청자", example = "[0,1]", required = true, schema = @Schema(type = "array[Integer]", format = "array[Integer]")), - @Parameter(name = "type", description = "0: 지원, 1: 초대", example = "[0,1]", required = true, schema = @Schema(type = "array[Integer]", format = "array[Integer]")), + @Parameter(name = "status", description = "0: 대기, 1: 승인된 신청자, 2: 거절된 신청자", example = "0,1", required = true, schema = @Schema(type = "string")), + @Parameter(name = "type", description = "0: 지원, 1: 초대", example = "0,1", required = true, schema = @Schema(type = "string")), @Parameter(name = "order", description = "정렬순", example = "desc", schema = @Schema(type = "string", format = "string"))}) ResponseEntity getAppliesCsvFileUrl( @PathVariable Integer meetingId, @@ -136,8 +136,8 @@ ResponseEntity getAppliesCsvFileUrl( @Operation(summary = "[TEMP] 모임 지원자 목록 csv 파일 다운로드", description = "모임 지원자 목록 csv 파일 다운로드") @Parameters({ - @Parameter(name = "status", description = "0: 대기, 1: 승인된 신청자, 2: 거절된 신청자", example = "[0,1]", required = true, schema = @Schema(type = "array[Integer]", format = "array[Integer]")), - @Parameter(name = "type", description = "0: 지원, 1: 초대", example = "[0,1]", required = true, schema = @Schema(type = "array[Integer]", format = "array[Integer]")), + @Parameter(name = "status", description = "0: 대기, 1: 승인된 신청자, 2: 거절된 신청자", example = "0,1", required = true, schema = @Schema(type = "string")), + @Parameter(name = "type", description = "0: 지원, 1: 초대", example = "0,1", required = true, schema = @Schema(type = "string")), @Parameter(name = "order", description = "정렬순", example = "desc", schema = @Schema(type = "string", format = "string"))}) ResponseEntity> getAppliesCsvFileUrlTemp( @PathVariable Integer meetingId, From 708c3f5ff4d7219623ecc1d1952acea31598bb56 Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Mon, 26 Aug 2024 00:42:42 +0900 Subject: [PATCH 36/47] =?UTF-8?q?refactor:=20=EC=98=88=EC=99=B8=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=EA=B0=9C=EC=84=A0=20(#335)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../advice/ControllerExceptionAdvice.java | 124 +++++++++++++++--- .../main/common/exception/ErrorStatus.java | 2 + ...esponseDto.java => ExceptionResponse.java} | 6 +- .../jwt/JwtAuthenticationEntryPoint.java | 10 ++ 4 files changed, 119 insertions(+), 23 deletions(-) rename main/src/main/java/org/sopt/makers/crew/main/common/exception/{CommonResponseDto.java => ExceptionResponse.java} (69%) diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/advice/ControllerExceptionAdvice.java b/main/src/main/java/org/sopt/makers/crew/main/common/advice/ControllerExceptionAdvice.java index 84375352..0afc8a6b 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/advice/ControllerExceptionAdvice.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/advice/ControllerExceptionAdvice.java @@ -1,35 +1,119 @@ package org.sopt.makers.crew.main.common.advice; import org.sopt.makers.crew.main.common.exception.BaseException; -import org.sopt.makers.crew.main.common.exception.CommonResponseDto; +import org.sopt.makers.crew.main.common.exception.ExceptionResponse; import org.sopt.makers.crew.main.common.exception.ErrorStatus; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingPathVariableException; import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; + +import jakarta.validation.ConstraintViolationException; +import lombok.extern.slf4j.Slf4j; @RestControllerAdvice +@Slf4j public class ControllerExceptionAdvice { - @ExceptionHandler(BaseException.class) - public ResponseEntity handleGlobalException(BaseException ex) { - return ResponseEntity.status(ex.getStatusCode()) - .body(CommonResponseDto.fail(ex.getErrorCode())); - } - - @ExceptionHandler(MissingServletRequestParameterException.class) - public ResponseEntity handleMissingParameter( - MissingServletRequestParameterException ex) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .body(CommonResponseDto.fail( - ErrorStatus.VALIDATION_REQUEST_MISSING_EXCEPTION.getErrorCode())); - } - - @ExceptionHandler(IllegalArgumentException.class) - public ResponseEntity handleIllegalArgument(IllegalArgumentException ex) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .body(CommonResponseDto.fail(ex.getMessage())); - } + @ExceptionHandler(BaseException.class) + public ResponseEntity handleGlobalException(BaseException e) { + log.warn("{}", e.getMessage()); + return ResponseEntity.status(e.getStatusCode()) + .body(ExceptionResponse.fail(e.getErrorCode())); + } + + @ExceptionHandler(MissingServletRequestParameterException.class) + public ResponseEntity handleMissingParameter( + MissingServletRequestParameterException e) { + log.warn("{}", e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ExceptionResponse.fail( + ErrorStatus.VALIDATION_REQUEST_MISSING_EXCEPTION.getErrorCode())); + } + + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity handleIllegalArgument(IllegalArgumentException e) { + log.warn("{}", e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ExceptionResponse.fail( + ErrorStatus.INVALID_INPUT_VALUE.getErrorCode())); + } + + @ExceptionHandler(ConstraintViolationException.class) // @Notnull 오류 + public ResponseEntity handleConstraintViolationException(ConstraintViolationException e) { + log.warn("{}", e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ExceptionResponse.fail( + ErrorStatus.INVALID_INPUT_VALUE.getErrorCode())); + } + + @ExceptionHandler(DataIntegrityViolationException.class) // null value 오류 + public ResponseEntity handleDataIntegrityViolationException(DataIntegrityViolationException e) { + log.warn("{}", e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ExceptionResponse.fail( + ErrorStatus.INVALID_INPUT_VALUE.getErrorCode())); + } + + @ExceptionHandler(HttpMessageNotReadableException.class) // null value 오류 + public ResponseEntity handleHttpMessageNotReadableException(HttpMessageNotReadableException e) { + log.warn("{}", e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ExceptionResponse.fail( + ErrorStatus.INVALID_INPUT_VALUE.getErrorCode())); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + log.warn("{}", e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ExceptionResponse.fail( + ErrorStatus.INVALID_INPUT_VALUE.getErrorCode())); + } + + /** + * path variable errors + */ + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + public ResponseEntity handleMethodArgumentTypeMismatchException( + MethodArgumentTypeMismatchException e) { + log.warn("{}", e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ExceptionResponse.fail( + ErrorStatus.INVALID_INPUT_VALUE.getErrorCode())); + } + + @ExceptionHandler(MissingPathVariableException.class) + public ResponseEntity handleMissingPathVariableException( + MissingPathVariableException e) { + log.warn("{}", e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ExceptionResponse.fail( + ErrorStatus.INVALID_INPUT_VALUE.getErrorCode())); + } + + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + public ResponseEntity handleHttpRequestMethodNotSupportedException( + HttpRequestMethodNotSupportedException e) { + log.warn("{}", e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ExceptionResponse.fail( + ErrorStatus.INVALID_INPUT_VALUE.getErrorCode())); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleException(Exception e) { + log.error("{}", e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ExceptionResponse.fail( + ErrorStatus.INTERNAL_SERVER_ERROR.getErrorCode())); + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/exception/ErrorStatus.java b/main/src/main/java/org/sopt/makers/crew/main/common/exception/ErrorStatus.java index 6574f4f6..b59c5209 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/exception/ErrorStatus.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/exception/ErrorStatus.java @@ -17,6 +17,8 @@ public enum ErrorStatus { */ VALIDATION_EXCEPTION("CF-001"), VALIDATION_REQUEST_MISSING_EXCEPTION("요청값이 입력되지 않았습니다."), + INVALID_INPUT_VALUE("요청값이 올바르지 않습니다."), + INVALID_INPUT_VALUE_FILTER("요청값 또는 토큰이 올바르지 않습니다."), NOT_FOUND_MEETING("모임이 없습니다."), NOT_FOUND_POST("존재하지 않는 게시글입니다."), NOT_FOUND_COMMENT("존재하지 않는 댓글입니다."), diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/exception/CommonResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/common/exception/ExceptionResponse.java similarity index 69% rename from main/src/main/java/org/sopt/makers/crew/main/common/exception/CommonResponseDto.java rename to main/src/main/java/org/sopt/makers/crew/main/common/exception/ExceptionResponse.java index 4d6cbfc9..98968b3c 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/exception/CommonResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/exception/ExceptionResponse.java @@ -7,12 +7,12 @@ @Builder @Getter @JsonInclude(JsonInclude.Include.NON_NULL) -public class CommonResponseDto { +public class ExceptionResponse { private final String errorCode; - public static CommonResponseDto fail(String errorCode) { - return CommonResponseDto.builder() + public static ExceptionResponse fail(String errorCode) { + return ExceptionResponse.builder() .errorCode(errorCode) .build(); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtAuthenticationEntryPoint.java b/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtAuthenticationEntryPoint.java index 02daa42c..ec62d7a8 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtAuthenticationEntryPoint.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/jwt/JwtAuthenticationEntryPoint.java @@ -1,14 +1,19 @@ package org.sopt.makers.crew.main.common.jwt; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.*; + import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; +import org.sopt.makers.crew.main.common.exception.ExceptionResponse; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; +import com.fasterxml.jackson.databind.ObjectMapper; + @Component public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { @@ -21,6 +26,11 @@ public void commence(HttpServletRequest request, HttpServletResponse response, public void setResponse(HttpServletResponse response) throws IOException { response.setContentType("application/json;charset=UTF-8"); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + + ObjectMapper mapper = new ObjectMapper(); + String jsonResponse = mapper.writeValueAsString(ExceptionResponse.fail(INVALID_INPUT_VALUE_FILTER.getErrorCode())); + + response.getWriter().write(jsonResponse); } } From 48660be7a0ed4ca7b188aa4410fdfc09e124be92 Mon Sep 17 00:00:00 2001 From: Yeseul Jo <68415644+yeseul106@users.noreply.github.com> Date: Mon, 26 Aug 2024 17:56:25 +0900 Subject: [PATCH 37/47] =?UTF-8?q?[FEAT]=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=20=ED=86=A0=EA=B8=80=20=EB=B0=8F=20?= =?UTF-8?q?=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EC=8B=A0=EA=B3=A0=20API=20V2=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20(#336)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FEAT] 모임 게시글 신고 api Controller 단 코드 구현 * [ADD] 모임 게시글 신고 api 관련 dto 추가 * [FEAT] 모임 게시글 신고 api Controller 단 코드 수정 * [FIX] Report 엔티티 연관관계 수정 * [FIX] 수정된 Report 엔티티에 맞게 댓글 신고 로직 수정 * [FEAT] 모임 게시글 신고 관련 service단 로직 구현 * [ADD] 모임 게시글 신고 관련 에러 메세지 추가 * [TEST] 모임 게시글 수정 테스트 코드 작성 및 댓글 신고 테스트 코드 수정 * [CHORE] 모임 게시글 신고 API nest.js 스웨거 deprecated 처리 * [FEAT] Like 엔티티 연관관계 수정 및 레포지토리 코드 구현 * [FEAT] 모임 게시글 좋아요 토글 api Controller 단 코드 구현 * [ADD] 모임 게시글 좋아요 토글 api 관련 dto 추가 * [FEAT] Post 엔티티 내에 likeCount 컬럼 업데이트 메서드 구현 * [FEAT] 모임 게시글 좋아요 토글 api Service 단 코드 구현 * [FIX] Like 엔티티 변경에 따른 commentService 로직 수정 * [TEST] 모임 게시글 좋아요 토글 api 테스트 코드 구현 * [CHORE] 모임 게시글 좋아요 토글 API nest.js 스웨거 deprecated 처리 * chore: 포맷터 적용 * chore: 포맷터 적용 --------- Co-authored-by: mikekks --- .../v2/service/CommentV2ServiceImpl.java | 20 ++-- .../main/common/exception/ErrorStatus.java | 3 +- .../makers/crew/main/entity/like/Like.java | 39 +------ .../crew/main/entity/like/LikeRepository.java | 33 +++--- .../makers/crew/main/entity/post/Post.java | 8 ++ .../crew/main/entity/report/Report.java | 108 +++++++++--------- .../main/entity/report/ReportRepository.java | 7 +- .../makers/crew/main/post/v2/PostV2Api.java | 17 ++- .../crew/main/post/v2/PostV2Controller.java | 20 ++++ .../dto/response/PostV2ReportResponseDto.java | 16 +++ .../PostV2SwitchPostLikeResponseDto.java | 16 +++ .../main/post/v2/service/PostV2Service.java | 6 + .../post/v2/service/PostV2ServiceImpl.java | 75 ++++++++++++ .../v2/service/CommentV2ServiceTest.java | 8 +- .../post/v2/service/PostV2ServiceTest.java | 86 ++++++++++++-- server/src/post/v1/post-v1.controller.ts | 2 + 16 files changed, 324 insertions(+), 140 deletions(-) create mode 100644 main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2ReportResponseDto.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2SwitchPostLikeResponseDto.java diff --git a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java index 5ea912b4..9f0fc855 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceImpl.java @@ -1,6 +1,8 @@ package org.sopt.makers.crew.main.comment.v2.service; -import static org.sopt.makers.crew.main.internal.notification.PushNotificationEnums.*; +import static org.sopt.makers.crew.main.internal.notification.PushNotificationEnums.NEW_COMMENT_MENTION_PUSH_NOTIFICATION_TITLE; +import static org.sopt.makers.crew.main.internal.notification.PushNotificationEnums.NEW_COMMENT_PUSH_NOTIFICATION_TITLE; +import static org.sopt.makers.crew.main.internal.notification.PushNotificationEnums.PUSH_NOTIFICATION_CATEGORY; import java.util.ArrayList; import java.util.HashMap; @@ -21,10 +23,10 @@ import org.sopt.makers.crew.main.comment.v2.dto.response.CommentV2UpdateCommentResponseDto; import org.sopt.makers.crew.main.comment.v2.dto.response.ReplyDto; import org.sopt.makers.crew.main.common.exception.BadRequestException; +import org.sopt.makers.crew.main.common.exception.ErrorStatus; import org.sopt.makers.crew.main.common.exception.ForbiddenException; import org.sopt.makers.crew.main.common.pagination.dto.PageMetaDto; import org.sopt.makers.crew.main.common.pagination.dto.PageOptionsDto; -import org.sopt.makers.crew.main.common.exception.ErrorStatus; import org.sopt.makers.crew.main.common.util.MentionSecretStringRemover; import org.sopt.makers.crew.main.common.util.Time; import org.sopt.makers.crew.main.entity.comment.Comment; @@ -199,20 +201,17 @@ public CommentV2GetCommentsResponseDto getComments(Integer postId, Integer page, */ @Override @Transactional - public CommentV2ReportCommentResponseDto reportComment(Integer commentId, Integer userId) - throws BadRequestException { + public CommentV2ReportCommentResponseDto reportComment(Integer commentId, Integer userId) { Comment comment = commentRepository.findByIdOrThrow(commentId); - User user = userRepository.findByIdOrThrow(userId); - - Optional existingReport = reportRepository.findByCommentAndUser(comment, user); - if (existingReport.isPresent()) { + if (reportRepository.existsByCommentIdAndUserId(commentId, userId)) { throw new BadRequestException(ErrorStatus.ALREADY_REPORTED_COMMENT.getErrorCode()); } Report report = Report.builder() + .userId(userId) .comment(comment) - .user(user) + .commentId(commentId) .build(); Report savedReport = reportRepository.save(report); @@ -280,7 +279,6 @@ public CommentV2SwitchCommentLikeResponseDto switchCommentToggle(Integer comment Integer userId) { Comment comment = commentRepository.findByIdOrThrow(commentId); - User user = userRepository.findByIdOrThrow(userId); boolean isLike = likeRepository.existsByUserIdAndCommentId(userId, commentId); @@ -291,9 +289,7 @@ public CommentV2SwitchCommentLikeResponseDto switchCommentToggle(Integer comment } Like like = Like.builder() - .user(user) .userId(userId) - .comment(comment) .commentId(commentId) .build(); diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/exception/ErrorStatus.java b/main/src/main/java/org/sopt/makers/crew/main/common/exception/ErrorStatus.java index b59c5209..a7665cee 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/exception/ErrorStatus.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/exception/ErrorStatus.java @@ -25,6 +25,7 @@ public enum ErrorStatus { FULL_MEETING_CAPACITY("정원이 꽉 찼습니다."), ALREADY_APPLIED_MEETING("이미 지원한 모임입니다."), ALREADY_REPORTED_COMMENT("이미 신고한 댓글입니다."), + ALREADY_REPORTED_POST("이미 신고한 게시글입니다."), NOT_IN_APPLY_PERIOD("모임 지원 기간이 아닙니다."), MISSING_GENERATION_PART("내 프로필에서 기수/파트 정보를 입력해주세요."), NOT_ACTIVE_GENERATION("활동 기수가 아닙니다."), @@ -32,7 +33,7 @@ public enum ErrorStatus { NOT_FOUND_APPLY("신청상태가 아닌 모임입니다."), ALREADY_PROCESSED_APPLY("이미 해당 상태로 처리된 신청 정보입니다."), MAX_IMAGE_UPLOAD_EXCEEDED("이미지는 최대 10개까지만 업로드 가능합니다."), - + /** * 401 UNAUTHORIZED */ diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/like/Like.java b/main/src/main/java/org/sopt/makers/crew/main/entity/like/Like.java index 6bba6192..019b0c99 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/like/Like.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/like/Like.java @@ -3,12 +3,9 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EntityListeners; -import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import java.time.LocalDateTime; @@ -18,9 +15,6 @@ import lombok.Getter; import lombok.NoArgsConstructor; -import org.sopt.makers.crew.main.entity.comment.Comment; -import org.sopt.makers.crew.main.entity.post.Post; -import org.sopt.makers.crew.main.entity.user.User; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; @@ -45,53 +39,28 @@ public class Like { @CreatedDate private LocalDateTime createdDate; - /** - * 신고자 - */ - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "userId", nullable = false) - private User user; - /** * 좋아요 누른사람 id */ - @Column(insertable = false, nullable = false, updatable = false) + @Column private int userId; - /** - * 게시글 - */ - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "postId") - private Post post; - /** * 게시글 id - 게시글 좋아요가 아닐 경우 null */ - @Column(insertable = false, updatable = false) + @Column private Integer postId; - /** - * 댓글 - */ - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "commentId") - private Comment comment; - /** * 댓글 id - 댓글 좋아요가 아닐 경우 null */ - @Column(insertable = false, updatable = false) + @Column private Integer commentId; @Builder - public Like(Integer userId, Integer postId, Integer commentId, User user, Post post, - Comment comment) { + public Like(Integer userId, Integer postId, Integer commentId) { this.userId = userId; - this.user = user; - this.post = post; this.postId = postId; - this.comment = comment; this.commentId = commentId; } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/like/LikeRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/like/LikeRepository.java index 775a9e21..a17c8623 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/like/LikeRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/like/LikeRepository.java @@ -9,28 +9,31 @@ public interface LikeRepository extends JpaRepository { - List findAllByUserIdAndCommentIdNotNull(Integer userId); + List findAllByUserIdAndCommentIdNotNull(Integer userId); - @Modifying(clearAutomatically = true) - @Transactional - @Query("DELETE FROM Like l WHERE l.postId = :postId") - void deleteAllByPostId(Integer postId); + @Modifying(clearAutomatically = true) + @Transactional + @Query("DELETE FROM Like l WHERE l.postId = :postId") + void deleteAllByPostId(Integer postId); - @Modifying(clearAutomatically = true) - @Transactional - @Query("DELETE FROM Like l WHERE l.postId IN :postIds") - void deleteAllByPostIdsInQuery(List postIds); + @Modifying(clearAutomatically = true) + @Transactional + @Query("DELETE FROM Like l WHERE l.postId IN :postIds") + void deleteAllByPostIdsInQuery(List postIds); - @Modifying(clearAutomatically = true) - @Transactional - @Query("DELETE FROM Like l WHERE l.commentId IN :commentIds") - void deleteAllByCommentIdsInQuery(List commentIds); + @Modifying(clearAutomatically = true) + @Transactional + @Query("DELETE FROM Like l WHERE l.commentId IN :commentIds") + void deleteAllByCommentIdsInQuery(List commentIds); boolean existsByUserIdAndCommentId(Integer userId, Integer commentId); void deleteByUserIdAndCommentId(Integer userId, Integer commentId); - List findAllByPostIdIsIn(List postIds); - List findAllByCommentIdIsIn(List commentIds); + int deleteByUserIdAndPostId(Integer userId, Integer postId); + + List findAllByPostIdIsIn(List postIds); + + List findAllByCommentIdIsIn(List commentIds); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/post/Post.java b/main/src/main/java/org/sopt/makers/crew/main/entity/post/Post.java index d08f1391..ee1f9c56 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/post/Post.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/post/Post.java @@ -139,6 +139,14 @@ public void decreaseCommentCount() { this.commentCount--; } + public void increaseLikeCount() { + this.likeCount++; + } + + public void decreaseLikeCount() { + this.likeCount--; + } + public void isWriter(Integer userId) { if (!this.userId.equals(userId)) { throw new ForbiddenException(FORBIDDEN_EXCEPTION.getErrorCode()); diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/report/Report.java b/main/src/main/java/org/sopt/makers/crew/main/entity/report/Report.java index 2827aee4..f722a0db 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/report/Report.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/report/Report.java @@ -10,14 +10,16 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; + import java.time.LocalDateTime; + import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; + import org.sopt.makers.crew.main.entity.comment.Comment; import org.sopt.makers.crew.main.entity.post.Post; -import org.sopt.makers.crew.main.entity.user.User; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; @@ -27,67 +29,59 @@ @Table(name = "report") @EntityListeners(AuditingEntityListener.class) public class Report { - /** - * Primary key - */ - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private int id; - - /** - * 작성일 - */ - @Column(name = "createdDate", nullable = false, columnDefinition = "TIMESTAMP") - @CreatedDate - private LocalDateTime createdDate; + /** + * Primary key + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private int id; - /** - * 신고자 - */ - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "userId", nullable = false) - private User user; + /** + * 작성일 + */ + @Column(name = "createdDate", nullable = false, columnDefinition = "TIMESTAMP") + @CreatedDate + private LocalDateTime createdDate; - /** - * 신고자 id - */ - @Column(insertable = false, updatable = false) - private int userId; + /** + * 신고자 id + */ + @Column(updatable = false) + private Integer userId; - /** - * 게시글 - */ - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "postId") - private Post post; + /** + * 게시글 + */ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "postId") + private Post post; - /** - * 게시글 id - 게시글 좋아요가 아닐 경우 null - */ - @Column(insertable = false, updatable = false) - private Integer postId; + /** + * 게시글 id - 게시글 좋아요가 아닐 경우 null + */ + @Column(insertable = false, updatable = false) + private Integer postId; - /** - * 댓글 - */ - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "commentId") - private Comment comment; + /** + * 댓글 + */ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "commentId") + private Comment comment; - /** - * 댓글 id - 댓글 좋아요가 아닐 경우 null - */ - @Column(insertable = false, updatable = false) - private int commentId; + /** + * 댓글 id - 댓글 좋아요가 아닐 경우 null + */ + @Column(insertable = false, updatable = false) + private Integer commentId; - @Builder - public Report(User user, int userId, Post post, int postId, Comment comment, - int commentId) { - this.user = user; - this.userId = userId; - this.post = post; - this.postId = postId; - this.comment = comment; - this.commentId = commentId; - } + @Builder + public Report(Integer userId, Post post, Integer postId, Comment comment, + Integer commentId) { + this.userId = userId; + this.post = post; + this.postId = postId; + this.comment = comment; + this.commentId = commentId; + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/report/ReportRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/report/ReportRepository.java index 56071d7d..02a5d4eb 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/report/ReportRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/report/ReportRepository.java @@ -1,11 +1,10 @@ package org.sopt.makers.crew.main.entity.report; -import java.util.Optional; -import org.sopt.makers.crew.main.entity.comment.Comment; -import org.sopt.makers.crew.main.entity.user.User; import org.springframework.data.jpa.repository.JpaRepository; public interface ReportRepository extends JpaRepository { - Optional findByCommentAndUser(Comment comment, User user); + boolean existsByCommentIdAndUserId(Integer commentId, Integer userId); + + boolean existsByPostIdAndUserId(Integer postId, Integer userId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Api.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Api.java index d7290e94..e4465313 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Api.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Api.java @@ -18,6 +18,8 @@ import org.sopt.makers.crew.main.post.v2.dto.response.PostV2CreatePostResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2GetPostCountResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2GetPostsResponseDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2ReportResponseDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2SwitchPostLikeResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2UpdatePostResponseDto; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; @@ -82,10 +84,21 @@ ResponseEntity mentionUserInPost( @Operation(summary = "모임 게시글 수정") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "성공"), - @ApiResponse(responseCode = "400", description = "\"게시글이 없습니다.\" or \"이미지는 최대 10개까지만 업로드 가능합니다.\""), - @ApiResponse(responseCode = "403", description = "권한이 없습니다.") + @ApiResponse(responseCode = "400", description = "\"게시글이 없습니다.\" or \"이미지는 최대 10개까지만 업로드 가능합니다.\"", content = @Content), + @ApiResponse(responseCode = "403", description = "권한이 없습니다.", content = @Content) }) ResponseEntity updatePost(@PathVariable Integer postId, @RequestBody PostV2UpdatePostBodyDto requestBody, Principal principal); + + @Operation(summary = "모임 게시글 신고") + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "성공"), + @ApiResponse(responseCode = "400", description = "\"게시글이 없습니다.\" or \"이미 신고한 게시글입니다.\"", content = @Content) + }) + ResponseEntity reportPost(@PathVariable Integer postId, Principal principal); + + @Operation(summary = "모임 게시글 좋아요 토글") + @ApiResponse(responseCode = "201", description = "성공") + ResponseEntity switchPostLike(@PathVariable Integer postId, Principal principal); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Controller.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Controller.java index 71072b99..81361c55 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Controller.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/PostV2Controller.java @@ -13,6 +13,8 @@ import org.sopt.makers.crew.main.post.v2.dto.response.PostV2CreatePostResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2GetPostCountResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2GetPostsResponseDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2ReportResponseDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2SwitchPostLikeResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2UpdatePostResponseDto; import org.sopt.makers.crew.main.post.v2.service.PostV2Service; import org.springframework.http.HttpStatus; @@ -98,4 +100,22 @@ public ResponseEntity updatePost(@PathVariable Inte Integer userId = UserUtil.getUserId(principal); return ResponseEntity.ok(postV2Service.updatePost(postId, requestBody, userId)); } + + @Override + @PostMapping("/{postId}/report") + @ResponseStatus(HttpStatus.CREATED) + public ResponseEntity reportPost(@PathVariable Integer postId, Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.status(HttpStatus.CREATED).body(postV2Service.reportPost(postId, userId)); + } + + @Override + @PostMapping("/{postId}/like") + @ResponseStatus(HttpStatus.CREATED) + public ResponseEntity switchPostLike(@PathVariable Integer postId, + Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.status(HttpStatus.CREATED).body(postV2Service.switchPostLike(postId, userId)); + } + } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2ReportResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2ReportResponseDto.java new file mode 100644 index 00000000..4d69436f --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2ReportResponseDto.java @@ -0,0 +1,16 @@ +package org.sopt.makers.crew.main.post.v2.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor(staticName = "of") +@Schema(name = "PostV2ReportResponseDto", description = "게시글 신고 응답 Dto") +public class PostV2ReportResponseDto { + + @Schema(description = "생성된 신고 id", example = "1") + @NotNull + private final Integer reportId; +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2SwitchPostLikeResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2SwitchPostLikeResponseDto.java new file mode 100644 index 00000000..bd655f65 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/dto/response/PostV2SwitchPostLikeResponseDto.java @@ -0,0 +1,16 @@ +package org.sopt.makers.crew.main.post.v2.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor(staticName = "of") +@Schema(name = "PostV2SwitchPostLikeResponseDto", description = "모임 게시글 좋아요 토글 응답 Dto") +public class PostV2SwitchPostLikeResponseDto { + + @Schema(description = "본인이 게시글 좋아요를 눌렀는지 여부", example = "true") + @NotNull + private final Boolean isLiked; +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2Service.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2Service.java index 0bbfce19..4e7da2c4 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2Service.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2Service.java @@ -8,6 +8,8 @@ import org.sopt.makers.crew.main.post.v2.dto.response.PostV2CreatePostResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2GetPostCountResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2GetPostsResponseDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2ReportResponseDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2SwitchPostLikeResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2UpdatePostResponseDto; public interface PostV2Service { @@ -25,4 +27,8 @@ public interface PostV2Service { void deletePost(Integer postId, Integer userId); PostV2UpdatePostResponseDto updatePost(Integer postId, PostV2UpdatePostBodyDto requestBody, Integer userId); + + PostV2ReportResponseDto reportPost(Integer postId, Integer userId); + + PostV2SwitchPostLikeResponseDto switchPostLike(Integer postId, Integer userId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java index 25fc134a..1b7f8838 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java @@ -1,6 +1,7 @@ package org.sopt.makers.crew.main.post.v2.service; import static java.util.stream.Collectors.toList; +import static org.sopt.makers.crew.main.common.exception.ErrorStatus.ALREADY_REPORTED_POST; import static org.sopt.makers.crew.main.common.exception.ErrorStatus.FORBIDDEN_EXCEPTION; import static org.sopt.makers.crew.main.common.exception.ErrorStatus.MAX_IMAGE_UPLOAD_EXCEEDED; import static org.sopt.makers.crew.main.internal.notification.PushNotificationEnums.NEW_POST_MENTION_PUSH_NOTIFICATION_TITLE; @@ -19,11 +20,14 @@ import org.sopt.makers.crew.main.entity.apply.enums.EnApplyStatus; import org.sopt.makers.crew.main.entity.comment.Comment; import org.sopt.makers.crew.main.entity.comment.CommentRepository; +import org.sopt.makers.crew.main.entity.like.Like; import org.sopt.makers.crew.main.entity.like.LikeRepository; import org.sopt.makers.crew.main.entity.meeting.Meeting; import org.sopt.makers.crew.main.entity.meeting.MeetingRepository; import org.sopt.makers.crew.main.entity.post.Post; import org.sopt.makers.crew.main.entity.post.PostRepository; +import org.sopt.makers.crew.main.entity.report.Report; +import org.sopt.makers.crew.main.entity.report.ReportRepository; import org.sopt.makers.crew.main.entity.user.User; import org.sopt.makers.crew.main.entity.user.UserRepository; import org.sopt.makers.crew.main.internal.notification.PushNotificationService; @@ -37,6 +41,8 @@ import org.sopt.makers.crew.main.post.v2.dto.response.PostV2CreatePostResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2GetPostCountResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2GetPostsResponseDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2ReportResponseDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2SwitchPostLikeResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2UpdatePostResponseDto; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; @@ -55,6 +61,7 @@ public class PostV2ServiceImpl implements PostV2Service { private final ApplyRepository applyRepository; private final CommentRepository commentRepository; private final LikeRepository likeRepository; + private final ReportRepository reportRepository; private final PushNotificationService pushNotificationService; @@ -226,4 +233,72 @@ public PostV2UpdatePostResponseDto updatePost(Integer postId, PostV2UpdatePostBo String.valueOf(time.now()), post.getImages()); } + + /** + * 모임 게시글 신고 + * + * @param postId 신고할 게시글 id + * @param userId 신고하는 유저 id + * @return 신고 ID + * @throws 400 존재하지 않는 게시글인 경우 + * @throws 400 이미 신고한 게시글인 경우 + * @apiNote 중복 신고는 되지 않음 + */ + @Override + @Transactional + public PostV2ReportResponseDto reportPost(Integer postId, Integer userId) { + Post post = postRepository.findByIdOrThrow(postId); + + if (reportRepository.existsByPostIdAndUserId(postId, userId)) { + throw new BadRequestException(ALREADY_REPORTED_POST.getErrorCode()); + } + + Report report = Report.builder() + .post(post) + .postId(postId) + .userId(userId) + .build(); + + Report savedReport = reportRepository.save(report); + + return PostV2ReportResponseDto.of(savedReport.getId()); + } + + /** + * 모임 게시글 좋아요 토글 + * + * @param postId 좋아요 누르는 게시글 id + * @param userId 좋아요 누르는 유저 id + * @return 해당 게시글을 좋아요 눌렀는지 여부 + * @apiNote 회원만 할 수 있음, 좋아요 버튼 누르면 삭제했다가 다시 생김 + */ + @Override + @Transactional + public PostV2SwitchPostLikeResponseDto switchPostLike(Integer postId, Integer userId) { + + Post post = postRepository.findByIdOrThrow(postId); + + // 좋아요 취소 + int deletedLikes = likeRepository.deleteByUserIdAndPostId(userId, postId); + + // 취소된 좋아요 정보가 없을 경우 + if (deletedLikes == 0) { + Like newLike = Like.builder() + .postId(postId) + .userId(userId) + .build(); + + likeRepository.save(newLike); + + // 좋아요 개수 증가 + post.increaseLikeCount(); + + return PostV2SwitchPostLikeResponseDto.of(true); + } + + // 취소된 경우 게시글 좋아요 개수를 감소시킴 + post.decreaseLikeCount(); + + return PostV2SwitchPostLikeResponseDto.of(false); + } } diff --git a/main/src/test/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceTest.java b/main/src/test/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceTest.java index 0c70e918..87690663 100644 --- a/main/src/test/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceTest.java +++ b/main/src/test/java/org/sopt/makers/crew/main/comment/v2/service/CommentV2ServiceTest.java @@ -96,7 +96,7 @@ void init() { .user(user) .userId(1).build(); - this.report = Report.builder().comment(comment).user(user).build(); + this.report = Report.builder().comment(comment).userId(user.getId()).build(); } @Nested @@ -173,8 +173,7 @@ class 댓글_신고 { void 댓글_신고_성공() { // given doReturn(comment).when(commentRepository).findByIdOrThrow(any()); - doReturn(user).when(userRepository).findByIdOrThrow(any()); - doReturn(Optional.empty()).when(reportRepository).findByCommentAndUser(any(), any()); + doReturn(false).when(reportRepository).existsByCommentIdAndUserId(any(), any()); doReturn(report).when(reportRepository).save(any()); // when @@ -189,8 +188,7 @@ class 댓글_신고 { void 댓글_신고_실패_이미_신고한_댓글() { // given doReturn(comment).when(commentRepository).findByIdOrThrow(any()); - doReturn(user).when(userRepository).findByIdOrThrow(any()); - doReturn(Optional.of(report)).when(reportRepository).findByCommentAndUser(any(), any()); + doReturn(true).when(reportRepository).existsByCommentIdAndUserId(any(), any()); // when & then assertThrows(BadRequestException.class, () -> { diff --git a/main/src/test/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceTest.java b/main/src/test/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceTest.java index 9a93d301..ac15acb6 100644 --- a/main/src/test/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceTest.java +++ b/main/src/test/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceTest.java @@ -14,19 +14,22 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.sopt.makers.crew.main.common.exception.BadRequestException; import org.sopt.makers.crew.main.common.exception.ForbiddenException; import org.sopt.makers.crew.main.common.util.Time; -import org.sopt.makers.crew.main.entity.comment.CommentRepository; +import org.sopt.makers.crew.main.entity.like.LikeRepository; import org.sopt.makers.crew.main.entity.meeting.Meeting; -import org.sopt.makers.crew.main.entity.meeting.MeetingRepository; import org.sopt.makers.crew.main.entity.meeting.enums.MeetingCategory; import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart; import org.sopt.makers.crew.main.entity.post.Post; import org.sopt.makers.crew.main.entity.post.PostRepository; +import org.sopt.makers.crew.main.entity.report.Report; +import org.sopt.makers.crew.main.entity.report.ReportRepository; import org.sopt.makers.crew.main.entity.user.User; import org.sopt.makers.crew.main.entity.user.UserFixture; -import org.sopt.makers.crew.main.entity.user.UserRepository; import org.sopt.makers.crew.main.post.v2.dto.request.PostV2UpdatePostBodyDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2ReportResponseDto; +import org.sopt.makers.crew.main.post.v2.dto.response.PostV2SwitchPostLikeResponseDto; import org.sopt.makers.crew.main.post.v2.dto.response.PostV2UpdatePostResponseDto; @ExtendWith(MockitoExtension.class) @@ -35,13 +38,11 @@ public class PostV2ServiceTest { @InjectMocks private PostV2ServiceImpl postV2Service; @Mock - private MeetingRepository meetingRepository; - @Mock - private UserRepository userRepository; - @Mock private PostRepository postRepository; @Mock - private CommentRepository commentRepository; + private ReportRepository reportRepository; + @Mock + private LikeRepository likeRepository; @Mock private Time time; @@ -52,6 +53,8 @@ public class PostV2ServiceTest { private Post post; + private Report report; + @BeforeEach void init() { user = UserFixture.createStaticUser(); @@ -81,6 +84,7 @@ void init() { String[] images = {"image1", "image2", "image3"}; post = Post.builder().user(user).title("title").contents("contents").images(images).meeting(meeting).build(); + report = Report.builder().post(post).postId(post.getId()).userId(user.getId()).build(); } @@ -117,9 +121,73 @@ class 게시글_수정 { // when & then assertThrows(ForbiddenException.class, () -> { postV2Service.updatePost(post.getId(), requestDto, - post.getUser().getId() + 1); + post.getUserId() + 1); }); } } + @Nested + class 게시글_신고 { + + @Test + void 게시글_신고_성공() { + // given + doReturn(post).when(postRepository).findByIdOrThrow(any()); + doReturn(false).when(reportRepository).existsByPostIdAndUserId(any(), any()); + doReturn(report).when(reportRepository).save(any()); + + // when + PostV2ReportResponseDto result = postV2Service.reportPost(post.getId(), + user.getId()); + + // then + Assertions.assertThat(result.getReportId()).isEqualTo(report.getId()); + } + + @Test + void 댓글_신고_실패_이미_신고한_댓글() { + // given + doReturn(post).when(postRepository).findByIdOrThrow(any()); + doReturn(true).when(reportRepository).existsByPostIdAndUserId(any(), any()); + + // when & then + assertThrows(BadRequestException.class, () -> { + postV2Service.reportPost(post.getId(), user.getId()); + }); + } + } + + @Nested + class 게시글_좋아요_토글 { + + @Test + void 기존에_게시글_좋아요_안눌렀을때_좋아요_누르기_성공() { + // given + doReturn(post).when(postRepository).findByIdOrThrow(any()); + doReturn(0).when(likeRepository).deleteByUserIdAndPostId(any(), any()); //기존에 좋아요 누른 적 없을 때 + + //when + PostV2SwitchPostLikeResponseDto result = postV2Service.switchPostLike(post.getId(), user.getId()); + + //then + Assertions.assertThat(result.getIsLiked()).isEqualTo(true); + Assertions.assertThat(post.getLikeCount()).isEqualTo(1); + } + + @Test + void 기존에_게시글_좋아요_눌렀을때_좋아요_취소_성공() { + // given + post.increaseLikeCount(); + doReturn(post).when(postRepository).findByIdOrThrow(any()); + doReturn(1).when(likeRepository).deleteByUserIdAndPostId(any(), any()); //기존에 좋아요 누른 적 있을 때 + + //when + PostV2SwitchPostLikeResponseDto result = postV2Service.switchPostLike(post.getId(), user.getId()); + + //then + Assertions.assertThat(result.getIsLiked()).isEqualTo(false); + Assertions.assertThat(post.getLikeCount()).isEqualTo(0); + } + } + } diff --git a/server/src/post/v1/post-v1.controller.ts b/server/src/post/v1/post-v1.controller.ts index 234c7221..2214876d 100644 --- a/server/src/post/v1/post-v1.controller.ts +++ b/server/src/post/v1/post-v1.controller.ts @@ -124,6 +124,7 @@ export class PostV1Controller { @ApiOperation({ summary: '게시글 좋아요 토글', + deprecated: true, }) @ApiOkResponseCommon(PostV1SwitchPostLikeResponseDto) @ApiBearerAuth() @@ -138,6 +139,7 @@ export class PostV1Controller { @ApiOperation({ summary: '모임 게시글 신고', + deprecated: true, }) @ApiOkResponseCommon(PostV1ReportPostResponseDto) @ApiResponse({ From 79c1a625f0c9cddde502d9f2f8ee92964f96d7a7 Mon Sep 17 00:00:00 2001 From: mikekks Date: Wed, 28 Aug 2024 16:20:04 +0900 Subject: [PATCH 38/47] =?UTF-8?q?fix:=20=ED=8A=B8=EB=9E=9C=EC=9E=AD?= =?UTF-8?q?=EC=85=98=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sopt/makers/crew/main/auth/v2/service/AuthV2ServiceImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/main/src/main/java/org/sopt/makers/crew/main/auth/v2/service/AuthV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/auth/v2/service/AuthV2ServiceImpl.java index ba6357a8..bedba902 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/auth/v2/service/AuthV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/auth/v2/service/AuthV2ServiceImpl.java @@ -28,6 +28,7 @@ public class AuthV2ServiceImpl implements AuthV2Service { private final JwtTokenProvider jwtTokenProvider; @Override + @Transactional public AuthV2ResponseDto loginUser(AuthV2RequestDto requestDto) { // 플그 서버로의 요청 From c0b957d62668fb42011f9e5ca10ed8d099f5a58e Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Thu, 29 Aug 2024 14:14:48 +0900 Subject: [PATCH 39/47] =?UTF-8?q?chore:=20=ED=95=98=EB=82=98=EC=9D=98=20?= =?UTF-8?q?=EA=B4=91=EA=B3=A0=20=EC=9D=B4=EB=AF=B8=EC=A7=80=EA=B0=80=20?= =?UTF-8?q?=ED=95=98=EB=82=98=EC=9D=98=20=EB=A7=81=ED=81=AC=EC=97=90=20?= =?UTF-8?q?=EB=A7=A4=ED=95=91=EB=90=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#338)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/advertisement/AdvertisementApi.java | 4 +- .../AdvertisementController.java | 6 +-- .../dto/AdvertisementGetDto.java | 36 ++++++++++++++++ .../dto/AdvertisementGetResponseDto.java | 22 ---------- .../dto/AdvertisementImageDto.java | 26 ------------ .../dto/AdvertisementsGetResponseDto.java | 18 ++++++++ .../service/AdvertisementService.java | 24 ++++------- .../entity/advertisement/Advertisement.java | 10 +++-- .../advertisement/AdvertisementImage.java | 42 ------------------- .../AdvertisementImageRepository.java | 9 ---- .../AdvertisementRepository.java | 4 +- 11 files changed, 76 insertions(+), 125 deletions(-) create mode 100644 main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementGetDto.java delete mode 100644 main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementGetResponseDto.java delete mode 100644 main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementImageDto.java create mode 100644 main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementsGetResponseDto.java delete mode 100644 main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/AdvertisementImage.java delete mode 100644 main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/AdvertisementImageRepository.java diff --git a/main/src/main/java/org/sopt/makers/crew/main/advertisement/AdvertisementApi.java b/main/src/main/java/org/sopt/makers/crew/main/advertisement/AdvertisementApi.java index 2bd9dbe9..04354eb7 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/advertisement/AdvertisementApi.java +++ b/main/src/main/java/org/sopt/makers/crew/main/advertisement/AdvertisementApi.java @@ -2,7 +2,7 @@ import java.security.Principal; -import org.sopt.makers.crew.main.advertisement.dto.AdvertisementGetResponseDto; +import org.sopt.makers.crew.main.advertisement.dto.AdvertisementsGetResponseDto; import org.sopt.makers.crew.main.entity.advertisement.enums.AdvertisementCategory; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -23,7 +23,7 @@ public interface AdvertisementApi { responseCode = "200", description = "성공"), }) - ResponseEntity getAdvertisement(@RequestParam(name = "category", required = true) AdvertisementCategory category, + ResponseEntity getAdvertisement(@RequestParam(name = "category", required = true) AdvertisementCategory category, Principal principal); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/advertisement/AdvertisementController.java b/main/src/main/java/org/sopt/makers/crew/main/advertisement/AdvertisementController.java index 31c5533e..30a234a3 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/advertisement/AdvertisementController.java +++ b/main/src/main/java/org/sopt/makers/crew/main/advertisement/AdvertisementController.java @@ -2,7 +2,7 @@ import java.security.Principal; -import org.sopt.makers.crew.main.advertisement.dto.AdvertisementGetResponseDto; +import org.sopt.makers.crew.main.advertisement.dto.AdvertisementsGetResponseDto; import org.sopt.makers.crew.main.advertisement.service.AdvertisementService; import org.sopt.makers.crew.main.entity.advertisement.enums.AdvertisementCategory; import org.springframework.http.ResponseEntity; @@ -22,11 +22,11 @@ public class AdvertisementController implements AdvertisementApi { @Override @GetMapping - public ResponseEntity getAdvertisement( + public ResponseEntity getAdvertisement( @RequestParam(name = "category") AdvertisementCategory category, Principal principal) { - AdvertisementGetResponseDto response = advertisementService.getAdvertisement(category); + AdvertisementsGetResponseDto response = advertisementService.getAdvertisement(category); return ResponseEntity.ok().body(response); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementGetDto.java b/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementGetDto.java new file mode 100644 index 00000000..d77fba22 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementGetDto.java @@ -0,0 +1,36 @@ +package org.sopt.makers.crew.main.advertisement.dto; + +import org.sopt.makers.crew.main.entity.advertisement.Advertisement; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +@Schema(name = "AdvertisementImageDto", description = "광고 구좌 이미지 Dto") +public class AdvertisementGetDto { + + @Schema(description = "[Desktop] 광고 구좌 이미지 url", example = "[pc 버전 url 형식]") + @NotNull + private final String desktopImageUrl; + + @Schema(description = "[mobile] 광고 구좌 이미지 url", example = "[mobile 버전 url 형식]") + @NotNull + private final String mobileImageUrl; + + @Schema(description = "광고 구좌 링크", example = "https://www.naver.com") + @NotNull + private final String advertisementLink; + + @Schema(description = "광고 이미지 순서, 서버에서 정렬해서 전달한다.
프론트에서 확인용으로 사용하기 위함", example = "2") + @NotNull + private final Long advertisementOrder; + + public static AdvertisementGetDto of(Advertisement advertisement) { + return new AdvertisementGetDto(advertisement.getAdvertisementDesktopImageUrl(), + advertisement.getAdvertisementMobileImageUrl(), advertisement.getAdvertisementLink(), + advertisement.getPriority()); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementGetResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementGetResponseDto.java deleted file mode 100644 index bd091e13..00000000 --- a/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementGetResponseDto.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.sopt.makers.crew.main.advertisement.dto; - -import java.util.List; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; - -@Schema(name = "AdvertisementGetResponseDto", description = "광고 구좌 조회 응답 Dto") -public record AdvertisementGetResponseDto( - @Schema(description = "광고 구좌 이미지 객체", example = "") - @NotNull - List advertisementImages, - @Schema(description = "광고 구좌 링크", example = "https://www.naver.com") - @NotNull - String advertisementLink -) { - public static AdvertisementGetResponseDto of(List advertisementImages, - String advertisementLink) { - - return new AdvertisementGetResponseDto(advertisementImages, advertisementLink); - } -} diff --git a/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementImageDto.java b/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementImageDto.java deleted file mode 100644 index 621c848c..00000000 --- a/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementImageDto.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.sopt.makers.crew.main.advertisement.dto; - -import org.sopt.makers.crew.main.entity.advertisement.AdvertisementImage; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -@Getter -@Schema(name = "AdvertisementImageDto", description = "광고 구좌 이미지 Dto") -public class AdvertisementImageDto { - - @Schema(description = "광고 구좌 이미지 url", example = "[url 형식]") - @NotNull - private final String imageUrl; - - @Schema(description = "광고 이미지 순서, 서버에서 정렬해서 전달한다.
프론트에서 확인용으로 사용하기 위함", example = "2") - @NotNull - private final int imageOrder; - - public static AdvertisementImageDto of(AdvertisementImage advertisementImage){ - return new AdvertisementImageDto(advertisementImage.getAdvertisementImageUrl(), advertisementImage.getImageOrder()); - } -} diff --git a/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementsGetResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementsGetResponseDto.java new file mode 100644 index 00000000..a1aa5bd6 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementsGetResponseDto.java @@ -0,0 +1,18 @@ +package org.sopt.makers.crew.main.advertisement.dto; + +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; + +@Schema(name = "AdvertisementGetResponseDto", description = "광고 구좌 조회 응답 Dto") +public record AdvertisementsGetResponseDto( + @Schema(description = "광고 구좌 이미지 객체", example = "") + @NotNull + List advertisementImages +) { + public static AdvertisementsGetResponseDto of(List advertisementImages) { + + return new AdvertisementsGetResponseDto(advertisementImages); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/advertisement/service/AdvertisementService.java b/main/src/main/java/org/sopt/makers/crew/main/advertisement/service/AdvertisementService.java index eef81cdd..53f6e198 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/advertisement/service/AdvertisementService.java +++ b/main/src/main/java/org/sopt/makers/crew/main/advertisement/service/AdvertisementService.java @@ -1,14 +1,11 @@ package org.sopt.makers.crew.main.advertisement.service; import java.util.List; -import java.util.Optional; -import org.sopt.makers.crew.main.advertisement.dto.AdvertisementGetResponseDto; -import org.sopt.makers.crew.main.advertisement.dto.AdvertisementImageDto; +import org.sopt.makers.crew.main.advertisement.dto.AdvertisementsGetResponseDto; +import org.sopt.makers.crew.main.advertisement.dto.AdvertisementGetDto; import org.sopt.makers.crew.main.common.util.Time; import org.sopt.makers.crew.main.entity.advertisement.Advertisement; -import org.sopt.makers.crew.main.entity.advertisement.AdvertisementImage; -import org.sopt.makers.crew.main.entity.advertisement.AdvertisementImageRepository; import org.sopt.makers.crew.main.entity.advertisement.AdvertisementRepository; import org.sopt.makers.crew.main.entity.advertisement.enums.AdvertisementCategory; import org.springframework.stereotype.Service; @@ -21,25 +18,20 @@ @Transactional(readOnly = true) public class AdvertisementService { private final AdvertisementRepository advertisementRepository; - private final AdvertisementImageRepository advertisementImageRepository; private final Time time; - public AdvertisementGetResponseDto getAdvertisement(AdvertisementCategory advertisementCategory) { - Optional advertisement = advertisementRepository.findFirstByAdvertisementCategoryAndAdvertisementEndDateBeforeAndAdvertisementStartDateAfterOrderByPriority( + public AdvertisementsGetResponseDto getAdvertisement(AdvertisementCategory advertisementCategory) { + List advertisements = advertisementRepository.findTop6ByAdvertisementCategoryAndAdvertisementEndDateAfterAndAdvertisementStartDateBeforeOrderByPriority( advertisementCategory, time.now(), time.now()); - if (advertisement.isEmpty()) { - return AdvertisementGetResponseDto.of(null, null); + if (advertisements.isEmpty()) { + return AdvertisementsGetResponseDto.of(null); } - List advertisementImages = advertisementImageRepository.findAllByAdvertisementOrderByImageOrder( - advertisement.get()); - - List imageDtos = advertisementImages.stream() - .map(AdvertisementImageDto::of) + List advertisementDtos = advertisements.stream().map(AdvertisementGetDto::of) .toList(); - return AdvertisementGetResponseDto.of(imageDtos, advertisement.get().getAdvertisementLink()); + return AdvertisementsGetResponseDto.of(advertisementDtos); } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/Advertisement.java b/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/Advertisement.java index eff52dc7..3f5183a6 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/Advertisement.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/Advertisement.java @@ -34,7 +34,10 @@ public class Advertisement { private Integer id; @NotNull - private String advertisementImageUrl; + private String advertisementDesktopImageUrl; + + @NotNull + private String advertisementMobileImageUrl; @NotNull private String advertisementLink; @@ -53,10 +56,11 @@ public class Advertisement { private LocalDateTime advertisementEndDate; @Builder - private Advertisement(String advertisementImageUrl, String advertisementLink, + private Advertisement(String advertisementDesktopImageUrl, String advertisementMobileImageUrl, String advertisementLink, AdvertisementCategory advertisementCategory, Long priority, LocalDateTime advertisementStartDate, LocalDateTime advertisementEndDate) { - this.advertisementImageUrl = advertisementImageUrl; + this.advertisementDesktopImageUrl = advertisementDesktopImageUrl; + this.advertisementMobileImageUrl = advertisementMobileImageUrl; this.advertisementLink = advertisementLink; this.advertisementCategory = advertisementCategory; this.priority = priority; diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/AdvertisementImage.java b/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/AdvertisementImage.java deleted file mode 100644 index b7356f6d..00000000 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/AdvertisementImage.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.sopt.makers.crew.main.entity.advertisement; - -import static jakarta.persistence.GenerationType.*; - -import org.springframework.data.jpa.domain.support.AuditingEntityListener; - -import jakarta.persistence.Entity; -import jakarta.persistence.EntityListeners; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.Table; -import jakarta.validation.constraints.NotNull; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@EntityListeners(AuditingEntityListener.class) -@Table(name = "advertisement_image") -public class AdvertisementImage { - - @Id - @GeneratedValue(strategy = IDENTITY) - private Integer id; - - @NotNull - private String advertisementImageUrl; - - @NotNull - private Integer imageOrder; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "advertisementId") - @NotNull - private Advertisement advertisement; - -} diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/AdvertisementImageRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/AdvertisementImageRepository.java deleted file mode 100644 index 205a03c9..00000000 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/AdvertisementImageRepository.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.sopt.makers.crew.main.entity.advertisement; - -import java.util.List; - -import org.springframework.data.jpa.repository.JpaRepository; - -public interface AdvertisementImageRepository extends JpaRepository { - List findAllByAdvertisementOrderByImageOrder(Advertisement advertisement); -} diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/AdvertisementRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/AdvertisementRepository.java index 90858448..2d907735 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/AdvertisementRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/advertisement/AdvertisementRepository.java @@ -1,12 +1,12 @@ package org.sopt.makers.crew.main.entity.advertisement; import java.time.LocalDateTime; -import java.util.Optional; +import java.util.List; import org.sopt.makers.crew.main.entity.advertisement.enums.AdvertisementCategory; import org.springframework.data.jpa.repository.JpaRepository; public interface AdvertisementRepository extends JpaRepository { - Optional findFirstByAdvertisementCategoryAndAdvertisementEndDateBeforeAndAdvertisementStartDateAfterOrderByPriority( + List findTop6ByAdvertisementCategoryAndAdvertisementEndDateAfterAndAdvertisementStartDateBeforeOrderByPriority( AdvertisementCategory advertisementCategory, LocalDateTime now1, LocalDateTime now2); } From 78f79ab5de3bd9f910395d603dd676b9cc1985c4 Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Thu, 29 Aug 2024 14:35:29 +0900 Subject: [PATCH 40/47] =?UTF-8?q?chore:=20=EB=B3=80=EC=88=98=EB=AA=85=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#340)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../crew/main/advertisement/dto/AdvertisementGetDto.java | 7 +------ .../advertisement/dto/AdvertisementsGetResponseDto.java | 6 +++--- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementGetDto.java b/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementGetDto.java index d77fba22..01e9eafe 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementGetDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementGetDto.java @@ -24,13 +24,8 @@ public class AdvertisementGetDto { @NotNull private final String advertisementLink; - @Schema(description = "광고 이미지 순서, 서버에서 정렬해서 전달한다.
프론트에서 확인용으로 사용하기 위함", example = "2") - @NotNull - private final Long advertisementOrder; - public static AdvertisementGetDto of(Advertisement advertisement) { return new AdvertisementGetDto(advertisement.getAdvertisementDesktopImageUrl(), - advertisement.getAdvertisementMobileImageUrl(), advertisement.getAdvertisementLink(), - advertisement.getPriority()); + advertisement.getAdvertisementMobileImageUrl(), advertisement.getAdvertisementLink()); } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementsGetResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementsGetResponseDto.java index a1aa5bd6..aa2e79ff 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementsGetResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/advertisement/dto/AdvertisementsGetResponseDto.java @@ -9,10 +9,10 @@ public record AdvertisementsGetResponseDto( @Schema(description = "광고 구좌 이미지 객체", example = "") @NotNull - List advertisementImages + List advertisements ) { - public static AdvertisementsGetResponseDto of(List advertisementImages) { + public static AdvertisementsGetResponseDto of(List advertisements) { - return new AdvertisementsGetResponseDto(advertisementImages); + return new AdvertisementsGetResponseDto(advertisements); } } From d2e868d2e456b16fccf34a6ab7e275273616f5b6 Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Thu, 29 Aug 2024 15:56:25 +0900 Subject: [PATCH 41/47] =?UTF-8?q?add:=20=EC=BD=94=EB=93=9C=20=ED=8F=AC?= =?UTF-8?q?=EB=A7=B7=ED=84=B0=20=EC=B6=94=EA=B0=80=20(#342)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/style-config/naver-checkstyle-rules.xml | 427 ++++++++++++++++++ .../style-config/naver-intellij-formatter.xml | 62 +++ 2 files changed, 489 insertions(+) create mode 100644 main/style-config/naver-checkstyle-rules.xml create mode 100644 main/style-config/naver-intellij-formatter.xml diff --git a/main/style-config/naver-checkstyle-rules.xml b/main/style-config/naver-checkstyle-rules.xml new file mode 100644 index 00000000..e21e93bc --- /dev/null +++ b/main/style-config/naver-checkstyle-rules.xml @@ -0,0 +1,427 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/main/style-config/naver-intellij-formatter.xml b/main/style-config/naver-intellij-formatter.xml new file mode 100644 index 00000000..658fc659 --- /dev/null +++ b/main/style-config/naver-intellij-formatter.xml @@ -0,0 +1,62 @@ + + + From 1d7c2367b0f928c4a1835b6a693e9bffef0c1c90 Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Thu, 29 Aug 2024 16:33:11 +0900 Subject: [PATCH 42/47] =?UTF-8?q?=08docs:=20=EB=A6=AC=EB=93=9C=EB=AF=B8=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 26c7c30c..01bfffac 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,8 @@ ## flow 1. Caddy를 통해 HTTPS를 적용하고, HTTP로 들어오는 요청을 HTTPS로 리다이렉트한다. -2. HTTPS로 들어온 요청을 Caddy가 받아서, Caddy는 요청을 받은 후에 해당 요청을 NodeJS/Spring 서버로 리버스프록시한다. -3. NodeJS/Spring 서버는 요청을 받아서, 요청에 맞는 Controller를 찾아서 해당 Controller에서 Service를 호출한다. +2. HTTPS로 들어온 요청을 Caddy가 받아서, Caddy는 요청을 받은 후에 해당 요청을 NestJS/Spring 서버로 리버스프록시한다. +3. NestJS/Spring 서버는 요청을 받아서, 요청에 맞는 Controller를 찾아서 해당 Controller에서 Service를 호출한다. 4. Service에서 로직을 처리한 후에, Repository를 통해 DB에 접근한다. 5. Repository는 DB에 접근해서 데이터를 가져온 후에, Service에게 데이터를 전달한다. 6. Service는 Repository로부터 받은 데이터를 가공해서 Controller에게 전달한다. @@ -37,7 +37,7 @@ ```bash . -├── Dockerfile # docker로 배포할 때 사용하는 파일. 현재는 사용하지 않음 +├── Dockerfile ├── jest.config.ts ├── nest-cli.json ├── package-lock.json @@ -127,7 +127,7 @@ bar # 예시 모듈 ## 환경 변수 - 환경 변수는 dev/prod 환경에 따라 다르게 설정되어야 한다. - - Nestjs + - NestJS - dev 환경: .dev.env - prod 환경: .prod.env - Spring @@ -162,7 +162,8 @@ bar # 예시 모듈 - 배포 서버: AWS EC2 - 배포 툴: Docker Compose -## 배포 방법 +## 수동 배포 방법 +- 현재는 배포 자동화가 되어있다. ```bash # Prod 배포 @@ -195,6 +196,19 @@ $ sudo docker image prune 7. 리뷰를 받은 후에 PR을 develop에 merge한다. 8. develop에 merge된 후에 develop환경 배포를 진행한다. -## 커밋 컨벤션 +## 🙏 Commit Convention +- 유다시티 컨벤션 -TBD +``` +feat: 새로운 기능 구현 +add: 기능구현까지는 아니지만 새로운 파일이 추가된 경우 +del: 기존 코드를 삭제한 경우 +fix: 버그, 오류 해결 +docs: README나 WIKI 등의 문서 작업 +style: 코드가 아닌 스타일 변경을 하는 경우 +refactor: 리팩토링 작업 +test: 테스트 코드 추가, 테스트 코드 리팩토링 +chore: 코드 수정, 내부 파일 수정 +``` + +## 기여 From cf6359de910039f232fa3a1fb82461e212c25e8a Mon Sep 17 00:00:00 2001 From: mikekks Date: Fri, 30 Aug 2024 11:56:30 +0900 Subject: [PATCH 43/47] =?UTF-8?q?chore:=20=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EC=85=98=20=EB=B0=A9=EC=8B=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/common/pagination/dto/page-options.dto.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/common/pagination/dto/page-options.dto.ts b/server/src/common/pagination/dto/page-options.dto.ts index a8442e13..ed132256 100644 --- a/server/src/common/pagination/dto/page-options.dto.ts +++ b/server/src/common/pagination/dto/page-options.dto.ts @@ -29,6 +29,6 @@ export class PageOptionsDto { readonly take?: number = 12; get skip(): number { - return (this.page - 1) * this.take; + return this.page === 1 ? 0 : 11 + (this.page - 2) * 12; } } From dddd708133fe0415f32ad2004c338991c32e7c27 Mon Sep 17 00:00:00 2001 From: mikekks Date: Fri, 30 Aug 2024 14:50:43 +0900 Subject: [PATCH 44/47] =?UTF-8?q?chore:=20=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EC=85=98=20=EB=B0=A9=EC=8B=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../crew/main/common/util/CustomPageable.java | 69 +++ .../v2/service/MeetingV2ServiceImpl.java | 9 +- .../post/v2/service/PostV2ServiceImpl.java | 497 +++++++++--------- 3 files changed, 326 insertions(+), 249 deletions(-) create mode 100644 main/src/main/java/org/sopt/makers/crew/main/common/util/CustomPageable.java diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/util/CustomPageable.java b/main/src/main/java/org/sopt/makers/crew/main/common/util/CustomPageable.java new file mode 100644 index 00000000..dd61075b --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/common/util/CustomPageable.java @@ -0,0 +1,69 @@ +package org.sopt.makers.crew.main.common.util; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; + +public class CustomPageable implements Pageable { + + private final int page; + private final int size; + private final Sort sort; + + public CustomPageable(int page, Sort sort) { + this.page = page; + this.sort = sort; + this.size = calculateSize(page); + } + + private int calculateSize(int page) { + // 첫 번째 페이지는 11개, 그 이후부터는 12개 + return page == 0 ? 11 : 12; + } + + @Override + public int getPageNumber() { + return this.page; + } + + @Override + public int getPageSize() { + return this.size; + } + + @Override + public long getOffset() { + // 오프셋 계산 + return page == 0 ? 0 : 11 + (page - 1) * 12; + } + + @Override + public Sort getSort() { + return this.sort; + } + + @Override + public Pageable next() { + return new CustomPageable(this.page + 1, this.sort); + } + + @Override + public Pageable previousOrFirst() { + return this.page == 0 ? this : new CustomPageable(this.page - 1, this.sort); + } + + @Override + public Pageable first() { + return new CustomPageable(0, this.sort); + } + + @Override + public boolean hasPrevious() { + return this.page > 0; + } + + @Override + public Pageable withPage(int pageNumber) { + // 새로운 페이지 번호로 CustomPageable 생성 + return new CustomPageable(pageNumber, this.sort); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java index 97becf4d..8f47392c 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java @@ -26,6 +26,7 @@ import org.sopt.makers.crew.main.common.exception.ServerException; import org.sopt.makers.crew.main.common.pagination.dto.PageMetaDto; import org.sopt.makers.crew.main.common.pagination.dto.PageOptionsDto; +import org.sopt.makers.crew.main.common.util.CustomPageable; import org.sopt.makers.crew.main.common.util.Time; import org.sopt.makers.crew.main.common.util.UserPartUtil; import org.sopt.makers.crew.main.entity.apply.Applies; @@ -68,6 +69,7 @@ import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetMeetingByIdResponseDto; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -227,9 +229,9 @@ public MeetingGetApplyListResponseDto findApplyList(MeetingGetAppliesQueryDto qu @Override public MeetingV2GetAllMeetingDto getMeetings(MeetingV2GetAllMeetingQueryDto queryCommand) { - + Sort sort = Sort.by(Sort.Direction.ASC, "id"); Page meetings = meetingRepository.findAllByQuery(queryCommand, - PageRequest.of(queryCommand.getPage() - 1, queryCommand.getTake()), time); + new CustomPageable(queryCommand.getPage() - 1, sort), time); List meetingIds = meetings.stream().map(Meeting::getId).toList(); Applies allApplies = new Applies(applyRepository.findAllByMeetingIdIn(meetingIds)); @@ -239,7 +241,8 @@ public MeetingV2GetAllMeetingDto getMeetings(MeetingV2GetAllMeetingQueryDto quer allApplies.getAppliedCount(meeting.getId()), time.now())) .toList(); - PageOptionsDto pageOptionsDto = new PageOptionsDto(queryCommand.getPage(), queryCommand.getTake()); + PageOptionsDto pageOptionsDto = new PageOptionsDto(meetings.getPageable().getPageNumber() + 1, + meetings.getPageable().getPageSize()); PageMetaDto pageMetaDto = new PageMetaDto(pageOptionsDto, (int)meetings.getTotalElements()); return MeetingV2GetAllMeetingDto.of(meetingResponseDtos, pageMetaDto); diff --git a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java index 1b7f8838..41fad4b2 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/post/v2/service/PostV2ServiceImpl.java @@ -9,11 +9,14 @@ import static org.sopt.makers.crew.main.internal.notification.PushNotificationEnums.PUSH_NOTIFICATION_CATEGORY; import java.util.List; + import lombok.RequiredArgsConstructor; + import org.sopt.makers.crew.main.common.exception.BadRequestException; import org.sopt.makers.crew.main.common.exception.ForbiddenException; import org.sopt.makers.crew.main.common.pagination.dto.PageMetaDto; import org.sopt.makers.crew.main.common.pagination.dto.PageOptionsDto; +import org.sopt.makers.crew.main.common.util.CustomPageable; import org.sopt.makers.crew.main.common.util.Time; import org.sopt.makers.crew.main.entity.apply.Apply; import org.sopt.makers.crew.main.entity.apply.ApplyRepository; @@ -47,6 +50,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -55,250 +59,251 @@ @Transactional(readOnly = true) public class PostV2ServiceImpl implements PostV2Service { - private final MeetingRepository meetingRepository; - private final UserRepository userRepository; - private final PostRepository postRepository; - private final ApplyRepository applyRepository; - private final CommentRepository commentRepository; - private final LikeRepository likeRepository; - private final ReportRepository reportRepository; - - private final PushNotificationService pushNotificationService; - - @Value("${push-notification.web-url}") - private String pushWebUrl; - - private final Time time; - - /** - * 모임 게시글 작성 - * - * @throws 403 모임에 속한 유저가 아닌 경우 - * @apiNote 모임에 속한 유저만 작성 가능 - */ - @Override - @Transactional - public PostV2CreatePostResponseDto createPost(PostV2CreatePostBodyDto requestBody, - Integer userId) { - Meeting meeting = meetingRepository.findByIdOrThrow(requestBody.getMeetingId()); - User user = userRepository.findByIdOrThrow(userId); - - List applies = applyRepository.findAllByMeetingId(meeting.getId()); - - boolean isInMeeting = applies.stream() - .anyMatch(apply -> apply.getUserId().equals(userId) - && apply.getStatus().equals(EnApplyStatus.APPROVE)); - - boolean isMeetingCreator = meeting.getUserId().equals(userId); - - if (isInMeeting == false && isMeetingCreator == false) { - throw new ForbiddenException(FORBIDDEN_EXCEPTION.getErrorCode()); - } - - Post post = Post.builder() - .title(requestBody.getTitle()) - .user(user) - .contents(requestBody.getContents()) - .images(requestBody.getImages()) - .meeting(meeting) - .build(); - - Post savedPost = postRepository.save(post); - - List userIdList = applyRepository.findAllByMeetingIdAndStatus(meeting.getId(), - EnApplyStatus.APPROVE) - .stream() - .map(apply -> String.valueOf(apply.getUser().getOrgId())) - .collect(toList()); - - String[] userIds = userIdList.toArray(new String[0]); - String pushNotificationContent = String.format("[%s의 새 글] : \"%s\"", - user.getName(), post.getTitle()); - String pushNotificationWeblink = pushWebUrl + "/detail?id=" + meeting.getId(); - - PushNotificationRequestDto pushRequestDto = PushNotificationRequestDto.of(userIds, - NEW_POST_PUSH_NOTIFICATION_TITLE.getValue(), - pushNotificationContent, - PUSH_NOTIFICATION_CATEGORY.getValue(), pushNotificationWeblink); - - pushNotificationService.sendPushNotification(pushRequestDto); - - return PostV2CreatePostResponseDto.of(savedPost.getId()); - } - - @Override - @Transactional(readOnly = true) - public PostV2GetPostsResponseDto getPosts(PostGetPostsCommand queryCommand, Integer userId) { - Page meetingPostListDtos = postRepository.findPostList(queryCommand, - PageRequest.of(queryCommand.getPage() - 1, queryCommand.getTake()), userId); - - PageOptionsDto pageOptionsDto = new PageOptionsDto(queryCommand.getPage(), - queryCommand.getTake()); - PageMetaDto pageMetaDto = new PageMetaDto(pageOptionsDto, - (int) meetingPostListDtos.getTotalElements()); - - return PostV2GetPostsResponseDto.of(meetingPostListDtos.getContent(), pageMetaDto); - } - - /** - * 모임 게시글 단건 조회 - * - * @throws 400 - * @apiNote 모임에 속한 유저만 작성 가능 - */ - @Override - @Transactional(readOnly = true) - public PostDetailBaseDto getPost(Integer userId, Integer postId) { - return postRepository.findPost(userId, postId); - } - - @Override - public void mentionUserInPost(PostV2MentionUserInPostRequestDto requestBody, Integer userId) { - User user = userRepository.findByIdOrThrow(userId); - Post post = postRepository.findByIdOrThrow(requestBody.getPostId()); - - String pushNotificationContent = String.format("[%s의 글] : \"%s\"", - user.getName(), post.getTitle()); - String pushNotificationWeblink = pushWebUrl + "/post?id=" + post.getId(); - - String[] userOrgIds = requestBody.getOrgIds().stream() - .map(Object::toString) - .toArray(String[]::new); - - PushNotificationRequestDto pushRequestDto = PushNotificationRequestDto.of( - userOrgIds, - NEW_POST_MENTION_PUSH_NOTIFICATION_TITLE.getValue(), - pushNotificationContent, - PUSH_NOTIFICATION_CATEGORY.getValue(), - pushNotificationWeblink - ); - - pushNotificationService.sendPushNotification(pushRequestDto); - } - - /** - * 모임 게시글 개수 조회 - * - * @apiNote 모든 유저가 조회 가능 - */ - @Override - @Transactional(readOnly = true) - public PostV2GetPostCountResponseDto getPostCount(Integer meetingId) { - return PostV2GetPostCountResponseDto.of(postRepository.countByMeetingId(meetingId)); - } - - /** - * 모임 게시글 삭제 - * - * @throws 403 글 작성자가 아닌 경우 - * @apiNote 글을 작성한 유저만 삭제 가능 - */ - @Override - @Transactional - public void deletePost(Integer postId, Integer userId) { - Post post = postRepository.findByIdOrThrow(postId); - post.isWriter(userId); - - List comments = commentRepository.findAllByPostIdOrderByCreatedDate(postId); - List commentIds = comments.stream().map(Comment::getId).toList(); - - commentRepository.deleteAllByPostId(postId); - likeRepository.deleteAllByPostId(postId); - likeRepository.deleteAllByCommentIdsInQuery(commentIds); - - postRepository.delete(post); - } - - /** - * 모임 게시글 수정 - * - * @throws 400 존재하지 않는 게시글인 경우 - * @throws 400 업로드하려는 이미지가 10개 초과인 경우 - * @throws 403 글 작성자가 아닌 경우 - * @apiNote 글을 작성한 유저만 수정 가능 - */ - @Override - @Transactional - public PostV2UpdatePostResponseDto updatePost(Integer postId, PostV2UpdatePostBodyDto requestBody, Integer userId) { - Post post = postRepository.findByIdOrThrow(postId); - post.isWriter(userId); - - if (requestBody.getImages().length > 10) { - throw new BadRequestException(MAX_IMAGE_UPLOAD_EXCEEDED.getErrorCode()); - } - - post.updatePost(requestBody.getTitle(), requestBody.getContents(), requestBody.getImages()); - - return PostV2UpdatePostResponseDto.of(post.getId(), post.getTitle(), post.getContents(), - String.valueOf(time.now()), - post.getImages()); - } - - /** - * 모임 게시글 신고 - * - * @param postId 신고할 게시글 id - * @param userId 신고하는 유저 id - * @return 신고 ID - * @throws 400 존재하지 않는 게시글인 경우 - * @throws 400 이미 신고한 게시글인 경우 - * @apiNote 중복 신고는 되지 않음 - */ - @Override - @Transactional - public PostV2ReportResponseDto reportPost(Integer postId, Integer userId) { - Post post = postRepository.findByIdOrThrow(postId); - - if (reportRepository.existsByPostIdAndUserId(postId, userId)) { - throw new BadRequestException(ALREADY_REPORTED_POST.getErrorCode()); - } - - Report report = Report.builder() - .post(post) - .postId(postId) - .userId(userId) - .build(); - - Report savedReport = reportRepository.save(report); - - return PostV2ReportResponseDto.of(savedReport.getId()); - } - - /** - * 모임 게시글 좋아요 토글 - * - * @param postId 좋아요 누르는 게시글 id - * @param userId 좋아요 누르는 유저 id - * @return 해당 게시글을 좋아요 눌렀는지 여부 - * @apiNote 회원만 할 수 있음, 좋아요 버튼 누르면 삭제했다가 다시 생김 - */ - @Override - @Transactional - public PostV2SwitchPostLikeResponseDto switchPostLike(Integer postId, Integer userId) { - - Post post = postRepository.findByIdOrThrow(postId); - - // 좋아요 취소 - int deletedLikes = likeRepository.deleteByUserIdAndPostId(userId, postId); - - // 취소된 좋아요 정보가 없을 경우 - if (deletedLikes == 0) { - Like newLike = Like.builder() - .postId(postId) - .userId(userId) - .build(); - - likeRepository.save(newLike); - - // 좋아요 개수 증가 - post.increaseLikeCount(); - - return PostV2SwitchPostLikeResponseDto.of(true); - } - - // 취소된 경우 게시글 좋아요 개수를 감소시킴 - post.decreaseLikeCount(); - - return PostV2SwitchPostLikeResponseDto.of(false); - } + private final MeetingRepository meetingRepository; + private final UserRepository userRepository; + private final PostRepository postRepository; + private final ApplyRepository applyRepository; + private final CommentRepository commentRepository; + private final LikeRepository likeRepository; + private final ReportRepository reportRepository; + + private final PushNotificationService pushNotificationService; + + @Value("${push-notification.web-url}") + private String pushWebUrl; + + private final Time time; + + /** + * 모임 게시글 작성 + * + * @throws 403 모임에 속한 유저가 아닌 경우 + * @apiNote 모임에 속한 유저만 작성 가능 + */ + @Override + @Transactional + public PostV2CreatePostResponseDto createPost(PostV2CreatePostBodyDto requestBody, + Integer userId) { + Meeting meeting = meetingRepository.findByIdOrThrow(requestBody.getMeetingId()); + User user = userRepository.findByIdOrThrow(userId); + + List applies = applyRepository.findAllByMeetingId(meeting.getId()); + + boolean isInMeeting = applies.stream() + .anyMatch(apply -> apply.getUserId().equals(userId) + && apply.getStatus().equals(EnApplyStatus.APPROVE)); + + boolean isMeetingCreator = meeting.getUserId().equals(userId); + + if (isInMeeting == false && isMeetingCreator == false) { + throw new ForbiddenException(FORBIDDEN_EXCEPTION.getErrorCode()); + } + + Post post = Post.builder() + .title(requestBody.getTitle()) + .user(user) + .contents(requestBody.getContents()) + .images(requestBody.getImages()) + .meeting(meeting) + .build(); + + Post savedPost = postRepository.save(post); + + List userIdList = applyRepository.findAllByMeetingIdAndStatus(meeting.getId(), + EnApplyStatus.APPROVE) + .stream() + .map(apply -> String.valueOf(apply.getUser().getOrgId())) + .collect(toList()); + + String[] userIds = userIdList.toArray(new String[0]); + String pushNotificationContent = String.format("[%s의 새 글] : \"%s\"", + user.getName(), post.getTitle()); + String pushNotificationWeblink = pushWebUrl + "/detail?id=" + meeting.getId(); + + PushNotificationRequestDto pushRequestDto = PushNotificationRequestDto.of(userIds, + NEW_POST_PUSH_NOTIFICATION_TITLE.getValue(), + pushNotificationContent, + PUSH_NOTIFICATION_CATEGORY.getValue(), pushNotificationWeblink); + + pushNotificationService.sendPushNotification(pushRequestDto); + + return PostV2CreatePostResponseDto.of(savedPost.getId()); + } + + @Override + @Transactional(readOnly = true) + public PostV2GetPostsResponseDto getPosts(PostGetPostsCommand queryCommand, Integer userId) { + Sort sort = Sort.by(Sort.Direction.ASC, "id"); + Page meetingPostListDtos = postRepository.findPostList(queryCommand, + new CustomPageable(queryCommand.getPage() - 1, sort), userId); + + PageOptionsDto pageOptionsDto = new PageOptionsDto(meetingPostListDtos.getPageable().getPageNumber() + 1, + meetingPostListDtos.getPageable().getPageSize()); + PageMetaDto pageMetaDto = new PageMetaDto(pageOptionsDto, + (int)meetingPostListDtos.getTotalElements()); + + return PostV2GetPostsResponseDto.of(meetingPostListDtos.getContent(), pageMetaDto); + } + + /** + * 모임 게시글 단건 조회 + * + * @throws 400 + * @apiNote 모임에 속한 유저만 작성 가능 + */ + @Override + @Transactional(readOnly = true) + public PostDetailBaseDto getPost(Integer userId, Integer postId) { + return postRepository.findPost(userId, postId); + } + + @Override + public void mentionUserInPost(PostV2MentionUserInPostRequestDto requestBody, Integer userId) { + User user = userRepository.findByIdOrThrow(userId); + Post post = postRepository.findByIdOrThrow(requestBody.getPostId()); + + String pushNotificationContent = String.format("[%s의 글] : \"%s\"", + user.getName(), post.getTitle()); + String pushNotificationWeblink = pushWebUrl + "/post?id=" + post.getId(); + + String[] userOrgIds = requestBody.getOrgIds().stream() + .map(Object::toString) + .toArray(String[]::new); + + PushNotificationRequestDto pushRequestDto = PushNotificationRequestDto.of( + userOrgIds, + NEW_POST_MENTION_PUSH_NOTIFICATION_TITLE.getValue(), + pushNotificationContent, + PUSH_NOTIFICATION_CATEGORY.getValue(), + pushNotificationWeblink + ); + + pushNotificationService.sendPushNotification(pushRequestDto); + } + + /** + * 모임 게시글 개수 조회 + * + * @apiNote 모든 유저가 조회 가능 + */ + @Override + @Transactional(readOnly = true) + public PostV2GetPostCountResponseDto getPostCount(Integer meetingId) { + return PostV2GetPostCountResponseDto.of(postRepository.countByMeetingId(meetingId)); + } + + /** + * 모임 게시글 삭제 + * + * @throws 403 글 작성자가 아닌 경우 + * @apiNote 글을 작성한 유저만 삭제 가능 + */ + @Override + @Transactional + public void deletePost(Integer postId, Integer userId) { + Post post = postRepository.findByIdOrThrow(postId); + post.isWriter(userId); + + List comments = commentRepository.findAllByPostIdOrderByCreatedDate(postId); + List commentIds = comments.stream().map(Comment::getId).toList(); + + commentRepository.deleteAllByPostId(postId); + likeRepository.deleteAllByPostId(postId); + likeRepository.deleteAllByCommentIdsInQuery(commentIds); + + postRepository.delete(post); + } + + /** + * 모임 게시글 수정 + * + * @throws 400 존재하지 않는 게시글인 경우 + * @throws 400 업로드하려는 이미지가 10개 초과인 경우 + * @throws 403 글 작성자가 아닌 경우 + * @apiNote 글을 작성한 유저만 수정 가능 + */ + @Override + @Transactional + public PostV2UpdatePostResponseDto updatePost(Integer postId, PostV2UpdatePostBodyDto requestBody, Integer userId) { + Post post = postRepository.findByIdOrThrow(postId); + post.isWriter(userId); + + if (requestBody.getImages().length > 10) { + throw new BadRequestException(MAX_IMAGE_UPLOAD_EXCEEDED.getErrorCode()); + } + + post.updatePost(requestBody.getTitle(), requestBody.getContents(), requestBody.getImages()); + + return PostV2UpdatePostResponseDto.of(post.getId(), post.getTitle(), post.getContents(), + String.valueOf(time.now()), + post.getImages()); + } + + /** + * 모임 게시글 신고 + * + * @param postId 신고할 게시글 id + * @param userId 신고하는 유저 id + * @return 신고 ID + * @throws 400 존재하지 않는 게시글인 경우 + * @throws 400 이미 신고한 게시글인 경우 + * @apiNote 중복 신고는 되지 않음 + */ + @Override + @Transactional + public PostV2ReportResponseDto reportPost(Integer postId, Integer userId) { + Post post = postRepository.findByIdOrThrow(postId); + + if (reportRepository.existsByPostIdAndUserId(postId, userId)) { + throw new BadRequestException(ALREADY_REPORTED_POST.getErrorCode()); + } + + Report report = Report.builder() + .post(post) + .postId(postId) + .userId(userId) + .build(); + + Report savedReport = reportRepository.save(report); + + return PostV2ReportResponseDto.of(savedReport.getId()); + } + + /** + * 모임 게시글 좋아요 토글 + * + * @param postId 좋아요 누르는 게시글 id + * @param userId 좋아요 누르는 유저 id + * @return 해당 게시글을 좋아요 눌렀는지 여부 + * @apiNote 회원만 할 수 있음, 좋아요 버튼 누르면 삭제했다가 다시 생김 + */ + @Override + @Transactional + public PostV2SwitchPostLikeResponseDto switchPostLike(Integer postId, Integer userId) { + + Post post = postRepository.findByIdOrThrow(postId); + + // 좋아요 취소 + int deletedLikes = likeRepository.deleteByUserIdAndPostId(userId, postId); + + // 취소된 좋아요 정보가 없을 경우 + if (deletedLikes == 0) { + Like newLike = Like.builder() + .postId(postId) + .userId(userId) + .build(); + + likeRepository.save(newLike); + + // 좋아요 개수 증가 + post.increaseLikeCount(); + + return PostV2SwitchPostLikeResponseDto.of(true); + } + + // 취소된 경우 게시글 좋아요 개수를 감소시킴 + post.decreaseLikeCount(); + + return PostV2SwitchPostLikeResponseDto.of(false); + } } From 11771c3a5b30892f07bfe125b5896564dee10bcd Mon Sep 17 00:00:00 2001 From: mikekks Date: Fri, 30 Aug 2024 22:11:22 +0900 Subject: [PATCH 45/47] =?UTF-8?q?chore:=20spring=20validation=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/main/build.gradle b/main/build.gradle index 7c0f7b78..28eb9001 100644 --- a/main/build.gradle +++ b/main/build.gradle @@ -35,6 +35,7 @@ dependencies { annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' implementation group: 'org.postgresql', name: 'postgresql', version: '42.6.0' + implementation 'org.springframework.boot:spring-boot-starter-validation' // jsonb 타입 핸들링 위함 implementation 'io.hypersistence:hypersistence-utils-hibernate-62:3.6.0' From ae8fb04ba57fe756d55237b9caf24bd66f3460aa Mon Sep 17 00:00:00 2001 From: Mingyu Song <100754581+mikekks@users.noreply.github.com> Date: Fri, 30 Aug 2024 22:38:53 +0900 Subject: [PATCH 46/47] =?UTF-8?q?refactor:=20=EB=AA=A8=EC=9E=84=20?= =?UTF-8?q?=EB=B0=B0=EB=84=88=20=EC=A1=B0=ED=9A=8C=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0=20(#347)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Map 방어 코드 작성 * refactor: 배너 조회 로직 쿼리 개선 --- .../crew/main/entity/apply/Applies.java | 22 +- .../makers/crew/main/entity/apply/Apply.java | 182 ++++++------- .../entity/meeting/MeetingRepository.java | 7 +- .../makers/crew/main/entity/post/Post.java | 253 +++++++++--------- .../crew/main/entity/post/PostRepository.java | 25 +- .../MeetingV2GetMeetingBannerResponseDto.java | 52 ++-- ...tingV2GetMeetingBannerResponseUserDto.java | 19 +- .../meeting/v2/service/MeetingV2Service.java | 30 ++- .../v2/service/MeetingV2ServiceImpl.java | 70 +++-- 9 files changed, 361 insertions(+), 299 deletions(-) diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Applies.java b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Applies.java index d59b6806..decbcbf6 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Applies.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Applies.java @@ -27,27 +27,45 @@ public Applies(List applies) { .collect(Collectors.groupingBy(Apply::getMeetingId)); } - public int getAppliedCount(Integer meetingId){ + public int getAppliedCount(Integer meetingId) { List applies = appliesMap.get(meetingId); - if(applies == null){ + if (applies == null) { return 0; } return applies.size(); } public long getApprovedCount(Integer meetingId) { + List applies = appliesMap.get(meetingId); + + if (applies == null) { + return 0; + } + return appliesMap.get(meetingId).stream() .filter(apply -> apply.getStatus().equals(EnApplyStatus.APPROVE)) .count(); } public Boolean isApply(Integer meetingId, Integer userId) { + List applies = appliesMap.get(meetingId); + + if (applies == null) { + return false; + } + return appliesMap.get(meetingId).stream() .anyMatch(apply -> apply.getUserId().equals(userId)); } public Boolean isApproved(Integer meetingId, Integer userId) { + List applies = appliesMap.get(meetingId); + + if (applies == null) { + return false; + } + return appliesMap.get(meetingId).stream() .anyMatch(apply -> apply.getUserId().equals(userId) && apply.getStatus().equals(EnApplyStatus.APPROVE)); diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Apply.java b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Apply.java index ea4deb97..706ef03a 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Apply.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/apply/Apply.java @@ -13,7 +13,9 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; + import java.time.LocalDateTime; + import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -36,94 +38,94 @@ @Table(name = "apply") public class Apply { - /** - * Primary Key - */ - @Id - @GeneratedValue(strategy = IDENTITY) - private Integer id; - - /** - * 지원 타입 - */ - @Column(name = "type", nullable = false, columnDefinition = "integer default 0") - @Convert(converter = ApplyTypeConverter.class) - private EnApplyType type; - - /** - * 지원한 모임 - */ - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "meetingId", nullable = false) - private Meeting meeting; - - /** - * 지원한 모임 ID - */ - @Column(insertable = false, updatable = false) - private Integer meetingId; - - /** - * 지원자 - */ - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "userId", nullable = false) - private User user; - - /** - * 지원자 ID - */ - @Column(insertable = false, updatable = false) - private Integer userId; - - /** - * 지원 동기 - */ - @Column(name = "content", nullable = false) - private String content; - - /** - * 지원한 날짜 - */ - @Column(name = "appliedDate", nullable = false, columnDefinition = "TIMESTAMP") - @CreatedDate - private LocalDateTime appliedDate; - - /** - * 지원 상태 - */ - @Column(name = "status", nullable = false, columnDefinition = "integer default 0") - @Convert(converter = ApplyStatusConverter.class) - private EnApplyStatus status; - - @Builder - public Apply(EnApplyType type, Meeting meeting, Integer meetingId, User user, Integer userId, - String content) { - this.type = type; - this.meeting = meeting; - this.meetingId = meetingId; - this.user = user; - this.userId = userId; - this.content = content; - this.appliedDate = LocalDateTime.now(); - this.status = EnApplyStatus.WAITING; - } - - public void updateApplyStatus(EnApplyStatus status) { - this.status = status; - } - - public void validateDuplicateUpdateApplyStatus(EnApplyStatus updatedApplyStatus){ - if(updatedApplyStatus.equals(this.getStatus())){ - throw new BadRequestException(ALREADY_PROCESSED_APPLY.getErrorCode()); - } - } - - public String getContent(Integer requestUserId){ - if(!this.userId.equals(requestUserId)){ - return ""; - } - - return this.content; - } + /** + * Primary Key + */ + @Id + @GeneratedValue(strategy = IDENTITY) + private Integer id; + + /** + * 지원 타입 + */ + @Column(name = "type", nullable = false, columnDefinition = "integer default 0") + @Convert(converter = ApplyTypeConverter.class) + private EnApplyType type; + + /** + * 지원한 모임 + */ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "meetingId", nullable = false) + private Meeting meeting; + + /** + * 지원한 모임 ID + */ + @Column(insertable = false, updatable = false) + private Integer meetingId; + + /** + * 지원자 + */ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "userId", nullable = false) + private User user; + + /** + * 지원자 ID + */ + @Column(insertable = false, updatable = false) + private Integer userId; + + /** + * 지원 동기 + */ + @Column(name = "content", nullable = false) + private String content; + + /** + * 지원한 날짜 + */ + @Column(name = "appliedDate", nullable = false, columnDefinition = "TIMESTAMP") + @CreatedDate + private LocalDateTime appliedDate; + + /** + * 지원 상태 + */ + @Column(name = "status", nullable = false, columnDefinition = "integer default 0") + @Convert(converter = ApplyStatusConverter.class) + private EnApplyStatus status; + + @Builder + public Apply(EnApplyType type, Meeting meeting, Integer meetingId, User user, Integer userId, + String content) { + this.type = type; + this.meeting = meeting; + this.meetingId = meetingId; + this.user = user; + this.userId = userId; + this.content = content; + this.appliedDate = LocalDateTime.now(); + this.status = EnApplyStatus.WAITING; + } + + public void updateApplyStatus(EnApplyStatus status) { + this.status = status; + } + + public void validateDuplicateUpdateApplyStatus(EnApplyStatus updatedApplyStatus) { + if (updatedApplyStatus.equals(this.getStatus())) { + throw new BadRequestException(ALREADY_PROCESSED_APPLY.getErrorCode()); + } + } + + public String getContent(Integer requestUserId) { + if (!this.userId.equals(requestUserId)) { + return ""; + } + + return this.content; + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingRepository.java index 776578ba..e8a70abe 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/meeting/MeetingRepository.java @@ -3,11 +3,11 @@ import static org.sopt.makers.crew.main.common.exception.ErrorStatus.NOT_FOUND_MEETING; import java.util.List; -import java.util.Optional; import org.sopt.makers.crew.main.common.exception.BadRequestException; import org.sopt.makers.crew.main.entity.user.User; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; public interface MeetingRepository extends JpaRepository, MeetingSearchRepository { @@ -15,10 +15,11 @@ public interface MeetingRepository extends JpaRepository, Meet List findAllByUser(User user); - Optional findById(Integer meetingId); - default Meeting findByIdOrThrow(Integer meetingId) { return findById(meetingId) .orElseThrow(() -> new BadRequestException(NOT_FOUND_MEETING.getErrorCode())); } + + @Query("SELECT m FROM Meeting m JOIN FETCH m.user ORDER BY m.id DESC LIMIT 20") + List findTop20ByOrderByIdDesc(); } \ No newline at end of file diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/post/Post.java b/main/src/main/java/org/sopt/makers/crew/main/entity/post/Post.java index ee1f9c56..aa8cf1dc 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/post/Post.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/post/Post.java @@ -13,11 +13,14 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; + import java.time.LocalDateTime; + import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; + import org.hibernate.annotations.Type; import org.sopt.makers.crew.main.common.exception.ForbiddenException; import org.sopt.makers.crew.main.entity.meeting.Meeting; @@ -33,129 +36,129 @@ @Table(name = "post") public class Post { - /** - * 게시글의 고유 식별자 - */ - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Integer id; - - /** - * 게시글 제목 - */ - @Column(nullable = false) - private String title; - - /** - * 게시글 내용 - */ - @Column(nullable = false, columnDefinition = "TEXT") - private String contents; - - /** - * 게시글 작성일 - */ - @Column(name = "createdDate", nullable = false, columnDefinition = "TIMESTAMP") - @CreatedDate - private LocalDateTime createdDate; - - /** - * 게시글 수정일 - */ - @Column(name = "updatedDate", nullable = false, columnDefinition = "TIMESTAMP") - @LastModifiedDate - private LocalDateTime updatedDate; - - /** - * 조회수 - */ - @Column(nullable = false, columnDefinition = "int default 0") - private int viewCount; - - /** - * 이미지 리스트 - */ - @Column(name = "images", columnDefinition = "text[]") - @Type(StringArrayType.class) - private String[] images; - - /** - * 작성자 정보 - */ - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "userId", nullable = false) - private User user; - - /** - * 작성자의 고유 식별자 - */ - @Column(insertable = false, updatable = false) - private Integer userId; - - /** - * 게시글이 속한 미팅 정보 - */ - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "meetingId", nullable = false) - private Meeting meeting; - - /** - * 게시글이 속한 미팅의 고유 식별자 - */ - @Column(insertable = false, updatable = false) - private Integer meetingId; - - /** - * 게시글에 달린 댓글 수 - */ - @Column(nullable = false, columnDefinition = "int default 0") - private int commentCount; - - /** - * 게시글에 대한 좋아요 수 - */ - @Column(nullable = false, columnDefinition = "int default 0") - private int likeCount; - - @Builder - public Post(String title, String contents, String[] images, User user, Meeting meeting) { - this.title = title; - this.contents = contents; - this.viewCount = 0; - this.images = images; - this.user = user; - this.userId = user.getId(); - this.meeting = meeting; - this.meetingId = meeting.getId(); - this.commentCount = 0; - this.likeCount = 0; - } - - public void increaseCommentCount() { - this.commentCount++; - } - - public void decreaseCommentCount() { - this.commentCount--; - } - - public void increaseLikeCount() { - this.likeCount++; - } - - public void decreaseLikeCount() { - this.likeCount--; - } - - public void isWriter(Integer userId) { - if (!this.userId.equals(userId)) { - throw new ForbiddenException(FORBIDDEN_EXCEPTION.getErrorCode()); - } - } - - public void updatePost(String title, String contents, String[] images) { - this.title = title; - this.contents = contents; - this.images = images; - } + /** + * 게시글의 고유 식별자 + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + /** + * 게시글 제목 + */ + @Column(nullable = false) + private String title; + + /** + * 게시글 내용 + */ + @Column(nullable = false, columnDefinition = "TEXT") + private String contents; + + /** + * 게시글 작성일 + */ + @Column(name = "createdDate", nullable = false, columnDefinition = "TIMESTAMP") + @CreatedDate + private LocalDateTime createdDate; + + /** + * 게시글 수정일 + */ + @Column(name = "updatedDate", nullable = false, columnDefinition = "TIMESTAMP") + @LastModifiedDate + private LocalDateTime updatedDate; + + /** + * 조회수 + */ + @Column(nullable = false, columnDefinition = "int default 0") + private int viewCount; + + /** + * 이미지 리스트 + */ + @Column(name = "images", columnDefinition = "text[]") + @Type(StringArrayType.class) + private String[] images; + + /** + * 작성자 정보 + */ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "userId", nullable = false) + private User user; + + /** + * 작성자의 고유 식별자 + */ + @Column(insertable = false, updatable = false) + private Integer userId; + + /** + * 게시글이 속한 미팅 정보 + */ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "meetingId", nullable = false) + private Meeting meeting; + + /** + * 게시글이 속한 미팅의 고유 식별자 + */ + @Column(insertable = false, updatable = false) + private Integer meetingId; + + /** + * 게시글에 달린 댓글 수 + */ + @Column(nullable = false, columnDefinition = "int default 0") + private int commentCount; + + /** + * 게시글에 대한 좋아요 수 + */ + @Column(nullable = false, columnDefinition = "int default 0") + private int likeCount; + + @Builder + public Post(String title, String contents, String[] images, User user, Meeting meeting) { + this.title = title; + this.contents = contents; + this.viewCount = 0; + this.images = images; + this.user = user; + this.userId = user.getId(); + this.meeting = meeting; + this.meetingId = meeting.getId(); + this.commentCount = 0; + this.likeCount = 0; + } + + public void increaseCommentCount() { + this.commentCount++; + } + + public void decreaseCommentCount() { + this.commentCount--; + } + + public void increaseLikeCount() { + this.likeCount++; + } + + public void decreaseLikeCount() { + this.likeCount--; + } + + public void isWriter(Integer userId) { + if (!this.userId.equals(userId)) { + throw new ForbiddenException(FORBIDDEN_EXCEPTION.getErrorCode()); + } + } + + public void updatePost(String title, String contents, String[] images) { + this.title = title; + this.contents = contents; + this.images = images; + } } \ No newline at end of file diff --git a/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostRepository.java b/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostRepository.java index 3dccb412..71b37fc4 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostRepository.java +++ b/main/src/main/java/org/sopt/makers/crew/main/entity/post/PostRepository.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.Optional; + import org.sopt.makers.crew.main.common.exception.BadRequestException; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; @@ -12,21 +13,19 @@ public interface PostRepository extends JpaRepository, PostSearchRepository { - Optional findById(Integer postId); - - default Post findByIdOrThrow(Integer postId) { - return findById(postId) - .orElseThrow(() -> new BadRequestException(NOT_FOUND_POST.getErrorCode())); - } + default Post findByIdOrThrow(Integer postId) { + return findById(postId) + .orElseThrow(() -> new BadRequestException(NOT_FOUND_POST.getErrorCode())); + } - Optional findFirstByMeetingIdOrderByIdDesc(Integer meetingId); + Integer countByMeetingId(Integer meetingId); - Integer countByMeetingId(Integer meetingId); + List findAllByMeetingId(Integer meetingId); - List findAllByMeetingId(Integer meetingId); + List findAllByMeetingIdIn(List meetingIds); - @Modifying(clearAutomatically = true) - @Transactional - @Query("DELETE FROM Post p WHERE p.meetingId = :meetingId") - void deleteAllByMeetingIdQuery(Integer meetingId); + @Modifying(clearAutomatically = true) + @Transactional + @Query("DELETE FROM Post p WHERE p.meetingId = :meetingId") + void deleteAllByMeetingIdQuery(Integer meetingId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingBannerResponseDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingBannerResponseDto.java index c543886a..51356df3 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingBannerResponseDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingBannerResponseDto.java @@ -2,36 +2,36 @@ import java.time.LocalDateTime; import java.util.List; -import java.util.Optional; +import org.sopt.makers.crew.main.entity.meeting.Meeting; import org.sopt.makers.crew.main.entity.meeting.enums.MeetingCategory; import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart; import org.sopt.makers.crew.main.entity.meeting.vo.ImageUrlVO; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; -import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.RequiredArgsConstructor; @Getter -@AllArgsConstructor(staticName = "of") +@RequiredArgsConstructor @Schema(name = "MeetingV2GetMeetingBannerResponseDto", description = "모임 배너 응답 Dto") public class MeetingV2GetMeetingBannerResponseDto { /** 모임 ID */ @Schema(description = "모임 id", example = "1") @NotNull - private Integer id; + private final Integer id; /** 유저 Crew ID */ @Schema(description = "크루에서 사용하는 userId", example = "1") @NotNull - private Integer userId; + private final Integer userId; /** 모임 제목 */ @Schema(description = "모임 제목", example = "모임 제목입니다1") @NotNull - private String title; + private final String title; /** * 모임 카테고리 @@ -40,7 +40,7 @@ public class MeetingV2GetMeetingBannerResponseDto { */ @Schema(description = "모임 카테고리", example = "스터디") @NotNull - private MeetingCategory category; + private final MeetingCategory category; /** * 썸네일 이미지 @@ -49,64 +49,76 @@ public class MeetingV2GetMeetingBannerResponseDto { */ @Schema(description = "모임 사진", example = "[url] 형식") @NotNull - private List imageURL; + private final List imageURL; /** 모임 활동 시작일 */ @Schema(description = "모임 활동 시작일", example = "2024-07-31T15:30:00") @NotNull - private LocalDateTime mStartDate; + private final LocalDateTime mStartDate; /** 모임 활동 종료일 */ @Schema(description = "모임 활동 종료일", example = "2024-08-25T15:30:00") @NotNull - private LocalDateTime mEndDate; + private final LocalDateTime mEndDate; /** 모임 모집 시작일 */ @Schema(description = "모임 모집 시작일", example = "2024-06-11T15:30:00") @NotNull - private LocalDateTime startDate; + private final LocalDateTime startDate; /** 모임 모집 종료일 */ @Schema(description = "모임 모집 종료일", example = "2024-06-17T15:30:00") @NotNull - private LocalDateTime endDate; + private final LocalDateTime endDate; /** 모임 인원 */ @Schema(description = "모집 인원", example = "20") @NotNull - private Integer capacity; + private final Integer capacity; /** 최근 활동 일자 */ @Schema(description = "최근 활동 일자", example = "2024-06-11T15:30:00") - private Optional recentActivityDate; + private final LocalDateTime recentActivityDate; /** 모임 타겟 기수 */ @Schema(description = "모임 타겟 기수", example = "33") @NotNull - private Integer targetActiveGeneration; + private final Integer targetActiveGeneration; /** 모임 타겟 파트 */ @Schema(description = "모임 타겟 파트", example = "[\"PM\", \"SERVER\"]") @NotNull - private MeetingJoinablePart[] joinableParts; + private final MeetingJoinablePart[] joinableParts; /** 지원자 수 */ @Schema(description = "지원자 수", example = "50") @NotNull - private Integer applicantCount; + private final long applicantCount; /** 가입된 지원자 수 */ @Schema(description = "가입된 지원자 수", example = "9") @NotNull - private Integer approvedUserCount; + private final long approvedUserCount; /** 개설자 정보 */ @Schema(description = "모임장 정보", example = "") @NotNull - private MeetingV2GetMeetingBannerResponseUserDto user; + private final MeetingV2GetMeetingBannerResponseUserDto user; /** 미팅 상태 */ @Schema(description = "모임 상태", example = "1") @NotNull - private Integer status; + private final Integer status; + + public static MeetingV2GetMeetingBannerResponseDto of(Meeting meeting, LocalDateTime recentActivityDate, + long applicantCount, long approvedUserCount, MeetingV2GetMeetingBannerResponseUserDto meetingCreator, LocalDateTime now) { + + return new MeetingV2GetMeetingBannerResponseDto(meeting.getId(), meeting.getUserId(), meeting.getTitle(), + meeting.getCategory(), + meeting.getImageURL(), meeting.getMStartDate(), meeting.getMEndDate(), meeting.getStartDate(), + meeting.getEndDate(), meeting.getCapacity(), recentActivityDate, meeting.getTargetActiveGeneration(), + meeting.getJoinableParts(), applicantCount, approvedUserCount, + meetingCreator, meeting.getMeetingStatus(now)); + } + } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingBannerResponseUserDto.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingBannerResponseUserDto.java index c46c2ee1..608d8427 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingBannerResponseUserDto.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingBannerResponseUserDto.java @@ -1,31 +1,38 @@ package org.sopt.makers.crew.main.meeting.v2.dto.response; +import org.sopt.makers.crew.main.entity.user.User; + import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; -import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.RequiredArgsConstructor; @Getter -@AllArgsConstructor(staticName = "of") +@RequiredArgsConstructor @Schema(name = "MeetingV2GetMeetingBannerResponseUserDto", description = "모임 배너 유저 Dto") public class MeetingV2GetMeetingBannerResponseUserDto { /** 개설자 crew ID */ @Schema(description = "모임장 id, 크루에서 사용하는 userId", example = "1") @NotNull - private Integer id; + private final Integer id; /** 개설자 */ @Schema(description = "모임장 이름", example = "홍길동") @NotNull - private String name; + private final String name; /** 개설자 playground ID */ @Schema(description = "모임장 org id, 메이커스 프로덕트에서 범용적으로 사용하는 userId", example = "1") @NotNull - private Integer orgId; + private final Integer orgId; /** 프로필 사진 */ @Schema(description = "모임장 프로필 사진", example = "[url] 형식") - private String profileImage; + private final String profileImage; + + public static MeetingV2GetMeetingBannerResponseUserDto of(User meetingCreator) { + return new MeetingV2GetMeetingBannerResponseUserDto(meetingCreator.getId(), meetingCreator.getName(), + meetingCreator.getOrgId(), meetingCreator.getProfileImage()); + } } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java index d668a0b1..9d254128 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java @@ -1,6 +1,7 @@ package org.sopt.makers.crew.main.meeting.v2.service; import java.util.List; + import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingGetAppliesQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingByOrgUserQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingQueryDto; @@ -18,29 +19,30 @@ public interface MeetingV2Service { - MeetingV2GetAllMeetingByOrgUserDto getAllMeetingByOrgUser( - MeetingV2GetAllMeetingByOrgUserQueryDto queryDto); + MeetingV2GetAllMeetingByOrgUserDto getAllMeetingByOrgUser( + MeetingV2GetAllMeetingByOrgUserQueryDto queryDto); - List getMeetingBanner(); + List getMeetingBanner(); - MeetingV2CreateMeetingResponseDto createMeeting(MeetingV2CreateMeetingBodyDto requestBody, Integer userId); + MeetingV2CreateMeetingResponseDto createMeeting(MeetingV2CreateMeetingBodyDto requestBody, Integer userId); - MeetingV2ApplyMeetingResponseDto applyMeeting(MeetingV2ApplyMeetingDto requestBody, Integer userId); + MeetingV2ApplyMeetingResponseDto applyMeeting(MeetingV2ApplyMeetingDto requestBody, Integer userId); - void applyMeetingCancel(Integer meetingId, Integer userId); + void applyMeetingCancel(Integer meetingId, Integer userId); - MeetingGetApplyListResponseDto findApplyList(MeetingGetAppliesQueryDto queryCommand, Integer meetingId, - Integer userId); + MeetingGetApplyListResponseDto findApplyList(MeetingGetAppliesQueryDto queryCommand, Integer meetingId, + Integer userId); - MeetingV2GetAllMeetingDto getMeetings(MeetingV2GetAllMeetingQueryDto queryCommand); + MeetingV2GetAllMeetingDto getMeetings(MeetingV2GetAllMeetingQueryDto queryCommand); - void deleteMeeting(Integer meetingId, Integer userId); + void deleteMeeting(Integer meetingId, Integer userId); - void updateMeeting(Integer meetingId, MeetingV2CreateMeetingBodyDto requestBody, Integer userId); + void updateMeeting(Integer meetingId, MeetingV2CreateMeetingBodyDto requestBody, Integer userId); - void updateApplyStatus(Integer meetingId, ApplyV2UpdateStatusBodyDto requestBody, Integer userId); + void updateApplyStatus(Integer meetingId, ApplyV2UpdateStatusBodyDto requestBody, Integer userId); - AppliesCsvFileUrlResponseDto getAppliesCsvFileUrl(Integer meetingId, List status, String order, Integer userId); + AppliesCsvFileUrlResponseDto getAppliesCsvFileUrl(Integer meetingId, List status, String order, + Integer userId); - MeetingV2GetMeetingByIdResponseDto getMeetingById(Integer meetingId, Integer userId); + MeetingV2GetMeetingByIdResponseDto getMeetingById(Integer meetingId, Integer userId); } diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java index 8f47392c..27e937c0 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java @@ -14,6 +14,7 @@ import java.util.Arrays; import java.util.Comparator; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.UUID; import java.util.stream.Collectors; @@ -80,7 +81,7 @@ @Transactional(readOnly = true) public class MeetingV2ServiceImpl implements MeetingV2Service { - private final static int ZERO = 0; + private static final int ZERO = 0; private final UserRepository userRepository; private final ApplyRepository applyRepository; @@ -130,33 +131,50 @@ public MeetingV2GetAllMeetingByOrgUserDto getAllMeetingByOrgUser( return MeetingV2GetAllMeetingByOrgUserDto.of(pagedUserJoinedList, pageMetaDto); } + /** + * @Note: 최근 활동 여부는 게시글 생성일자를 기준으로 진행한다. + * */ @Override public List getMeetingBanner() { - return meetingRepository.findAll() + + List meetings = meetingRepository.findTop20ByOrderByIdDesc(); + List meetingIds = meetings.stream().map(Meeting::getId).toList(); + + List posts = postRepository.findAllByMeetingIdIn(meetingIds); + List filterLatestPosts = filterLatestPostsByMeetingId(posts); + Map postMap = filterLatestPosts.stream() + .collect(Collectors.toMap(post -> post.getMeeting().getId(), post -> post)); + + Applies applies = new Applies(applyRepository.findAllByMeetingIdIn(meetingIds)); + + return getResponseDto(meetings, postMap, applies); + } + + + private List filterLatestPostsByMeetingId(List posts) { + return posts.stream() + .collect(Collectors.groupingBy(Post::getMeetingId, + Collectors.maxBy(Comparator.comparing(Post::getCreatedDate)))) + .values() .stream() - .sorted(Comparator.comparing(Meeting::getId).reversed()) - .limit(20) + .flatMap(Optional::stream) + .collect(Collectors.toList()); + } + + private List getResponseDto(List meetings, + Map postMap, Applies applies) { + return meetings.stream() .map(meeting -> { - Optional recentPost = postRepository.findFirstByMeetingIdOrderByIdDesc(meeting.getId()); - Optional recentActivityDate = recentPost.map(Post::getCreatedDate); - - List applies = applyRepository.findAllByMeetingId(meeting.getId()); - Integer applicantCount = applies.size(); - Integer appliedUserCount = applies.stream() - .filter(apply -> apply.getStatus().equals(EnApplyStatus.APPROVE)).toList().size(); - - User meetingLeader = userRepository.findByIdOrThrow(meeting.getUserId()); - MeetingV2GetMeetingBannerResponseUserDto meetingLeaderDto = MeetingV2GetMeetingBannerResponseUserDto - .of(meetingLeader.getId(), meetingLeader.getName(), meetingLeader.getOrgId(), - meetingLeader.getProfileImage()); - - return MeetingV2GetMeetingBannerResponseDto.of(meeting.getId(), meeting.getUserId(), - meeting.getTitle(), meeting.getCategory(), meeting.getImageURL(), - meeting.getMStartDate(), meeting.getMEndDate(), meeting.getStartDate(), - meeting.getEndDate(), - meeting.getCapacity(), recentActivityDate, meeting.getTargetActiveGeneration(), - meeting.getJoinableParts(), applicantCount, appliedUserCount, meetingLeaderDto, - meeting.getMeetingStatus(time.now())); + MeetingV2GetMeetingBannerResponseUserDto meetingCreatorDto = MeetingV2GetMeetingBannerResponseUserDto.of(meeting.getUser()); + + LocalDateTime recentActivityDate = null; + if (postMap.containsKey(meeting.getId())) { + recentActivityDate = postMap.get(meeting.getId()).getCreatedDate(); + } + + return MeetingV2GetMeetingBannerResponseDto.of(meeting, recentActivityDate, + applies.getAppliedCount(meeting.getId()), applies.getApprovedCount(meeting.getId()), + meetingCreatorDto, time.now()); }).toList(); } @@ -335,7 +353,8 @@ public MeetingV2GetMeetingByIdResponseDto getMeetingById(Integer meetingId, Inte Meeting meeting = meetingRepository.findByIdOrThrow(meetingId); User meetingCreator = userRepository.findByIdOrThrow(meeting.getUserId()); - Applies applies = new Applies(applyRepository.findAllByMeetingIdWithUser(meetingId, List.of(WAITING, APPROVE, REJECT), ORDER_ASC)); + Applies applies = new Applies( + applyRepository.findAllByMeetingIdWithUser(meetingId, List.of(WAITING, APPROVE, REJECT), ORDER_ASC)); Boolean isHost = meeting.checkMeetingLeader(user.getId()); Boolean isApply = applies.isApply(meetingId, user.getId()); @@ -346,7 +365,6 @@ public MeetingV2GetMeetingByIdResponseDto getMeetingById(Integer meetingId, Inte .map(apply -> ApplyWholeInfoDto.of(apply, apply.getUser(), userId)) .toList(); - return MeetingV2GetMeetingByIdResponseDto.of(meeting, approvedCount, isHost, isApply, isApproved, meetingCreator, applyWholeInfoDtos, time.now()); } From e4668ee9f796cf569bdb0e0547b314b9aa3e30d1 Mon Sep 17 00:00:00 2001 From: mikekks Date: Fri, 30 Aug 2024 23:13:37 +0900 Subject: [PATCH 47/47] =?UTF-8?q?setting:=20=EB=A6=B4=EB=A6=AC=EC=A6=88=20?= =?UTF-8?q?=EB=85=B8=ED=8A=B8=20=EC=9E=90=EB=8F=99=ED=99=94=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/release-drafter-config.yml | 38 +++++++++++++++++++++++++++ .github/workflows/release-drafter.yml | 14 ++++++++++ 2 files changed, 52 insertions(+) create mode 100644 .github/release-drafter-config.yml create mode 100644 .github/workflows/release-drafter.yml diff --git a/.github/release-drafter-config.yml b/.github/release-drafter-config.yml new file mode 100644 index 00000000..5c7227a1 --- /dev/null +++ b/.github/release-drafter-config.yml @@ -0,0 +1,38 @@ +name-template: 'v$RESOLVED_VERSION' +tag-template: 'v$RESOLVED_VERSION' +categories: + - title: '🎁 새로운 기능이 추가되었어요' + labels: ['🎁 feature'] + - title: '🐞 기존 버그가 수정되었어요' + labels: ['🐞 fix'] + - title: '🐬 코드를 개선했어요' + labels: + - '🛠️ refactor' + - '🧪 test' + - '🪛 chore' + - title: '⚙️ 프로젝트를 개선했어요' + labels: + - '🪄 setting' + - '📚 documentation' + - '🏭 environment' + - title: '🚀 배포' + labels: + - '🚀 deployment' + +change-template: '- $TITLE #$NUMBER @$AUTHOR ' +template: | + ## 이번 버전의 변경사항은 아래와 같아요 + --- + $CHANGES +no-changes-template: '변경사항이 없어요' +version-resolver: + major: + labels: + - '1️⃣ major' + minor: + labels: + - '2️⃣ minor' + patch: + labels: + - '3️⃣ patch' + default: patch \ No newline at end of file diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml new file mode 100644 index 00000000..1153c604 --- /dev/null +++ b/.github/workflows/release-drafter.yml @@ -0,0 +1,14 @@ +name: Release Drafter +on: + push: + branches: + - main +jobs: + update_release_draft: + runs-on: ubuntu-latest + steps: + - uses: release-drafter/release-drafter@v5 + with: + config-name: release-drafter-config.yml + env: + GITHUB_TOKEN: ${{ secrets.ACTION_TOKEN }} \ No newline at end of file