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

[OING-318] feature: 미션 게시글 등록 기능에 미션 등록 권한 검증 로직 추가 #251

Merged
merged 11 commits into from
May 9, 2024
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
import com.oing.component.SentryGateway;
import com.oing.domain.ErrorReportDTO;
import com.oing.dto.response.ErrorResponse;
import com.oing.exception.TokenNotValidException;
import com.oing.exception.DomainException;
import com.oing.exception.ErrorCode;
import com.oing.exception.*;
import io.jsonwebtoken.ExpiredJwtException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolationException;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ public ArrayResponse<DailyCalendarResponse> getDailyCalendar(String yearMonthDay
List<DailyCalendarResponse> dailyCalendarResponses = new ArrayList<>();
LocalDate date = LocalDate.parse(yearMonthDay, DateTimeFormatter.ISO_DATE);

Collection<PostResponse> survivalPosts = postController.fetchDailyFeeds(1, 10, date, null, "ASC", SURVIVAL, loginMemberId, true).results();
Collection<PostResponse> missionPosts = postController.fetchDailyFeeds(1, 10, date, null, "ASC", MISSION, loginMemberId, true).results();
Collection<PostResponse> survivalPosts = postController.fetchDailyFeeds(1, 10, date, null, "ASC", SURVIVAL, loginMemberId).results();
Collection<PostResponse> missionPosts = postController.fetchDailyFeeds(1, 10, date, null, "ASC", MISSION, loginMemberId).results();
String missionContent = missionBridge.getContentByDate(date);
boolean allFamilyMembersUploaded = getAllFamilyMembersUploaded(survivalPosts, loginFamilyId);

Expand Down
19 changes: 13 additions & 6 deletions gateway/src/main/java/com/oing/controller/MeController.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.oing.dto.response.DefaultResponse;
import com.oing.dto.response.FamilyResponse;
import com.oing.dto.response.MemberResponse;
import com.oing.exception.AlreadyInFamilyException;
import com.oing.exception.FamilyNotFoundException;
import com.oing.restapi.MeApi;
import com.oing.service.FamilyInviteLinkService;
Expand Down Expand Up @@ -46,7 +47,7 @@ public DefaultResponse registerFcmToken(
AddFcmTokenRequest request, String loginMemberId
) {
String token = request.fcmToken();
if(loginMemberId == null || loginMemberId.length() != 26) throw new InvalidParameterException();
if (loginMemberId == null || loginMemberId.length() != 26) throw new InvalidParameterException();

boolean result = memberDeviceService.addDevice(loginMemberId, token);
return new DefaultResponse(result);
Expand All @@ -65,28 +66,34 @@ public DefaultResponse deleteFcmToken(
@Override
public FamilyResponse joinFamily(JoinFamilyRequest request, String loginMemberId) {
log.info("Member {} is trying to join to family", loginMemberId);

FamilyInviteLink link = familyInviteLinkService.retrieveDeepLinkDetails(request.inviteCode());
Family targetFamily = familyService.getFamilyById(link.getFamilyId());

Member member = memberService.getMemberByMemberId(loginMemberId);
// TODO: iOS 업데이트 이슈로 온보딩 플로우에 갖힌 유저를 위해 일시적으로 예외 핸들링 주석 처리 !!!
// if (member.hasFamily()) throw new AlreadyInFamilyException();
if (member.hasFamily()) {
throw new AlreadyInFamilyException();
}

member.setFamilyId(targetFamily.getId());
log.info("Member {} has joined to family {}", loginMemberId, targetFamily.getId());

log.info("Member {} has joined to family {}", loginMemberId, targetFamily.getId());
return FamilyResponse.of(targetFamily);
}

@Transactional
@Override
public FamilyResponse createFamilyAndJoin(String loginMemberId) {
log.info("Member {} is trying to create a family", loginMemberId);

Member member = memberService.getMemberByMemberId(loginMemberId);
// TODO: iOS 업데이트 이슈로 온보딩 플로우에 갖힌 유저를 위해 일시적으로 예외 핸들링 주석 처리 !!!
// if (member.hasFamily()) throw new AlreadyInFamilyException();
if (member.hasFamily()) {
throw new AlreadyInFamilyException();
}

Family family = familyService.createFamily();
member.setFamilyId(family.getId());

log.info("Member {} has created and joined to a family", loginMemberId);
return FamilyResponse.of(family);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public record DaytimePageResponse(
@Schema(description = "날 찌른 목록")
List<MainPagePickerResponse> pickers,

@Schema(description = "미션 해금까지 남은 업로드 횟수 (언제나 2로 모킹됨)", example = "3")
@Schema(description = "미션 해금까지 남은 업로드 횟수", example = "3")
Integer leftUploadCountUntilMissionUnlock,

@Schema(description = "미션 해금 여부", example = "true")
Expand Down
87 changes: 69 additions & 18 deletions gateway/src/test/java/com/oing/restapi/PostApiTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import java.time.LocalDateTime;
import java.time.ZonedDateTime;

import static com.oing.exception.ErrorCode.MISSION_POST_ACCESS_DENIED_FAMILY;
import static com.oing.exception.ErrorCode.MISSION_POST_CREATE_ACCESS_DENIED_MEMBER;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
Expand Down Expand Up @@ -118,26 +120,75 @@ void setUp() {
.andExpect(jsonPath("$.url").exists());
}

@Test
void 게시물_추가_테스트() throws Exception {
//given
CreatePostRequest request = new CreatePostRequest("https://test.com/bucket/images/feed.jpg",
"content", ZonedDateTime.now());
@Nested
class createPost {
@Test
void 생존신고_게시물_추가_테스트() throws Exception {
//given
CreatePostRequest request = new CreatePostRequest("https://test.com/bucket/images/feed.jpg",
"content", ZonedDateTime.now());

//when
ResultActions resultActions = mockMvc.perform(
post("/v1/posts")
.header("X-AUTH-TOKEN", TEST_MEMBER1_TOKEN)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request))
);
//when
ResultActions resultActions = mockMvc.perform(
post("/v1/posts")
.header("X-AUTH-TOKEN", TEST_MEMBER1_TOKEN)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request))
);

//then
resultActions
.andExpect(status().isOk())
.andExpect(jsonPath("$.authorId").value(TEST_MEMBER1_ID))
.andExpect(jsonPath("$.imageUrl").value(request.imageUrl()))
.andExpect(jsonPath("$.content").value(request.content()));
//then
resultActions
.andExpect(status().isOk())
.andExpect(jsonPath("$.authorId").value(TEST_MEMBER1_ID))
.andExpect(jsonPath("$.imageUrl").value(request.imageUrl()))
.andExpect(jsonPath("$.content").value(request.content()));
}

@Test
void 미션_키_획득하지_않았을_때_미션_게시물_추가_예외_테스트() throws Exception {
//given
postRepository.save(new Post(TEST_POST_ID, TEST_MEMBER2_ID, TEST_FAMILY_ID, PostType.SURVIVAL, "img", "img",
"content"));
CreatePostRequest request = new CreatePostRequest("https://test.com/bucket/images/feed.jpg",
"content", ZonedDateTime.now());
PostType type = PostType.MISSION;

//when
ResultActions resultActions = mockMvc.perform(
post("/v1/posts")
.param("type", "MISSION")
.header("X-AUTH-TOKEN", TEST_MEMBER1_TOKEN)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request))
);

//then
resultActions
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.message").value(MISSION_POST_CREATE_ACCESS_DENIED_MEMBER.getMessage()));
}

@Test
void 미션_키는_획득했으나_해당_회원이_생존신고_하지_않았을_때_미션_게시물_추가_예외_테스트() throws Exception {
//given
CreatePostRequest request = new CreatePostRequest("https://test.com/bucket/images/feed.jpg",
"content", ZonedDateTime.now());
PostType type = PostType.MISSION;

//when
ResultActions resultActions = mockMvc.perform(
post("/v1/posts")
.param("type", "MISSION")
.header("X-AUTH-TOKEN", TEST_MEMBER1_TOKEN)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request))
);

//then
resultActions
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.message").value(MISSION_POST_ACCESS_DENIED_FAMILY.getMessage()));
}
}

@Test
Expand Down
26 changes: 9 additions & 17 deletions post/src/main/java/com/oing/controller/PostController.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,8 @@ public PreSignedUrlResponse requestPresignedUrl(PreSignedUrlRequest request, Str

@Override
public PaginationResponse<PostResponse> fetchDailyFeeds(Integer page, Integer size, LocalDate date, String memberId,
String sort, PostType type, String loginMemberId, boolean available) {
String sort, PostType type, String loginMemberId) {
String familyId = memberBridge.getFamilyIdByMemberId(loginMemberId);

// TODO: 미션 게시물 접근 가능한 가족인지 검증하는 로직 필요 (프론트 요청으로 나중에 추가)
if (type == PostType.MISSION) {
validateMissionPostAccessFamily(available);
}
PaginationDTO<Post> fetchResult = postService.searchMemberPost(
page, size, date, memberId, loginMemberId, familyId,
sort == null || sort.equalsIgnoreCase("ASC"), type
Expand All @@ -64,27 +59,24 @@ public PaginationResponse<PostResponse> fetchDailyFeeds(Integer page, Integer si
.map(PostResponse::from);
}

private void validateMissionPostAccessFamily(boolean available) {
if (!available) {
throw new MissionPostAccessDeniedFamilyException();
}
}

@Override
public PostResponse createPost(CreatePostRequest request, PostType type, String loginFamilyId, String loginMemberId, boolean available) {
public PostResponse createPost(CreatePostRequest request, PostType type, String loginFamilyId, String loginMemberId) {
log.info("Member {} is trying to create post", loginMemberId);

// TODO: 미션 게시물 업로드 가능한 사용자인지 검증하는 로직 필요 (프론트 요청으로 나중에 추가)
if (type == PostType.MISSION) {
validateMissionPostCreateAccessMember(available);
validateMissionPostCreateAccessMember(loginFamilyId, loginMemberId);
}
Post savedPost = postService.createMemberPost(request, type, loginMemberId, loginFamilyId);
log.info("Member {} has created post {}", loginMemberId, savedPost.getId());
return PostResponse.from(savedPost);
}

private void validateMissionPostCreateAccessMember(boolean available) {
if (!available) {
private void validateMissionPostCreateAccessMember(String loginFamilyId, String loginMemberId) {
if (!postService.isCreatedSurvivalPostByMajority(loginFamilyId)) {
throw new MissionPostAccessDeniedFamilyException();
}

if (!postService.isCreatedSurvivalPostToday(loginMemberId, loginFamilyId)) {
throw new MissionPostCreateAccessDeniedMemberException();
}
}
Expand Down
12 changes: 2 additions & 10 deletions post/src/main/java/com/oing/restapi/PostApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,7 @@ PaginationResponse<PostResponse> fetchDailyFeeds(

@Parameter(hidden = true)
@LoginMemberId
String loginMemberId,

@RequestParam(required = false, defaultValue = "true")
@Parameter(description = "미션 게시물 조회 가능 여부 조작 필드", example = "true")
boolean available
String loginMemberId
);

@Operation(summary = "게시물 생성", description = "게시물을 생성합니다.")
Expand All @@ -95,11 +91,7 @@ PostResponse createPost(

@Parameter(hidden = true)
@LoginMemberId
String loginMemberId,

@RequestParam(required = false, defaultValue = "true")
@Parameter(description = "미션 게시물 업로드 가능 여부 조작 필드", example = "true")
boolean available
String loginMemberId
);

@Operation(summary = "단일 게시물 조회", description = "ID를 통해 게시물을 조회합니다.")
Expand Down
5 changes: 5 additions & 0 deletions post/src/main/java/com/oing/service/PostService.java
Original file line number Diff line number Diff line change
Expand Up @@ -186,4 +186,9 @@ public long countMonthlyPostByMemberId(LocalDate date, String memberId) {

return postRepository.countMonthlyPostByMemberId(year, month, memberId);
}

public boolean isCreatedSurvivalPostToday(String memberId, String familyId) {
LocalDate today = ZonedDateTime.now().toLocalDate();
return postRepository.existsByMemberIdAndFamilyIdAndTypeAndCreatedAt(memberId, familyId, PostType.SURVIVAL, today);
}
}
20 changes: 14 additions & 6 deletions post/src/test/java/com/oing/service/PostServiceTest.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
package com.oing.service;

import com.oing.domain.Emoji;
import com.oing.domain.Post;
import com.oing.domain.PostType;
import com.oing.domain.Reaction;
import com.oing.dto.request.PostReactionRequest;
import com.oing.repository.PostRepository;
import com.oing.repository.ReactionRepository;
import com.oing.util.IdentityGenerator;
import com.oing.util.PreSignedUrlGenerator;
import org.junit.jupiter.api.Test;
Expand All @@ -17,6 +11,7 @@
import org.springframework.context.ApplicationEventPublisher;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
Expand Down Expand Up @@ -48,4 +43,17 @@ public class PostServiceTest {
//then
assertEquals(0, postService.calculateRemainingSurvivalPostCountUntilMissionUnlocked(familyId));
}

@Test
void 미션_키_획득_여부_조회_테스트() {
//given
String familyId = "1";

//when
when(postRepository.countFamilyMembersByFamilyIdAtYesterday(familyId)).thenReturn(4);
when(postRepository.countTodaySurvivalPostsByFamilyId(familyId)).thenReturn(3);

//then
assertTrue(postService.isCreatedSurvivalPostByMajority(familyId));
}
}
Loading