Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v1.9.0 #384

Merged
merged 9 commits into from
May 16, 2024
19 changes: 12 additions & 7 deletions src/main/java/com/depromeet/domain/feed/api/FeedController.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.depromeet.domain.feed.api;

import com.depromeet.domain.feed.application.FeedService;
import com.depromeet.domain.feed.domain.FeedVisibility;
import com.depromeet.domain.feed.dto.response.FeedOneByProfileResponse;
import com.depromeet.domain.feed.dto.response.FeedOneResponse;
import com.depromeet.domain.feed.dto.response.FeedSliceResponse;
Expand All @@ -9,6 +10,8 @@
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
Expand All @@ -23,6 +26,7 @@ public class FeedController {

private final FeedService feedService;

@Deprecated
@Operation(summary = "피드 탭", description = "피드 탭을 조회합니다.")
@GetMapping
public List<FeedOneResponse> feedFindAll(
Expand All @@ -36,13 +40,14 @@ public FeedSliceResponse feedFindByPage(
@RequestParam int size,
@RequestParam(required = false) Long lastId,
@RequestParam(value = "visibility", required = false) MissionVisibility visibility) {
if (visibility == MissionVisibility.ALL) {
// 전체 피드 탭
return feedService.findAllFeed(size, lastId);
} else {
// 팔로워 피드 탭
return feedService.findFollowerFeed(size, lastId);
}
return feedService.findFeed(size, lastId, visibility);
}

@Operation(summary = "피드 탭 V2 (페이지네이션)", description = "피드 탭을 조회합니다.")
@GetMapping("/me/v2")
public Slice<FeedOneResponse> feedFindByPageV2(
@RequestParam(required = false) FeedVisibility visibility, Pageable pageable) {
return feedService.findFeedV2(visibility, pageable);
}

@Operation(summary = "프로필 피드", description = "피드 탭을 조회합니다.")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
package com.depromeet.domain.feed.application;

import com.depromeet.domain.feed.domain.FeedVisibility;
import com.depromeet.domain.feed.dto.response.FeedOneByProfileResponse;
import com.depromeet.domain.feed.dto.response.FeedOneResponse;
import com.depromeet.domain.feed.dto.response.FeedSliceResponse;
import com.depromeet.domain.follow.application.FollowService;
import com.depromeet.domain.follow.dao.MemberRelationRepository;
import com.depromeet.domain.follow.domain.MemberRelation;
import com.depromeet.domain.member.dao.MemberRepository;
import com.depromeet.domain.member.domain.Member;
import com.depromeet.domain.mission.dao.MissionRepository;
import com.depromeet.domain.mission.domain.MissionVisibility;
import com.depromeet.domain.missionRecord.dao.MissionRecordRepository;
import com.depromeet.domain.missionRecord.domain.MissionRecord;
import com.depromeet.domain.reaction.application.ReactionService;
import com.depromeet.global.util.MemberUtil;
import com.depromeet.global.util.SecurityUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -25,12 +29,16 @@
@RequiredArgsConstructor
@Transactional
public class FeedService {
private final ReactionService reactionService;
private final MemberUtil memberUtil;
private final MissionRepository missionRepository;
private final MissionRecordRepository missionRecordRepository;
private final MemberRelationRepository memberRelationRepository;
private final SecurityUtil securityUtil;
private final MemberRepository memberRepository;
private final FollowService followService;

@Deprecated
@Transactional(readOnly = true)
public List<FeedOneResponse> findAllFeedByVisibility(MissionVisibility visibilities) {
if (visibilities == MissionVisibility.ALL) {
Expand All @@ -45,8 +53,36 @@ public List<FeedOneResponse> findAllFeedByVisibility(MissionVisibility visibilit
return missionRecordRepository.findFeedAll(sourceMembers);
}

// 전체 피드 탭
@Transactional(readOnly = true)
public FeedSliceResponse findFeed(int size, Long lastId, MissionVisibility visibility) {
if (visibility == MissionVisibility.ALL) {
return findAllFeed(size, lastId);
}
return findFollowerFeed(size, lastId);
}

@Transactional(readOnly = true)
public Slice<FeedOneResponse> findFeedV2(FeedVisibility visibility, Pageable pageable) {
if (visibility == FeedVisibility.ALL) {
return findAllFeedV2(pageable);
}
return findFollowingFeedV2(pageable);
}

private Slice<FeedOneResponse> findAllFeedV2(Pageable pageable) {
return missionRecordRepository.findAllFetch(pageable).map(FeedOneResponse::from);
}

private Slice<FeedOneResponse> findFollowingFeedV2(Pageable pageable) {
final Member currentMember = memberUtil.getCurrentMember();
List<Member> followingMembers = followService.getFollowingMembers(currentMember);

return missionRecordRepository
.findAllFetchByFollowings(pageable, followingMembers)
.map(FeedOneResponse::from);
}

// 전체 피드 탭
public FeedSliceResponse findAllFeed(int size, Long lastId) {
final List<Member> members = memberRepository.findAll();
Slice<FeedOneResponse> feedByVisibilityAndPage =
Expand All @@ -56,7 +92,6 @@ public FeedSliceResponse findAllFeed(int size, Long lastId) {
}

// 팔로워 피드 탭
@Transactional(readOnly = true)
public FeedSliceResponse findFollowerFeed(int size, Long lastId) {
final Member currentMember = memberUtil.getCurrentMember();
List<Member> sourceMembers = getSourceMembers(currentMember.getId());
Expand Down Expand Up @@ -89,7 +124,7 @@ public List<FeedOneByProfileResponse> findAllFeedByTargetId(Long targetId) {
private List<Member> getSourceMembers(Long currentMemberId) {
return memberRelationRepository.findAllBySourceId(currentMemberId).stream()
.map(MemberRelation::getTarget)
.collect(Collectors.toList());
.toList();
}

private boolean isMyFeedRequired(Long targetId, Long sourceId) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.depromeet.domain.feed.domain;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum FeedVisibility {
ALL("전체 피드"),
FOLLOWING("팔로잉 피드"),
;

private final String value;
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
package com.depromeet.domain.feed.dto.response;

import static com.depromeet.domain.reaction.dto.response.ReactionGroupByEmojiResponse.*;
import static java.util.Comparator.*;

import com.depromeet.domain.comment.dto.response.CommentDto;
import com.depromeet.domain.member.domain.Member;
import com.depromeet.domain.mission.domain.Mission;
import com.depromeet.domain.missionRecord.domain.MissionRecord;
import com.depromeet.domain.reaction.dto.response.ReactionGroupByEmojiResponse;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.querydsl.core.annotations.QueryProjection;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;

public record FeedOneResponse(
@Schema(description = "작성자 ID", defaultValue = "1") Long memberId,
Expand All @@ -20,14 +29,14 @@ public record FeedOneResponse(
description = "미션 기록 인증 사진 Url",
defaultValue = "https://image.10mm.today/default.png")
String recordImageUrl,
@Schema(description = "미션 수행한 시간", defaultValue = "21") long duration,
@Schema(description = "미션 수행 시간 (분 단위)", defaultValue = "21") long duration,
@Schema(description = "미션 시작한 지 N일차", defaultValue = "3") long sinceDay,
@JsonFormat(
shape = JsonFormat.Shape.STRING,
pattern = "yyyy-MM-dd HH:mm:ss",
timezone = "Asia/Seoul")
@Schema(
description = "미션 기록 시작 시간",
description = "미션 시작 일시",
defaultValue = "2024-01-06 00:00:00",
type = "string")
LocalDateTime startedAt,
Expand All @@ -36,7 +45,7 @@ public record FeedOneResponse(
pattern = "yyyy-MM-dd HH:mm:ss",
timezone = "Asia/Seoul")
@Schema(
description = "미션 기록 종료 시간",
description = "미션 종료 일시",
defaultValue = "2024-01-20 00:34:00",
type = "string")
LocalDateTime finishedAt,
Expand All @@ -45,10 +54,12 @@ public record FeedOneResponse(
pattern = "yyyy-MM-dd HH:mm:ss",
timezone = "Asia/Seoul")
@Schema(
description = "미션 기록 시작 시간",
description = "미션 기록 시작 일시",
defaultValue = "2024-01-06 00:00:00",
type = "string")
LocalDateTime recordStartedAt) {
LocalDateTime recordStartedAt,
@Schema(description = "리액션 타입별 그룹") List<ReactionGroupByEmojiResponse> reactions,
@Schema(description = "댓글 목록") List<CommentDto> comments) {
@QueryProjection
public FeedOneResponse(
Long memberId,
Expand All @@ -73,38 +84,44 @@ public FeedOneResponse(
remark,
recordImageUrl,
duration.toMinutes(),
ChronoUnit.DAYS.between(startedAt, recordStartedAt) + 1,
calculateSinceDay(startedAt, recordStartedAt),
startedAt,
finishedAt,
recordStartedAt);
recordStartedAt,
null,
null);
}

public static FeedOneResponse of(
Long memberId,
String nickname,
String profileImage,
Long missionId,
String name,
Long recordId,
String remark,
String recordImageUrl,
Duration duration,
LocalDateTime startedAt,
LocalDateTime finishedAt,
LocalDateTime recordStartedAt) {
// TODO: 다른 DTO에 존재하는 sinceDay 중복 계산 로직 제거
private static long calculateSinceDay(LocalDateTime startedAt, LocalDateTime recordStartedAt) {
return ChronoUnit.DAYS.between(startedAt, recordStartedAt) + 1;
}

public static FeedOneResponse from(MissionRecord missionRecord) {
Mission mission = missionRecord.getMission();
Member member = mission.getMember();

List<ReactionGroupByEmojiResponse> reactions =
groupByEmojiType(missionRecord.getReactions());

List<CommentDto> comments =
missionRecord.getComments().stream().map(CommentDto::from).toList();

return new FeedOneResponse(
memberId,
nickname,
profileImage,
missionId,
name,
recordId,
remark,
recordImageUrl,
duration.toMinutes(),
Duration.between(startedAt, LocalDateTime.now()).toDays() + 1,
startedAt,
finishedAt,
recordStartedAt);
member.getId(),
member.getProfile().getNickname(),
member.getProfile().getProfileImageUrl(),
mission.getId(),
mission.getName(),
missionRecord.getId(),
missionRecord.getRemark(),
missionRecord.getImageUrl(),
missionRecord.getDuration().toMinutes(),
calculateSinceDay(mission.getStartedAt(), missionRecord.getStartedAt()),
mission.getStartedAt(),
mission.getFinishedAt(),
missionRecord.getStartedAt(),
reactions,
comments);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,18 @@ public FollowerDeletedResponse deleteFollower(Long targetId) {
: FollowerDeletedResponse.from(FollowStatus.NOT_FOLLOWING);
}

/**
* 특정 멤버가 팔로우 중인 멤버 목록을 조회합니다.
*
* @param source 특정 멤버
* @return 팔로우 중인 멤버 목록
*/
public List<Member> getFollowingMembers(Member source) {
return memberRelationRepository.findAllBySource(source).stream()
.map(MemberRelation::getTarget)
.toList();
}

private static void getFollowStatusIncludeList(
List<Member> targetMembers,
List<MemberRelation> currentMemberSources,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,6 @@ public interface MemberRelationRepository
List<MemberRelation> findAllBySourceIdAndTargetIn(Long sourceId, List<Member> targetIds);

List<MemberRelation> findAllByTargetId(Long targetId);

List<MemberRelation> findAllBySource(Member source);
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import com.depromeet.global.error.exception.ErrorCode;
import com.depromeet.global.util.MemberUtil;
import com.depromeet.global.util.SpringEnvironmentUtil;
import com.depromeet.infra.config.storage.StorageProperties;
import com.depromeet.infra.config.s3.S3Properties;
import java.util.Date;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
Expand All @@ -38,7 +38,7 @@
public class ImageService {
private final MemberUtil memberUtil;
private final SpringEnvironmentUtil springEnvironmentUtil;
private final StorageProperties storageProperties;
private final S3Properties s3Properties;
private final AmazonS3 amazonS3;
private final MissionRecordRepository missionRecordRepository;
private final MissionRecordTtlRepository missionRecordTtlRepository;
Expand All @@ -62,7 +62,7 @@ public PresignedUrlResponse createMissionRecordPresignedUrl(
request.imageFileExtension());
GeneratePresignedUrlRequest generatePresignedUrlRequest =
createGeneratePreSignedUrlRequest(
storageProperties.bucket(),
s3Properties.bucket(),
fileName,
request.imageFileExtension().getUploadExtension());

Expand Down Expand Up @@ -113,7 +113,7 @@ public PresignedUrlResponse createMemberProfilePresignedUrl(
request.imageFileExtension());
GeneratePresignedUrlRequest generatePresignedUrlRequest =
createGeneratePreSignedUrlRequest(
storageProperties.bucket(),
s3Properties.bucket(),
fileName,
request.imageFileExtension().getUploadExtension());

Expand Down Expand Up @@ -205,9 +205,9 @@ private String createUploadImageUrl(
Long targetId,
String imageKey,
ImageFileExtension imageFileExtension) {
return storageProperties.endpoint()
return s3Properties.endpoint()
+ "/"
+ storageProperties.bucket()
+ s3Properties.bucket()
+ "/"
+ springEnvironmentUtil.getCurrentProfile()
+ "/"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import static com.depromeet.domain.missionRecord.domain.QMissionRecord.*;

import com.depromeet.domain.member.domain.Member;
import com.depromeet.domain.mission.domain.DurationStatus;
import com.depromeet.domain.missionRecord.domain.ImageUploadStatus;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.time.LocalDateTime;
Expand Down Expand Up @@ -32,6 +33,7 @@ public List<Member> findMissionNonCompletedMembers(LocalDateTime today) {
missionRecord
.isNull()
.or(missionRecord.uploadStatus.ne(ImageUploadStatus.COMPLETE)),
mission.durationStatus.eq(DurationStatus.IN_PROGRESS),
member.fcmInfo.fcmToken.isNotNull(),
mission.startedAt.loe(today),
mission.finishedAt.goe(today))
Expand Down
Loading
Loading