Skip to content

Commit

Permalink
[SAMBAD-249] 질문 활성 및 비활성화 체크 스케줄러 추가, 질문 조회 로직 리팩토링
Browse files Browse the repository at this point in the history
  • Loading branch information
kkjsw17 committed Aug 15, 2024
1 parent 5b43698 commit 39704c8
Show file tree
Hide file tree
Showing 15 changed files with 218 additions and 141 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.depromeet.sambad.moring.common.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;

@Configuration
@EnableScheduling
public class SchedulingConfig {
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@ public interface EventRepository {
Optional<Event> findFirstByUserIdAndMeetingIdAndStatusAndType(
Long userId, Long meetingId, EventStatus eventStatus, EventType type
);

List<Event> findByMeetingIdAndStatusAndType(Long meetingId, EventStatus eventStatus, EventType eventType);
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package org.depromeet.sambad.moring.event.application;

import static org.depromeet.sambad.moring.event.domain.EventStatus.*;

import java.util.List;

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 org.depromeet.sambad.moring.event.presentation.excepiton.NotFoundEventException;
import org.depromeet.sambad.moring.event.presentation.response.PollingEventListResponse;
Expand All @@ -12,7 +13,9 @@
import org.springframework.transaction.annotation.Transactional;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
@RequiredArgsConstructor
public class EventService {
Expand All @@ -21,7 +24,11 @@ public class EventService {

@Transactional
public void publish(Long userId, Long meetingId, EventType type) {
meetingMemberValidator.validateUserIsMemberOfMeeting(userId, meetingId);
if (meetingMemberValidator.isNotMemberOfMeeting(userId, meetingId)) {
log.warn("User is not member of meeting. userId: {}, meetingId: {}", userId, meetingId);
return;
}

Event event = Event.publish(userId, meetingId, type);
eventRepository.save(event);
}
Expand All @@ -35,14 +42,14 @@ public void inactivate(Long eventId) {

@Transactional
public void inactivateLastEventByType(Long userId, Long meetingId, EventType type) {
eventRepository.findFirstByUserIdAndMeetingIdAndStatusAndType(userId, meetingId, EventStatus.ACTIVE, type)
eventRepository.findFirstByUserIdAndMeetingIdAndStatusAndType(userId, meetingId, ACTIVE, type)
.ifPresent(Event::inactivate);
}

@Transactional
public PollingEventListResponse getActiveEvents(Long userId, Long meetingId) {
meetingMemberValidator.validateUserIsMemberOfMeeting(userId, meetingId);
List<Event> events = eventRepository.findByUserIdAndMeetingIdAndStatus(userId, meetingId, EventStatus.ACTIVE);
List<Event> events = eventRepository.findByUserIdAndMeetingIdAndStatus(userId, meetingId, ACTIVE);
events.forEach(Event::inactivateIfExpired);

List<Event> notExpiredEvents = events.stream()
Expand All @@ -52,6 +59,12 @@ public PollingEventListResponse getActiveEvents(Long userId, Long meetingId) {
return PollingEventListResponse.from(notExpiredEvents);
}

@Transactional
public void inactivateLastEventsOfAllMemberByType(Long meetingId, EventType eventType) {
List<Event> events = eventRepository.findByMeetingIdAndStatusAndType(meetingId, ACTIVE, eventType);
events.forEach(Event::inactivate);
}

private Event getEventById(Long eventId) {
return eventRepository.findById(eventId)
.orElseThrow(NotFoundEventException::new);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package org.depromeet.sambad.moring.event.domain;

import static jakarta.persistence.EnumType.STRING;
import static jakarta.persistence.GenerationType.IDENTITY;
import static lombok.AccessLevel.PROTECTED;
import static org.depromeet.sambad.moring.event.domain.EventStatus.ACTIVE;
import static org.depromeet.sambad.moring.event.domain.EventStatus.INACTIVE;
import static jakarta.persistence.EnumType.*;
import static jakarta.persistence.GenerationType.*;
import static lombok.AccessLevel.*;
import static org.depromeet.sambad.moring.event.domain.EventStatus.*;
import static org.depromeet.sambad.moring.meeting.question.domain.MeetingQuestion.*;

import java.time.LocalDateTime;
Expand Down Expand Up @@ -50,7 +49,7 @@ private Event(Long userId, Long meetingId, EventType type, EventStatus status, L
}

public static Event publish(Long userId, Long meetingId, EventType type) {
LocalDateTime expiredAt = LocalDateTime.now().plusHours(RESPONSE_TIME_LIMIT_HOURS);
LocalDateTime expiredAt = LocalDateTime.now().plusSeconds(RESPONSE_TIME_LIMIT_SECONDS);
return new Event(userId, meetingId, type, ACTIVE, expiredAt);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ public interface EventJpaRepository extends JpaRepository<Event, Long> {
Optional<Event> findFirstByUserIdAndMeetingIdAndStatusAndTypeOrderByIdDesc(
Long userId, Long meetingId, EventStatus eventStatus, EventType type
);

List<Event> findByMeetingIdAndStatusAndType(Long meetingId, EventStatus eventStatus, EventType eventType);
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ public Optional<Event> findFirstByUserIdAndMeetingIdAndStatusAndType(
userId, meetingId, eventStatus, type);
}

@Override
public List<Event> findByMeetingIdAndStatusAndType(Long meetingId, EventStatus eventStatus, EventType eventType) {
return eventJpaRepository.findByMeetingIdAndStatusAndType(meetingId, eventStatus, eventType);
}

@Override
public void save(Event event) {
eventJpaRepository.save(event);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,18 @@ public void validateHostMaxCount(Long meetingId) {
}

public void validateUserIsMemberOfMeeting(Long userId, Long meetingId) {
if (!meetingMemberRepository.isUserMemberOfMeeting(userId, meetingId)) {
if (isNotMemberOfMeeting(userId, meetingId)) {
throw new UserNotMemberOfMeetingException();
}
}

public void validateMemberIsMemberOfMeeting(Long memberId, Long meetingId) {
if (!meetingMemberRepository.isUserMemberOfMeeting(memberId, meetingId)) {
if (isNotMemberOfMeeting(memberId, meetingId)) {
throw new MeetingMemberNotFoundException();
}
}

public boolean isNotMemberOfMeeting(Long userId, Long meetingId) {
return !meetingMemberRepository.isUserMemberOfMeeting(userId, meetingId);
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package org.depromeet.sambad.moring.meeting.question.application;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

import org.depromeet.sambad.moring.meeting.member.domain.MeetingMember;
import org.depromeet.sambad.moring.meeting.question.domain.MeetingQuestion;
import org.depromeet.sambad.moring.meeting.question.presentation.response.ActiveMeetingQuestionResponse;
import org.depromeet.sambad.moring.meeting.question.domain.MeetingQuestionStatus;
import org.depromeet.sambad.moring.meeting.question.presentation.response.FullInactiveMeetingQuestionListResponse;
import org.depromeet.sambad.moring.meeting.question.presentation.response.MeetingQuestionStatisticsDetail;
import org.depromeet.sambad.moring.meeting.question.presentation.response.MostInactiveMeetingQuestionListResponse;
Expand All @@ -19,12 +20,8 @@ public interface MeetingQuestionRepository {

boolean existsByQuestion(Long meetingId, Long questionId);

ActiveMeetingQuestionResponse findActiveOneByMeeting(Long meetingId, Long loginMeetingMemberId);

Optional<MeetingQuestion> findActiveOneByMeeting(Long meetingId);

Optional<MeetingQuestion> findRegisteredOneByMeeting(Long meetingId);

MostInactiveMeetingQuestionListResponse findMostInactiveList(Long meetingId);

FullInactiveMeetingQuestionListResponse findFullInactiveList(Long meetingId, Pageable pageable);
Expand All @@ -34,4 +31,10 @@ public interface MeetingQuestionRepository {
List<MeetingQuestionStatisticsDetail> findStatistics(Long meetingQuestionId);

List<MeetingMember> findMeetingMembersByMeetingQuestionId(Long meetingQuestionId);

List<MeetingQuestion> findAllByStatusAndExpiredAtBefore(MeetingQuestionStatus status, LocalDateTime now);

Optional<MeetingQuestion> findFirstByMeetingIdAndStatus(Long meetingId, MeetingQuestionStatus status);

boolean isAnswered(Long meetingQuestionId, Long meetingMemberId);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.depromeet.sambad.moring.meeting.question.application;

import static org.depromeet.sambad.moring.event.domain.EventType.*;
import static org.depromeet.sambad.moring.meeting.question.domain.MeetingQuestionStatus.*;

import java.time.Clock;
import java.time.LocalDateTime;
Expand Down Expand Up @@ -52,15 +53,17 @@ public ActiveMeetingQuestionResponse save(Long userId, Long meetingId, MeetingQu
MeetingMember nextTargetMember = meetingMemberService.getById(request.meetingMemberId());
loginMember.validateNextTarget(nextTargetMember);

Optional<MeetingQuestion> registeredMeetingQuestion = findRegisteredMeetingQuestion(meetingId);
Optional<MeetingQuestion> registeredMeetingQuestion = meetingQuestionRepository.findFirstByMeetingIdAndStatus(
meetingId, ACTIVE);
Question activeQuestion = questionService.getById(request.questionId());
validateNonDuplicateQuestion(meetingId, activeQuestion.getId());

Meeting meeting = loginMember.getMeeting();
MeetingQuestion nowMeetingQuestion = null;
MeetingQuestion nowMeetingQuestion;

if (registeredMeetingQuestion.isPresent()) {
nowMeetingQuestion = registeredMeetingQuestion.get();
nowMeetingQuestion.setQuestion(loginMember, activeQuestion);
nowMeetingQuestion.registerQuestion(loginMember, activeQuestion);
} else {
nowMeetingQuestion = createActiveQuestion(meeting, loginMember, activeQuestion);
}
Expand Down Expand Up @@ -95,10 +98,23 @@ public MeetingQuestionAndAnswerListResponse getActiveMeetingQuestionAndAnswerLis
return MeetingQuestionAndAnswerListResponse.of(activeMeetingQuestion.get());
}

public ActiveMeetingQuestionResponse findActiveOne(Long userId, Long meetingId) {
public Optional<ActiveMeetingQuestionResponse> findActiveOne(Long userId, Long meetingId) {
MeetingMember meetingMember = meetingMemberService.getByUserIdAndMeetingId(userId, meetingId);
Meeting meeting = meetingMember.getMeeting();
return meetingQuestionRepository.findActiveOneByMeeting(meeting.getId(), meetingMember.getId());
Optional<MeetingQuestion> activeMeetingQuestionOpt = meetingQuestionRepository.findActiveOneByMeeting(
meeting.getId());

if (activeMeetingQuestionOpt.isPresent()) {
MeetingQuestion activeMeetingQuestion = activeMeetingQuestionOpt.get();
boolean isAnswered = meetingQuestionRepository.isAnswered(
activeMeetingQuestion.getId(), meetingMember.getId());

return activeMeetingQuestion.getQuestion() == null
? Optional.of(ActiveMeetingQuestionResponse.questionNotRegisteredOf(activeMeetingQuestion))
: Optional.of(ActiveMeetingQuestionResponse.questionRegisteredOf(activeMeetingQuestion, isAnswered));
}

return Optional.empty();
}

public MostInactiveMeetingQuestionListResponse findMostInactiveList(Long userId, Long meetingId) {
Expand Down Expand Up @@ -150,10 +166,6 @@ private Optional<MeetingQuestion> findActiveMeetingQuestion(Long meetingId) {
return meetingQuestionRepository.findActiveOneByMeeting(meetingId);
}

private Optional<MeetingQuestion> findRegisteredMeetingQuestion(Long meetingId) {
return meetingQuestionRepository.findRegisteredOneByMeeting(meetingId);
}

private void validateNonDuplicateQuestion(Long meetingId, Long questionId) {
boolean isDuplicateQuestion = meetingQuestionRepository.existsByQuestion(meetingId, questionId);
if (isDuplicateQuestion) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package org.depromeet.sambad.moring.meeting.question.application;

import static java.time.LocalDateTime.*;
import static org.depromeet.sambad.moring.event.domain.EventType.*;
import static org.depromeet.sambad.moring.meeting.question.domain.MeetingQuestionStatus.*;

import java.util.List;

import org.depromeet.sambad.moring.event.application.EventService;
import org.depromeet.sambad.moring.meeting.meeting.domain.Meeting;
import org.depromeet.sambad.moring.meeting.question.domain.MeetingQuestion;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@RequiredArgsConstructor
@Component
public class MeetingQuestionStatusCheckScheduler {

private final MeetingQuestionRepository meetingQuestionRepository;
private final EventService eventService;

@Scheduled(fixedDelay = 1000 * 5)
@Transactional
public void inactivate() {
// 활성 상태이지만 만료 시간이 지나 INACTIVE 상태가 되어야 하는 질문 목록 조회
List<MeetingQuestion> targets = meetingQuestionRepository.findAllByStatusAndExpiredAtBefore(ACTIVE, now());

// 질문 비활성화 처리
targets.forEach(MeetingQuestion::updateStatusToInactive);

// 해당 모임의 다음 질문 활성화 처리 및 이벤트 발행
targets.stream()
.map(MeetingQuestion::getMeeting)
.forEach(this::activate);

// 결과 로깅
logResults(targets);
}

private void activate(Meeting meeting) {
Long meetingId = meeting.getId();

meetingQuestionRepository.findFirstByMeetingIdAndStatus(meetingId, NOT_STARTED)
.ifPresent(targetMeetingQuestion -> {
targetMeetingQuestion.updateStatusToActive(now());
reissueTargetMemberEvent(targetMeetingQuestion);
log.info("Activated not started meeting question. {}", targetMeetingQuestion.getId());
});
}

private void reissueTargetMemberEvent(MeetingQuestion target) {
Long meetingId = target.getMeeting().getId();
Long targetMemberUserId = target.getTargetMember().getUser().getId();

eventService.inactivateLastEventsOfAllMemberByType(meetingId, QUESTION_REGISTERED);
eventService.inactivateLastEventByType(targetMemberUserId, meetingId, TARGET_MEMBER);
eventService.publish(targetMemberUserId, meetingId, TARGET_MEMBER);
}

private void logResults(List<MeetingQuestion> targets) {
if (!targets.isEmpty()) {
List<Long> inactivatedIds = targets.stream().map(MeetingQuestion::getId).toList();
log.info("Inactivated {} meeting questions. {}", targets.size(), inactivatedIds);
}
}
}
Loading

0 comments on commit 39704c8

Please sign in to comment.