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,10 +2,12 @@

import java.util.List;
import life.mosu.mosuserver.domain.admin.ApplicationQueryRepositoryImpl;
import life.mosu.mosuserver.domain.admin.RefundQueryRepositoryImpl;
import life.mosu.mosuserver.domain.admin.StudentQueryRepositoryImpl;
import life.mosu.mosuserver.presentation.admin.dto.ApplicationExcelDto;
import life.mosu.mosuserver.presentation.admin.dto.ApplicationFilter;
import life.mosu.mosuserver.presentation.admin.dto.ApplicationListResponse;
import life.mosu.mosuserver.presentation.admin.dto.RefundListResponse;
import life.mosu.mosuserver.presentation.admin.dto.SchoolLunchResponse;
import life.mosu.mosuserver.presentation.admin.dto.StudentExcelDto;
import life.mosu.mosuserver.presentation.admin.dto.StudentFilter;
Expand All @@ -23,6 +25,7 @@ public class AdminService {

private final StudentQueryRepositoryImpl studentQueryRepository;
private final ApplicationQueryRepositoryImpl applicationQueryRepository;
private final RefundQueryRepositoryImpl refundQueryRepository;

public Page<StudentListResponse> getStudents(StudentFilter filter, Pageable pageable) {
return studentQueryRepository.searchAllStudents(filter, pageable);
Expand All @@ -45,4 +48,8 @@ public List<ApplicationExcelDto> getApplicationExcelData() {
return applicationQueryRepository.searchAllApplicationsForExcel();
}

public Page<RefundListResponse> getRefunds(Pageable pageable) {
return refundQueryRepository.searchAllRefunds(pageable);
}

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

import life.mosu.mosuserver.presentation.admin.dto.RefundListResponse;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

public interface RefundQueryRepository {

Page<RefundListResponse> searchAllRefunds(Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package life.mosu.mosuserver.domain.admin;

import static life.mosu.mosuserver.domain.base.BaseTimeEntity.formatDate;

import com.querydsl.core.Tuple;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.util.List;
import life.mosu.mosuserver.domain.application.QApplicationJpaEntity;
import life.mosu.mosuserver.domain.applicationschool.QApplicationSchoolJpaEntity;
import life.mosu.mosuserver.domain.payment.PaymentMethod;
import life.mosu.mosuserver.domain.payment.QPaymentJpaEntity;
import life.mosu.mosuserver.domain.profile.QProfileJpaEntity;
import life.mosu.mosuserver.domain.refund.QRefundJpaEntity;
import life.mosu.mosuserver.presentation.admin.dto.RefundListResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
public class RefundQueryRepositoryImpl implements RefundQueryRepository {

private final JPAQueryFactory queryFactory;

QRefundJpaEntity refund = QRefundJpaEntity.refundJpaEntity;
QApplicationSchoolJpaEntity appSchool = QApplicationSchoolJpaEntity.applicationSchoolJpaEntity;
QApplicationJpaEntity application = QApplicationJpaEntity.applicationJpaEntity;
QProfileJpaEntity profile = QProfileJpaEntity.profileJpaEntity;
QPaymentJpaEntity payment = QPaymentJpaEntity.paymentJpaEntity;
Comment on lines +28 to +32

Choose a reason for hiding this comment

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

medium

These QueryDSL Q-type fields are effectively constants. It's a good practice to declare them as private static final to make it clear they are stateless, shared, and immutable.

Suggested change
QRefundJpaEntity refund = QRefundJpaEntity.refundJpaEntity;
QApplicationSchoolJpaEntity appSchool = QApplicationSchoolJpaEntity.applicationSchoolJpaEntity;
QApplicationJpaEntity application = QApplicationJpaEntity.applicationJpaEntity;
QProfileJpaEntity profile = QProfileJpaEntity.profileJpaEntity;
QPaymentJpaEntity payment = QPaymentJpaEntity.paymentJpaEntity;
private static final QRefundJpaEntity refund = QRefundJpaEntity.refundJpaEntity;
private static final QApplicationSchoolJpaEntity appSchool = QApplicationSchoolJpaEntity.applicationSchoolJpaEntity;
private static final QApplicationJpaEntity application = QApplicationJpaEntity.applicationJpaEntity;
private static final QProfileJpaEntity profile = QProfileJpaEntity.profileJpaEntity;
private static final QPaymentJpaEntity payment = QPaymentJpaEntity.paymentJpaEntity;

Comment on lines +28 to +32
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Make QueryDSL entity references static final fields.

QueryDSL entity references should be static final fields since they're constants and don't need to be instantiated per repository instance.

-    QRefundJpaEntity refund = QRefundJpaEntity.refundJpaEntity;
-    QApplicationSchoolJpaEntity appSchool = QApplicationSchoolJpaEntity.applicationSchoolJpaEntity;
-    QApplicationJpaEntity application = QApplicationJpaEntity.applicationJpaEntity;
-    QProfileJpaEntity profile = QProfileJpaEntity.profileJpaEntity;
-    QPaymentJpaEntity payment = QPaymentJpaEntity.paymentJpaEntity;
+    private static final QRefundJpaEntity refund = QRefundJpaEntity.refundJpaEntity;
+    private static final QApplicationSchoolJpaEntity appSchool = QApplicationSchoolJpaEntity.applicationSchoolJpaEntity;
+    private static final QApplicationJpaEntity application = QApplicationJpaEntity.applicationJpaEntity;
+    private static final QProfileJpaEntity profile = QProfileJpaEntity.profileJpaEntity;
+    private static final QPaymentJpaEntity payment = QPaymentJpaEntity.paymentJpaEntity;
📝 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
QRefundJpaEntity refund = QRefundJpaEntity.refundJpaEntity;
QApplicationSchoolJpaEntity appSchool = QApplicationSchoolJpaEntity.applicationSchoolJpaEntity;
QApplicationJpaEntity application = QApplicationJpaEntity.applicationJpaEntity;
QProfileJpaEntity profile = QProfileJpaEntity.profileJpaEntity;
QPaymentJpaEntity payment = QPaymentJpaEntity.paymentJpaEntity;
private static final QRefundJpaEntity refund = QRefundJpaEntity.refundJpaEntity;
private static final QApplicationSchoolJpaEntity appSchool = QApplicationSchoolJpaEntity.applicationSchoolJpaEntity;
private static final QApplicationJpaEntity application = QApplicationJpaEntity.applicationJpaEntity;
private static final QProfileJpaEntity profile = QProfileJpaEntity.profileJpaEntity;
private static final QPaymentJpaEntity payment = QPaymentJpaEntity.paymentJpaEntity;
🤖 Prompt for AI Agents
In
src/main/java/life/mosu/mosuserver/domain/admin/RefundQueryRepositoryImpl.java
around lines 23 to 27, the QueryDSL entity references are declared as instance
fields but should be static final fields because they are constants. Change the
declarations of refund, appSchool, application, profile, and payment to be
static final to avoid unnecessary instantiation for each repository instance.


@Override
public Page<RefundListResponse> searchAllRefunds(Pageable pageable) {
long total = baseQuery().fetch().size();

List<RefundListResponse> content = baseQuery()
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch()
.stream()
.map(this::mapToResponse)
.toList();

return new PageImpl<>(content, pageable, total);

}

private JPAQuery<Tuple> baseQuery() {
return queryFactory
.select(
refund.id,
appSchool.examinationNumber,
profile.userName,
profile.phoneNumber,
refund.createdAt,
refund.agreedAt,
payment.paymentMethod,
refund.reason
)
.from(refund)
.leftJoin(appSchool).on(refund.applicationSchoolId.eq(appSchool.id))
.leftJoin(application).on(appSchool.applicationId.eq(application.id))
.leftJoin(profile).on(profile.userId.eq(application.userId))
.leftJoin(payment).on(payment.applicationSchoolId.eq(appSchool.id));
}

private RefundListResponse mapToResponse(Tuple tuple) {
PaymentMethod paymentMethod = tuple.get(payment.paymentMethod);
return new RefundListResponse(
tuple.get(refund.id),
tuple.get(appSchool.examinationNumber),
tuple.get(profile.userName),
tuple.get(profile.phoneNumber),
formatDate(tuple.get(refund.createdAt)),
formatDate(tuple.get(refund.agreedAt)),
paymentMethod != null ? paymentMethod.getName() : "N/A",
tuple.get(refund.reason)
);
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
import java.util.List;
import java.util.Optional;
import life.mosu.mosuserver.domain.application.QApplicationJpaEntity;
import life.mosu.mosuserver.domain.profile.Education;
import life.mosu.mosuserver.domain.profile.Gender;
import life.mosu.mosuserver.domain.profile.Grade;
import life.mosu.mosuserver.domain.profile.QProfileJpaEntity;
import life.mosu.mosuserver.presentation.admin.dto.StudentExcelDto;
import life.mosu.mosuserver.presentation.admin.dto.StudentFilter;
Expand Down Expand Up @@ -118,31 +121,39 @@ private long getTotalCount(QProfileJpaEntity profile, String name, String phone)
}

private StudentListResponse mapToResponse(Tuple tuple, QProfileJpaEntity profile) {
Education education = tuple.get(profile.education);
Gender gender = tuple.get(profile.gender);
Grade grade = tuple.get(profile.grade);

Long examCount = Optional.ofNullable(tuple.get(examCountExpr))
.orElse(0L);
return new StudentListResponse(
tuple.get(profile.userName),
tuple.get(profile.birth) != null ? tuple.get(profile.birth).toString() : null,
tuple.get(profile.phoneNumber),
tuple.get(profile.gender) != null ? tuple.get(profile.gender).name() : null,
tuple.get(profile.education),
gender != null ? gender.getGenderName() : null,
education != null ? education.getEducationName() : null,
tuple.get(profile.schoolInfo.schoolName),
tuple.get(profile.grade),
grade != null ? grade.getGradeName() : null,
examCount.intValue()
);
}

private StudentExcelDto mapToExcel(Tuple tuple, QProfileJpaEntity profile) {
Education education = tuple.get(profile.education);
Gender gender = tuple.get(profile.gender);
Grade grade = tuple.get(profile.grade);

Long examCount = Optional.ofNullable(tuple.get(examCountExpr))
.orElse(0L);
return new StudentExcelDto(
tuple.get(profile.userName),
tuple.get(profile.birth) != null ? tuple.get(profile.birth).toString() : null,
tuple.get(profile.phoneNumber),
tuple.get(profile.gender) != null ? tuple.get(profile.gender).name() : null,
tuple.get(profile.education),
gender != null ? gender.getGenderName() : null,
education != null ? education.getEducationName() : null,
tuple.get(profile.schoolInfo.schoolName),
tuple.get(profile.grade),
grade != null ? grade.getGradeName() : null,
examCount.intValue()
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ public abstract class BaseTimeEntity {
@Column(name = "updated_at")
private LocalDateTime updatedAt;

public static String formatDate(java.time.LocalDateTime dateTime) {
return dateTime != null ? dateTime.toLocalDate().toString() : null;
}

public String getCreatedAt() {
return createdAt != null ? createdAt.format(
DateTimeFormatter.ofPattern("yyyy-MM-dd")) : null;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package life.mosu.mosuserver.domain.inquiry;

import static life.mosu.mosuserver.domain.base.BaseTimeEntity.formatDate;

import com.querydsl.core.Tuple;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.ComparableExpressionBase;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Optional;
import life.mosu.mosuserver.presentation.inquiry.dto.InquiryResponse;
Expand All @@ -20,8 +21,6 @@
@RequiredArgsConstructor
public class InquiryQueryRepositoryImpl implements InquiryQueryRepository {

private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");

private final JPAQueryFactory queryFactory;
private final QInquiryJpaEntity inquiry = QInquiryJpaEntity.inquiryJpaEntity;

Expand Down Expand Up @@ -92,14 +91,14 @@ private long getTotalCount(QInquiryJpaEntity inquiry, InquiryStatus status) {
}

private InquiryResponse mapToResponse(Tuple tuple) {
InquiryStatus status = tuple.get(inquiry.status);
return new InquiryResponse(
tuple.get(inquiry.id),
tuple.get(inquiry.title),
tuple.get(inquiry.content),
tuple.get(inquiry.author),
tuple.get(inquiry.status),
tuple.get(inquiry.createdAt) != null ? tuple.get(inquiry.createdAt)
.format(FORMATTER)
: null);
status != null ? status.getStatusName() : "N/A",
formatDate(tuple.get(inquiry.createdAt))
);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
package life.mosu.mosuserver.domain.inquiry;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum InquiryStatus {
PENDING,
COMPLETED,
PENDING("미응답"),
COMPLETED("완료");

private final String statusName;
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package life.mosu.mosuserver.domain.payment;

import java.util.Arrays;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@RequiredArgsConstructor
@Slf4j
@Getter
public enum PaymentMethod {
EASY_PAY("간편결제"),
CARD("카드"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@
import life.mosu.mosuserver.presentation.admin.dto.ApplicationExcelDto;
import life.mosu.mosuserver.presentation.admin.dto.ApplicationFilter;
import life.mosu.mosuserver.presentation.admin.dto.ApplicationListResponse;
import life.mosu.mosuserver.presentation.admin.dto.RefundListResponse;
import life.mosu.mosuserver.presentation.admin.dto.SchoolLunchResponse;
import life.mosu.mosuserver.presentation.admin.dto.StudentExcelDto;
import life.mosu.mosuserver.presentation.admin.dto.StudentFilter;
import life.mosu.mosuserver.presentation.admin.dto.StudentListResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
Expand Down Expand Up @@ -96,4 +98,14 @@ public void downloadApplicationInfo(HttpServletResponse response) throws IOExcep
excelFile.write(response.getOutputStream());
}

@GetMapping("/refunds")
// @PreAuthorize("isAuthenticated() and hasRole('ADMIN')")

Choose a reason for hiding this comment

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

critical

The @PreAuthorize annotation is commented out. This exposes an admin endpoint to any user, which is a critical security vulnerability. Please uncomment this line to ensure only authenticated administrators can access this endpoint.

Suggested change
// @PreAuthorize("isAuthenticated() and hasRole('ADMIN')")
@PreAuthorize("isAuthenticated() and hasRole('ADMIN')")

public ResponseEntity<ApiResponseWrapper<Page<RefundListResponse>>> getRefundCounts(
@PageableDefault(size = 15) Pageable pageable
) {
Page<RefundListResponse> result = adminService.getRefunds(pageable);
return ResponseEntity.ok(
ApiResponseWrapper.success(HttpStatus.OK, "환불 신청 수 조회 성공", result));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package life.mosu.mosuserver.presentation.admin.dto;

public record RefundListResponse(
Long refundId,
String examNumber,
String name,
String phone,
String requestedAt,
String completedAt,
String paymentMethod,
String reason

) {

}
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package life.mosu.mosuserver.presentation.admin.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import life.mosu.mosuserver.domain.profile.Education;
import life.mosu.mosuserver.domain.profile.Grade;
import life.mosu.mosuserver.global.annotation.ExcelColumn;
import lombok.AllArgsConstructor;
import lombok.Getter;
Expand Down Expand Up @@ -30,15 +28,15 @@ public class StudentExcelDto {

@Schema(description = "학력", example = "HIGH_SCHOOL")
@ExcelColumn(headerName = "학력")
private final Education educationLevel;
private final String educationLevel;

@Schema(description = "학교명", example = "서울고등학교")
@ExcelColumn(headerName = "학교명")
private final String schoolName;

@Schema(description = "학년", example = "THIRD")
@ExcelColumn(headerName = "학년")
private final Grade grade;
private final String grade;

@Schema(description = "시험 응시 횟수", example = "2")
@ExcelColumn(headerName = "시험 응시 횟수")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package life.mosu.mosuserver.presentation.admin.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import life.mosu.mosuserver.domain.profile.Education;
import life.mosu.mosuserver.domain.profile.Grade;

@Schema(description = "학생 목록 응답 DTO")
public record StudentListResponse(
Expand All @@ -20,13 +18,13 @@ public record StudentListResponse(
String gender,

@Schema(description = "학력", example = "ENROLLED")
Education educationLevel,
String educationLevel,

@Schema(description = "학교명", example = "서울고등학교")
String schoolName,

@Schema(description = "학년", example = "HIGH_1")
Grade grade,
String grade,

@Schema(description = "시험 응시 횟수", example = "2")
int examCount
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
import life.mosu.mosuserver.domain.inquiry.InquiryJpaEntity;
import life.mosu.mosuserver.domain.inquiry.InquiryStatus;
import life.mosu.mosuserver.domain.inquiryAnswer.InquiryAnswerJpaEntity;

@Schema(description = "1:1 문의 응답 DTO")
Expand All @@ -16,9 +15,9 @@ public record InquiryDetailResponse(
String content,
@Schema(description = "작성자", example = "홍길동")
String author,
@Schema(description = "문의 상태 (WAITING: 답변 대기, COMPLETED: 답변 완료)", example = "WAITING")
InquiryStatus status,
@Schema(description = "문의 등록일", example = "2025-07-10T10:00:00")
@Schema(description = "문의 상태 (미응답, 완료)", example = "완료")
String status,
@Schema(description = "문의 등록일", example = "2025-07-10")
String createdAt,

List<AttachmentDetailResponse> attachments,
Expand All @@ -36,7 +35,7 @@ public static InquiryDetailResponse of(
inquiry.getTitle(),
inquiry.getContent(),
inquiry.getAuthor(),
inquiry.getStatus(),
inquiry.getStatus().getStatusName(),
inquiry.getCreatedAt(),
Comment on lines +38 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

Potential date formatting inconsistency.

The factory method uses inquiry.getCreatedAt() directly, while the repository implementation uses formatDate(). This could lead to inconsistent date formatting across the application.

Consider using the centralized date formatting:

-                inquiry.getCreatedAt(),
+                formatDate(inquiry.getCreatedAt()),

You'll need to add the static import:

+import static life.mosu.mosuserver.domain.base.BaseTimeEntity.formatDate;
📝 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
inquiry.getStatus().getStatusName(),
inquiry.getCreatedAt(),
// Add at the top of src/main/java/life/mosu/mosuserver/presentation/inquiry/dto/InquiryDetailResponse.java
import static life.mosu.mosuserver.domain.base.BaseTimeEntity.formatDate;
// … inside your factory method …
inquiry.getStatus().getStatusName(),
formatDate(inquiry.getCreatedAt()),
// …
🤖 Prompt for AI Agents
In
src/main/java/life/mosu/mosuserver/presentation/inquiry/dto/InquiryDetailResponse.java
around lines 38 to 39, the code uses inquiry.getCreatedAt() directly, which may
cause inconsistent date formatting compared to other parts of the application
that use formatDate(). To fix this, replace inquiry.getCreatedAt() with a call
to the centralized formatDate() method to ensure consistent date formatting.
Also, add the necessary static import for formatDate() at the top of the file.

attachments,
answer
Expand Down
Loading