From 14f55917c3fe0a3169e11c4bd3de205cce6754ca Mon Sep 17 00:00:00 2001 From: Kijun Kwon Date: Thu, 22 Aug 2024 00:24:33 +0900 Subject: [PATCH] =?UTF-8?q?[SAMBAD-271]=20=EC=95=8C=EB=A6=BC=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20=EB=B0=8F=20=EC=95=8C=EB=A6=BC?= =?UTF-8?q?=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=ED=85=9C=ED=94=8C=EB=A6=BF=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../moring/common/config/CacheConfig.java | 23 ++++++++ .../logging/ExecutionLoggingAdvice.java | 3 +- .../moring/common/logging/NoLogging.java | 14 +++++ .../EventMessageTemplateRepository.java | 11 ++++ .../event/application/EventRepository.java | 4 ++ .../event/application/EventService.java | 42 ++++++++++++--- .../sambad/moring/event/domain/Event.java | 21 ++++++-- .../event/domain/EventMessageTemplate.java | 43 +++++++++++++++ .../sambad/moring/event/domain/EventType.java | 17 ------ .../event/domain/MapToJsonConverter.java | 41 ++++++++++++++ .../infrastructure/EventJpaRepository.java | 4 ++ .../EventMessageTemplateJpaRepository.java | 12 +++++ .../EventMessageTemplateRepositoryImpl.java | 35 ++++++++++++ .../event/infrastructure/EventProperties.java | 9 ++++ .../infrastructure/EventRepositoryImpl.java | 9 ++++ .../event/presentation/EventController.java | 54 ++++++++++++++----- .../response/EventListResponse.java | 23 ++++++++ .../presentation/response/EventResponse.java | 49 +++++++++++++++++ .../application/HandWavingService.java | 26 ++++++++- src/main/resources/application.yml | 9 +++- 20 files changed, 407 insertions(+), 42 deletions(-) create mode 100644 src/main/java/org/depromeet/sambad/moring/common/config/CacheConfig.java create mode 100644 src/main/java/org/depromeet/sambad/moring/common/logging/NoLogging.java create mode 100644 src/main/java/org/depromeet/sambad/moring/event/application/EventMessageTemplateRepository.java create mode 100644 src/main/java/org/depromeet/sambad/moring/event/domain/EventMessageTemplate.java create mode 100644 src/main/java/org/depromeet/sambad/moring/event/domain/MapToJsonConverter.java create mode 100644 src/main/java/org/depromeet/sambad/moring/event/infrastructure/EventMessageTemplateJpaRepository.java create mode 100644 src/main/java/org/depromeet/sambad/moring/event/infrastructure/EventMessageTemplateRepositoryImpl.java create mode 100644 src/main/java/org/depromeet/sambad/moring/event/infrastructure/EventProperties.java create mode 100644 src/main/java/org/depromeet/sambad/moring/event/presentation/response/EventListResponse.java create mode 100644 src/main/java/org/depromeet/sambad/moring/event/presentation/response/EventResponse.java diff --git a/src/main/java/org/depromeet/sambad/moring/common/config/CacheConfig.java b/src/main/java/org/depromeet/sambad/moring/common/config/CacheConfig.java new file mode 100644 index 00000000..bc33009f --- /dev/null +++ b/src/main/java/org/depromeet/sambad/moring/common/config/CacheConfig.java @@ -0,0 +1,23 @@ +package org.depromeet.sambad.moring.common.config; + +import java.util.List; + +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.concurrent.ConcurrentMapCacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@EnableCaching +@Configuration +public class CacheConfig { + + @Bean + public CacheManager cacheManager() { + ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager(); + cacheManager.setAllowNullValues(false); + cacheManager.setCacheNames(List.of("eventTemplates")); + return cacheManager; + } + +} diff --git a/src/main/java/org/depromeet/sambad/moring/common/logging/ExecutionLoggingAdvice.java b/src/main/java/org/depromeet/sambad/moring/common/logging/ExecutionLoggingAdvice.java index 8910bd32..43dfa2f8 100644 --- a/src/main/java/org/depromeet/sambad/moring/common/logging/ExecutionLoggingAdvice.java +++ b/src/main/java/org/depromeet/sambad/moring/common/logging/ExecutionLoggingAdvice.java @@ -29,7 +29,8 @@ public class ExecutionLoggingAdvice { + "!execution(* org.depromeet.sambad.moring.*.infrastructure.*Properties.*(..)) && " + "!execution(* org.depromeet.sambad.moring.*.*.infrastructure.*Properties.*(..)) && " + "!execution(* org.depromeet.sambad.moring.common..*(..)) && " - + "!execution(* org.depromeet.sambad.moring.*.*.annotation..*(..))" + + "!execution(* org.depromeet.sambad.moring.*.*.annotation..*(..)) && " + + "!@annotation(org.depromeet.sambad.moring.common.logging.NoLogging)" ) private void logPointcut() { } diff --git a/src/main/java/org/depromeet/sambad/moring/common/logging/NoLogging.java b/src/main/java/org/depromeet/sambad/moring/common/logging/NoLogging.java new file mode 100644 index 00000000..313baabd --- /dev/null +++ b/src/main/java/org/depromeet/sambad/moring/common/logging/NoLogging.java @@ -0,0 +1,14 @@ +package org.depromeet.sambad.moring.common.logging; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 메서드에 부착 시, ExecutionLoggingAdvice 내 로깅 대상에서 제외합니다. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface NoLogging { +} diff --git a/src/main/java/org/depromeet/sambad/moring/event/application/EventMessageTemplateRepository.java b/src/main/java/org/depromeet/sambad/moring/event/application/EventMessageTemplateRepository.java new file mode 100644 index 00000000..ea5efc7b --- /dev/null +++ b/src/main/java/org/depromeet/sambad/moring/event/application/EventMessageTemplateRepository.java @@ -0,0 +1,11 @@ +package org.depromeet.sambad.moring.event.application; + +import java.util.Optional; + +import org.depromeet.sambad.moring.event.domain.EventMessageTemplate; +import org.depromeet.sambad.moring.event.domain.EventType; + +public interface EventMessageTemplateRepository { + + Optional findByType(EventType type); +} diff --git a/src/main/java/org/depromeet/sambad/moring/event/application/EventRepository.java b/src/main/java/org/depromeet/sambad/moring/event/application/EventRepository.java index 4d223318..1548ab62 100644 --- a/src/main/java/org/depromeet/sambad/moring/event/application/EventRepository.java +++ b/src/main/java/org/depromeet/sambad/moring/event/application/EventRepository.java @@ -1,5 +1,6 @@ package org.depromeet.sambad.moring.event.application; +import java.time.LocalDateTime; import java.util.List; import java.util.Optional; @@ -19,4 +20,7 @@ Optional findFirstByUserIdAndMeetingIdAndStatusAndType( ); List findByMeetingIdAndStatusAndType(Long meetingId, EventStatus eventStatus, EventType eventType); + + List findByUserIdAndMeetingIdAndCreatedAtAfterOrderByCreatedAtDesc( + Long userId, Long meetingId, LocalDateTime keepDays); } diff --git a/src/main/java/org/depromeet/sambad/moring/event/application/EventService.java b/src/main/java/org/depromeet/sambad/moring/event/application/EventService.java index 8fb959dd..c439a685 100644 --- a/src/main/java/org/depromeet/sambad/moring/event/application/EventService.java +++ b/src/main/java/org/depromeet/sambad/moring/event/application/EventService.java @@ -2,10 +2,13 @@ import static org.depromeet.sambad.moring.event.domain.EventStatus.*; +import java.time.LocalDateTime; import java.util.List; +import java.util.Map; import org.depromeet.sambad.moring.event.domain.Event; import org.depromeet.sambad.moring.event.domain.EventType; +import org.depromeet.sambad.moring.event.infrastructure.EventProperties; import org.depromeet.sambad.moring.event.presentation.excepiton.NotFoundEventException; import org.depromeet.sambad.moring.event.presentation.response.PollingEventListResponse; import org.depromeet.sambad.moring.meeting.member.domain.MeetingMemberValidator; @@ -18,35 +21,53 @@ @Slf4j @Service @RequiredArgsConstructor +@Transactional public class EventService { + private final EventRepository eventRepository; + private final EventMessageTemplateRepository eventMessageTemplateRepository; private final MeetingMemberValidator meetingMemberValidator; - @Transactional + private final EventProperties eventProperties; + public void publish(Long userId, Long meetingId, EventType type) { + this.publish(userId, meetingId, type, Map.of(), Map.of()); + } + + public void publish( + Long userId, Long meetingId, EventType type, Map contentsMap, Map additionalData + ) { if (meetingMemberValidator.isNotUserOfMeeting(userId, meetingId)) { log.warn("User is not member of meeting. userId: {}, meetingId: {}", userId, meetingId); return; } - Event event = Event.publish(userId, meetingId, type); + String message = constructEventMessage(type, contentsMap); + + Event event = Event.publish(userId, meetingId, type, message, additionalData); eventRepository.save(event); } - @Transactional public void inactivate(Long eventId) { Event event = getEventById(eventId); event.inactivate(); eventRepository.save(event); } - @Transactional + @Transactional(readOnly = true) + public List getEvents(Long userId, Long meetingId) { + meetingMemberValidator.validateUserIsMemberOfMeeting(userId, meetingId); + LocalDateTime keepDays = LocalDateTime.now().minusDays(eventProperties.keepDays()); + + return eventRepository.findByUserIdAndMeetingIdAndCreatedAtAfterOrderByCreatedAtDesc( + userId, meetingId, keepDays); + } + public void inactivateLastEventByType(Long userId, Long meetingId, EventType type) { eventRepository.findFirstByUserIdAndMeetingIdAndStatusAndType(userId, meetingId, ACTIVE, type) .ifPresent(Event::inactivate); } - @Transactional public PollingEventListResponse getActiveEvents(Long userId, Long meetingId) { meetingMemberValidator.validateUserIsMemberOfMeeting(userId, meetingId); List events = eventRepository.findByUserIdAndMeetingIdAndStatus(userId, meetingId, ACTIVE); @@ -59,12 +80,21 @@ public PollingEventListResponse getActiveEvents(Long userId, Long meetingId) { return PollingEventListResponse.from(notExpiredEvents); } - @Transactional public void inactivateLastEventsOfAllMemberByType(Long meetingId, EventType eventType) { List events = eventRepository.findByMeetingIdAndStatusAndType(meetingId, ACTIVE, eventType); events.forEach(Event::inactivate); } + private String constructEventMessage(EventType type, Map contentsMap) { + if (contentsMap.isEmpty()) { + return null; + } + + return eventMessageTemplateRepository.findByType(type) + .map(template -> template.replaceTemplateVariables(contentsMap)) + .orElse(null); + } + private Event getEventById(Long eventId) { return eventRepository.findById(eventId) .orElseThrow(NotFoundEventException::new); diff --git a/src/main/java/org/depromeet/sambad/moring/event/domain/Event.java b/src/main/java/org/depromeet/sambad/moring/event/domain/Event.java index 5fa59e31..ea6ae1f7 100644 --- a/src/main/java/org/depromeet/sambad/moring/event/domain/Event.java +++ b/src/main/java/org/depromeet/sambad/moring/event/domain/Event.java @@ -7,10 +7,12 @@ import static org.depromeet.sambad.moring.meeting.question.domain.MeetingQuestion.*; import java.time.LocalDateTime; +import java.util.Map; import org.depromeet.sambad.moring.common.domain.BaseTimeEntity; import jakarta.persistence.Column; +import jakarta.persistence.Convert; import jakarta.persistence.Entity; import jakarta.persistence.Enumerated; import jakarta.persistence.GeneratedValue; @@ -38,19 +40,32 @@ public class Event extends BaseTimeEntity { @Enumerated(STRING) private EventStatus status; + private String message; + private LocalDateTime expiredAt; - private Event(Long userId, Long meetingId, EventType type, EventStatus status, LocalDateTime expiredAt) { + @Column(columnDefinition = "text") + @Convert(converter = MapToJsonConverter.class) + private Map additionalData = Map.of(); + + private Event( + Long userId, Long meetingId, EventType type, EventStatus status, String message, LocalDateTime expiredAt, + Map additionalData + ) { this.userId = userId; this.meetingId = meetingId; this.type = type; this.status = status; + this.message = message; this.expiredAt = expiredAt; + this.additionalData = additionalData; } - public static Event publish(Long userId, Long meetingId, EventType type) { + public static Event publish( + Long userId, Long meetingId, EventType type, String message, Map additionalData + ) { LocalDateTime expiredAt = LocalDateTime.now().plusSeconds(RESPONSE_TIME_LIMIT_SECONDS); - return new Event(userId, meetingId, type, ACTIVE, expiredAt); + return new Event(userId, meetingId, type, ACTIVE, message, expiredAt, additionalData); } public void inactivate() { diff --git a/src/main/java/org/depromeet/sambad/moring/event/domain/EventMessageTemplate.java b/src/main/java/org/depromeet/sambad/moring/event/domain/EventMessageTemplate.java new file mode 100644 index 00000000..912701f8 --- /dev/null +++ b/src/main/java/org/depromeet/sambad/moring/event/domain/EventMessageTemplate.java @@ -0,0 +1,43 @@ +package org.depromeet.sambad.moring.event.domain; + +import static jakarta.persistence.EnumType.*; +import static jakarta.persistence.GenerationType.*; +import static lombok.AccessLevel.*; + +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@NoArgsConstructor(access = PROTECTED) +public class EventMessageTemplate { + + @Id + @GeneratedValue(strategy = IDENTITY) + @Column(name = "event_template_id") + private Long id; + + @Enumerated(STRING) + @Column(columnDefinition = "varchar(50)") + private EventType type; + + private String template; + + public String replaceTemplateVariables(Map contentsMap) { + String message = template; + for (String key : contentsMap.keySet()) { + String regex = Pattern.quote("#{" + key + "}"); + message = message.replaceAll(regex, Matcher.quoteReplacement(contentsMap.get(key))); + } + return message; + } +} diff --git a/src/main/java/org/depromeet/sambad/moring/event/domain/EventType.java b/src/main/java/org/depromeet/sambad/moring/event/domain/EventType.java index 8d444a98..17b155c2 100644 --- a/src/main/java/org/depromeet/sambad/moring/event/domain/EventType.java +++ b/src/main/java/org/depromeet/sambad/moring/event/domain/EventType.java @@ -1,25 +1,8 @@ package org.depromeet.sambad.moring.event.domain; -import java.util.HashSet; -import java.util.Set; - -import org.depromeet.sambad.moring.meeting.member.domain.MeetingMember; -import org.depromeet.sambad.moring.meeting.question.domain.MeetingQuestion; - public enum EventType { QUESTION_REGISTERED, TARGET_MEMBER, HAND_WAVING_REQUESTED, ; - - public static Set of(MeetingQuestion meetingQuestion, MeetingMember loginMember) { - Set eventTypes = new HashSet<>(); - if (meetingQuestion.getQuestion() != null) { - eventTypes.add(EventType.QUESTION_REGISTERED); - } - if (meetingQuestion.getTargetMember().equals(loginMember)) { - eventTypes.add(EventType.TARGET_MEMBER); - } - return eventTypes; - } } diff --git a/src/main/java/org/depromeet/sambad/moring/event/domain/MapToJsonConverter.java b/src/main/java/org/depromeet/sambad/moring/event/domain/MapToJsonConverter.java new file mode 100644 index 00000000..4960587b --- /dev/null +++ b/src/main/java/org/depromeet/sambad/moring/event/domain/MapToJsonConverter.java @@ -0,0 +1,41 @@ +package org.depromeet.sambad.moring.event.domain; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; + +@Converter +public class MapToJsonConverter implements AttributeConverter, String> { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Override + public String convertToDatabaseColumn(Map attribute) { + if (attribute == null) { + return null; + } + try { + return objectMapper.writeValueAsString(attribute); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException("Could not convert map to JSON", e); + } + } + + @Override + public Map convertToEntityAttribute(String dbData) { + if (dbData == null) { + return null; + } + try { + return objectMapper.readValue(dbData, HashMap.class); + } catch (IOException e) { + throw new IllegalArgumentException("Could not convert JSON to map", e); + } + } +} diff --git a/src/main/java/org/depromeet/sambad/moring/event/infrastructure/EventJpaRepository.java b/src/main/java/org/depromeet/sambad/moring/event/infrastructure/EventJpaRepository.java index 93758250..7b99cb98 100644 --- a/src/main/java/org/depromeet/sambad/moring/event/infrastructure/EventJpaRepository.java +++ b/src/main/java/org/depromeet/sambad/moring/event/infrastructure/EventJpaRepository.java @@ -1,5 +1,6 @@ package org.depromeet.sambad.moring.event.infrastructure; +import java.time.LocalDateTime; import java.util.List; import java.util.Optional; @@ -16,4 +17,7 @@ Optional findFirstByUserIdAndMeetingIdAndStatusAndTypeOrderByIdDesc( ); List findByMeetingIdAndStatusAndType(Long meetingId, EventStatus eventStatus, EventType eventType); + + List findByUserIdAndMeetingIdAndCreatedAtAfterOrderByCreatedAtDesc( + Long userId, Long meetingId, LocalDateTime keepDays); } diff --git a/src/main/java/org/depromeet/sambad/moring/event/infrastructure/EventMessageTemplateJpaRepository.java b/src/main/java/org/depromeet/sambad/moring/event/infrastructure/EventMessageTemplateJpaRepository.java new file mode 100644 index 00000000..40fd3cfe --- /dev/null +++ b/src/main/java/org/depromeet/sambad/moring/event/infrastructure/EventMessageTemplateJpaRepository.java @@ -0,0 +1,12 @@ +package org.depromeet.sambad.moring.event.infrastructure; + +import java.util.Optional; + +import org.depromeet.sambad.moring.event.domain.EventMessageTemplate; +import org.depromeet.sambad.moring.event.domain.EventType; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface EventMessageTemplateJpaRepository extends JpaRepository { + + Optional findByType(EventType type); +} diff --git a/src/main/java/org/depromeet/sambad/moring/event/infrastructure/EventMessageTemplateRepositoryImpl.java b/src/main/java/org/depromeet/sambad/moring/event/infrastructure/EventMessageTemplateRepositoryImpl.java new file mode 100644 index 00000000..af024b83 --- /dev/null +++ b/src/main/java/org/depromeet/sambad/moring/event/infrastructure/EventMessageTemplateRepositoryImpl.java @@ -0,0 +1,35 @@ +package org.depromeet.sambad.moring.event.infrastructure; + +import java.util.Optional; + +import org.depromeet.sambad.moring.common.logging.NoLogging; +import org.depromeet.sambad.moring.event.application.EventMessageTemplateRepository; +import org.depromeet.sambad.moring.event.domain.EventMessageTemplate; +import org.depromeet.sambad.moring.event.domain.EventType; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Repository; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RequiredArgsConstructor +@Repository +public class EventMessageTemplateRepositoryImpl implements EventMessageTemplateRepository { + + private final EventMessageTemplateJpaRepository eventMessageTemplateJpaRepository; + + @Override + @Cacheable(value = "eventTemplates", key = "#type", unless = "#result == null") + public Optional findByType(EventType type) { + return eventMessageTemplateJpaRepository.findByType(type); + } + + @NoLogging + @CacheEvict(value = "eventTemplates", allEntries = true) + @Scheduled(fixedRateString = "${caching.spring.event-templates-ttl}") + public void evictAllCaches() { + } +} diff --git a/src/main/java/org/depromeet/sambad/moring/event/infrastructure/EventProperties.java b/src/main/java/org/depromeet/sambad/moring/event/infrastructure/EventProperties.java new file mode 100644 index 00000000..39154f49 --- /dev/null +++ b/src/main/java/org/depromeet/sambad/moring/event/infrastructure/EventProperties.java @@ -0,0 +1,9 @@ +package org.depromeet.sambad.moring.event.infrastructure; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "event") +public record EventProperties( + Long keepDays +) { +} diff --git a/src/main/java/org/depromeet/sambad/moring/event/infrastructure/EventRepositoryImpl.java b/src/main/java/org/depromeet/sambad/moring/event/infrastructure/EventRepositoryImpl.java index 1cf973e1..6c0469c2 100644 --- a/src/main/java/org/depromeet/sambad/moring/event/infrastructure/EventRepositoryImpl.java +++ b/src/main/java/org/depromeet/sambad/moring/event/infrastructure/EventRepositoryImpl.java @@ -1,5 +1,6 @@ package org.depromeet.sambad.moring.event.infrastructure; +import java.time.LocalDateTime; import java.util.List; import java.util.Optional; @@ -39,6 +40,14 @@ public List findByMeetingIdAndStatusAndType(Long meetingId, EventStatus e return eventJpaRepository.findByMeetingIdAndStatusAndType(meetingId, eventStatus, eventType); } + @Override + public List findByUserIdAndMeetingIdAndCreatedAtAfterOrderByCreatedAtDesc( + Long userId, Long meetingId, LocalDateTime keepDays + ) { + return eventJpaRepository.findByUserIdAndMeetingIdAndCreatedAtAfterOrderByCreatedAtDesc( + userId, meetingId, keepDays); + } + @Override public void save(Event event) { eventJpaRepository.save(event); diff --git a/src/main/java/org/depromeet/sambad/moring/event/presentation/EventController.java b/src/main/java/org/depromeet/sambad/moring/event/presentation/EventController.java index 61fca441..65c8be53 100644 --- a/src/main/java/org/depromeet/sambad/moring/event/presentation/EventController.java +++ b/src/main/java/org/depromeet/sambad/moring/event/presentation/EventController.java @@ -1,6 +1,10 @@ package org.depromeet.sambad.moring.event.presentation; +import java.util.List; + import org.depromeet.sambad.moring.event.application.EventService; +import org.depromeet.sambad.moring.event.domain.Event; +import org.depromeet.sambad.moring.event.presentation.response.EventListResponse; import org.depromeet.sambad.moring.event.presentation.response.PollingEventListResponse; import org.depromeet.sambad.moring.user.presentation.resolver.UserId; import org.springframework.http.ResponseEntity; @@ -18,24 +22,37 @@ import jakarta.validation.constraints.Positive; import lombok.RequiredArgsConstructor; -@Tag(name = "Polling 을 위한 이벤트 api", description = "Polling 을 위한 이벤트 api / 담당자 : 이한음") +@Tag(name = "알림 조회 관련 API", description = "알림 관련 CURD 및 모달 폴링 담당 / 담당자 : 이한음, 권기준") @RestController @RequiredArgsConstructor -@RequestMapping("/v1") +@RequestMapping("/v1/events") public class EventController { private final EventService eventService; - @Operation(summary = "이벤트 비활성화", description = "해당 이벤트 알림을 비활성화 합니다.") + @Operation(summary = "알림 목록 조회", description = """ +

Description

+ 모임원이 수령해야 하는 알림 목록을 조회합니다. + +

Event Type List

+ * QUESTION_REGISTERED: 릴레이 질문이 등록되어 답변 가능한 경우 + * TARGET_MEMBER: 릴레이 질문 등록 대상자로 선정된 경우 + * HAND_WAVING_REQUESTED: 손 흔들기 요청이 들어온 경우 + +

Additional Data

+ Event Type에 따라 addionalData가 다르게 반환됩니다. + * HAND_WAVING_REQUESTED: {"handWavingId": 1}""") @ApiResponses({ - @ApiResponse(responseCode = "200", description = "이벤트 비활성화 성공"), - @ApiResponse(responseCode = "404", description = "NOT_FOUND_EVENT") + @ApiResponse(responseCode = "200", description = "알림 목록 조회 성공"), + @ApiResponse(responseCode = "403", description = "USER_NOT_MEMBER_OF_MEETING") }) - @PatchMapping("/events/{eventId}/inactivate") - public ResponseEntity inactivateEvent( - @Parameter(description = "이벤트 ID", example = "1", required = true) @PathVariable("eventId") @Positive Long eventId + @GetMapping("/meetings/{meetingId}") + public ResponseEntity getEvents( + @UserId Long userId, + @Parameter(description = "모임 ID", example = "1", required = true) + @PathVariable("meetingId") @Positive Long meetingId ) { - eventService.inactivate(eventId); - return ResponseEntity.ok().build(); + List events = eventService.getEvents(userId, meetingId); + return ResponseEntity.ok(EventListResponse.from(events)); } @Operation(summary = "사용자가 받아야 하는 이벤트 목록 조회", description = """ @@ -44,13 +61,13 @@ public ResponseEntity inactivateEvent( Event List * QUESTION_REGISTERED: 릴레이 질문이 등록되어 답변 가능한 경우 * TARGET_MEMBER: 릴레이 질문 등록 대상자로 선정된 경우 - """) + """) @ApiResponses({ @ApiResponse(responseCode = "200", description = "사용자가 받아야 하는 이벤트의 목록 조회 성공"), @ApiResponse(responseCode = "204", description = "사용자가 받아야 하는 이벤트 없음"), @ApiResponse(responseCode = "403", description = "USER_NOT_MEMBER_OF_MEETING") }) - @GetMapping("/events/{meetingId}") + @GetMapping(value = {"/meetings/{meetingId}/polling", "/{meetingId}"}) public ResponseEntity getActiveEvents( @UserId Long userId, @Parameter(description = "모임 ID", example = "1", required = true) @PathVariable("meetingId") @Positive Long meetingId @@ -58,4 +75,17 @@ public ResponseEntity getActiveEvents( PollingEventListResponse response = eventService.getActiveEvents(userId, meetingId); return response.toResponseEntity(); } + + @Operation(summary = "이벤트 비활성화", description = "해당 이벤트 알림을 비활성화 합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "이벤트 비활성화 성공"), + @ApiResponse(responseCode = "404", description = "NOT_FOUND_EVENT") + }) + @PatchMapping("/{eventId}/inactivate") + public ResponseEntity inactivateEvent( + @Parameter(description = "이벤트 ID", example = "1", required = true) @PathVariable("eventId") @Positive Long eventId + ) { + eventService.inactivate(eventId); + return ResponseEntity.ok().build(); + } } diff --git a/src/main/java/org/depromeet/sambad/moring/event/presentation/response/EventListResponse.java b/src/main/java/org/depromeet/sambad/moring/event/presentation/response/EventListResponse.java new file mode 100644 index 00000000..7f966b5a --- /dev/null +++ b/src/main/java/org/depromeet/sambad/moring/event/presentation/response/EventListResponse.java @@ -0,0 +1,23 @@ +package org.depromeet.sambad.moring.event.presentation.response; + +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.*; + +import java.util.List; + +import org.depromeet.sambad.moring.event.domain.Event; + +import io.swagger.v3.oas.annotations.media.Schema; + +public record EventListResponse( + @Schema(description = "이벤트 목록", requiredMode = REQUIRED) + List contents +) { + + public static EventListResponse from(List events) { + List contents = events.stream() + .map(EventResponse::from) + .toList(); + + return new EventListResponse(contents); + } +} diff --git a/src/main/java/org/depromeet/sambad/moring/event/presentation/response/EventResponse.java b/src/main/java/org/depromeet/sambad/moring/event/presentation/response/EventResponse.java new file mode 100644 index 00000000..03910ce4 --- /dev/null +++ b/src/main/java/org/depromeet/sambad/moring/event/presentation/response/EventResponse.java @@ -0,0 +1,49 @@ +package org.depromeet.sambad.moring.event.presentation.response; + +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.*; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.depromeet.sambad.moring.event.domain.Event; +import org.depromeet.sambad.moring.event.domain.EventStatus; +import org.depromeet.sambad.moring.event.domain.EventType; + +import io.swagger.v3.oas.annotations.media.Schema; + +public record EventResponse( + @Schema(description = "이벤트 ID", example = "1", requiredMode = REQUIRED) + Long eventId, + + @Schema(description = "이벤트 타입", example = "QUESTION_REGISTERED", requiredMode = REQUIRED) + EventType type, + + @Schema(description = "이벤트 메시지", example = "질문이 등록되었습니다.", requiredMode = NOT_REQUIRED) + List messages, + + @Schema(description = "이벤트 상태", example = "ACTIVE", requiredMode = REQUIRED) + EventStatus status, + + @Schema(description = "이벤트 타입 별로 필요한 추가 데이터", example = "{\"handWavingId\": 9}", requiredMode = NOT_REQUIRED) + Map additionalData, + + @Schema(description = "이벤트 생성 시간 타임 스탬프", example = "1724252079282", requiredMode = REQUIRED) + Long createdAt +) { + public static EventResponse from(Event event) { + List messages = Optional.ofNullable(event.getMessage()) + .map(message -> Arrays.asList(message.split("\n"))) + .orElse(List.of()); + + return new EventResponse( + event.getId(), + event.getType(), + messages, + event.getStatus(), + event.getAdditionalData(), + event.getCreatedAtWithEpochMilli() + ); + } +} diff --git a/src/main/java/org/depromeet/sambad/moring/meeting/handwaving/application/HandWavingService.java b/src/main/java/org/depromeet/sambad/moring/meeting/handwaving/application/HandWavingService.java index 7f50a1fd..23d9e918 100644 --- a/src/main/java/org/depromeet/sambad/moring/meeting/handwaving/application/HandWavingService.java +++ b/src/main/java/org/depromeet/sambad/moring/meeting/handwaving/application/HandWavingService.java @@ -1,6 +1,8 @@ package org.depromeet.sambad.moring.meeting.handwaving.application; -import static org.depromeet.sambad.moring.event.domain.EventType.HAND_WAVING_REQUESTED; +import static org.depromeet.sambad.moring.event.domain.EventType.*; + +import java.util.Map; import org.depromeet.sambad.moring.event.application.EventService; import org.depromeet.sambad.moring.meeting.handwaving.domain.HandWaving; @@ -32,7 +34,8 @@ public void sendHandWaving(Long userId, Long meetingId, HandWavingRequest reques HandWaving handWaving = HandWaving.send(sender, receiver); handWavingRepository.save(handWaving); - eventService.publish(userId, meetingId, HAND_WAVING_REQUESTED); + + publishRequestedEvent(handWaving); } public HandWavingStatusResponse getHandWavingStatus(Long userId, Long meetingId, Long receiverMemberId) { @@ -66,4 +69,23 @@ private HandWaving getHandWavingById(Long handWavingId) { return handWavingRepository.findById(handWavingId) .orElseThrow(NotFoundHandWavingException::new); } + + private void publishRequestedEvent(HandWaving handWaving) { + MeetingMember sender = handWaving.getSender(); + MeetingMember receiver = handWaving.getReceiver(); + + Map contentsMap = Map.of( + "sender", sender.getName(), + "receiver", receiver.getName() + ); + + Map additionalData = Map.of( + "handWavingId", handWaving.getId() + ); + + Long userId = sender.getUser().getId(); + Long meetingId = sender.getMeeting().getId(); + + eventService.publish(userId, meetingId, HAND_WAVING_REQUESTED, contentsMap, additionalData); + } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 059e33e2..801dcefa 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -124,4 +124,11 @@ management: os: enabled: true java: - enabled: true \ No newline at end of file + enabled: true + +caching: + spring: + event-templates-ttl: ${EVENT_TEMPLATES_TTL:1800} + +event: + keep-days: ${EVENT_KEEP_DAYS:7}