Skip to content

Commit

Permalink
Merge pull request #199 from TRIP-Side-Project/dev
Browse files Browse the repository at this point in the history
pr for merge
  • Loading branch information
kwondongwook authored Jan 5, 2024
2 parents 426d172 + 9751f32 commit 1fdd8bd
Show file tree
Hide file tree
Showing 10 changed files with 70 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.springframework.web.client.HttpClientErrorException;

import java.util.List;
import java.util.Set;

@RequiredArgsConstructor
@Component
Expand Down Expand Up @@ -55,8 +56,8 @@ public void updateData()
List<CreateItemRequest> createItemRequests = naverApiService.toCreateItemRequest(shoppingItems);
for (CreateItemRequest createItemRequest : createItemRequests) {
Item item = itemService.createItem(createItemRequest);
notificationService.createNotification(item, createItemRequest.getTagNames());
sseEmitterMap.sendToAll("notification",new SseNotificationResponse(item.getId(), createItemRequest.getTagNames()));
Set<Long> memberIds = notificationService.createNotifications(item, createItemRequest.getTagNames());
sseEmitterMap.send(memberIds,"notification",new SseNotificationResponse(item.getId(), createItemRequest.getTagNames()));
/**
*
* 알림이 가져야할 데이터가 itemId, memberId
Expand Down
37 changes: 20 additions & 17 deletions src/main/java/com/api/trip/common/sse/emitter/SseEmitterMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

Expand All @@ -15,40 +16,42 @@
@Slf4j
public class SseEmitterMap {

private final Map<String, SseEmitter> sseEmitterMap = new ConcurrentHashMap<>();
private final Map<Long, SseEmitter> sseEmitterMap = new ConcurrentHashMap<>();

public void put(String email, SseEmitter sseEmitter) {
sseEmitter.onCompletion(() -> remove(email));
public void put(Long memberId, SseEmitter sseEmitter) {
sseEmitter.onCompletion(() -> remove(memberId));
sseEmitter.onTimeout(sseEmitter::complete);
sseEmitterMap.put(email, sseEmitter);
log.info("connected with {}, the number of connections is {}", email, sseEmitterMap.size());
sseEmitterMap.put(memberId, sseEmitter);
log.info("connected with {}, the number of connections is {}", memberId, sseEmitterMap.size());
}

public void remove(String email) {
sseEmitterMap.remove(email);
log.info("disconnected with {}, the number of connections is {}", email, sseEmitterMap.size());
public void remove(Long memberId) {
sseEmitterMap.remove(memberId);
log.info("disconnected with {}, the number of connections is {}", memberId, sseEmitterMap.size());
}

public void send(String email, String eventName, Object eventData) {
SseEmitter sseEmitter = sseEmitterMap.get(email);
public void send(Long memberId, String eventName, Object eventData) {
SseEmitter sseEmitter = sseEmitterMap.get(memberId);
try {
sseEmitter.send(
event()
.name(eventName)
.data(eventData)
);
} catch (IOException | IllegalStateException e) {
remove(email);
remove(memberId);
}
}

public void sendToAll(String eventName, Object eventData) {
public void send(Collection<Long> memberIds, String eventName, Object eventData) {
SseEventBuilder sseEventBuilder = event().name(eventName).data(eventData);
sseEmitterMap.forEach((email, sseEmitter) -> {
try {
sseEmitter.send(sseEventBuilder);
} catch (IOException | IllegalStateException e) {
remove(email);
sseEmitterMap.forEach((memberId, sseEmitter) -> {
if (memberIds.contains(memberId)) {
try {
sseEmitter.send(sseEventBuilder);
} catch (IOException | IllegalStateException e) {
remove(memberId);
}
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.api.trip.domain.article.controller.dto.*;
import com.api.trip.domain.article.service.ArticleService;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
Expand All @@ -17,32 +18,37 @@ public class ArticleController {

private final ArticleService articleService;

@Operation(summary = "게시글 등록")
@PostMapping
public ResponseEntity<Long> createArticle(@RequestBody @Valid CreateArticleRequest request) {
String email = SecurityContextHolder.getContext().getAuthentication().getName();
return ResponseEntity.ok(articleService.createArticle(request, email));
}

@Operation(summary = "게시글 수정")
@PatchMapping("/{articleId}")
public ResponseEntity<Void> updateArticle(@PathVariable Long articleId, @RequestBody @Valid UpdateArticleRequest request) {
String email = SecurityContextHolder.getContext().getAuthentication().getName();
articleService.updateArticle(articleId, request, email);
return ResponseEntity.ok().build();
}

@Operation(summary = "게시글 삭제")
@DeleteMapping("/{articleId}")
public ResponseEntity<Void> deleteArticle(@PathVariable Long articleId) {
String email = SecurityContextHolder.getContext().getAuthentication().getName();
articleService.deleteArticle(articleId, email);
return ResponseEntity.ok().build();
}

@Operation(summary = "게시글 상세 조회")
@GetMapping("/{articleId}")
public ResponseEntity<ReadArticleResponse> readArticle(@PathVariable Long articleId) {
String email = SecurityContextHolder.getContext().getAuthentication().getName();
return ResponseEntity.ok(articleService.readArticle(articleId, email));
}

@Operation(summary = "게시글 목록 조회 (게시글 검색)")
@GetMapping
public ResponseEntity<GetArticlesResponse> getArticles(
@PageableDefault(size = 8) Pageable pageable,
Expand All @@ -54,6 +60,7 @@ public ResponseEntity<GetArticlesResponse> getArticles(
return ResponseEntity.ok(articleService.getArticles(pageable, sortCode, category, title, tagName));
}

@Operation(summary = "내가 쓴 게시글 목록 조회")
@GetMapping("/me")
public ResponseEntity<GetMyArticlesResponse> getMyArticles() {
String email = SecurityContextHolder.getContext().getAuthentication().getName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public class ReadArticleResponse {
private String writerNickname;
private String writerRole;
private String writerProfileImg;
private String writerIntro;
private List<String> tags;
private String content;
private long viewCount;
Expand All @@ -34,6 +35,7 @@ public static ReadArticleResponse of(Article article, List<ArticleTag> articleTa
.title(article.getTitle())
.writerId(writer.getId())
.writerNickname(writer.getNickname())
.writerIntro(writer.getIntro())
.writerProfileImg(writer.getProfileImg())
.writerRole(writer.getRole().name())
.tags(articleTags.stream().map(articleTag -> articleTag.getTag().getName()).toList())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.api.trip.domain.articlefile.controller;

import com.api.trip.domain.articlefile.service.ArticleFileService;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.context.SecurityContextHolder;
Expand All @@ -16,6 +17,7 @@ public class ArticleFileController {

private final ArticleFileService articleFileService;

@Operation(summary = "게시글 파일(이미지) 업로드")
@PostMapping
public ResponseEntity<String> upload(MultipartFile file) {
String email = SecurityContextHolder.getContext().getAuthentication().getName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.api.trip.domain.comment.controller.dto.GetMyCommentsResponse;
import com.api.trip.domain.comment.controller.dto.UpdateCommentRequest;
import com.api.trip.domain.comment.service.CommentService;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
Expand All @@ -18,31 +19,36 @@ public class CommentController {

private final CommentService commentService;

@Operation(summary = "(대)댓글 등록")
@PostMapping
public ResponseEntity<Long> createComment(@RequestBody @Valid CreateCommentRequest request) {
String email = SecurityContextHolder.getContext().getAuthentication().getName();
return ResponseEntity.ok(commentService.createComment(request, email));
}

@Operation(summary = "(대)댓글 수정")
@PatchMapping("/{commentId}")
public ResponseEntity<Void> updateComment(@PathVariable Long commentId, @RequestBody @Valid UpdateCommentRequest request) {
String email = SecurityContextHolder.getContext().getAuthentication().getName();
commentService.updateComment(commentId, request, email);
return ResponseEntity.ok().build();
}

@Operation(summary = "(대)댓글 삭제")
@DeleteMapping("/{commentId}")
public ResponseEntity<Void> deleteComment(@PathVariable Long commentId) {
String email = SecurityContextHolder.getContext().getAuthentication().getName();
commentService.deleteComment(commentId, email);
return ResponseEntity.ok().build();
}

@Operation(summary = "(대)댓글 목록 조회")
@GetMapping
public ResponseEntity<GetCommentsResponse> getComments(Long articleId) {
return ResponseEntity.ok(commentService.getComments(articleId));
}

@Operation(summary = "내가 쓴 (대)댓글 목록 조회")
@GetMapping("/me")
public ResponseEntity<GetMyCommentsResponse> getMyComments() {
String email = SecurityContextHolder.getContext().getAuthentication().getName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.api.trip.domain.interestarticle.controller.dto.CreateInterestArticleRequest;
import com.api.trip.domain.interestarticle.controller.dto.GetMyInterestArticlesResponse;
import com.api.trip.domain.interestarticle.service.InterestArticleService;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
Expand All @@ -16,19 +17,22 @@ public class InterestArticleController {

private final InterestArticleService interestArticleService;

@Operation(summary = "게시글 좋아요")
@PostMapping
public ResponseEntity<Long> createInterestArticle(@RequestBody @Valid CreateInterestArticleRequest request) {
String email = SecurityContextHolder.getContext().getAuthentication().getName();
return ResponseEntity.ok(interestArticleService.createInterestArticle(request, email));
}

@Operation(summary = "게시글 좋아요 취소")
@DeleteMapping("/{interestArticleId}")
public ResponseEntity<Void> deleteInterestArticle(@PathVariable Long interestArticleId) {
String email = SecurityContextHolder.getContext().getAuthentication().getName();
interestArticleService.deleteInterestArticle(interestArticleId, email);
return ResponseEntity.ok().build();
}

@Operation(summary = "내가 좋아한 게시글 목록 조회")
@GetMapping("/me")
public ResponseEntity<GetMyInterestArticlesResponse> getMyInterestArticles() {
String email = SecurityContextHolder.getContext().getAuthentication().getName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
import com.api.trip.domain.member.model.Member;
import com.api.trip.domain.tag.model.Tag;
import com.api.trip.domain.tag.repository.TagRepository;
import com.api.trip.domain.tag.service.TagService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Service
@Slf4j
Expand Down Expand Up @@ -45,8 +46,8 @@ public List<String> getInterestTag(Member member) {
}

@Transactional(readOnly = true)
public List<Member> getMemberByTags(List<String> tagNames){
public Set<Member> getMembersByTagNames(List<String> tagNames) {
List<InterestTag> interestTags = interestTagRepository.findInterestTagsByTagNames(tagNames);
return interestTags.stream().map(InterestTag::getMember).toList();
return interestTags.stream().map(InterestTag::getMember).collect(Collectors.toSet());
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package com.api.trip.domain.notification.controller;

import com.api.trip.common.exception.CustomException;
import com.api.trip.common.exception.ErrorCode;
import com.api.trip.common.sse.emitter.SseEmitterMap;
import com.api.trip.domain.member.model.Member;
import com.api.trip.domain.member.repository.MemberRepository;
import com.api.trip.domain.notification.controller.dto.DeleteNotificationRequest;
import com.api.trip.domain.notification.controller.dto.GetMyNotificationsResponse;
import com.api.trip.domain.notification.controller.dto.ReadNotificationRequest;
import com.api.trip.domain.notification.service.NotificationService;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
Expand All @@ -19,38 +24,47 @@
@RequiredArgsConstructor
public class NotificationController {

private final MemberRepository memberRepository;
private final NotificationService notificationService;
private final SseEmitterMap sseEmitterMap;

@Operation(summary = "SSE 연결")
@GetMapping(value = "/connect", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public ResponseEntity<SseEmitter> connect() {
String email = SecurityContextHolder.getContext().getAuthentication().getName();
Member member = memberRepository.findByEmail(email)
.orElseThrow(() -> new CustomException(ErrorCode.UNAUTHORIZED));

SseEmitter sseEmitter = new SseEmitter(3600000L);
sseEmitterMap.put(email, sseEmitter);
sseEmitterMap.send(email, "connect", LocalDateTime.now());
sseEmitterMap.put(member.getId(), sseEmitter);
sseEmitterMap.send(member.getId(), "connect", LocalDateTime.now());
return ResponseEntity.ok(sseEmitter);
}

@Operation(summary = "알림 목록 조회")
@GetMapping("/me")
public ResponseEntity<GetMyNotificationsResponse> getMyNotifications() {
String email = SecurityContextHolder.getContext().getAuthentication().getName();
return ResponseEntity.ok(notificationService.getMyNotifications(email));
}

@Operation(summary = "알림 읽음 처리")
@PatchMapping
public ResponseEntity<Void> readNotification(@RequestBody ReadNotificationRequest request) {
String email = SecurityContextHolder.getContext().getAuthentication().getName();
notificationService.readNotification(request, email);
return ResponseEntity.ok().build();
}

@Operation(summary = "알림 삭제")
@DeleteMapping
public ResponseEntity<Void> deleteNotification(@RequestBody DeleteNotificationRequest request) {
String email = SecurityContextHolder.getContext().getAuthentication().getName();
notificationService.deleteNotification(request, email);
return ResponseEntity.ok().build();
}

@Operation(summary = "알림 목록 삭제")
@DeleteMapping("/me")
public ResponseEntity<Void> deleteMyNotifications() {
String email = SecurityContextHolder.getContext().getAuthentication().getName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Service
@Transactional
Expand All @@ -27,8 +29,8 @@ public class NotificationService {
private final NotificationRepository notificationRepository;
private final InterestTagService interestTagService;

public void createNotification(Item item, List<String> tagNames) {
List<Member> receivers = interestTagService.getMemberByTags(tagNames);
public Set<Long> createNotifications(Item item, List<String> tagNames) {
Set<Member> receivers = interestTagService.getMembersByTagNames(tagNames);

receivers.forEach(member -> {
notificationRepository.save(
Expand All @@ -38,6 +40,8 @@ public void createNotification(Item item, List<String> tagNames) {
.build()
);
});

return receivers.stream().map(Member::getId).collect(Collectors.toSet());
}

@Transactional(readOnly = true)
Expand Down

0 comments on commit 1fdd8bd

Please sign in to comment.