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.5.0 #301

Merged
merged 9 commits into from
Feb 9, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public SocialLoginResponse socialLoginMember(IdTokenRequest request, OauthProvid

TokenPairResponse loginResponse = getLoginResponse(member);

return SocialLoginResponse.from(loginResponse);
return SocialLoginResponse.from(member, loginResponse);
}

private Member fetchOrCreate(OidcUser oidcUser) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package com.depromeet.domain.auth.dto.response;

import com.depromeet.domain.member.domain.Member;
import io.swagger.v3.oas.annotations.media.Schema;

public record SocialLoginResponse(
@Schema(description = "멤버 ID", defaultValue = "1") Long memberId,
@Schema(description = "엑세스 토큰", defaultValue = "accessToken") String accessToken,
@Schema(description = "리프레시 토큰", defaultValue = "refreshToken") String refreshToken,
@Schema(description = "게스트 여부", defaultValue = "false") boolean isGuest) {

public static SocialLoginResponse from(TokenPairResponse tokenPairResponse) {
public static SocialLoginResponse from(Member member, TokenPairResponse tokenPairResponse) {
return new SocialLoginResponse(
tokenPairResponse.accessToken(), tokenPairResponse.refreshToken(), false);
member.getId(),
tokenPairResponse.accessToken(),
tokenPairResponse.refreshToken(),
false);
}
}
34 changes: 34 additions & 0 deletions src/main/java/com/depromeet/domain/feed/api/FeedController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.depromeet.domain.feed.api;

import com.depromeet.domain.feed.application.FeedService;
import com.depromeet.domain.feed.dto.response.FeedOneByProfileResponse;
import com.depromeet.domain.feed.dto.response.FeedOneResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Tag(name = "6. [피드]", description = "피드 관련 API입니다.")
@RequestMapping("/feed")
@RequiredArgsConstructor
public class FeedController {

private final FeedService feedService;

@Operation(summary = "피드 탭", description = "피드 탭을 조회합니다.")
@GetMapping("/me")
public List<FeedOneResponse> feedFindAll() {
return feedService.findAllFeed();
}

@Operation(summary = "프로필 피드", description = "피드 탭을 조회합니다.")
@GetMapping("/{memberId}")
public List<FeedOneByProfileResponse> feedFindAllByTargetId(@PathVariable Long memberId) {
return feedService.findAllFeedByTargetId(memberId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.depromeet.domain.feed.application;

import com.depromeet.domain.feed.dto.response.FeedOneByProfileResponse;
import com.depromeet.domain.feed.dto.response.FeedOneResponse;
import com.depromeet.domain.follow.dao.MemberRelationRepository;
import com.depromeet.domain.follow.domain.MemberRelation;
import com.depromeet.domain.member.domain.Member;
import com.depromeet.domain.mission.domain.MissionVisibility;
import com.depromeet.domain.missionRecord.dao.MissionRecordRepository;
import com.depromeet.domain.missionRecord.domain.MissionRecord;
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.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

// TODO: Redis 사용해서 캐싱 작업 필요
@Service
@RequiredArgsConstructor
@Transactional
public class FeedService {
private final MemberUtil memberUtil;
private final MissionRecordRepository missionRecordRepository;
private final MemberRelationRepository memberRelationRepository;
private final SecurityUtil securityUtil;

@Transactional(readOnly = true)
public List<FeedOneResponse> findAllFeed() {
final Member currentMember = memberUtil.getCurrentMember();
List<Member> members =
memberRelationRepository.findAllBySourceId(currentMember.getId()).stream()
.map(MemberRelation::getTarget)
.collect(Collectors.toList());
members.add(currentMember);

return missionRecordRepository.findFeedAll(members);
}

@Transactional(readOnly = true)
public List<FeedOneByProfileResponse> findAllFeedByTargetId(Long targetId) {
final Long sourceId = securityUtil.getCurrentMemberId();

if (isMyFeedRequired(targetId, sourceId)) {
return findFeedByOtherMember(sourceId, targetId);
}
return findFeedByCurrentMember(sourceId);
}

private boolean isMyFeedRequired(Long targetId, Long sourceId) {
return !targetId.equals(sourceId);
}

private List<FeedOneByProfileResponse> findFeedByOtherMember(Long sourceId, Long targetId) {
final Member targetMember = memberUtil.getMemberByMemberId(targetId);

// 팔로우 관계 true: visibility.FOLLOW and ALL, false: visibility.ALL only
boolean isMemberRelationExistsWithMe =
memberRelationRepository.existsBySourceIdAndTargetId(
sourceId, targetMember.getId());
List<MissionVisibility> visibilities =
determineVisibilityConditionsByRelationsWithMe(isMemberRelationExistsWithMe);
List<MissionRecord> feedAllByMemberId =
missionRecordRepository.findFeedAllByMemberId(targetId, visibilities);
return extractFeedResponses(feedAllByMemberId);
}

private List<FeedOneByProfileResponse> findFeedByCurrentMember(Long sourceId) {
List<MissionVisibility> visibilities =
List.of(MissionVisibility.NONE, MissionVisibility.FOLLOWER, MissionVisibility.ALL);
List<MissionRecord> feedAllByMemberId =
missionRecordRepository.findFeedAllByMemberId(sourceId, visibilities);
return extractFeedResponses(feedAllByMemberId);
}

private List<FeedOneByProfileResponse> extractFeedResponses(List<MissionRecord> records) {
return records.stream().map(FeedOneByProfileResponse::of).toList();
}

private List<MissionVisibility> determineVisibilityConditionsByRelationsWithMe(
boolean isMemberRelationExistsWithMe) {
List<MissionVisibility> visibilities = new ArrayList<>();
visibilities.add(MissionVisibility.ALL);

if (isMemberRelationExistsWithMe) {
visibilities.add(MissionVisibility.FOLLOWER);
}
return visibilities;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.depromeet.domain.feed.dto.response;

import com.depromeet.domain.missionRecord.domain.MissionRecord;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.Duration;
import java.time.LocalDateTime;

public record FeedOneByProfileResponse(
@Schema(description = "미션 ID", defaultValue = "1") Long missionId,
@Schema(description = "미션 기록 ID", defaultValue = "1") Long recordId,
@Schema(description = "미션 이름", defaultValue = "default name") String name,
@Schema(
description = "미션 기록 인증 사진 Url",
defaultValue = "https://image.10mm.today/default.png")
String recordImageUrl,
@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 = "미션 기록 시작 시간",
defaultValue = "2023-01-06 00:00:00",
type = "string")
LocalDateTime startedAt,
@JsonFormat(
shape = JsonFormat.Shape.STRING,
pattern = "yyyy-MM-dd HH:mm:ss",
timezone = "Asia/Seoul")
@Schema(
description = "미션 기록 종료 시간",
defaultValue = "2024-01-20 00:34:00",
type = "string")
LocalDateTime finishedAt) {

public static FeedOneByProfileResponse of(MissionRecord record) {
return new FeedOneByProfileResponse(
record.getMission().getId(),
record.getId(),
record.getMission().getName(),
record.getImageUrl(),
record.getDuration().toMinutes(),
Duration.between(record.getStartedAt(), LocalDateTime.now()).toDays() + 1,
record.getStartedAt(),
record.getFinishedAt());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package com.depromeet.domain.feed.dto.response;

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;

public record FeedOneResponse(
@Schema(description = "작성자 ID", defaultValue = "1") Long memberId,
@Schema(description = "작성자 닉네임", defaultValue = "default name") String nickname,
@Schema(description = "작성자 프로필 이미지", defaultValue = "https://image.10mm.today/default.png")
String profileImage,
@Schema(description = "미션 ID", defaultValue = "1") Long missionId,
@Schema(description = "미션 이름", defaultValue = "default name") String name,
@Schema(description = "미션 기록 ID", defaultValue = "1") Long recordId,
@Schema(description = "미션 일지 내용", defaultValue = "default remark") String remark,
@Schema(
description = "미션 기록 인증 사진 Url",
defaultValue = "https://image.10mm.today/default.png")
String recordImageUrl,
@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 = "미션 기록 시작 시간",
defaultValue = "2024-01-06 00:00:00",
type = "string")
LocalDateTime startedAt,
@JsonFormat(
shape = JsonFormat.Shape.STRING,
pattern = "yyyy-MM-dd HH:mm:ss",
timezone = "Asia/Seoul")
@Schema(
description = "미션 기록 종료 시간",
defaultValue = "2024-01-20 00:34:00",
type = "string")
LocalDateTime finishedAt) {
@QueryProjection
public FeedOneResponse(
Long memberId,
String nickname,
String profileImage,
Long missionId,
String name,
Long recordId,
String remark,
String recordImageUrl,
Duration duration,
LocalDateTime startedAt,
LocalDateTime finishedAt) {
this(
memberId,
nickname,
profileImage,
missionId,
name,
recordId,
remark,
recordImageUrl,
duration.toMinutes(),
Duration.between(startedAt, LocalDateTime.now()).toDays() + 1,
startedAt,
finishedAt);
}

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) {
return new FeedOneResponse(
memberId,
nickname,
profileImage,
missionId,
name,
recordId,
remark,
recordImageUrl,
duration.toMinutes(),
Duration.between(startedAt, LocalDateTime.now()).toDays() + 1,
startedAt,
finishedAt);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
import com.depromeet.domain.follow.application.FollowService;
import com.depromeet.domain.follow.dto.request.FollowCreateRequest;
import com.depromeet.domain.follow.dto.request.FollowDeleteRequest;
import com.depromeet.domain.follow.dto.response.FollowFindMeInfoResponse;
import com.depromeet.domain.follow.dto.response.FollowFindTargetInfoResponse;
import com.depromeet.domain.follow.dto.response.FollowListResponse;
import com.depromeet.domain.follow.dto.response.MemberFollowedResponse;
import com.depromeet.domain.follow.dto.response.*;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
Expand Down Expand Up @@ -63,4 +60,10 @@ public List<MemberFollowedResponse> followedUserFindAll() {
public FollowListResponse followList(@PathVariable Long targetId) {
return followService.findFollowList(targetId);
}

@DeleteMapping({"/{targetId}"})
@Operation(summary = "팔로워 삭제", description = "내 팔로워 목록 중 targetId로 팔로워를 삭제합니다.")
public ResponseEntity<FollowerDeletedResponse> followerDelete(@PathVariable Long targetId) {
return ResponseEntity.ok(followService.deleteFollower(targetId));
}
}
Loading
Loading