Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import grep.neogulcoder.domain.alram.type.AlarmType;
import grep.neogulcoder.domain.alram.type.DomainType;
import java.time.LocalDateTime;
import lombok.Builder;
import lombok.Data;

Expand All @@ -22,28 +23,33 @@ public class AlarmResponse {

private boolean checked;

public static AlarmResponse toResponse(Long id, Long receiverUserId, AlarmType alarmType, DomainType domainType,
Long domainId, String message, boolean checked) {
private LocalDateTime createdDate;

public static AlarmResponse toResponse(Long id, Long receiverUserId, AlarmType alarmType,
DomainType domainType,
Long domainId, String message, boolean checked, LocalDateTime createdDate) {
return AlarmResponse.builder()
.id(id)
.receiverUserId(receiverUserId)
.alarmType(alarmType)
.domainType(domainType)
.domainId(domainId)
.message(message)
.checked(checked)
.build();
.id(id)
.receiverUserId(receiverUserId)
.alarmType(alarmType)
.domainType(domainType)
.domainId(domainId)
.message(message)
.checked(checked)
.createdDate(createdDate)
.build();
}

@Builder
private AlarmResponse(Long id, Long receiverUserId, AlarmType alarmType, DomainType domainType,
Long domainId, String message, boolean checked) {
Long domainId, String message, boolean checked, LocalDateTime createdDate) {
this.id = id;
this.receiverUserId = receiverUserId;
this.alarmType = alarmType;
this.domainType = domainType;
this.domainId = domainId;
this.message = message;
this.checked = checked;
this.createdDate = createdDate;
}
}
122 changes: 81 additions & 41 deletions src/main/java/grep/neogulcoder/domain/alram/service/AlarmService.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import grep.neogulcoder.domain.alram.repository.AlarmRepository;
import grep.neogulcoder.domain.alram.type.AlarmType;
import grep.neogulcoder.domain.alram.type.DomainType;
import grep.neogulcoder.domain.recruitment.post.RecruitmentPost;
import grep.neogulcoder.domain.recruitment.post.repository.RecruitmentPostRepository;
import grep.neogulcoder.domain.study.Study;
import grep.neogulcoder.domain.study.StudyMember;
import grep.neogulcoder.domain.study.enums.StudyMemberRole;
Expand All @@ -15,18 +17,21 @@
import grep.neogulcoder.domain.study.repository.StudyMemberQueryRepository;
import grep.neogulcoder.domain.study.repository.StudyMemberRepository;
import grep.neogulcoder.domain.study.repository.StudyRepository;
import grep.neogulcoder.domain.studyapplication.StudyApplication;
import grep.neogulcoder.domain.studyapplication.event.ApplicationEvent;
import grep.neogulcoder.domain.studyapplication.event.ApplicationStatusChangedEvent;
import grep.neogulcoder.domain.studyapplication.repository.ApplicationRepository;
import grep.neogulcoder.domain.timevote.event.TimeVotePeriodCreatedEvent;
import grep.neogulcoder.global.exception.business.BusinessException;
import grep.neogulcoder.global.exception.business.NotFoundException;
import grep.neogulcoder.global.provider.finder.MessageFinder;

import java.util.List;

import lombok.RequiredArgsConstructor;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import static grep.neogulcoder.domain.recruitment.RecruitmentErrorCode.NOT_FOUND;
import static grep.neogulcoder.domain.study.exception.code.StudyErrorCode.*;
import static grep.neogulcoder.domain.studyapplication.exception.code.ApplicationErrorCode.*;

Expand All @@ -40,54 +45,59 @@ public class AlarmService {
private final StudyRepository studyRepository;
private final StudyMemberQueryRepository studyMemberQueryRepository;
private final StudyMemberRepository studyMemberRepository;
private final RecruitmentPostRepository recruitmentPostRepository;
private final ApplicationRepository applicationRepository;

@Transactional
public void saveAlarm(Long receiverId, AlarmType alarmType, DomainType domainType, Long domainId) {
public void saveAlarm(Long receiverId, AlarmType alarmType, DomainType domainType,
Long domainId) {
String message = messageFinder.findMessage(alarmType, domainType, domainId);
alarmRepository.save(Alarm.init(alarmType, receiverId, domainType, domainId, message));
}

public List<AlarmResponse> getAllUncheckedAlarms(Long receiverUserId) {
return alarmRepository.findAllByReceiverUserIdAndCheckedFalse(receiverUserId).stream()
.map(alarm -> AlarmResponse.toResponse(
alarm.getId(),
alarm.getReceiverUserId(),
alarm.getAlarmType(),
alarm.getDomainType(),
alarm.getDomainId(),
alarm.getMessage(),
alarm.isChecked()))
.toList();
.map(alarm -> AlarmResponse.toResponse(
alarm.getId(),
alarm.getReceiverUserId(),
alarm.getAlarmType(),
alarm.getDomainType(),
alarm.getDomainId(),
alarm.getMessage(),
alarm.isChecked(),
alarm.getCreatedDate()))
.toList();
}

public List<AlarmResponse> getAllAlarms(Long receiverUserId) {
return alarmRepository.findAllByReceiverUserId(receiverUserId).stream()
.map(alarm -> AlarmResponse.toResponse(
alarm.getId(),
alarm.getReceiverUserId(),
alarm.getAlarmType(),
alarm.getDomainType(),
alarm.getDomainId(),
alarm.getMessage(),
alarm.isChecked()))
.toList();
.map(alarm -> AlarmResponse.toResponse(
alarm.getId(),
alarm.getReceiverUserId(),
alarm.getAlarmType(),
alarm.getDomainType(),
alarm.getDomainId(),
alarm.getMessage(),
alarm.isChecked(),
alarm.getCreatedDate()))
.toList();
}

@Transactional
public void checkAllAlarmWithoutInvite(Long receiverUserId) {
List<Alarm> alarms = alarmRepository.findAllByReceiverUserIdAndCheckedFalse(receiverUserId);
alarms.stream()
.filter(alarm -> alarm.getAlarmType() != AlarmType.INVITE)
.forEach(Alarm::checkAlarm);
.filter(alarm -> alarm.getAlarmType() != AlarmType.INVITE)
.forEach(Alarm::checkAlarm);
}

@EventListener
public void handleStudyInviteEvent(StudyInviteEvent event) {
saveAlarm(
event.targetUserId(),
AlarmType.INVITE,
DomainType.STUDY,
event.studyId()
event.targetUserId(),
AlarmType.INVITE,
DomainType.STUDY,
event.studyId()
);
}

Expand All @@ -111,36 +121,39 @@ public void rejectInvite(Long alarmId) {

@EventListener
public void handleStudyExtendEvent(StudyExtendEvent event) {
List<StudyMember> members = studyMemberRepository.findAllByStudyIdAndActivatedTrue(event.studyId());
List<StudyMember> members = studyMemberRepository.findAllByStudyIdAndActivatedTrue(
event.studyId());

for (StudyMember member : members) {
if (!member.isLeader()) {
saveAlarm(
member.getUserId(),
AlarmType.STUDY_EXTEND,
DomainType.STUDY,
event.studyId()
member.getUserId(),
AlarmType.STUDY_EXTEND,
DomainType.STUDY,
event.studyId()
);
}
}
}

@EventListener
public void handleStudyExtensionReminderEvent(StudyExtensionReminderEvent event) {
StudyMember leader = studyMemberRepository.findByStudyIdAndRoleAndActivatedTrue(event.studyId(), StudyMemberRole.LEADER)
.orElseThrow(() -> new BusinessException(STUDY_LEADER_NOT_FOUND));
StudyMember leader = studyMemberRepository.findByStudyIdAndRoleAndActivatedTrue(
event.studyId(), StudyMemberRole.LEADER)
.orElseThrow(() -> new BusinessException(STUDY_LEADER_NOT_FOUND));

saveAlarm(
leader.getUserId(),
AlarmType.STUDY_EXTENSION_REMINDER,
DomainType.STUDY,
event.studyId()
leader.getUserId(),
AlarmType.STUDY_EXTENSION_REMINDER,
DomainType.STUDY,
event.studyId()
);
}

@EventListener
public void handleTimeVotePeriodCreatedEvent(TimeVotePeriodCreatedEvent event) {
List<StudyMember> members = studyMemberRepository.findAllByStudyIdAndActivatedTrue(event.studyId());
List<StudyMember> members = studyMemberRepository.findAllByStudyIdAndActivatedTrue(
event.studyId());

for (StudyMember member : members) {
if (!member.getUserId().equals(event.excludedUserId())) {
Expand All @@ -154,13 +167,40 @@ public void handleTimeVotePeriodCreatedEvent(TimeVotePeriodCreatedEvent event) {
}
}

@EventListener
public void handleApplicationEvent(ApplicationEvent event) {
RecruitmentPost recruitmentPost = recruitmentPostRepository.findByIdAndActivatedTrue(event.recruitmentPostId())
.orElseThrow(() -> new BusinessException(NOT_FOUND));

saveAlarm(
recruitmentPost.getUserId(),
AlarmType.STUDY_APPLICATION,
DomainType.RECRUITMENT_POST,
event.recruitmentPostId()
);
}

@EventListener
public void handleApplicationStatusChangedEvent(ApplicationStatusChangedEvent event) {
StudyApplication application = applicationRepository.findByIdAndActivatedTrue(event.applicationId())
.orElseThrow(() -> new BusinessException(APPLICATION_NOT_FOUND));

saveAlarm(
application.getUserId(),
event.alarmType(),
DomainType.STUDY_APPLICATION,
application.getId()
);
}

private Alarm findValidAlarm(Long alarmId) {
return alarmRepository.findById(alarmId).orElseThrow(() -> new NotFoundException(AlarmErrorCode.ALARM_NOT_FOUND));
return alarmRepository.findById(alarmId)
.orElseThrow(() -> new NotFoundException(AlarmErrorCode.ALARM_NOT_FOUND));
}

private Study findValidStudy(Long studyId) {
return studyRepository.findById(studyId)
.orElseThrow(() -> new NotFoundException(STUDY_NOT_FOUND));
.orElseThrow(() -> new NotFoundException(STUDY_NOT_FOUND));
}

private void validateParticipantStudyLimit(Long userId) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package grep.neogulcoder.domain.alram.type;

public enum AlarmType {
INVITE, STUDY_EXTEND, STUDY_EXTENSION_REMINDER, TIME_VOTE_REQUEST
INVITE,
STUDY_EXTEND,
STUDY_EXTENSION_REMINDER,
TIME_VOTE_REQUEST,
STUDY_APPLICATION,
STUDY_APPLICATION_APPROVED,
STUDY_APPLICATION_REJECTED
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package grep.neogulcoder.domain.alram.type;

public enum DomainType {
STUDY, TIME_VOTE
STUDY,
TIME_VOTE,
RECRUITMENT_POST,
STUDY_APPLICATION
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package grep.neogulcoder.domain.groupchat.controller;

import grep.neogulcoder.domain.groupchat.controller.dto.response.ChatMessagePagingResponse;
import grep.neogulcoder.domain.groupchat.controller.dto.response.GroupChatMessageResponseDto;
import grep.neogulcoder.domain.groupchat.service.GroupChatService;
import grep.neogulcoder.global.response.ApiResponse;
Expand All @@ -17,15 +18,13 @@ public class GroupChatRestController implements GroupChatRestSpecification {
// 과거 채팅 메시지 페이징 조회 (무한 스크롤용)
@Override
@GetMapping("/study/{studyId}/messages")
public ApiResponse<PageResponse<GroupChatMessageResponseDto>> getMessages(
public ApiResponse<ChatMessagePagingResponse> getMessages(
@PathVariable("studyId") Long studyId,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size
) {
// 서비스에서 페이징된 메시지 조회
PageResponse<GroupChatMessageResponseDto> pageResponse =
groupChatService.getMessages(studyId, page, size);

return ApiResponse.success(pageResponse);
ChatMessagePagingResponse response = groupChatService.getMessages(studyId, page, size);
return ApiResponse.success(response);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package grep.neogulcoder.domain.groupchat.controller;

import grep.neogulcoder.domain.groupchat.controller.dto.response.ChatMessagePagingResponse;
import grep.neogulcoder.domain.groupchat.controller.dto.response.GroupChatMessageResponseDto;
import grep.neogulcoder.global.response.ApiResponse;
import grep.neogulcoder.global.response.PageResponse;
Expand Down Expand Up @@ -73,7 +74,7 @@ public interface GroupChatRestSpecification {
"""
)

ApiResponse<PageResponse<GroupChatMessageResponseDto>> getMessages(
ApiResponse<ChatMessagePagingResponse> getMessages(
@Parameter(description = "스터디 ID", example = "1")
@PathVariable("studyId") Long studyId,

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package grep.neogulcoder.domain.groupchat.controller.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Getter;
import org.springframework.data.domain.Page;

import java.util.List;

@Getter
public class ChatMessagePagingResponse {

@Schema(description = "채팅 메시지 목록")
private final List<GroupChatMessageResponseDto> content;

@Schema(description = "현재 페이지 번호", example = "0")
private final int currentPage;

@Schema(description = "페이지 크기", example = "20")
private final int size;

@Schema(description = "전체 페이지 수", example = "5")
private final int totalPages;

@Schema(description = "전체 메시지 수", example = "100")
private final long totalElements;

@Schema(description = "다음 페이지 존재 여부", example = "true")
private final boolean hasNext;

@Builder
private ChatMessagePagingResponse(Page<GroupChatMessageResponseDto> page) {
this.content = page.getContent();
this.currentPage = page.getNumber();
this.size = page.getSize();
this.totalPages = page.getTotalPages();
this.totalElements = page.getTotalElements();
this.hasNext = page.hasNext();
}

public static ChatMessagePagingResponse of(Page<GroupChatMessageResponseDto> page) {
return new ChatMessagePagingResponse(page);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ public interface GroupChatMessageRepository extends JpaRepository<GroupChatMessa

// 채팅방(roomId)에 속한 메시지를 전송 시간 내림차순으로 페이징 조회
@Query("SELECT m FROM GroupChatMessage m " +
"WHERE m.groupChatRoom.roomId = :roomId " +
"JOIN FETCH m.groupChatRoom r " +
"WHERE r.roomId = :roomId " +
"ORDER BY m.sentAt ASC")
Page<GroupChatMessage> findMessagesByRoomIdAsc(@Param("roomId") Long roomId, Pageable pageable);

Expand Down
Loading