diff --git a/src/main/java/zipdabang/server/base/Code.java b/src/main/java/zipdabang/server/base/Code.java index 0839cb9..f1a201a 100644 --- a/src/main/java/zipdabang/server/base/Code.java +++ b/src/main/java/zipdabang/server/base/Code.java @@ -90,7 +90,11 @@ public enum Code { ALREADY_BLOCKED_MEMBER(HttpStatus.OK, 4062, "이미 차단된 사용자입니다."), BLOCK_SELF(HttpStatus.OK, 4063, "자신을 차단할 수 없습니다."), + // BAD_REQUEST + TARGET_MEMBER_NOT_FOUND(HttpStatus.OK, 4064,"대상 사용자가 없습니다.."), + //FORBIDDEN + SELF_FOLLOW_FORBIDDEN(HttpStatus.OK, 4065, "스스로 팔로우는 안됩니다."), // market error @@ -115,11 +119,13 @@ public enum Code { //BAD_REQUEST NOT_COMMENT_OWNER(HttpStatus.OK, 4108, "본인이 작성한 댓글이 아닙니다. 변경할 수 없습니다"), //BAD_REQUEST - RECIPE_OWNER(HttpStatus.OK, 4109, "본인의 레시피입니다. 신고/차단할 수 없습니다"), + RECIPE_OWNER(HttpStatus.OK, 4109, "본인의 레시피입니다. 좋아요/스크랩/신고/차단할 수 없습니다"), //BAD_REQUEST - COMMENT_OWNER(HttpStatus.OK, 4110, "본인의 댓글입니다. 신고/차단할 수 없습니다"), + COMMENT_OWNER(HttpStatus.OK, 4110, "본인의 댓글입니다. 좋아요/스크랩/신고/차단할 수 없습니다"), //BAD_REQUEST NO_TEMP_RECIPE_EXIST(HttpStatus.OK, 4111, "해당 임시저장 Id가 존재하지 않습니다."), + + //INTERNAL_SERVER_ERROR INTERNAL_ERROR(HttpStatus.OK, 5000, "Internal server Error"), //INTERNAL_SERVER_ERROR diff --git a/src/main/java/zipdabang/server/converter/MemberConverter.java b/src/main/java/zipdabang/server/converter/MemberConverter.java index 851b2e8..e06484e 100644 --- a/src/main/java/zipdabang/server/converter/MemberConverter.java +++ b/src/main/java/zipdabang/server/converter/MemberConverter.java @@ -337,4 +337,65 @@ public static MemberResponseDto.PagingMemberListDto toPagingMemberListDto(Page followingMembers = owner.getFollowingList().stream() + .map(Follow::getTargetMember).collect(Collectors.toList()); + + return MemberResponseDto.FollowerInfoDto.builder() + .id(member.getMemberId()) + .caption(member.getCaption()) + .nickname(member.getNickname()) + .imageUrl(member.getProfileUrl()) + .isFollowing(followingMembers.contains(member)) + .build(); + } + + public static MemberResponseDto.FollowingListDto toFollowingListDto(Page followList){ + List followInfoDtoList = followList.stream() + .map(follow -> toFollowInfoDto(follow.getTargetMember())).collect(Collectors.toList()); + + return MemberResponseDto.FollowingListDto.builder() + .followingList(followInfoDtoList) + .isFirst(followList.isFirst()) + .isLast(followList.isLast()) + .totalPage(followList.getTotalPages()) + .totalElements(followList.getTotalElements()) + .currentPageElements(followList.getNumberOfElements()) + .build(); + } + + public static MemberResponseDto.FollowerListDto toFollowerListDto(Page followList, Member owner){ + List followerInfoDtoList = followList.stream() + .map(follow -> toFollowerInfoDto(follow.getFollowingMember(), owner)).collect(Collectors.toList()); + + return MemberResponseDto.FollowerListDto.builder() + .followerList(followerInfoDtoList) + .isFirst(followList.isFirst()) + .isLast(followList.isLast()) + .totalPage(followList.getTotalPages()) + .totalElements(followList.getTotalElements()) + .currentPageElements(followList.getNumberOfElements()) + .build(); + } } diff --git a/src/main/java/zipdabang/server/converter/RecipeConverter.java b/src/main/java/zipdabang/server/converter/RecipeConverter.java index bd10027..f04d1d9 100644 --- a/src/main/java/zipdabang/server/converter/RecipeConverter.java +++ b/src/main/java/zipdabang/server/converter/RecipeConverter.java @@ -63,6 +63,28 @@ public class RecipeConverter { private static AmazonS3Manager staticAmazonS3Manager; private static TimeConverter staticTimeConverter; + public static RecipeResponseDto.PerCategoryPreview toPerCategoryPreview(Long categoryId, List recipeList, Member member) { + return RecipeResponseDto.PerCategoryPreview.builder() + .categoryId(categoryId) + .totalElements(recipeList.size()) + .recipeList(recipeList.stream() + .map(recipe -> toRecipePreviewDto(recipe,member)) + .collect(Collectors.toList())) + .build(); + } + + public static RecipeResponseDto.RecipePreviewDto toRecipePreviewDto(Recipe recipe, Member member) { + return RecipeResponseDto.RecipePreviewDto.builder() + .recipeId(recipe.getId()) + .recipeName(recipe.getName()) + .nickname(recipe.getMember().getNickname()) + .likes(recipe.getTotalLike()) + .scraps(recipe.getTotalScrap()) + .isLiked(staticLikesRepository.findByRecipeAndMember(recipe, member).isPresent()) + .isScrapped(staticScrapRepository.findByRecipeAndMember(recipe,member).isPresent()) + .build(); + } + @PostConstruct public void init() { diff --git a/src/main/java/zipdabang/server/domain/member/Follow.java b/src/main/java/zipdabang/server/domain/member/Follow.java index 3d37723..55cbf1e 100644 --- a/src/main/java/zipdabang/server/domain/member/Follow.java +++ b/src/main/java/zipdabang/server/domain/member/Follow.java @@ -20,7 +20,28 @@ public class Follow extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + // follow 객체 @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "target_id") - private Member member; + private Member targetMember; + + // follow 주체 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "following_id") + private Member followingMember; + + public void setTargetMember(Member targetMember){ + if (this.targetMember != null) + targetMember.getFollowerList().remove(this); + this.targetMember = targetMember; + targetMember.getFollowerList().add(this); + } + + public void setFollowingMember(Member followingMember){ + if (this.followingMember != null) + followingMember.getFollowerList().remove(this); + this.followingMember = followingMember; + followingMember.getFollowerList().add(this); + } + } diff --git a/src/main/java/zipdabang/server/domain/member/Member.java b/src/main/java/zipdabang/server/domain/member/Member.java index ec8a7e8..73e7f0a 100644 --- a/src/main/java/zipdabang/server/domain/member/Member.java +++ b/src/main/java/zipdabang/server/domain/member/Member.java @@ -43,6 +43,8 @@ public class Member extends BaseEntity { @Column(length = 18) private String phoneNum; + private String caption; + @Column(length = 6) private String birth; @@ -107,6 +109,13 @@ public class Member extends BaseEntity { @OneToMany(mappedBy = "member", cascade = CascadeType.ALL) private List inqueryList; + // 나를 따르는 = follow 테이블에서 targetMember가 나인, + @OneToMany(mappedBy = "targetMember", cascade = CascadeType.ALL) + private List followerList; + + // 내가 따르는 = follow 테이블에서 followingMember가 나인, + @OneToMany(mappedBy = "followingMember", cascade = CascadeType.ALL) + private List followingList; public void setProfileUrl(String profileUrl) { this.profileUrl = profileUrl; diff --git a/src/main/java/zipdabang/server/repository/memberRepositories/FollowRepository.java b/src/main/java/zipdabang/server/repository/memberRepositories/FollowRepository.java new file mode 100644 index 0000000..c51679e --- /dev/null +++ b/src/main/java/zipdabang/server/repository/memberRepositories/FollowRepository.java @@ -0,0 +1,14 @@ +package zipdabang.server.repository.memberRepositories; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import zipdabang.server.domain.member.Follow; +import zipdabang.server.domain.member.Member; + +public interface FollowRepository extends JpaRepository { + + Page findAllByTargetMember(Member member, PageRequest pageRequest); + Page findAllByFollowingMember(Member member, PageRequest pageRequest); +} diff --git a/src/main/java/zipdabang/server/service/MemberService.java b/src/main/java/zipdabang/server/service/MemberService.java index 290786b..eefaa7d 100644 --- a/src/main/java/zipdabang/server/service/MemberService.java +++ b/src/main/java/zipdabang/server/service/MemberService.java @@ -3,6 +3,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import zipdabang.server.domain.Category; +import zipdabang.server.domain.member.Follow; import zipdabang.server.domain.member.Inquery; import zipdabang.server.domain.enums.DeregisterType; import zipdabang.server.domain.member.Member; @@ -22,6 +23,8 @@ public interface MemberService { Optional checkExistNickname(String nickname); + Optional findMemberById(Long id); + public void existByPhoneNumber(String phoneNum); OAuthJoin.OAuthJoinDto joinInfoComplete(MemberRequestDto.MemberInfoDto request, String type); @@ -56,4 +59,10 @@ public interface MemberService { public void blockMember(Member owner, Long blocked); public void unblockMember(Member owner, Long blockedId); public Page findBlockedMember(Integer page, Member member); + + public Follow createFollow(Long targetId, Member member); + + Page findFollowing(Member member, Integer page); + + Page findFollower(Member member, Integer page); } diff --git a/src/main/java/zipdabang/server/service/RecipeService.java b/src/main/java/zipdabang/server/service/RecipeService.java index bf916bf..5d5fb9e 100644 --- a/src/main/java/zipdabang/server/service/RecipeService.java +++ b/src/main/java/zipdabang/server/service/RecipeService.java @@ -57,4 +57,6 @@ public interface RecipeService { TempRecipe tempCreate(RecipeRequestDto.TempRecipeDto request, MultipartFile thumbnail, List stepImages, Member member) throws IOException; TempRecipe tempUpdate(Long tempId, RecipeRequestDto.TempRecipeDto request, MultipartFile thumbnail, List stepImages, Member member) throws IOException; + + List getTop5RecipePerCategory(Long categoryId); } diff --git a/src/main/java/zipdabang/server/service/serviceImpl/MemberServiceImpl.java b/src/main/java/zipdabang/server/service/serviceImpl/MemberServiceImpl.java index 770296b..ec9ce12 100644 --- a/src/main/java/zipdabang/server/service/serviceImpl/MemberServiceImpl.java +++ b/src/main/java/zipdabang/server/service/serviceImpl/MemberServiceImpl.java @@ -75,6 +75,11 @@ public class MemberServiceImpl implements MemberService { private final DeregisterRepository deregisterRepository; private final DeregisterReasonRepository deregisterReasonRepository; private final BlockedMemberRepository blockedMemberRepository; +<<<<<<< HEAD +======= + + private final FollowRepository followRepository; +>>>>>>> a5dc21e0132e8f3e84329ab4d71729bbff08fef8 @Value("${paging.size}") private Integer pageSize; @@ -120,6 +125,11 @@ public Optional checkExistNickname(String nickname){ return memberRepository.findByNickname(nickname); } + @Override + public Optional findMemberById(Long id) { + return memberRepository.findById(id); + } + @Override public void existByPhoneNumber(String phoneNum) { if (memberRepository.existsByPhoneNum(phoneNum)) { @@ -356,6 +366,40 @@ public Page findBlockedMember(Integer page, Member member) { return blockedMembers; } + + @Override + @Transactional + public Follow createFollow(Long targetId, Member member) { + + if(targetId.equals(member.getMemberId())) + throw new MemberException(Code.SELF_FOLLOW_FORBIDDEN); + Follow follow = MemberConverter.toFollow(); + follow.setFollowingMember(member); + follow.setTargetMember(memberRepository.findById(targetId).get()); + return followRepository.save(follow); + } + + @Override + public Page findFollowing(Member member, Integer page) { + page -= 1; + Page followingMember = followRepository.findAllByFollowingMember(member, PageRequest.of(page, pageSize, Sort.by(Sort.Direction.DESC, "createdAt"))); + + if(followingMember.getTotalPages() <= page) + throw new MemberException(Code.OVER_PAGE_INDEX_ERROR); + + return followingMember; + } + + @Override + public Page findFollower(Member member, Integer page) { + page -= 1; + Page followerMember = followRepository.findAllByTargetMember(member, PageRequest.of(page, pageSize, Sort.by(Sort.Direction.DESC, "createdAt"))); + + if(followerMember.getTotalPages() <= page) + throw new MemberException(Code.OVER_PAGE_INDEX_ERROR); + + return followerMember; + } } diff --git a/src/main/java/zipdabang/server/service/serviceImpl/RecipeServiceImpl.java b/src/main/java/zipdabang/server/service/serviceImpl/RecipeServiceImpl.java index 15c7c40..8ea214a 100644 --- a/src/main/java/zipdabang/server/service/serviceImpl/RecipeServiceImpl.java +++ b/src/main/java/zipdabang/server/service/serviceImpl/RecipeServiceImpl.java @@ -29,6 +29,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; import static zipdabang.server.domain.recipe.QComment.comment; @@ -306,6 +307,9 @@ private List getBlockedMember(Member member) { public Recipe updateLikeOnRecipe(Long recipeId, Member member) { Recipe recipe = recipeRepository.findById(recipeId).orElseThrow(() -> new RecipeException(Code.NO_RECIPE_EXIST)); + if(recipe.getMember() == member) + throw new RecipeException(Code.RECIPE_OWNER); + Optional likesExist = likesRepository.findByRecipeAndMember(recipe,member); if(likesExist.isEmpty()) { @@ -325,6 +329,9 @@ public Recipe updateLikeOnRecipe(Long recipeId, Member member) { public Recipe updateScrapOnRecipe(Long recipeId, Member member) { Recipe recipe = recipeRepository.findById(recipeId).orElseThrow(() -> new RecipeException(Code.NO_RECIPE_EXIST)); + if(recipe.getMember() == member) + throw new RecipeException(Code.RECIPE_OWNER); + Optional scrapExist = scrapRepository.findByRecipeAndMember(recipe,member); if(scrapExist.isEmpty()) { @@ -344,6 +351,27 @@ public List getAllRecipeCategories() { return recipeCategoryRepository.findAll(); } + @Override + public List getTop5RecipePerCategory(Long categoryId) { + QRecipe qRecipe = recipe; + QRecipeCategoryMapping qRecipeCategoryMapping = recipeCategoryMapping; + + AtomicLong index = new AtomicLong(1); + List recipeList = queryFactory + .selectFrom(recipe) + .join(recipe.categoryMappingList, recipeCategoryMapping).fetchJoin() + .where( + recipeCategoryMapping.category.id.eq(categoryId) + ) + .limit(5) + .orderBy(recipe.totalLike.desc(), recipe.createdAt.desc()) + .fetch(); + + log.info(recipeList.toString()); + + return recipeList; + } + @Override public Page recipeListByCategory(Long categoryId, Integer pageIndex, Member member, String order) { diff --git a/src/main/java/zipdabang/server/validation/annotation/ExistMember.java b/src/main/java/zipdabang/server/validation/annotation/ExistMember.java new file mode 100644 index 0000000..7dd28db --- /dev/null +++ b/src/main/java/zipdabang/server/validation/annotation/ExistMember.java @@ -0,0 +1,17 @@ +package zipdabang.server.validation.annotation; + +import zipdabang.server.validation.validator.ExistMemberRequestBodyValidator; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.*; + +@Documented +@Constraint(validatedBy = ExistMemberRequestBodyValidator.class) +@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +public @interface ExistMember { + String message() default "해당하는 유저가 존재하지 않습니다."; + Class[] groups() default {}; + Class[] payload() default {}; +} diff --git a/src/main/java/zipdabang/server/validation/annotation/ExistRecipeCategory.java b/src/main/java/zipdabang/server/validation/annotation/ExistRecipeCategory.java new file mode 100644 index 0000000..7a98b5b --- /dev/null +++ b/src/main/java/zipdabang/server/validation/annotation/ExistRecipeCategory.java @@ -0,0 +1,18 @@ +package zipdabang.server.validation.annotation; + + +import zipdabang.server.validation.validator.ExistRecipeCategoryValidator; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.*; + +@Documented +@Constraint(validatedBy = ExistRecipeCategoryValidator.class) +@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +public @interface ExistRecipeCategory { + String message() default "범위에 없는 categoryId를 전달했습니다."; + Class[] groups() default {}; + Class[] payload() default {}; +} diff --git a/src/main/java/zipdabang/server/validation/validator/ExistMemberRequestBodyValidator.java b/src/main/java/zipdabang/server/validation/validator/ExistMemberRequestBodyValidator.java new file mode 100644 index 0000000..94935da --- /dev/null +++ b/src/main/java/zipdabang/server/validation/validator/ExistMemberRequestBodyValidator.java @@ -0,0 +1,37 @@ +package zipdabang.server.validation.validator; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import zipdabang.server.base.Code; +import zipdabang.server.domain.member.Member; +import zipdabang.server.service.MemberService; +import zipdabang.server.validation.annotation.ExistMember; +import zipdabang.server.web.dto.requestDto.MemberRequestDto; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.util.Optional; + +@Component +@RequiredArgsConstructor +public class ExistMemberRequestBodyValidator implements ConstraintValidator{ + + private final MemberService memberService; + + @Override + public void initialize(ExistMember constraintAnnotation) { + ConstraintValidator.super.initialize(constraintAnnotation); + } + + @Override + public boolean isValid(Long value, ConstraintValidatorContext context) { + + Optional memberById = memberService.findMemberById(value); + if(memberById.isEmpty()) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(Code.TARGET_MEMBER_NOT_FOUND.toString()).addConstraintViolation(); + return false; + } + return true; + } +} diff --git a/src/main/java/zipdabang/server/validation/validator/ExistRecipeCategoryValidator.java b/src/main/java/zipdabang/server/validation/validator/ExistRecipeCategoryValidator.java new file mode 100644 index 0000000..96bf65e --- /dev/null +++ b/src/main/java/zipdabang/server/validation/validator/ExistRecipeCategoryValidator.java @@ -0,0 +1,35 @@ +package zipdabang.server.validation.validator; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import zipdabang.server.base.Code; +import zipdabang.server.domain.recipe.RecipeCategory; +import zipdabang.server.repository.recipeRepositories.RecipeCategoryRepository; +import zipdabang.server.validation.annotation.ExistRecipeCategory; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.util.Optional; + +@Component +@RequiredArgsConstructor +public class ExistRecipeCategoryValidator implements ConstraintValidator { + + private final RecipeCategoryRepository recipeCategoryRepository; + + @Override + public void initialize(ExistRecipeCategory constraintAnnotation) { + ConstraintValidator.super.initialize(constraintAnnotation); + } + + @Override + public boolean isValid(Long value, ConstraintValidatorContext context) { + Optional findRecipeCategory = recipeCategoryRepository.findById(value); + if(findRecipeCategory.isEmpty()){ + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(Code.NO_RECIPE_CATEGORY_EXIST.toString()).addConstraintViolation(); + return false; + } + return true; + } +} diff --git a/src/main/java/zipdabang/server/web/controller/MemberRestController.java b/src/main/java/zipdabang/server/web/controller/MemberRestController.java index 3ea7ff7..5b35d16 100644 --- a/src/main/java/zipdabang/server/web/controller/MemberRestController.java +++ b/src/main/java/zipdabang/server/web/controller/MemberRestController.java @@ -24,9 +24,13 @@ import zipdabang.server.base.Code; import zipdabang.server.base.ResponseDto; import zipdabang.server.base.exception.handler.MemberException; +<<<<<<< HEAD import zipdabang.server.base.exception.handler.RecipeException; +======= +>>>>>>> a5dc21e0132e8f3e84329ab4d71729bbff08fef8 import zipdabang.server.converter.MemberConverter; import zipdabang.server.domain.Category; +import zipdabang.server.domain.member.Follow; import zipdabang.server.domain.member.Inquery; import zipdabang.server.domain.member.Member; import zipdabang.server.redis.domain.RefreshToken; @@ -37,6 +41,7 @@ import zipdabang.server.validation.annotation.CheckPage; import zipdabang.server.validation.annotation.CheckTempMember; import zipdabang.server.validation.annotation.CheckDeregister; +import zipdabang.server.validation.annotation.ExistMember; import zipdabang.server.web.dto.requestDto.MemberRequestDto; import zipdabang.server.web.dto.responseDto.MemberResponseDto; @@ -314,8 +319,8 @@ public ResponseDto tempLogin() { @Parameters({ @Parameter(name = "member", hidden = true), }) - @PostMapping(value = "/members/inquiries",consumes ={ MediaType.MULTIPART_FORM_DATA_VALUE } ) - public ResponseDto createInquery(@CheckTempMember @AuthMember Member member, @ModelAttribute @Valid MemberRequestDto.InqueryDto request){ + @PostMapping(value = "/members/inquiries", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) + public ResponseDto createInquery(@CheckTempMember @AuthMember Member member, @ModelAttribute @Valid MemberRequestDto.InqueryDto request) { Inquery inquery = memberService.createInquery(member, request); return ResponseDto.of(MemberConverter.toMemberInqueryResultDto(inquery)); } @@ -332,11 +337,12 @@ public ResponseDto createInquery(@Chec @ApiResponse(responseCode = "4055", description = "BAD_REQEUST , 페이지 번호가 초과함", content = @Content(schema = @Schema(implementation = ResponseDto.class))), }) - public ResponseDto showInquery(@CheckTempMember @AuthMember Member member, @RequestParam(name = "page",required = true) @CheckPage Integer page){ + public ResponseDto showInquery(@CheckTempMember @AuthMember Member member, @RequestParam(name = "page", required = true) @CheckPage Integer page) { Page inqueryPage = memberService.findInquery(member, page); return ResponseDto.of(MemberConverter.toInqueryListDto(inqueryPage)); } - @Operation(summary = "[figma 더보기 - 회원 탈퇴] 회원 탈퇴 API ✔️🔑", description = "회원 탈퇴 API입니다.
테스트를 위해 임시로 해당 유저의 상세주소를 \"TEST\" 로 설정하면(상세정보 수정 API - zipCode) 탈퇴 불가능한 경우로 처리되도록 해놨습니다.
deregisterTypes 종류
"+ + + @Operation(summary = "[figma 더보기 - 회원 탈퇴] 회원 탈퇴 API ✔️🔑", description = "회원 탈퇴 API입니다.
테스트를 위해 임시로 해당 유저의 상세주소를 \"TEST\" 로 설정하면(상세정보 수정 API - zipCode) 탈퇴 불가능한 경우로 처리되도록 해놨습니다.
deregisterTypes 종류
" + "- NOTHING_TO_BUY(\"사고싶은 물건이 없어요.\"),
" + "- DISINTERESTED(\"앱을 이용하지 않아요.\"),
" + "- UNCOMFORTABLE(\"앱 이용이 불편해요.\"),
" + @@ -410,4 +416,51 @@ else if (page < 1) Page blockedMembers = memberService.findBlockedMember(page, member); return ResponseDto.of(MemberConverter.toPagingMemberListDto(blockedMembers)); } +} + + + @Operation(summary = "🎪팔로우하기 API", description = "팔로우하기 API 입니다.") + @PostMapping("/members/followings/{targetId}") + @Parameters({ + @Parameter(name = "member", hidden = true) + }) + @ApiResponses({ + @ApiResponse(responseCode = "2000", description = "OK 성공"), + @ApiResponse(responseCode = "4064", description = "BAD_REQEUST , 팔로우하려는 대상이 없음", content = @Content(schema = @Schema(implementation = ResponseDto.class))), + @ApiResponse(responseCode = "4065", description = "FORBIDDEN , 스스로는 팔로우가 안됩니다", content = @Content(schema = @Schema(implementation = ResponseDto.class))), + }) + public ResponseDto followMember(@CheckTempMember @AuthMember Member member, @ExistMember @PathVariable(name = "targetId") Long targetId){ + Follow follow = memberService.createFollow(targetId, member); + return ResponseDto.of(MemberConverter.toFollowingResultDto(follow)); + } + + @Operation(summary = "🎪팔로우중인 사용자 조회 API", description = "팔로우중인 사용자 조회 API 입니다. 페이지 주세요") + @GetMapping("/members/followings") + @Parameters({ + @Parameter(name = "member", hidden = true) + }) + @ApiResponses({ + @ApiResponse(responseCode = "2000", description = "OK 성공"), + @ApiResponse(responseCode = "4054", description = "BAD_REQUEST , 페이지 번호가 없거나 0 이하", content = @Content(schema = @Schema(implementation = ResponseDto.class))), + @ApiResponse(responseCode = "4055", description = "BAD_REQUEST , 페이지 번호가 초과함", content = @Content(schema = @Schema(implementation = ResponseDto.class))), + }) + public ResponseDto getFollowingMember(@CheckPage Integer page, @CheckTempMember @AuthMember Member member){ + Page following = memberService.findFollowing(member, page); + return ResponseDto.of(MemberConverter.toFollowingListDto(following)); + } + + @Operation(summary = "🎪나를 팔로잉 하는 사용자 조회 API", description = "나를 팔로잉 하는 사용자 조회 API 입니다. 페이지 주세요") + @GetMapping("/members/followers") + @Parameters({ + @Parameter(name = "member", hidden = true) + }) + @ApiResponses({ + @ApiResponse(responseCode = "2000", description = "OK 성공"), + @ApiResponse(responseCode = "4054", description = "BAD_REQUEST , 페이지 번호가 없거나 0 이하", content = @Content(schema = @Schema(implementation = ResponseDto.class))), + @ApiResponse(responseCode = "4055", description = "BAD_REQUEST , 페이지 번호가 초과함", content = @Content(schema = @Schema(implementation = ResponseDto.class))), + }) + public ResponseDto getFollowerMember(@CheckPage Integer page, @CheckTempMember @AuthMember Member member){ + Page follower = memberService.findFollower(member, page); + return ResponseDto.of(MemberConverter.toFollowerListDto(follower, member)); + } } \ No newline at end of file diff --git a/src/main/java/zipdabang/server/web/controller/RecipeRestController.java b/src/main/java/zipdabang/server/web/controller/RecipeRestController.java index a23489e..ede5bdf 100644 --- a/src/main/java/zipdabang/server/web/controller/RecipeRestController.java +++ b/src/main/java/zipdabang/server/web/controller/RecipeRestController.java @@ -14,7 +14,6 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; -import org.springframework.web.servlet.view.RedirectView; import zipdabang.server.auth.handler.annotation.AuthMember; import zipdabang.server.base.Code; import zipdabang.server.base.ResponseDto; @@ -24,6 +23,7 @@ import zipdabang.server.domain.recipe.*; import zipdabang.server.service.RecipeService; import zipdabang.server.validation.annotation.CheckTempMember; +import zipdabang.server.validation.annotation.ExistRecipeCategory; import zipdabang.server.web.dto.requestDto.RecipeRequestDto; import zipdabang.server.web.dto.responseDto.RecipeResponseDto; @@ -192,10 +192,7 @@ public ResponseDto searchRecipePre @Parameter(name = "keyword", description = "query string 검색할 단어") }) @GetMapping(value = "/members/recipes/search/{categoryId}") - public ResponseDto searchRecipe(@PathVariable Long categoryId, @RequestParam(name = "keyword") String keyword, @RequestParam(name = "pageIndex", required = false) Integer pageIndex, @AuthMember Member member) { - - if (recipeService.checkRecipeCategoryExist(categoryId) == false) - throw new RecipeException(Code.NO_RECIPE_CATEGORY_EXIST); + public ResponseDto searchRecipe(@ExistRecipeCategory @PathVariable Long categoryId, @RequestParam(name = "keyword") String keyword, @RequestParam(name = "pageIndex", required = false) Integer pageIndex, @AuthMember Member member) { if (pageIndex == null) pageIndex = 1; @@ -214,6 +211,31 @@ else if (pageIndex < 1) return ResponseDto.of(RecipeConverter.toPagingRecipeDtoList(recipes, member)); } + @Operation(summary = "🍹figma 레시피2, 레시피 카테고리 별 top5 화면 API 🔑 ✔", description = "레시피 카테고리별 top5 조회 화면 API입니다.") + @ApiResponses({ + @ApiResponse(responseCode = "2000", description = "OK, 목록이 있을 땐 이 응답임"), + @ApiResponse(responseCode = "2100", description = "OK, 목록이 없을 경우", content = @Content(schema = @Schema(implementation = ResponseDto.class))), + @ApiResponse(responseCode = "4003", description = "UNAUTHORIZED, 토큰 모양이 이상함, 토큰 제대로 주세요", content = @Content(schema = @Schema(implementation = ResponseDto.class))), + @ApiResponse(responseCode = "4005", description = "UNAUTHORIZED, 엑세스 토큰 만료, 리프레시 토큰 사용", content = @Content(schema = @Schema(implementation = ResponseDto.class))), + @ApiResponse(responseCode = "4008", description = "UNAUTHORIZED, 토큰 없음, 토큰 줘요", content = @Content(schema = @Schema(implementation = ResponseDto.class))), + @ApiResponse(responseCode = "4052", description = "BAD_REQUEST, 사용자가 없습니다. 이 api에서 이거 생기면 백앤드 개발자 호출", content = @Content(schema = @Schema(implementation = ResponseDto.class))), + @ApiResponse(responseCode = "4105", description = "BAD_REQUEST, 해당 id를 가진 레시피 카테고리가 없습니다. 잘못 보내줬어요", content = @Content(schema = @Schema(implementation = ResponseDto.class))), + + @ApiResponse(responseCode = "5000", description = "SERVER ERROR, 백앤드 개발자에게 알려주세요", content = @Content(schema = @Schema(implementation = ResponseDto.class))), + }) + @Parameters({ + @Parameter(name = "member", hidden = true), + }) + @GetMapping(value = "/members/recipes/categories/{categoryId}/top5") + public ResponseDto RecipeCategoryTop5(@ExistRecipeCategory @PathVariable Long categoryId, @AuthMember Member member) { + + List recipeList = recipeService.getTop5RecipePerCategory(categoryId); + + log.info(recipeList.toString()); + + return ResponseDto.of(RecipeConverter.toPerCategoryPreview(categoryId, recipeList ,member)); + } + @Operation(summary = "🍹figma 레시피2, 카테고리 별 레시피 목록 조회 API 🔑 ✔", description = "카테고리 별 레시피 목록 조회 화면 API입니다. pageIndex로 페이징") @ApiResponses({ @ApiResponse(responseCode = "2000", description = "OK, 목록이 있을 땐 이 응답임"), @@ -235,10 +257,7 @@ else if (pageIndex < 1) @Parameter(name = "order", description = "query string 조회 방식. 인기순: likes, 조회순: views, 최신순: latest로 넘겨주세요, 기본값 latest") }) @GetMapping(value = "/members/recipes/categories/{categoryId}") - public ResponseDto recipeListByCategory(@PathVariable Long categoryId, @RequestParam(name = "order", required = false) String order, @RequestParam(name = "pageIndex", required = false) Integer pageIndex, @AuthMember Member member) { - - if (recipeService.checkRecipeCategoryExist(categoryId) == false) - throw new RecipeException(Code.NO_RECIPE_CATEGORY_EXIST); + public ResponseDto recipeListByCategory(@ExistRecipeCategory @PathVariable Long categoryId, @RequestParam(name = "order", required = false) String order, @RequestParam(name = "pageIndex", required = false) Integer pageIndex, @AuthMember Member member) { if (pageIndex == null) pageIndex = 1; @@ -337,6 +356,7 @@ public ResponseDto recipeWeekBest(@AuthMember M @ApiResponse(responseCode = "4008", description = "UNAUTHORIZED, 토큰 없음, 토큰 줘요", content = @Content(schema = @Schema(implementation = ResponseDto.class))), @ApiResponse(responseCode = "4052", description = "BAD_REQUEST, 사용자가 없습니다. 이 api에서 이거 생기면 백앤드 개발자 호출", content = @Content(schema = @Schema(implementation = ResponseDto.class))), @ApiResponse(responseCode = "4101", description = "BAD_REQUEST, 해당 recipeId를 가진 recipe가 없어요", content = @Content(schema = @Schema(implementation = ResponseDto.class))), + @ApiResponse(responseCode = "4109", description = "BAD_REQUEST, 본인의 레시피입니다. 좋아요/스크랩할 수 없습니다", content = @Content(schema = @Schema(implementation = ResponseDto.class))), @ApiResponse(responseCode = "5000", description = "SERVER ERROR, 백앤드 개발자에게 알려주세요", content = @Content(schema = @Schema(implementation = ResponseDto.class))), }) @Parameters({ @@ -357,6 +377,7 @@ public ResponseDto recipeScrapOrCancel(@PathV @ApiResponse(responseCode = "4008", description = "UNAUTHORIZED, 토큰 없음, 토큰 줘요", content = @Content(schema = @Schema(implementation = ResponseDto.class))), @ApiResponse(responseCode = "4052", description = "BAD_REQUEST, 사용자가 없습니다. 이 api에서 이거 생기면 백앤드 개발자 호출", content = @Content(schema = @Schema(implementation = ResponseDto.class))), @ApiResponse(responseCode = "4101", description = "BAD_REQUEST, 해당 recipeId를 가진 recipe가 없어요", content = @Content(schema = @Schema(implementation = ResponseDto.class))), + @ApiResponse(responseCode = "4109", description = "BAD_REQUEST, 본인의 레시피입니다. 좋아요/스크랩할 수 없습니다", content = @Content(schema = @Schema(implementation = ResponseDto.class))), @ApiResponse(responseCode = "5000", description = "SERVER ERROR, 백앤드 개발자에게 알려주세요", content = @Content(schema = @Schema(implementation = ResponseDto.class))), }) @Parameters({ diff --git a/src/main/java/zipdabang/server/web/dto/requestDto/MemberRequestDto.java b/src/main/java/zipdabang/server/web/dto/requestDto/MemberRequestDto.java index 45f6dac..d43bbef 100644 --- a/src/main/java/zipdabang/server/web/dto/requestDto/MemberRequestDto.java +++ b/src/main/java/zipdabang/server/web/dto/requestDto/MemberRequestDto.java @@ -5,13 +5,13 @@ import javax.persistence.EnumType; import javax.persistence.Enumerated; import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import java.util.List; import org.springframework.lang.Nullable; import org.springframework.web.multipart.MultipartFile; import zipdabang.server.domain.enums.DeregisterType; -import zipdabang.server.web.dto.responseDto.MemberResponseDto; public class MemberRequestDto { diff --git a/src/main/java/zipdabang/server/web/dto/responseDto/MemberResponseDto.java b/src/main/java/zipdabang/server/web/dto/responseDto/MemberResponseDto.java index 4cc6033..1048475 100644 --- a/src/main/java/zipdabang/server/web/dto/responseDto/MemberResponseDto.java +++ b/src/main/java/zipdabang/server/web/dto/responseDto/MemberResponseDto.java @@ -210,4 +210,62 @@ public static class PagingMemberListDto { Boolean isLast; } + @Builder + @Getter + @AllArgsConstructor(access = AccessLevel.PROTECTED) + @NoArgsConstructor(access = AccessLevel.PROTECTED) + public static class FollowingResultDto{ + Long targetId; + LocalDateTime followAt; + } + + @Builder + @Getter + @AllArgsConstructor(access = AccessLevel.PROTECTED) + @NoArgsConstructor(access = AccessLevel.PROTECTED) + public static class FollowInfoDto{ + Long id; + String nickname; + String imageUrl; + String caption; + } + + @Builder + @Getter + @AllArgsConstructor(access = AccessLevel.PROTECTED) + @NoArgsConstructor(access = AccessLevel.PROTECTED) + public static class FollowerInfoDto{ + Long id; + String nickname; + String imageUrl; + String caption; + Boolean isFollowing; + } + + + @Builder + @Getter + @AllArgsConstructor(access = AccessLevel.PROTECTED) + @NoArgsConstructor(access = AccessLevel.PROTECTED) + public static class FollowingListDto{ + List followingList; + Long totalElements; + Integer currentPageElements; + Integer totalPage; + Boolean isFirst; + Boolean isLast; + } + + @Builder + @Getter + @AllArgsConstructor(access = AccessLevel.PROTECTED) + @NoArgsConstructor(access = AccessLevel.PROTECTED) + public static class FollowerListDto{ + List followerList; + Long totalElements; + Integer currentPageElements; + Integer totalPage; + Boolean isFirst; + Boolean isLast; + } } diff --git a/src/main/java/zipdabang/server/web/dto/responseDto/RecipeResponseDto.java b/src/main/java/zipdabang/server/web/dto/responseDto/RecipeResponseDto.java index 19d3d76..0f63822 100644 --- a/src/main/java/zipdabang/server/web/dto/responseDto/RecipeResponseDto.java +++ b/src/main/java/zipdabang/server/web/dto/responseDto/RecipeResponseDto.java @@ -29,6 +29,31 @@ public static class TempRecipeStatusDto{ private String calledAt; } + @Builder + @Getter + @AllArgsConstructor(access = AccessLevel.PROTECTED) + @NoArgsConstructor(access = AccessLevel.PROTECTED) + public static class PerCategoryPreview{ + private List recipeList; + private Long categoryId; + private Integer totalElements; + } + + @Builder + @Getter + @AllArgsConstructor(access = AccessLevel.PROTECTED) + @NoArgsConstructor(access = AccessLevel.PROTECTED) + public static class RecipePreviewDto { + private Long recipeId; + private String recipeName; + private String nickname; + private String thumbnailUrl; + private Long likes; + private Long scraps; + private Boolean isLiked; + private Boolean isScrapped; + } + @Builder @Getter @AllArgsConstructor(access = AccessLevel.PROTECTED) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index e50765b..fbf03ab 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -14,9 +14,11 @@ spring: ## # local redis # redis: # host: localhost - redis: - host: zipdabang-redis.osattk.ng.0001.apn2.cache.amazonaws.com + briefing-redis.bjyb5r.ng.0001.apne1.cache.amazonaws.com + +# redis: +# host: zipdabang-redis.osattk.ng.0001.apn2.cache.amazonaws.com batch: jdbc: initialize-schema: always