Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
5d47a07
MOSU-28 feat: 알림톡 관련 설정 추가
chominju02 Jul 16, 2025
44eb9f3
MOSU-28 feat: ApplicationNotifyStrategy 구현
chominju02 Jul 16, 2025
f9a4a8a
MOSU-28 feat: 각 알림톡의 request 생성을 위한 strategy 구현
chominju02 Jul 16, 2025
abeeae7
MOSU-28 feat: 각 알림톡 변수 dto 구현
chominju02 Jul 16, 2025
f1828d8
MOSU-28 feat: 알림톡 request 관련 구현
chominju02 Jul 16, 2025
397caea
MOSU-28 feat: MessageSourceConfig 구현
chominju02 Jul 16, 2025
e65f5e2
MOSU-28 feat: WebClientConfig 구현
chominju02 Jul 16, 2025
f4247b5
MOSU-28 feat: NotifyStrategy 인터페이스 구현 및 NotifyStrategyMap 구현
chominju02 Jul 16, 2025
8a2bbd5
MOSU-28 feat: Notify 관련 request message dto 구현
chominju02 Jul 16, 2025
d76a5c8
MOSU-28 feat: NotifyEventTemplate 구현 (request 형식 생성)
chominju02 Jul 16, 2025
ecbca02
MOSU-28 feat: NotifyStatus 구현, EventStatus 구현
chominju02 Jul 16, 2025
97b4ad8
MOSU-28 feat: Notify 관련 errorCode 구현
chominju02 Jul 16, 2025
bc5b2dd
MOSU-28 feat: 알림톡 메세지 작성
chominju02 Jul 16, 2025
34ed522
MOSU-28 feat: NotifyConstants 작성
chominju02 Jul 16, 2025
f245fd3
MOSU-28 feat: NotifyEvent, NotifyEventListener, NotifyEventPublisher 구현
chominju02 Jul 16, 2025
401a17a
MOSU-28 feat: ReactiveEventListener 어노테이션 구현
chominju02 Jul 16, 2025
633d821
MOSU-28 feat: 답변 완료시 알림톡 발송 연동
chominju02 Jul 16, 2025
88c9b4d
MOSU-28 chore: webflux 의존성 추가
chominju02 Jul 16, 2025
4cd5e74
MOSU-28 feat: NotifyService 구현
chominju02 Jul 16, 2025
2a0a9c1
MOSU-28 feat: ApplicationNotify, OneWeekNotify 를 위한 정보 조회 구현
chominju02 Jul 16, 2025
9ff2c9a
MOSU-28 feat: RefundNofity 를 위한 정보 조회 구현
chominju02 Jul 16, 2025
32009b9
MOSU-28 feat: Application 리팩토링을 위한 임시 로직 구현
chominju02 Jul 16, 2025
f118a2d
feat: Notify 관련 DTO 클래스 및 상태 추가
polyglot-k Jul 16, 2025
d768f97
feat: LunaSoftNotifier 및 NotifyClientAdapter, NotifyProperties 클래스 추가
polyglot-k Jul 16, 2025
747d4bb
feat: NotifyStrategy 인터페이스 및 관련 전략 클래스 리팩토링, targetPhoneNumber 매개변수 추가
polyglot-k Jul 16, 2025
d5400be
feat: NotifyConstants 클래스 리팩토링 및 NotifyEvent DTO에서 불필요한 import 제거
polyglot-k Jul 16, 2025
7977e6c
feat: NotifyService 클래스를 새로 추가하고 기존 NotifyService 클래스를 제거하여 구조 개선
polyglot-k Jul 16, 2025
5d80983
feat: NotifyEventTemplateGenerator 클래스 추가 및 메시지 처리 기능 구현
polyglot-k Jul 16, 2025
8601b0a
feat: NotifyEvent 및 NotifyEventTemplate 클래스 제거
polyglot-k Jul 16, 2025
79e5ec6
feat: NotifyEventPublisher 및 NotifyEventListener에서 NotifyEvent import…
polyglot-k Jul 16, 2025
c5e51ed
feat: InquiryAnswerService에서 불필요한 import 및 주석 제거
polyglot-k Jul 16, 2025
aa28d41
feat: DatabaseInitializer에서 전화번호를 고정 값으로 변경
polyglot-k Jul 16, 2025
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
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ dependencies {

// poi-excel
implementation 'org.apache.poi:poi-ooxml:5.4.0'

implementation 'org.springframework.boot:spring-boot-starter-webflux'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import life.mosu.mosuserver.domain.applicationschool.ApplicationSchoolJpaRepository;
import life.mosu.mosuserver.domain.school.SchoolJpaEntity;
import life.mosu.mosuserver.domain.school.SchoolJpaRepository;
import life.mosu.mosuserver.domain.school.SchoolQueryRepositoryImpl;
import life.mosu.mosuserver.global.exception.CustomRuntimeException;
import life.mosu.mosuserver.global.exception.ErrorCode;
import life.mosu.mosuserver.global.util.FileRequest;
Expand All @@ -31,6 +32,7 @@ public class ApplicationService {
private final ApplicationJpaRepository applicationJpaRepository;
private final ApplicationSchoolJpaRepository applicationSchoolJpaRepository;
private final AdmissionTicketImageJpaRepository admissionTicketImageJpaRepository;
private final SchoolQueryRepositoryImpl schoolQueryRepository;
private final SchoolJpaRepository schoolJpaRepository;

// 신청
Expand All @@ -42,32 +44,85 @@ public ApplicationResponse apply(Long userId, ApplicationRequest request) {
ApplicationJpaEntity application = applicationJpaRepository.save(request.toEntity(userId));
Long applicationId = application.getId();

//수험표 저장
admissionTicketImageJpaRepository.save(
createAdmissionTicketImageIfPresent(request.admissionTicket(), applicationId));

for (ApplicationSchoolRequest schoolRequest : schoolRequests) {
//해당 학교가 존재하는 학교인지
Long schoolId = schoolJpaRepository.findBySchoolNameAndAreaAndExamDate(
schoolRequest.schoolName(),
schoolRequest.validatedArea(schoolRequest.area()),
schoolRequest.examDate())
.orElseThrow(() -> new CustomRuntimeException(ErrorCode.SCHOOL_NOT_FOUND))
.getId();

//해당 학교를 이미 신청하였는지
if (applicationSchoolJpaRepository.existsByUserIdAndSchoolId(userId, schoolId)) {
throw new CustomRuntimeException(ErrorCode.APPLICATION_SCHOOL_ALREADY_APPLIED);
}

//해당 학교를 찾을 수 있는지
SchoolJpaEntity school = schoolJpaRepository.findById(schoolId)
.orElseThrow(() -> new CustomRuntimeException(ErrorCode.SCHOOL_NOT_FOUND));
Comment on lines 48 to 67
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

학교 존재 확인과 기존의 신청이 있는지 검증을 하고 수험표 저장 로직을 수행해야 할 것 같은데 혹시 다른 의도가 있는 건가요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수정 중에 있습니다! 확인했어요


//해당 학교를 신청했음을 저장하기
ApplicationSchoolJpaEntity applicationSchool = schoolRequest.toEntity(userId,
applicationId, school);

//반환용 리스트에 저장하기
savedEntities.add(applicationSchoolJpaRepository.save(applicationSchool));
}

return ApplicationResponse.of(applicationId, savedEntities);
}

//신청 - 리팩토링
// @Transactional
// public ApplicationResponse apply(Long userId, ApplicationRequest request) {
// Set<ApplicationSchoolRequest> schoolRequests = request.schools();
//// List<ApplicationSchoolJpaEntity> savedEntities = new ArrayList<>();
//
// ApplicationJpaEntity application = applicationJpaRepository.save(request.toEntity(userId));
// Long applicationId = application.getId();
//
// //수험표 저장
// admissionTicketImageJpaRepository.save(
// createAdmissionTicketImageIfPresent(request.admissionTicket(), applicationId));
//
// List<Triple<String, Area, LocalDate>> conditions = schoolRequests.stream()
// .map(school -> Triple.of(
// school.schoolName(),
// school.validatedArea(school.area()),
// school.examDate()
// ))
// .toList();
//
// //이름, 지역, 날짜 조건에 맞는 학교가 있는지
// List<Long> schoolIds = schoolQueryRepository.findSchoolsByConditions(conditions);
// if (schoolIds.size() != conditions.size()) {
// throw new CustomRuntimeException(ErrorCode.SCHOOL_NOT_FOUND);
// }
//
// //해당 학교를 이미 신청하였는지
// if (applicationSchoolJpaRepository.existsByUserIdAndSchoolIds(userId, schoolIds)) {
// throw new CustomRuntimeException(ErrorCode.APPLICATION_SCHOOL_ALREADY_APPLIED);
// }
//

/// / //해당 학교를 찾을 수 있는지 / SchoolJpaEntity school =
/// schoolJpaRepository.findById(schoolId) / .orElseThrow(() -> new
/// CustomRuntimeException(ErrorCode.SCHOOL_NOT_FOUND));
//
// //해당 학교를 신청했음을 저장하기
// ApplicationSchoolJpaEntity applicationSchool = schoolRequest.toEntity(userId,
// applicationId, school);
//
// //반환용 리스트에 저장하기
// savedEntities.add(applicationSchoolJpaRepository.save(applicationSchool));
//
// return ApplicationResponse.of(applicationId, savedEntities);
// }

// 전체 신청 내역 조회
@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
import life.mosu.mosuserver.domain.inquiry.InquiryJpaRepository;
import life.mosu.mosuserver.domain.inquiryAnswer.InquiryAnswerJpaEntity;
import life.mosu.mosuserver.domain.inquiryAnswer.InquiryAnswerJpaRepository;
import life.mosu.mosuserver.domain.profile.ProfileJpaRepository;
import life.mosu.mosuserver.global.exception.CustomRuntimeException;
import life.mosu.mosuserver.global.exception.ErrorCode;
import life.mosu.mosuserver.infra.notify.NotifyEventPublisher;
import life.mosu.mosuserver.presentation.inquiry.dto.InquiryAnswerRequest;
import life.mosu.mosuserver.presentation.inquiry.dto.InquiryAnswerUpdateRequest;
import life.mosu.mosuserver.presentation.inquiry.dto.InquiryDetailResponse;
Expand All @@ -22,6 +24,9 @@ public class InquiryAnswerService {
private final InquiryAnswerAttachmentService answerAttachmentService;
private final InquiryAnswerJpaRepository inquiryAnswerJpaRepository;
private final InquiryJpaRepository inquiryJpaRepository;
private final ProfileJpaRepository profileJpaRepository;
private final NotifyEventPublisher notifyEventPublisher;


@Transactional
public void createInquiryAnswer(Long postId, InquiryAnswerRequest request) {
Expand All @@ -36,6 +41,13 @@ public void createInquiryAnswer(Long postId, InquiryAnswerRequest request) {

answerAttachmentService.createAttachment(request.attachments(), answerEntity);
inquiryEntity.updateStatusToComplete();

// ProfileJpaEntity profile = profileJpaRepository.findByUserId(inquiryEntity.getUserId())
// .orElseThrow(() -> new RuntimeException(""));
// NotifyEvent event = NotifyEvent.create(NotifyStatus.INQUIRY_ANSWER_SUCCESS,
// profile.getPhoneNumber(), postId);
// notifyEventPublisher.notify(event);

}

@Transactional
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package life.mosu.mosuserver.application.notify;

import life.mosu.mosuserver.domain.profile.ProfileJpaEntity;
import life.mosu.mosuserver.domain.profile.ProfileJpaRepository;
import life.mosu.mosuserver.global.exception.CustomRuntimeException;
import life.mosu.mosuserver.global.exception.ErrorCode;
import life.mosu.mosuserver.infra.notify.NotifyClientAdapter;
import life.mosu.mosuserver.infra.notify.dto.NotifyEvent;
import life.mosu.mosuserver.infra.notify.dto.NotifyEventRequest;
import life.mosu.mosuserver.infra.notify.strategy.NotifyStrategy;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;

@Slf4j
@Service
@RequiredArgsConstructor
public class NotifyService {

private final NotifyClientAdapter notifier;
private final ProfileJpaRepository profileJpaRepository;
private final ApplicationContext applicationContext;

public void notify(NotifyEvent event) {
NotifyStrategy strategy = getNotifyStrategy(event.status().getStrategyName());

ProfileJpaEntity profile = profileJpaRepository.findByUserId(event.targetId())
.orElseThrow(() -> new CustomRuntimeException(ErrorCode.PROFILE_NOT_FOUND));
String parsedPhoneNumber = profile.getPhoneNumber().replaceAll("-", "");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add validation for phone number processing.

The current implementation assumes the phone number always contains hyphens and doesn't validate the format. Consider adding validation.

-        String parsedPhoneNumber = profile.getPhoneNumber().replaceAll("-", "");
+        String phoneNumber = profile.getPhoneNumber();
+        if (phoneNumber == null || phoneNumber.trim().isEmpty()) {
+            throw new CustomRuntimeException(ErrorCode.INVALID_PHONE_NUMBER);
+        }
+        String parsedPhoneNumber = phoneNumber.replaceAll("-", "");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
String parsedPhoneNumber = profile.getPhoneNumber().replaceAll("-", "");
String phoneNumber = profile.getPhoneNumber();
if (phoneNumber == null || phoneNumber.trim().isEmpty()) {
throw new CustomRuntimeException(ErrorCode.INVALID_PHONE_NUMBER);
}
String parsedPhoneNumber = phoneNumber.replaceAll("-", "");
🤖 Prompt for AI Agents
In src/main/java/life/mosu/mosuserver/application/notify/NotifyService.java at
line 30, the code removes hyphens from the phone number without validating its
format. Add validation before processing the phone number to ensure it matches
the expected pattern (e.g., digits with optional hyphens). If the format is
invalid, handle it appropriately, such as throwing an exception or returning an
error, to prevent processing incorrect phone numbers.


NotifyEventRequest request = strategy.apply(parsedPhoneNumber, event);

notifier.send(request);
}

private NotifyStrategy getNotifyStrategy(String strategyName) {
return (NotifyStrategy) applicationContext.getBean(strategyName);
}
Comment on lines +37 to +39
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for missing strategy beans.

The current implementation will throw a NoSuchBeanDefinitionException if the strategy bean is not found. Consider adding proper error handling.

    private NotifyStrategy getNotifyStrategy(String strategyName) {
-        return (NotifyStrategy) applicationContext.getBean(strategyName);
+        try {
+            return (NotifyStrategy) applicationContext.getBean(strategyName);
+        } catch (NoSuchBeanDefinitionException e) {
+            throw new CustomRuntimeException(ErrorCode.STRATEGY_NOT_FOUND);
+        }
    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private NotifyStrategy getNotifyStrategy(String strategyName) {
return (NotifyStrategy) applicationContext.getBean(strategyName);
}
private NotifyStrategy getNotifyStrategy(String strategyName) {
try {
return (NotifyStrategy) applicationContext.getBean(strategyName);
} catch (NoSuchBeanDefinitionException e) {
throw new CustomRuntimeException(ErrorCode.STRATEGY_NOT_FOUND);
}
}
🤖 Prompt for AI Agents
In src/main/java/life/mosu/mosuserver/application/notify/NotifyService.java
around lines 37 to 39, the method getNotifyStrategy currently retrieves a bean
by name without handling the case where the bean does not exist, which throws
NoSuchBeanDefinitionException. Modify the method to catch this exception and
handle it gracefully, such as by logging an error and returning null or throwing
a custom exception with a clear message, to improve robustness.


}
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,33 @@
public interface ApplicationSchoolJpaRepository extends
JpaRepository<ApplicationSchoolJpaEntity, Long> {

// boolean existsByUserIdAndSchoolIds(Long userId, List<Long> schoolId);

boolean existsByUserIdAndSchoolId(Long userId, Long schoolId);

List<ApplicationSchoolJpaEntity> findAllByApplicationId(Long applicationId);

boolean existsByApplicationId(Long applicationId);


@Query("SELECT COUNT(a) = :size FROM ApplicationSchoolJpaEntity a WHERE a.id IN :applicationSchoolIds")
boolean existsAllByIds(@Param("applicationSchoolIds") List<Long> applicationSchoolIds,
@Param("size") long size);

@Query("""
SELECT p.paymentKey, a.examDate, a.schoolName, a.lunch
FROM ApplicationSchoolJpaEntity a
LEFT JOIN PaymentJpaEntity p ON a.id = p.applicationSchoolId
WHERE a.id = :applicationSchoolId
""")
ApplicationSchoolNotifyProjection findPaymentByApplicationSchoolId(Long applicationSchoolId);

@Query("""
SELECT p.paymentKey, a.examDate, a.schoolName
FROM ApplicationSchoolJpaEntity a
JOIN PaymentJpaEntity p ON a.id = p.applicationSchoolId
WHERE a.id = :applicationSchoolId
""")
OneWeekNotifyProjection findOneWeekNotifyByApplicationSchoolId(Long applicationSchoolId);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package life.mosu.mosuserver.domain.applicationschool;

import java.time.LocalDate;
import life.mosu.mosuserver.domain.application.Lunch;

public record ApplicationSchoolNotifyProjection(
String paymentKey,
LocalDate examDate,
String schoolName,
Lunch lunch
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package life.mosu.mosuserver.domain.applicationschool;

import java.time.LocalDate;

public record OneWeekNotifyProjection(
String paymentKey,
LocalDate examDate,
String schoolName
) {

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
package life.mosu.mosuserver.domain.refund;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

public interface RefundJpaRepository extends JpaRepository<RefundJpaEntity, Long> {

@Query("""
SELECT
p.paymentKey AS paymentKey,
a.examDate AS examDate,
p.paymentMethod AS paymentMethod,
r.reason AS reason
FROM RefundJpaEntity r
LEFT JOIN PaymentJpaEntity p ON r.applicationSchoolId = p.applicationSchoolId
LEFT JOIN ApplicationSchoolJpaEntity a ON r.applicationSchoolId = a.id
WHERE r.applicationSchoolId = :applicationSchoolId
""")
Comment on lines +8 to +18

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The query for findRefundByApplicationSchoolId is missing a.schoolName in the SELECT clause. The RefundNotifyProjection record expects this field, and its absence will cause a mapping error at runtime. Please add a.schoolName AS schoolName to the projection.

    @Query("""
                SELECT
                    p.paymentKey AS paymentKey,
                    a.examDate AS examDate,
                    a.schoolName AS schoolName,
                    p.paymentMethod AS paymentMethod,
                    r.reason AS reason
                FROM RefundJpaEntity r
                LEFT JOIN PaymentJpaEntity p ON r.applicationSchoolId = p.applicationSchoolId
                LEFT JOIN ApplicationSchoolJpaEntity a ON r.applicationSchoolId = a.id
                WHERE r.applicationSchoolId = :applicationSchoolId
            """)

RefundNotifyProjection findRefundByApplicationSchoolId(Long applicationSchoolId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package life.mosu.mosuserver.domain.refund;

import java.time.LocalDate;
import life.mosu.mosuserver.domain.payment.PaymentMethod;

public record RefundNotifyProjection(
String paymentKey,
LocalDate examDate,
String schoolName,
PaymentMethod paymentMethod,
String reason
// TODO: 환불 금액 추가
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package life.mosu.mosuserver.domain.school;

import java.time.LocalDate;
import java.util.List;
import org.apache.commons.lang3.tuple.Triple;

public interface SchoolQueryRepository {

List<Long> findSchoolsByConditions(
List<Triple<String, Area, LocalDate>> conditions);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package life.mosu.mosuserver.domain.school;

import com.querydsl.core.BooleanBuilder;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.time.LocalDate;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.tuple.Triple;
import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
public class SchoolQueryRepositoryImpl implements SchoolQueryRepository {

private final JPAQueryFactory queryFactory;

@Override
public List<Long> findSchoolsByConditions(
List<Triple<String, Area, LocalDate>> conditions) {
BooleanBuilder builder = new BooleanBuilder();

for (Triple<String, Area, LocalDate> cond : conditions) {
builder.or(
QSchoolJpaEntity.schoolJpaEntity.schoolName.eq(cond.getLeft())
.and(QSchoolJpaEntity.schoolJpaEntity.area.eq(cond.getMiddle()))
.and(QSchoolJpaEntity.schoolJpaEntity.examDate.eq(cond.getRight()))
);
}

return queryFactory
.select(QSchoolJpaEntity.schoolJpaEntity.id)
.from(QSchoolJpaEntity.schoolJpaEntity)
.where(builder)
.fetch();
}
Comment on lines +18 to +35
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider adding validation for empty conditions list.

The method doesn't handle the case where the conditions list is empty, which would result in a query with no WHERE clause, potentially returning all schools.

@Override
public List<Long> findSchoolsByConditions(
        List<Triple<String, Area, LocalDate>> conditions) {
+    if (conditions == null || conditions.isEmpty()) {
+        return List.of();
+    }
+    
    BooleanBuilder builder = new BooleanBuilder();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public List<Long> findSchoolsByConditions(
List<Triple<String, Area, LocalDate>> conditions) {
BooleanBuilder builder = new BooleanBuilder();
for (Triple<String, Area, LocalDate> cond : conditions) {
builder.or(
QSchoolJpaEntity.schoolJpaEntity.schoolName.eq(cond.getLeft())
.and(QSchoolJpaEntity.schoolJpaEntity.area.eq(cond.getMiddle()))
.and(QSchoolJpaEntity.schoolJpaEntity.examDate.eq(cond.getRight()))
);
}
return queryFactory
.select(QSchoolJpaEntity.schoolJpaEntity.id)
.from(QSchoolJpaEntity.schoolJpaEntity)
.where(builder)
.fetch();
}
@Override
public List<Long> findSchoolsByConditions(
List<Triple<String, Area, LocalDate>> conditions) {
if (conditions == null || conditions.isEmpty()) {
return List.of();
}
BooleanBuilder builder = new BooleanBuilder();
for (Triple<String, Area, LocalDate> cond : conditions) {
builder.or(
QSchoolJpaEntity.schoolJpaEntity.schoolName.eq(cond.getLeft())
.and(QSchoolJpaEntity.schoolJpaEntity.area.eq(cond.getMiddle()))
.and(QSchoolJpaEntity.schoolJpaEntity.examDate.eq(cond.getRight()))
);
}
return queryFactory
.select(QSchoolJpaEntity.schoolJpaEntity.id)
.from(QSchoolJpaEntity.schoolJpaEntity)
.where(builder)
.fetch();
}
🤖 Prompt for AI Agents
In
src/main/java/life/mosu/mosuserver/domain/school/SchoolQueryRepositoryImpl.java
around lines 18 to 35, the method findSchoolsByConditions does not check if the
conditions list is empty, which can lead to a query without a WHERE clause
returning all records. Add a validation at the start of the method to check if
the conditions list is empty or null, and if so, return an empty list
immediately to prevent unintended full table scans.



}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package life.mosu.mosuserver.global.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Async
@EventListener
public @interface ReactiveEventListener {
Comment on lines +10 to +14
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Annotation name & meta-configuration are misleading

The composed annotation wires @Async + @EventListener, but nothing here is “reactive” in the Project-Reactor sense. This may confuse future maintainers who’ll expect a Flux/Mono contract.

Suggestions:

-@Target(ElementType.METHOD)
-@Retention(RetentionPolicy.RUNTIME)
-@Async
-@EventListener
-public @interface ReactiveEventListener {
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Async
+@EventListener
+public @interface AsyncEventListener { // clearer intent
 }

• Add @Documented for Javadoc visibility.
• Rename to AsyncEventListener (or similar) unless you truly require Reactor types.
Remember to @EnableAsync in a configuration class; otherwise the async executor is never activated.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Async
@EventListener
public @interface ReactiveEventListener {
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Async
@EventListener
public @interface AsyncEventListener { // clearer intent
}
🤖 Prompt for AI Agents
In
src/main/java/life/mosu/mosuserver/global/annotation/ReactiveEventListener.java
around lines 10 to 14, the annotation name and meta-configuration are misleading
because it combines @Async and @EventListener but does not involve any
Project-Reactor reactive types like Flux or Mono. Rename the annotation to
AsyncEventListener to better reflect its behavior, add the @Documented
annotation for Javadoc visibility, and ensure that @EnableAsync is present in a
configuration class to activate the async executor.


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package life.mosu.mosuserver.global.config;

import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;

@Configuration
public class MessageSourceConfig {

@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:messages");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package life.mosu.mosuserver.global.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.WebClient;

@Configuration
public class WebClientConfig {

@Bean
public WebClient webClient() {
return WebClient.builder()
.defaultHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE)
.build();
}
Comment on lines +11 to +16
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Set sensible timeouts & use header constants

A bare WebClient without timeouts risks hanging threads under network issues.

-        return WebClient.builder()
-                .defaultHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE)
-                .build();
+        return WebClient.builder()
+                .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
+                .clientConnector(new ReactorClientHttpConnector(
+                        HttpClient.create()
+                                  .responseTimeout(Duration.ofSeconds(5))
+                                  .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000)
+                ))
+                .build();

• Use HttpHeaders.CONTENT_TYPE constant.
• Configure connect & read timeouts (values adjustable).
This prevents resource leaks and speeds up failure recovery.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Bean
public WebClient webClient() {
return WebClient.builder()
.defaultHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE)
.build();
}
@Bean
public WebClient webClient() {
return WebClient.builder()
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.clientConnector(new ReactorClientHttpConnector(
HttpClient.create()
.responseTimeout(Duration.ofSeconds(5))
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000)
))
.build();
}
🤖 Prompt for AI Agents
In src/main/java/life/mosu/mosuserver/global/config/WebClientConfig.java around
lines 11 to 16, the WebClient bean is created without any timeout settings and
uses a hardcoded string for the Content-Type header. To fix this, replace the
hardcoded "Content-Type" string with the HttpHeaders.CONTENT_TYPE constant, and
configure sensible connect and read timeouts on the WebClient builder using
appropriate timeout values to prevent hanging threads and improve failure
handling.

}
Loading