Skip to content

Commit

Permalink
feat(#41): heachi notify
Browse files Browse the repository at this point in the history
feat(#41): heachi notify
  • Loading branch information
ghdcksgml1 authored Sep 14, 2023
2 parents a5dd2fa + 224c757 commit 85d16ad
Show file tree
Hide file tree
Showing 17 changed files with 334 additions and 210 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,4 @@
@RequiredArgsConstructor
public class JpaConfig {

private final ReactiveMongoTemplate reactiveMongoTemplate;

// notify Collection Setting
@Bean
public void mongoDBinit() {
// Collection 초기 세팅을 위해 Notify 객체를 생성했다가 지움
reactiveMongoTemplate.insert(Notify.builder().build())
.flatMap(notify -> reactiveMongoTemplate.remove(notify)
.then(reactiveMongoTemplate.executeCommand("{ convertToCapped: 'notify', size: 8192 }"))
.then(reactiveMongoTemplate.executeCommand("{ collStats: 'notify' }"))
.doOnNext(stats -> System.out.println("stats = " + stats))
)
.subscribe();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import lombok.Builder;
import lombok.Getter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

Expand All @@ -13,6 +14,7 @@
@Getter
@ToString
@Document(collection = "notify")
@Slf4j
public class Notify {
@Id
private String id;
Expand Down Expand Up @@ -41,7 +43,18 @@ private Notify(String sendUserId, List<String> receiveUserIds, NotifyType type,
this.checked = checked;
}

public void receiverUserCheckedNotify() {
public void receiverUserCheckedNotify(String receiverUserId) {
// receiver에 해당 사용자가 있는지 확인한다.
this.receiveUserIds.stream()
.filter(id -> id.equals(receiverUserId))
.findFirst()
.orElseThrow(() -> {
log.error(">>>> ReceiverIds에 해당 사용자가 존재하지 않습니다 : {}", receiverUserId);
throw new IllegalArgumentException();
});

// 있다면, 체크표시
checked.add(receiverUserId);
checkedTime.put(receiverUserId, LocalDateTime.now());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@
import reactor.core.publisher.Flux;

@Repository
public interface NotifyRepository extends ReactiveMongoRepository<Notify, String> {
public interface NotifyRepository extends ReactiveMongoRepository<Notify, String>, NotifyRepositoryCustom {

@Tailable
@Query("{ receiveUserIds : ?0 }")
public Flux<Notify> findByReceiveUserIds(String userIds);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.heachi.mongo.define.notify.repository;

import com.heachi.mongo.define.notify.Notify;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

public interface NotifyRepositoryCustom {

/**
* userId가 받는 알림을 페이징한다. (ReceiveUserIds In userId)
*/
public Flux<Notify> findNotifyByReceiveUserIdsPaging(String userId, int page);

/**
* notifyId로 notify를 검색하는데, userId가 recevierUserIds에 존재하는지
*/
public Mono<Notify> findNotifyByIdWhereReceiveUserIdsIn(String userId, String notifyId);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.heachi.mongo.define.notify.repository;

import com.heachi.mongo.define.notify.Notify;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Component
@RequiredArgsConstructor
public class NotifyRepositoryImpl implements NotifyRepositoryCustom {

private final ReactiveMongoTemplate mongoTemplate;

@Override
public Flux<Notify> findNotifyByReceiveUserIdsPaging(String userId, int page) {
return Mono.just(PageRequest.of(page, 10, Sort.by("createdTime").descending()))
.map(pageable -> {
Query query = new Query()
.with(pageable);
query.addCriteria(Criteria.where("receiveUserIds").in(userId));

return query;
}
).flatMapMany(query -> mongoTemplate.find(query, Notify.class, "notify"));
}

@Override
public Mono<Notify> findNotifyByIdWhereReceiveUserIdsIn(String userId, String notifyId) {
return Mono.just(new Query().addCriteria(Criteria.where("id").is(notifyId)
.and("receiveUserIds").in(userId)))
.flatMap(query -> mongoTemplate.findOne(query, Notify.class, "notify"));
}


}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,5 @@
@SpringBootTest
class JpaConfigTest extends TestConfig {

@Autowired
private ReactiveMongoTemplate reactiveMongoTemplate;

@Test
@DisplayName("Spring Applicatoin이 띄워질때, MongoDB capped 설정이 true가 된다.")
void mongoDBConfigurationInitialized() {
// when
reactiveMongoTemplate.executeCommand("{ collStats: 'notify' }")
// then
.as(StepVerifier::create)
.expectNextMatches(document -> {
assertThat(document.get("capped")).isEqualTo(true);
assertThat(document.get("totalSize")).isEqualTo(8192);

return true;
})
.verifyComplete();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@

import com.heachi.mongo.TestConfig;
import com.heachi.mongo.define.notify.Notify;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import reactor.test.StepVerifier;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
Expand All @@ -26,9 +26,10 @@ class NotifyRepositoryTest extends TestConfig {

@AfterEach
void tearDown() {
notifyRepository.deleteAll();
notifyRepository.deleteAll().subscribe();
}


@Test
@DisplayName("Notify 객체를 만들어 저장할 수 있다.")
void saveTest() {
Expand All @@ -50,18 +51,70 @@ void saveTest() {
}

@Test
@Disabled
@DisplayName("ReceiveUserIds 리스트에 ReceiveUserId가 존재하면 해당 Notify를 반환한다.")
void findByReceiveUserIds() {
@DisplayName("ReceiveUserId를 넣고, 페이지를 넣으면 해당 페이지의 Notify가 10개가 나온다.")
void selectNotifyWhereReceiveUserIdLimit10Pagination() {
// given
List<Notify> list = new ArrayList<>();
for (int i=0; i<10; i++) {
Notify notify = Notify.builder()
.sendUserId("ghdcksgml")
.receiveUserIds(List.of("ghdcksgml1"))
.createdTime(LocalDateTime.now())
.build();
list.add(notify);
}


Flux<Notify> flux1 = notifyRepository.saveAll(list);
Flux<Notify> flux2 = notifyRepository.findNotifyByReceiveUserIdsPaging("ghdcksgml1", 0);


// when
StepVerifier.create(Flux.concat(flux1, flux2).log())
// then
.expectSubscription()
.expectNextCount(10)
.expectNextCount(10)
.verifyComplete();
}

@Test
@DisplayName("notifyId가 일치하고, receiverUserIds에 나의 요청하는 아이디가 있다면, Notify를 가져온다.")
void selectNotifyWhereNotifyIdAndReceiverUserIdsIn() {
// given
Notify notify = Notify.builder()
.sendUserId("홍찬희")
.receiveUserIds(List.of("ghdcksgml1", "ghdcksgml2", "ghdcksgml3"))
.sendUserId("ghdcksgml")
.receiveUserIds(List.of("ghdcksgml1"))
.createdTime(LocalDateTime.now())
.build();

Notify savedNotify = notifyRepository.save(notify).block();

// when
Mono<Notify> save = notifyRepository.save(notify);
Flux<Notify> ghdcksgml1 = notifyRepository.findByReceiveUserIds("ghdcksgml1");
assertThat(ghdcksgml1.collectList().block()).isEqualTo(notify.getSendUserId());
StepVerifier.create(notifyRepository.findNotifyByIdWhereReceiveUserIdsIn("ghdcksgml1", savedNotify.getId()))
// then
.expectSubscription()
.expectNextCount(1)
.verifyComplete();
}

@Test
@DisplayName("notifyId가 일치하지만, receiverUserIds에 요청하는 아이디가 존재하지 않는다면, 권한이 없으므로 아무것도 리턴되지 않는다.")
void selectNotifyWhereNotifyIdAndReceiverUserIdsInNotMatching() {
// given
Notify notify = Notify.builder()
.sendUserId("ghdcksgml")
.receiveUserIds(List.of("ghdcksgml1"))
.createdTime(LocalDateTime.now())
.build();

Notify savedNotify = notifyRepository.save(notify).block();

// when
StepVerifier.create(notifyRepository.findNotifyByIdWhereReceiveUserIdsIn("ghdcksgml", savedNotify.getId()))
// then
.expectSubscription()
.expectNextCount(0)
.verifyComplete();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.heachi.notify;

import com.heachi.admin.common.utils.DateDistance;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.reactive.config.EnableWebFlux;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,19 @@
package com.heachi.notify.api.controller;

import com.heachi.admin.common.exception.ExceptionMessage;
import com.heachi.admin.common.exception.oauth.OAuthException;
import com.heachi.admin.common.exception.notify.NotifyException;
import com.heachi.admin.common.response.JsonResult;
import com.heachi.external.clients.auth.AuthClients;
import com.heachi.notify.api.controller.request.NotifyRegistRequest;
import com.heachi.notify.api.service.auth.AuthService;
import com.heachi.notify.api.service.notify.request.NotifyServiceRegistRequest;
import com.heachi.notify.api.service.notify.NotifyService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.reactive.function.client.WebClientRequestException;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import java.net.ConnectException;
import java.time.Duration;

@RestController
@RequestMapping("/notify")
@RequiredArgsConstructor
Expand All @@ -27,22 +22,52 @@ public class NotifyController {
private final NotifyService notifyService;
private final AuthService authService;

/**
* 알림 받기
*/
@GetMapping(value = "/", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<JsonResult> receive(@RequestHeader(value = "Authorization", required = false, defaultValue = "token") String headers) {
public Flux<JsonResult> receive(
@RequestHeader(value = "Authorization", required = false, defaultValue = "token") String headers,
@RequestParam(name = "page", defaultValue = "0") int page) {

return authService.getUserId(headers)
.flatMapMany(sendUserId -> notifyService.receive(sendUserId))
.flatMapMany(sendUserId -> notifyService.receive(sendUserId, page))
.subscribeOn(Schedulers.boundedElastic()); // publisher의 스케줄러를 boundedElastic으로 변경
}

/**
* 알림 추가하기
*/
@PostMapping("/")
public Mono<JsonResult> registNotify(
@RequestHeader(value = "Authorization", required = false, defaultValue = "token") String headers,
@RequestBody NotifyRegistRequest request) {

return authService.getUserId(headers)
.flatMap(sendUserId ->
request.getReceiveUserIds().stream().filter(receiverId -> receiverId.equals(sendUserId))
.findFirst()
.map(id -> Mono.error(new NotifyException(ExceptionMessage.NOTIFY_DUPLICATE_ID)))
.orElseGet(() -> Mono.just(sendUserId))
)
.flatMap(sendUserId -> notifyService
.registNotify(NotifyServiceRegistRequest.of(request, sendUserId))
.registNotify(NotifyServiceRegistRequest.of(request, sendUserId.toString()))
.thenReturn(JsonResult.successOf()))
.subscribeOn(Schedulers.boundedElastic()); // publisher의 스케줄러를 boundedElastic으로 변경
}

/**
* 사용자 알림 읽기 이벤트
*/
@GetMapping("/read/{notifyId}")
public Mono<JsonResult> readNotify(
@RequestHeader(value = "Authorization", required = false, defaultValue = "token") String headers,
@PathVariable("notifyId") String notifyId) {

return authService.getUserId(headers)
.flatMap(userId -> notifyService.readNotify(userId, notifyId))
.onErrorMap(throwable -> new NotifyException(ExceptionMessage.NOTIFY_NOT_FOUND))
.map(notify -> JsonResult.successOf())
.subscribeOn(Schedulers.boundedElastic());
}
}
Loading

0 comments on commit 85d16ad

Please sign in to comment.