Skip to content

Commit

Permalink
Merge pull request #115 from Menjil-Menjil/105-feat-following-page
Browse files Browse the repository at this point in the history
105 feat following page - 기능 구현 완료, 테스트 코드 미완성
  • Loading branch information
megymj authored Oct 7, 2023
2 parents 49e058b + d4a6ab8 commit aefb1bd
Show file tree
Hide file tree
Showing 10 changed files with 451 additions and 34 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package seoultech.capstone.menjil.domain.chat.dao;

import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.stereotype.Repository;
Expand All @@ -11,9 +12,21 @@
@Repository
public interface QaListRepository extends MongoRepository<QaList, String> {

@Query(value = "{ 'mentor_nickname' : ?0, 'answer' : { '$ne' : null } }", fields = "{ 'question_summary' : 1 }")
/*
이 메서드에서는, question_summary field 외에 다른 필드는 필요하지 않으므로
쿼리성능 향상을 위해, fields 에서 question_summary 값만 가져오도록 설정하였다.
But in MongoDb, the _id field is included by default even if you don't explicitly specify it.
This is just the standard behavior of MongoDB
*/
@Query(value = "{ 'mentor_nickname' : ?0, 'answer' : { '$ne' : null } }",
fields = "{ 'question_summary' : 1 }")
List<QaList> findAnsweredQuestionsByMentor(String mentorNickname, Pageable pageable);

@Query(value = "{'mentor_nickname' : ?0, 'answer' : { '$ne' : null } }",
fields = "{ 'question_origin' : 1, 'question_summary' : 1, " +
"'answer' : 1, 'answer_time': 1 }")
List<QaList> findQuestionAndAnswerWithMentorNickname(String mentorNickname, Sort sort);

Long countByMentorNicknameAndAnswerIsNotNull(String mentorNickname);

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@
import org.springframework.data.web.PageableDefault;
import org.springframework.http.HttpStatus;
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 org.springframework.web.bind.annotation.*;
import seoultech.capstone.menjil.domain.following.application.FollowingService;
import seoultech.capstone.menjil.domain.following.dto.response.FollowingMentorInfoResponse;
import seoultech.capstone.menjil.domain.following.dto.response.FollowingMentorResponse;
import seoultech.capstone.menjil.global.common.dto.ApiResponse;
import seoultech.capstone.menjil.global.exception.SuccessCode;
Expand All @@ -27,10 +25,19 @@ public class FollowingController {
private final int PAGE_SIZE = 9;

@GetMapping()
public ResponseEntity<ApiResponse<Page<FollowingMentorResponse>>> getFollowMentorsOfUser(@RequestParam("nickname") String nickname, @PageableDefault(size = PAGE_SIZE, sort = {"createdDate"},
public ResponseEntity<ApiResponse<Page<FollowingMentorResponse>>> getAllFollowMentors(@RequestParam("nickname") String nickname, @PageableDefault(size = PAGE_SIZE, sort = {"createdDate"},
direction = Sort.Direction.ASC) Pageable pageable) {
return ResponseEntity.status(HttpStatus.OK)
.body(ApiResponse.success(SuccessCode.GET_FOLLOW_MENTOR_LIST_AVAILABLE,
followingService.getFollowMentorsOfUser(nickname, pageable)));
.body(ApiResponse.success(SuccessCode.GET_ALL_FOLLOW_MENTOR_SUCCESS,
followingService.getAllFollowMentors(nickname, pageable)));
}

@GetMapping("/info")
public ResponseEntity<ApiResponse<FollowingMentorInfoResponse>> getFollowMentorInfo(
@RequestParam("nickname") String nickname,
@RequestParam("followNickname") String followNickname) {
return ResponseEntity.status(HttpStatus.OK)
.body(ApiResponse.success(SuccessCode.GET_FOLLOW_MENTOR_INFO_SUCCESS,
followingService.getFollowMentorInfo(nickname, followNickname)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,25 @@
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import seoultech.capstone.menjil.domain.auth.dao.UserRepository;
import seoultech.capstone.menjil.domain.auth.domain.User;
import seoultech.capstone.menjil.domain.chat.dao.QaListRepository;
import seoultech.capstone.menjil.domain.chat.domain.QaList;
import seoultech.capstone.menjil.domain.follow.dao.FollowRepository;
import seoultech.capstone.menjil.domain.follow.domain.Follow;
import seoultech.capstone.menjil.domain.following.dto.FollowingUserInfo;
import seoultech.capstone.menjil.domain.following.dto.FollowingQaDto;
import seoultech.capstone.menjil.domain.following.dto.FollowingUserDto;
import seoultech.capstone.menjil.domain.following.dto.FollowingUserInfoDto;
import seoultech.capstone.menjil.domain.following.dto.response.FollowingMentorInfoResponse;
import seoultech.capstone.menjil.domain.following.dto.response.FollowingMentorResponse;
import seoultech.capstone.menjil.global.exception.CustomException;
import seoultech.capstone.menjil.global.exception.ErrorCode;
import seoultech.capstone.menjil.global.handler.AwsS3Handler;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Slf4j
@RequiredArgsConstructor
Expand All @@ -41,7 +43,8 @@ public class FollowingService {
@Value("${cloud.aws.s3.bucket}")
private String BUCKET_NAME;

public Page<FollowingMentorResponse> getFollowMentorsOfUser(String nickname, Pageable pageable) {
@Transactional
public Page<FollowingMentorResponse> getAllFollowMentors(String nickname, Pageable pageable) {

// get follows
Page<Follow> page = followRepository.findFollowsByUserNickname(nickname, pageable);
Expand All @@ -51,39 +54,70 @@ public Page<FollowingMentorResponse> getFollowMentorsOfUser(String nickname, Pag
// 1. get follows
User user = userRepository.findUserByNickname(followNickname)
.orElseThrow(() -> new CustomException(ErrorCode.SERVER_ERROR));
FollowingUserInfo followingUserInfo = FollowingUserInfo.fromUserEntity(user);
FollowingUserDto followingUserDto = FollowingUserDto.fromUserEntity(user);

// set AWS S3 presigned url
followingUserInfo.setImgUrl(String.valueOf(awsS3Handler.generatePresignedUrl(
followingUserDto.setImgUrl(String.valueOf(awsS3Handler.generatePresignedUrl(
BUCKET_NAME, user.getImgUrl(), Duration.ofDays(AWS_URL_DURATION))));

// 2. get last answered messages
List<String> lastAnsweredMessages = getLastAnsweredMessages(followNickname);

// 3. get followers count
// TODO: user와 follow를 join하면 쿼리를 두 번 날리지 않아도 될 것으로 생각됨. 추후 JPA 공부한 뒤 적용해볼 것
Long followersCount = followRepository.countByFollowNickname(followNickname);

// 4. get answers count
Long answersCount = qaListRepository.countByMentorNicknameAndAnswerIsNotNull(followNickname);

return FollowingMentorResponse.of(followingUserInfo, lastAnsweredMessages, followersCount, answersCount);
return FollowingMentorResponse.of(followingUserDto, lastAnsweredMessages, followersCount, answersCount);

});
return followMentorInfoResponse;
}

private List<String> getLastAnsweredMessages(String mentorNickname) {
Pageable pageable = PageRequest.of(0, 2,
Sort.by(Sort.Direction.DESC, "question_time", "id")); // Get only the first 2 documents and sort them by 'question_time' and 'id' in descending order
@Transactional
public FollowingMentorInfoResponse getFollowMentorInfo(String nickname, String followNickname) {
User user = userRepository.findUserByNickname(followNickname)
.orElseThrow(() -> new CustomException(ErrorCode.SERVER_ERROR));

// 1. 사용자 정보
FollowingUserInfoDto followingUserInfoDto = FollowingUserInfoDto.fromUserEntity(user);
followingUserInfoDto.setImgUrl(String.valueOf(awsS3Handler.generatePresignedUrl(
BUCKET_NAME, user.getImgUrl(), Duration.ofDays(AWS_URL_DURATION))));

// 2. 작성 답변 개수
Long answersCount = qaListRepository.countByMentorNicknameAndAnswerIsNotNull(followNickname);

// 3. 작성 질문/답변 목록 리스트
Sort sort = Sort.by(Sort.Order.asc("answer_time"));
List<FollowingQaDto> followingQaDtos = qaListRepository.findQuestionAndAnswerWithMentorNickname(followNickname, sort)
.stream()
.map(q -> new FollowingQaDto(q.getQuestionOrigin(), q.getQuestionSummary(),
q.getAnswer(), q.getAnswerTime()))
.collect(Collectors.toList());

// TODO: 4. 추천 답변 개수
// TODO: 5. 멘토링 후기

return FollowingMentorInfoResponse.of(followingUserInfoDto, answersCount, followingQaDtos);
}

protected List<String> getLastAnsweredMessages(String mentorNickname) {
int page = 0;
int size = 2;
// Get only the first 2 documents and sort them by 'question_time' and 'id' in descending order
Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "question_time", "id"));
List<QaList> qaLists = qaListRepository.findAnsweredQuestionsByMentor(mentorNickname, pageable);

if (qaLists.isEmpty()) {
return new ArrayList<>();
} else if (qaLists.size() == 1) {
return Stream.of(qaLists.get(0))
.map(QaList::getQuestionSummary)
.collect(Collectors.toList());
}
return List.of(qaLists.get(0).getQuestionSummary(), qaLists.get(1).getQuestionSummary());
/*
At first, I checked if qaLists is empty(), or size == 1, or else. But
The stream() and map() method calls will produce an empty list if qaLists is empty,
and will otherwise produce a list of question summaries,
making the explicit checks for qaLists.isEmpty() and qaLists.size() == 1 unnecessary.
*/
return qaLists.stream()
.map(QaList::getQuestionSummary)
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package seoultech.capstone.menjil.domain.following.dto;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class FollowingQaDto {

private String questionOrigin;
private String questionSummary;
private String answer;

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
private LocalDateTime answerTime;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class FollowingUserInfo {
public class FollowingUserDto {

private String nickname;
private String company; // 재직 중인 회사
Expand All @@ -22,8 +22,8 @@ public void setImgUrl(String imgUrl) {
this.imgUrl = imgUrl;
}

public static FollowingUserInfo fromUserEntity(User user) {
return new FollowingUserInfo(user.getNickname(), user.getCompany(), user.getField(),
public static FollowingUserDto fromUserEntity(User user) {
return new FollowingUserDto(user.getNickname(), user.getCompany(), user.getField(),
user.getTechStack(), user.getSchool(), user.getMajor(), user.getImgUrl());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package seoultech.capstone.menjil.domain.following.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import seoultech.capstone.menjil.domain.auth.domain.User;

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class FollowingUserInfoDto {

private String nickname;
private String company; // 재직 중인 회사
private String field; // 관심 분야
private String school;
private String major; // 본전공
private String subMajor; // 복수전공

private String minor; // 부전공
private String techStack; // 기술 스택
private String imgUrl;

private String career;
private String certificate;
private String awards;
private String activity;


public void setImgUrl(String imgUrl) {
this.imgUrl = imgUrl;
}

public static FollowingUserInfoDto fromUserEntity(User user) {
return new FollowingUserInfoDto(user.getNickname(), user.getCompany(), user.getField(),
user.getSchool(), user.getMajor(), user.getSubMajor(), user.getMinor(),
user.getTechStack(), user.getImgUrl(),
user.getOptionInfo().getCareer(), user.getOptionInfo().getCertificate(),
user.getOptionInfo().getAwards(), user.getOptionInfo().getActivity()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package seoultech.capstone.menjil.domain.following.dto.response;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import seoultech.capstone.menjil.domain.following.dto.FollowingQaDto;
import seoultech.capstone.menjil.domain.following.dto.FollowingUserInfoDto;

import java.util.List;

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class FollowingMentorInfoResponse {

private FollowingUserInfoDto followingUserInfoDto;
private Long answersCount;
private List<FollowingQaDto> answers;

public static FollowingMentorInfoResponse of(FollowingUserInfoDto userInfo,
Long answersCount, List<FollowingQaDto> answers) {
return new FollowingMentorInfoResponse(userInfo, answersCount, answers);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import seoultech.capstone.menjil.domain.following.dto.FollowingUserInfo;
import seoultech.capstone.menjil.domain.following.dto.FollowingUserDto;

import java.util.List;

Expand All @@ -12,12 +12,12 @@
@AllArgsConstructor
public class FollowingMentorResponse {

private FollowingUserInfo followingUserInfo;
private FollowingUserDto followingUserDto;
private List<String> lastAnsweredMessages; // 가장 최근에 답변한 질문(최대 2개)
private Long followersCount;
private Long answersCount;

public static FollowingMentorResponse of(FollowingUserInfo userInfo, List<String> lastAnsweredMessages,
public static FollowingMentorResponse of(FollowingUserDto userInfo, List<String> lastAnsweredMessages,
Long followersCount, Long answersCount) {
return new FollowingMentorResponse(userInfo, lastAnsweredMessages, followersCount, answersCount);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ public enum SuccessCode {
FOLLOW_CHECK_SUCCESS(HttpStatus.OK.value(), "팔로우 조회에 성공하셨습니다"),

// following
GET_FOLLOW_MENTOR_LIST_AVAILABLE(HttpStatus.OK.value(), "팔로우 멘토 리스트를 불러오는데 성공하였습니다"),

GET_ALL_FOLLOW_MENTOR_SUCCESS(HttpStatus.OK.value(), "팔로우 멘토 리스트를 불러오는데 성공하였습니다"),
GET_FOLLOW_MENTOR_INFO_SUCCESS(HttpStatus.OK.value(), "팔로우 멘토 정보를 불러오는데 성공하였습니다"),

/**
* 201 CREATED
Expand Down
Loading

0 comments on commit aefb1bd

Please sign in to comment.