From effbf11ed82d5c41ea174bd05d0554bcac35b7c3 Mon Sep 17 00:00:00 2001 From: chominju Date: Wed, 9 Jul 2025 00:43:42 +0900 Subject: [PATCH 01/17] =?UTF-8?q?MOSU-39=20feat:=20=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=EC=9E=90=20=EC=8B=A0=EC=B2=AD=20=EB=82=B4=EC=97=AD=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=EA=B4=80=EB=A0=A8,=20=ED=95=99=EA=B5=90=EB=B3=84=20?= =?UTF-8?q?=EB=8F=84=EC=8B=9C=EB=9D=BD=20=EC=88=98=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?controller=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/admin/AdminController.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/main/java/life/mosu/mosuserver/presentation/admin/AdminController.java b/src/main/java/life/mosu/mosuserver/presentation/admin/AdminController.java index bdd35b7e..42365283 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/admin/AdminController.java +++ b/src/main/java/life/mosu/mosuserver/presentation/admin/AdminController.java @@ -9,6 +9,10 @@ import life.mosu.mosuserver.application.admin.AdminService; import life.mosu.mosuserver.global.util.ApiResponseWrapper; import life.mosu.mosuserver.global.util.excel.SimpleExcelFile; +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.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; @@ -17,6 +21,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; @@ -30,6 +35,7 @@ public class AdminController { private final AdminService adminService; @GetMapping("/students") + @PreAuthorize("isAuthenticated() and hasRole('ADMIN')") public ResponseEntity>> getStudents( @Valid @ModelAttribute StudentFilter filter, Pageable pageable @@ -39,6 +45,7 @@ public ResponseEntity>> getStudents } @GetMapping("/excel/students") + @PreAuthorize("isAuthenticated() and hasRole('ADMIN')") public void downloadStudentInfo( HttpServletResponse response) throws IOException { String fileName = URLEncoder.encode("학생정보목록.xlsx", StandardCharsets.UTF_8) @@ -55,4 +62,39 @@ public void downloadStudentInfo( excelFile.write(response.getOutputStream()); } + @GetMapping("/lunches") + @PreAuthorize("isAuthenticated() and hasRole('ADMIN')") + public ResponseEntity>> getLunchCounts() { + List result = adminService.getLunchCounts(); + return ResponseEntity.ok( + ApiResponseWrapper.success(HttpStatus.OK, "학교별 도시락 신청 수 조회 성공", result)); + } + + @GetMapping("/applications") + @PreAuthorize("isAuthenticated() and hasRole('ADMIN')") + public ResponseEntity>> getApplications( + @Valid @ModelAttribute ApplicationFilter filter, + Pageable pageable + ) { + Page result = adminService.getApplications(filter, pageable); + return ResponseEntity.ok(ApiResponseWrapper.success(HttpStatus.OK, "신청 목록 조회 성공", result)); + } + + @GetMapping("/excel/applications") + @PreAuthorize("isAuthenticated() and hasRole('ADMIN')") + public void downloadApplicationInfo(HttpServletResponse response) throws IOException { + String fileName = URLEncoder.encode("신청 목록.xlsx", StandardCharsets.UTF_8) + .replaceAll("\\+", "%20"); + + response.setContentType( + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + fileName); + + List data = adminService.getApplicationExcelData(); + SimpleExcelFile excelFile = new SimpleExcelFile<>(data, + ApplicationExcelDto.class); + + excelFile.write(response.getOutputStream()); + } + } From 0310b8724fa1d752d856bdc7ee8af9f8ddd3847f Mon Sep 17 00:00:00 2001 From: chominju Date: Wed, 9 Jul 2025 00:44:13 +0900 Subject: [PATCH 02/17] =?UTF-8?q?MOSU-39=20feat:=20=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=EC=9E=90=20=EC=8B=A0=EC=B2=AD=20=EB=82=B4=EC=97=AD=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=EA=B4=80=EB=A0=A8,=20=ED=95=99=EA=B5=90=EB=B3=84=20?= =?UTF-8?q?=EB=8F=84=EC=8B=9C=EB=9D=BD=20=EC=88=98=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?service=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/admin/AdminService.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/main/java/life/mosu/mosuserver/application/admin/AdminService.java b/src/main/java/life/mosu/mosuserver/application/admin/AdminService.java index 106a1c91..5f6c7293 100644 --- a/src/main/java/life/mosu/mosuserver/application/admin/AdminService.java +++ b/src/main/java/life/mosu/mosuserver/application/admin/AdminService.java @@ -1,7 +1,12 @@ package life.mosu.mosuserver.application.admin; import java.util.List; +import life.mosu.mosuserver.domain.admin.ApplicationQueryRepositoryImpl; 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.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; @@ -17,6 +22,7 @@ public class AdminService { private final StudentQueryRepositoryImpl studentQueryRepository; + private final ApplicationQueryRepositoryImpl applicationQueryRepository; public Page getStudents(StudentFilter filter, Pageable pageable) { return studentQueryRepository.searchAllStudents(filter, pageable); @@ -25,4 +31,18 @@ public Page getStudents(StudentFilter filter, Pageable page public List getStudentExcelData() { return studentQueryRepository.searchAllStudentsForExcel(); } + + public List getLunchCounts() { + return applicationQueryRepository.searchAllSchoolLunches(); + } + + public Page getApplications(ApplicationFilter filter, + Pageable pageable) { + return applicationQueryRepository.searchAllApplications(filter, pageable); + } + + public List getApplicationExcelData() { + return applicationQueryRepository.searchAllApplicationsForExcel(); + } + } From 745be062d1ea25eea268caac4fc4a18007e71dc4 Mon Sep 17 00:00:00 2001 From: chominju Date: Wed, 9 Jul 2025 00:44:33 +0900 Subject: [PATCH 03/17] =?UTF-8?q?MOSU-39=20feat:=20=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=EC=9E=90=20=EC=8B=A0=EC=B2=AD=20=EB=82=B4=EC=97=AD=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=EA=B4=80=EB=A0=A8,=20=ED=95=99=EA=B5=90=EB=B3=84=20?= =?UTF-8?q?=EB=8F=84=EC=8B=9C=EB=9D=BD=20=EC=88=98=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?repository=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/ApplicationQueryRepository.java | 19 ++ .../admin/ApplicationQueryRepositoryImpl.java | 260 ++++++++++++++++++ 2 files changed, 279 insertions(+) create mode 100644 src/main/java/life/mosu/mosuserver/domain/admin/ApplicationQueryRepository.java create mode 100644 src/main/java/life/mosu/mosuserver/domain/admin/ApplicationQueryRepositoryImpl.java diff --git a/src/main/java/life/mosu/mosuserver/domain/admin/ApplicationQueryRepository.java b/src/main/java/life/mosu/mosuserver/domain/admin/ApplicationQueryRepository.java new file mode 100644 index 00000000..0bf98b5a --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/domain/admin/ApplicationQueryRepository.java @@ -0,0 +1,19 @@ +package life.mosu.mosuserver.domain.admin; + +import java.util.List; +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.SchoolLunchResponse; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface ApplicationQueryRepository { + + Page searchAllApplications(ApplicationFilter filter, + Pageable pageable); + + List searchAllApplicationsForExcel(); + + List searchAllSchoolLunches(); +} diff --git a/src/main/java/life/mosu/mosuserver/domain/admin/ApplicationQueryRepositoryImpl.java b/src/main/java/life/mosu/mosuserver/domain/admin/ApplicationQueryRepositoryImpl.java new file mode 100644 index 00000000..dc162c69 --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/domain/admin/ApplicationQueryRepositoryImpl.java @@ -0,0 +1,260 @@ +package life.mosu.mosuserver.domain.admin; + +import com.querydsl.core.Tuple; +import com.querydsl.core.types.Predicate; +import com.querydsl.core.types.dsl.EnumPath; +import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.jpa.impl.JPAQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.time.Duration; +import java.time.format.DateTimeFormatter; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import life.mosu.mosuserver.domain.application.Lunch; +import life.mosu.mosuserver.domain.application.QAdmissionTicketImageJpaEntity; +import life.mosu.mosuserver.domain.application.QApplicationJpaEntity; +import life.mosu.mosuserver.domain.application.Subject; +import life.mosu.mosuserver.domain.applicationschool.QApplicationSchoolJpaEntity; +import life.mosu.mosuserver.domain.payment.QPaymentJpaEntity; +import life.mosu.mosuserver.domain.profile.QProfileJpaEntity; +import life.mosu.mosuserver.domain.school.QSchoolJpaEntity; +import life.mosu.mosuserver.domain.user.QOAuthUserJpaEntity; +import life.mosu.mosuserver.domain.user.QUserJpaEntity; +import life.mosu.mosuserver.infra.property.S3Properties; +import life.mosu.mosuserver.infra.storage.application.S3Service; +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.SchoolLunchResponse; +import life.mosu.mosuserver.presentation.applicationschool.dto.AdmissionTicketResponse; +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 ApplicationQueryRepositoryImpl implements ApplicationQueryRepository { + + private static final DateTimeFormatter EXCEL_DT_FORMATTER = + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + private final JPAQueryFactory queryFactory; + private final S3Properties s3Properties; + private final S3Service s3Service; + + private final QProfileJpaEntity profile = QProfileJpaEntity.profileJpaEntity; + private final QApplicationJpaEntity application = QApplicationJpaEntity.applicationJpaEntity; + private final QApplicationSchoolJpaEntity applicationSchool = QApplicationSchoolJpaEntity.applicationSchoolJpaEntity; + private final QPaymentJpaEntity payment = QPaymentJpaEntity.paymentJpaEntity; + private final QAdmissionTicketImageJpaEntity admissionTicketImage = QAdmissionTicketImageJpaEntity.admissionTicketImageJpaEntity; + private final QUserJpaEntity user = QUserJpaEntity.userJpaEntity; + private final QOAuthUserJpaEntity oAuthUser = QOAuthUserJpaEntity.oAuthUserJpaEntity; + private final QSchoolJpaEntity school = QSchoolJpaEntity.schoolJpaEntity; + + @Override + public Page searchAllApplications(ApplicationFilter filter, + Pageable pageable) { + + JPAQuery query = baseQuery() + .where( + buildNameCondition(filter.name()), + buildPhoneCondition(filter.phone()) + ) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()); + + List content = query.fetch().stream() + .map(tuple -> { + Long appSchoolId = tuple.get(applicationSchool.id); + Set subjects = appSchoolId != null + ? findSubjectsByApplicationSchoolId(appSchoolId) + : new HashSet<>(); + return mapToResponse(tuple, subjects); + }) + .toList(); + + return new PageImpl<>(content, pageable, content.size()); + } + + @Override + public List searchAllApplicationsForExcel() { + JPAQuery query = baseQuery(); + return query.fetch().stream() + .map(tuple -> { + Long appSchoolId = tuple.get(applicationSchool.id); + Set subjects = appSchoolId != null + ? findSubjectsByApplicationSchoolId(appSchoolId) + : new HashSet<>(); + return mapToExcel(tuple, subjects); + }) + .toList(); + } + + @Override + public List searchAllSchoolLunches() { + return queryFactory + .select( + school.schoolName, + applicationSchool.lunch.count() + ) + .from(applicationSchool) + .rightJoin(school).on(applicationSchool.schoolId.eq(school.id)) + .where(applicationSchool.lunch.ne(Lunch.NONE)) + .groupBy(school.id, school.schoolName) + .fetch() + .stream() + .map(t -> new SchoolLunchResponse( + t.get(school.schoolName), + t.get(applicationSchool.lunch.count()) + )) + .toList(); + } + + + private JPAQuery baseQuery() { + return queryFactory + .select( + applicationSchool.id, + payment.paymentKey, + applicationSchool.examinationNumber, + profile.userName, + profile.gender, + profile.birth, + profile.phoneNumber, + application.guardianPhoneNumber, + profile.education, + profile.schoolInfo.schoolName, + profile.grade, + applicationSchool.lunch, + applicationSchool.schoolName, + applicationSchool.examDate, + admissionTicketImage.s3Key, + admissionTicketImage.fileName, + payment.paymentStatus, + payment.paymentMethod, + application.createdAt + ) + .from(applicationSchool) + .leftJoin(application).on(applicationSchool.applicationId.eq(application.id)) + .leftJoin(payment).on(payment.applicationId.eq(application.id)) + .leftJoin(user).on(application.userId.eq(user.id)) + .leftJoin(oAuthUser).on(application.userId.eq(user.id)) + .leftJoin(profile).on(profile.userId.eq(user.id)) + .leftJoin(admissionTicketImage) + .on(admissionTicketImage.applicationId.eq(application.id)); + } + + private Predicate buildNameCondition(String name) { + return (name == null || name.isBlank()) + ? null + : profile.userName.contains(name); + } + + private Predicate buildPhoneCondition(String phone) { + return (phone == null || phone.isBlank()) + ? null + : profile.phoneNumber.contains(phone); + } + + private Set findSubjectsByApplicationSchoolId(Long applicationSchoolId) { + EnumPath subject = Expressions.enumPath(Subject.class, "subject"); + return new HashSet<>( + queryFactory + .select(subject) + .from(applicationSchool) + .join(applicationSchool.subjects, subject) + .where(applicationSchool.id.eq(applicationSchoolId)) + .fetch() + ); + } + + private ApplicationListResponse mapToResponse(Tuple tuple, Set subjects) { + Set subjectNames = subjects.stream() + .map(Subject::getSubjectName) + .collect(Collectors.toSet()); + + String lunchName = tuple.get(applicationSchool.lunch).getLunchName(); + + String s3Key = tuple.get(admissionTicketImage.s3Key); + String url = getAdmissionTicketImageUrl(s3Key); + + AdmissionTicketResponse admissionTicket = AdmissionTicketResponse.of( + url, + tuple.get(profile.userName), + tuple.get(profile.birth), + tuple.get(applicationSchool.examinationNumber), + subjectNames, + tuple.get(applicationSchool.schoolName) + ); + + return new ApplicationListResponse( + tuple.get(payment.paymentKey), + tuple.get(applicationSchool.examinationNumber), + tuple.get(profile.userName), + tuple.get(profile.gender), + tuple.get(profile.birth), + tuple.get(profile.phoneNumber), + tuple.get(application.guardianPhoneNumber), + tuple.get(profile.education), + tuple.get(profile.schoolInfo.schoolName), + tuple.get(profile.grade), + lunchName, + subjectNames, + tuple.get(applicationSchool.schoolName), + tuple.get(applicationSchool.examDate), + tuple.get(admissionTicketImage.fileName), + tuple.get(payment.paymentStatus), + tuple.get(payment.paymentMethod), + tuple.get(application.createdAt), + admissionTicket + ); + } + + private ApplicationExcelDto mapToExcel(Tuple tuple, Set subjects) { + Set subjectNames = subjects.stream() + .map(Subject::getSubjectName) + .collect(Collectors.toSet()); + + String lunchName = tuple.get(applicationSchool.lunch).getLunchName(); + String genderName = tuple.get(profile.gender).getGenderName(); + String gradeName = tuple.get(profile.grade).getGradeName(); + String educationName = tuple.get(profile.education).getEducationName(); + String appliedAt = tuple.get(application.createdAt) + .format(EXCEL_DT_FORMATTER); + + return new ApplicationExcelDto( + tuple.get(payment.paymentKey), + tuple.get(applicationSchool.examinationNumber), + tuple.get(profile.userName), + genderName, + tuple.get(profile.birth), + tuple.get(profile.phoneNumber), + tuple.get(application.guardianPhoneNumber), + educationName, + tuple.get(profile.schoolInfo.schoolName), + gradeName, + lunchName, + subjectNames, + tuple.get(applicationSchool.schoolName), + tuple.get(applicationSchool.examDate), + tuple.get(admissionTicketImage.fileName), + tuple.get(payment.paymentStatus), + tuple.get(payment.paymentMethod), + appliedAt + ); + } + + private String getAdmissionTicketImageUrl(String s3Key) { + if (s3Key == null || s3Key.isBlank()) { + return null; + } + return s3Service.getPreSignedUrl( + s3Key, + Duration.ofMinutes(s3Properties.getPresignedUrlExpirationMinutes()) + ); + } +} From a2ef70edf9b59f58c7efb3b99360ff397a871cc7 Mon Sep 17 00:00:00 2001 From: chominju Date: Wed, 9 Jul 2025 00:44:54 +0900 Subject: [PATCH 04/17] =?UTF-8?q?MOSU-39=20feat:=20ApplicationListResponse?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/dto/ApplicationListResponse.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/main/java/life/mosu/mosuserver/presentation/admin/dto/ApplicationListResponse.java diff --git a/src/main/java/life/mosu/mosuserver/presentation/admin/dto/ApplicationListResponse.java b/src/main/java/life/mosu/mosuserver/presentation/admin/dto/ApplicationListResponse.java new file mode 100644 index 00000000..7628bcdc --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/presentation/admin/dto/ApplicationListResponse.java @@ -0,0 +1,35 @@ +package life.mosu.mosuserver.presentation.admin.dto; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Set; +import life.mosu.mosuserver.domain.payment.PaymentMethod; +import life.mosu.mosuserver.domain.payment.PaymentStatus; +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.presentation.applicationschool.dto.AdmissionTicketResponse; + +public record ApplicationListResponse( + String paymentNumber, + String examinationNumber, + String name, + Gender gender, + LocalDate birth, + String phoneNumber, + String guardianPhoneNumber, + Education educationLevel, + String schoolName, + Grade grade, + String lunch, + Set subjects, + String examSchoolName, + LocalDate examDate, + String admissionTicketImage, + PaymentStatus paymentStatus, + PaymentMethod paymentMethod, + LocalDateTime applicationDate, + AdmissionTicketResponse admissionTicket +) { + +} From 4bba82d095d615298cd59883b7328af538fdd176 Mon Sep 17 00:00:00 2001 From: chominju Date: Wed, 9 Jul 2025 00:45:09 +0900 Subject: [PATCH 05/17] =?UTF-8?q?MOSU-39=20feat:=20ApplicationExcelDto=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/dto/ApplicationExcelDto.java | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 src/main/java/life/mosu/mosuserver/presentation/admin/dto/ApplicationExcelDto.java diff --git a/src/main/java/life/mosu/mosuserver/presentation/admin/dto/ApplicationExcelDto.java b/src/main/java/life/mosu/mosuserver/presentation/admin/dto/ApplicationExcelDto.java new file mode 100644 index 00000000..b20b826a --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/presentation/admin/dto/ApplicationExcelDto.java @@ -0,0 +1,68 @@ +package life.mosu.mosuserver.presentation.admin.dto; + +import java.time.LocalDate; +import java.util.Set; +import life.mosu.mosuserver.domain.payment.PaymentMethod; +import life.mosu.mosuserver.domain.payment.PaymentStatus; +import life.mosu.mosuserver.global.annotation.ExcelColumn; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public class ApplicationExcelDto { + + @ExcelColumn(headerName = "결제 번호") + private final String paymentNumber; + + @ExcelColumn(headerName = "수험 번호") + private final String examinationNumber; + + @ExcelColumn(headerName = "이름") + private final String name; + + @ExcelColumn(headerName = "성별") + private final String gender; + + @ExcelColumn(headerName = "생년월일") + private final LocalDate birth; + + @ExcelColumn(headerName = "전화번호") + private final String phoneNumber; + + @ExcelColumn(headerName = "보호자 전화번호") + private final String guardianPhoneNumber; + + @ExcelColumn(headerName = "학력") + private final String educationLevel; + + @ExcelColumn(headerName = "학교명") + private final String schoolName; + + @ExcelColumn(headerName = "학년") + private final String grade; + + @ExcelColumn(headerName = "도시락") + private final String lunch; + + @ExcelColumn(headerName = "응시 과목") + private final Set subjects; + + @ExcelColumn(headerName = "시험 학교") + private final String examSchoolName; + + @ExcelColumn(headerName = "시험 일자") + private final LocalDate examDate; + + @ExcelColumn(headerName = "수험표 사진") + private final String admissionTicketImage; + + @ExcelColumn(headerName = "결제 상태") + private final PaymentStatus paymentStatus; + + @ExcelColumn(headerName = "결제 방법") + private final PaymentMethod paymentMethod; + + @ExcelColumn(headerName = "신청 일시") + private final String applicationDate; +} From 1fcacd47c21daa89ebaa456b2c735a77c35bee11 Mon Sep 17 00:00:00 2001 From: chominju Date: Wed, 9 Jul 2025 00:45:25 +0900 Subject: [PATCH 06/17] =?UTF-8?q?MOSU-39=20feat:=20ApplicationFilter=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/admin/dto/ApplicationFilter.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/main/java/life/mosu/mosuserver/presentation/admin/dto/ApplicationFilter.java diff --git a/src/main/java/life/mosu/mosuserver/presentation/admin/dto/ApplicationFilter.java b/src/main/java/life/mosu/mosuserver/presentation/admin/dto/ApplicationFilter.java new file mode 100644 index 00000000..72ac129c --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/presentation/admin/dto/ApplicationFilter.java @@ -0,0 +1,14 @@ +package life.mosu.mosuserver.presentation.admin.dto; + +import life.mosu.mosuserver.global.annotation.PhoneNumberPattern; + +import java.time.LocalDate; + +public record ApplicationFilter( + String name, + @PhoneNumberPattern String phone, + String schoolName, + LocalDate applicationDate +) { + +} From 51e82d6dbca26447a4d082b60d604c63feaf6aae Mon Sep 17 00:00:00 2001 From: chominju Date: Wed, 9 Jul 2025 00:46:11 +0900 Subject: [PATCH 07/17] =?UTF-8?q?MOSU-39=20feat:=20Application=20guardianP?= =?UTF-8?q?honeNumber=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/ApplicationJpaEntity.java | 20 ++++++++++++++----- .../application/dto/ApplicationRequest.java | 5 ++++- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/main/java/life/mosu/mosuserver/domain/application/ApplicationJpaEntity.java b/src/main/java/life/mosu/mosuserver/domain/application/ApplicationJpaEntity.java index 6fd336ee..982e3473 100644 --- a/src/main/java/life/mosu/mosuserver/domain/application/ApplicationJpaEntity.java +++ b/src/main/java/life/mosu/mosuserver/domain/application/ApplicationJpaEntity.java @@ -1,6 +1,11 @@ package life.mosu.mosuserver.domain.application; -import jakarta.persistence.*; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import life.mosu.mosuserver.domain.base.BaseTimeEntity; import lombok.AccessLevel; import lombok.Builder; @@ -21,6 +26,9 @@ public class ApplicationJpaEntity extends BaseTimeEntity { @Column(name = "user_id") private Long userId; + @Column(name = "guardian_phone_number") + private String guardianPhoneNumber; + @Column(name = "recommender_phone_number") private String recommenderPhoneNumber; @@ -33,13 +41,15 @@ public class ApplicationJpaEntity extends BaseTimeEntity { @Builder public ApplicationJpaEntity( - final Long userId, - final String recommenderPhoneNumber, - final boolean agreedToNotices, - final boolean agreedToRefundPolicy + final Long userId, + final String guardianPhoneNumber, + final String recommenderPhoneNumber, + final boolean agreedToNotices, + final boolean agreedToRefundPolicy ) { this.userId = userId; + this.guardianPhoneNumber = guardianPhoneNumber; this.recommenderPhoneNumber = recommenderPhoneNumber; this.agreedToNotices = agreedToNotices; this.agreedToRefundPolicy = agreedToRefundPolicy; diff --git a/src/main/java/life/mosu/mosuserver/presentation/application/dto/ApplicationRequest.java b/src/main/java/life/mosu/mosuserver/presentation/application/dto/ApplicationRequest.java index 07f1df75..1b478e26 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/application/dto/ApplicationRequest.java +++ b/src/main/java/life/mosu/mosuserver/presentation/application/dto/ApplicationRequest.java @@ -3,11 +3,13 @@ import jakarta.validation.constraints.NotNull; import java.util.Set; import life.mosu.mosuserver.domain.application.ApplicationJpaEntity; +import life.mosu.mosuserver.global.annotation.PhoneNumberPattern; import life.mosu.mosuserver.global.util.FileRequest; public record ApplicationRequest( FileRequest admissionTicket, - String recommenderPhoneNumber, + @PhoneNumberPattern String guardianPhoneNumber, + @PhoneNumberPattern String recommenderPhoneNumber, @NotNull Set schools, @NotNull AgreementRequest agreementRequest ) { @@ -15,6 +17,7 @@ public record ApplicationRequest( public ApplicationJpaEntity toEntity(Long userId) { return ApplicationJpaEntity.builder() .userId(userId) + .guardianPhoneNumber(guardianPhoneNumber) .recommenderPhoneNumber(recommenderPhoneNumber) .agreedToNotices(agreementRequest().agreedToNotices()) .agreedToRefundPolicy(agreementRequest().agreedToRefundPolicy()) From 19fb1ce6def443d1c0b535d72f6fd52f2b37e75a Mon Sep 17 00:00:00 2001 From: chominju Date: Wed, 9 Jul 2025 00:47:16 +0900 Subject: [PATCH 08/17] =?UTF-8?q?MOSU-39=20chore:=20ApplicationRequest=20a?= =?UTF-8?q?ddress=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20school=EC=9D=98=20add?= =?UTF-8?q?ress=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/ApplicationService.java | 20 +++++-- .../dto/ApplicationSchoolRequest.java | 59 +++++++++++-------- 2 files changed, 51 insertions(+), 28 deletions(-) diff --git a/src/main/java/life/mosu/mosuserver/application/application/ApplicationService.java b/src/main/java/life/mosu/mosuserver/application/application/ApplicationService.java index 7d639bf8..b969be97 100644 --- a/src/main/java/life/mosu/mosuserver/application/application/ApplicationService.java +++ b/src/main/java/life/mosu/mosuserver/application/application/ApplicationService.java @@ -1,5 +1,6 @@ package life.mosu.mosuserver.application.application; +import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -9,6 +10,9 @@ import life.mosu.mosuserver.domain.application.ApplicationJpaRepository; import life.mosu.mosuserver.domain.application.ApplicationSchoolJpaRepository; import life.mosu.mosuserver.domain.applicationschool.ApplicationSchoolJpaEntity; +import life.mosu.mosuserver.domain.school.AddressJpaVO; +import life.mosu.mosuserver.domain.school.SchoolJpaEntity; +import life.mosu.mosuserver.domain.school.SchoolJpaRepository; import life.mosu.mosuserver.global.exception.CustomRuntimeException; import life.mosu.mosuserver.global.exception.ErrorCode; import life.mosu.mosuserver.global.util.FileRequest; @@ -29,12 +33,13 @@ public class ApplicationService { private final ApplicationJpaRepository applicationJpaRepository; private final ApplicationSchoolJpaRepository applicationSchoolJpaRepository; private final AdmissionTicketImageJpaRepository admissionTicketImageJpaRepository; + private final SchoolJpaRepository schoolJpaRepository; // 신청 @Transactional public ApplicationResponse apply(Long userId, ApplicationRequest request) { Set schools = request.schools(); - List schoolEntities = new java.util.ArrayList<>(List.of()); + List schoolEntities = new ArrayList<>(); Set schoolIds = schools.stream() .map(ApplicationSchoolRequest::schoolId) @@ -45,16 +50,23 @@ public ApplicationResponse apply(Long userId, ApplicationRequest request) { } ApplicationJpaEntity application = request.toEntity(userId); - ApplicationJpaEntity applicationJpaEntity = applicationJpaRepository.save(application); Long applicationId = applicationJpaEntity.getId(); + admissionTicketImageJpaRepository.save( createAdmissionTicketImageIfPresent(request.admissionTicket(), applicationId)); schools.forEach(applicationSchoolRequest -> { + SchoolJpaEntity school = schoolJpaRepository.findById( + applicationSchoolRequest.schoolId()) + .orElseThrow(() -> new CustomRuntimeException(ErrorCode.SCHOOL_NOT_FOUND)); + + AddressJpaVO address = school.getAddress(); ApplicationSchoolJpaEntity applicationSchoolJpaEntity = applicationSchoolRequest.toEntity( - userId, applicationId); - schoolEntities.add(applicationSchoolJpaRepository.save(applicationSchoolJpaEntity)); + userId, applicationId, address); + ApplicationSchoolJpaEntity saved = applicationSchoolJpaRepository.save( + applicationSchoolJpaEntity); + schoolEntities.add(saved); }); return ApplicationResponse.of(applicationId, schoolEntities); diff --git a/src/main/java/life/mosu/mosuserver/presentation/application/dto/ApplicationSchoolRequest.java b/src/main/java/life/mosu/mosuserver/presentation/application/dto/ApplicationSchoolRequest.java index 7cdff731..9418a42f 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/application/dto/ApplicationSchoolRequest.java +++ b/src/main/java/life/mosu/mosuserver/presentation/application/dto/ApplicationSchoolRequest.java @@ -1,45 +1,56 @@ package life.mosu.mosuserver.presentation.application.dto; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.time.LocalDate; +import java.util.Set; +import java.util.stream.Collectors; import life.mosu.mosuserver.domain.application.Lunch; import life.mosu.mosuserver.domain.application.Subject; import life.mosu.mosuserver.domain.applicationschool.ApplicationSchoolJpaEntity; +import life.mosu.mosuserver.domain.school.AddressJpaVO; import life.mosu.mosuserver.domain.school.Area; import life.mosu.mosuserver.global.exception.CustomRuntimeException; import life.mosu.mosuserver.global.exception.ErrorCode; -import java.time.LocalDate; -import java.util.Set; -import java.util.stream.Collectors; - public record ApplicationSchoolRequest( - Long schoolId, - String schoolName, - String area, - AddressRequest address, - LocalDate examDate, - String lunch, - Set subjects + @NotNull(message = "학교 ID는 필수입니다.") + Long schoolId, + + @NotBlank(message = "학교 이름은 필수입니다.") + String schoolName, + + String area, + + @NotNull(message = "시험 날짜는 필수입니다.") + LocalDate examDate, + + @NotBlank(message = "점심 여부는 필수입니다.") + String lunch, + + Set subjects ) { - public ApplicationSchoolJpaEntity toEntity(Long userId, Long applicationId) { + public ApplicationSchoolJpaEntity toEntity(Long userId, Long applicationId, + AddressJpaVO address) { return ApplicationSchoolJpaEntity.builder() - .userId(userId) - .applicationId(applicationId) - .schoolId(schoolId) - .schoolName(schoolName) - .area(validatedArea(area)) - .address(address.toValueObject()) - .examDate(examDate) - .lunch(validatedLunch(lunch)) - .subjects(validatedSubjects(subjects)) - .build(); + .userId(userId) + .applicationId(applicationId) + .schoolId(schoolId) + .schoolName(schoolName) + .area(validatedArea(area)) + .address(address) + .examDate(examDate) + .lunch(validatedLunch(lunch)) + .subjects(validatedSubjects(subjects)) + .build(); } private Set validatedSubjects(Set subjects) { try { return subjects.stream() - .map(Subject::valueOf) - .collect(Collectors.toSet()); + .map(Subject::valueOf) + .collect(Collectors.toSet()); } catch (IllegalArgumentException e) { throw new CustomRuntimeException(ErrorCode.WRONG_SUBJECT_TYPE); } From 4d8d36c5dbcaa4f0fa1226596874dbba5610b6d8 Mon Sep 17 00:00:00 2001 From: chominju Date: Wed, 9 Jul 2025 00:47:35 +0900 Subject: [PATCH 09/17] =?UTF-8?q?MOSU-39=20chore:=20enum=20=EA=B0=92=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mosu/mosuserver/domain/profile/Education.java | 11 +++++++++-- .../mosu/mosuserver/domain/profile/Gender.java | 12 ++++++++++-- .../life/mosu/mosuserver/domain/profile/Grade.java | 14 +++++++++++--- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/main/java/life/mosu/mosuserver/domain/profile/Education.java b/src/main/java/life/mosu/mosuserver/domain/profile/Education.java index ce39120f..a0992a49 100644 --- a/src/main/java/life/mosu/mosuserver/domain/profile/Education.java +++ b/src/main/java/life/mosu/mosuserver/domain/profile/Education.java @@ -1,6 +1,13 @@ package life.mosu.mosuserver.domain.profile; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor public enum Education { - ENROLLED, // 재학생 - GRADUATED // 졸업생 + ENROLLED("재학생"), + GRADUATED("졸업생"); + + private final String educationName; } diff --git a/src/main/java/life/mosu/mosuserver/domain/profile/Gender.java b/src/main/java/life/mosu/mosuserver/domain/profile/Gender.java index 405a34b7..cea7404a 100644 --- a/src/main/java/life/mosu/mosuserver/domain/profile/Gender.java +++ b/src/main/java/life/mosu/mosuserver/domain/profile/Gender.java @@ -1,6 +1,14 @@ package life.mosu.mosuserver.domain.profile; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor public enum Gender { - MALE, - FEMALE, + MALE("남자"), + FEMALE("여자"); + + private final String genderName; + } diff --git a/src/main/java/life/mosu/mosuserver/domain/profile/Grade.java b/src/main/java/life/mosu/mosuserver/domain/profile/Grade.java index 4183c92d..2fa853f0 100644 --- a/src/main/java/life/mosu/mosuserver/domain/profile/Grade.java +++ b/src/main/java/life/mosu/mosuserver/domain/profile/Grade.java @@ -1,7 +1,15 @@ package life.mosu.mosuserver.domain.profile; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor public enum Grade { - HIGH_1, - HIGH_2, - HIGH_3; + HIGH_1("고등학교 1학년"), + HIGH_2("고등학교 2학년"), + HIGH_3("고등학교 3학년"); + + private final String gradeName; + } From a52124ffad04907a4271607a2694b2d1155c4a42 Mon Sep 17 00:00:00 2001 From: chominju Date: Wed, 9 Jul 2025 00:47:54 +0900 Subject: [PATCH 10/17] =?UTF-8?q?MOSU-39=20chore:=20ObjectMapper=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mosuserver/global/config/ObjectMapperConfig.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/life/mosu/mosuserver/global/config/ObjectMapperConfig.java b/src/main/java/life/mosu/mosuserver/global/config/ObjectMapperConfig.java index 3474212e..e88d69ab 100644 --- a/src/main/java/life/mosu/mosuserver/global/config/ObjectMapperConfig.java +++ b/src/main/java/life/mosu/mosuserver/global/config/ObjectMapperConfig.java @@ -1,13 +1,19 @@ package life.mosu.mosuserver.global.config; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class ObjectMapperConfig { + @Bean - ObjectMapper mapper(){ - return new ObjectMapper(); + public ObjectMapper mapper() { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerModule(new JavaTimeModule()); + objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + return objectMapper; } } From 2a54e0b18e2657d30f5ffce5632cedc43fcac572 Mon Sep 17 00:00:00 2001 From: chominju Date: Wed, 9 Jul 2025 00:48:55 +0900 Subject: [PATCH 11/17] =?UTF-8?q?MOSU-39=20chore:=20PhoneNumberPattern=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/annotation/PhoneNumberPattern.java | 6 +- .../profile/dto/EditProfileRequest.java | 7 +- .../profile/dto/ProfileRequest.java | 64 +++++++++---------- 3 files changed, 36 insertions(+), 41 deletions(-) diff --git a/src/main/java/life/mosu/mosuserver/global/annotation/PhoneNumberPattern.java b/src/main/java/life/mosu/mosuserver/global/annotation/PhoneNumberPattern.java index 4ef456c1..d03eb5f6 100644 --- a/src/main/java/life/mosu/mosuserver/global/annotation/PhoneNumberPattern.java +++ b/src/main/java/life/mosu/mosuserver/global/annotation/PhoneNumberPattern.java @@ -9,15 +9,15 @@ import java.lang.annotation.Target; @Pattern( - regexp = "^010-\\d{4}-\\d{4}$", - message = "전화번호 형식은 010-XXXX-XXXX이어야 합니다." + regexp = "^01[016789]-\\d{3,4}-\\d{4}$", + message = "전화번호 형식은 010-XXXX-XXXX 이어야 합니다." ) @Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = {}) public @interface PhoneNumberPattern { - String message() default "전화번호 형식은 010-XXXX-XXXX이어야 합니다."; + String message() default "전화번호 형식은 010-XXXX-XXXX 이어야 합니다."; Class[] groups() default {}; diff --git a/src/main/java/life/mosu/mosuserver/presentation/profile/dto/EditProfileRequest.java b/src/main/java/life/mosu/mosuserver/presentation/profile/dto/EditProfileRequest.java index a04ea4bf..9bbaee08 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/profile/dto/EditProfileRequest.java +++ b/src/main/java/life/mosu/mosuserver/presentation/profile/dto/EditProfileRequest.java @@ -2,10 +2,10 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Pattern; 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.global.annotation.PhoneNumberPattern; import life.mosu.mosuserver.global.exception.CustomRuntimeException; import life.mosu.mosuserver.global.exception.ErrorCode; @@ -22,10 +22,7 @@ public record EditProfileRequest( String gender, @NotBlank(message = "휴대폰 번호는 필수입니다.") - @Pattern( - regexp = "^01[016789]\\d{7,8}$", - message = "휴대폰 번호 형식이 올바르지 않습니다. 예: 01012345678" - ) + @PhoneNumberPattern String phoneNumber, String email, diff --git a/src/main/java/life/mosu/mosuserver/presentation/profile/dto/ProfileRequest.java b/src/main/java/life/mosu/mosuserver/presentation/profile/dto/ProfileRequest.java index 78892a06..19e4ca5f 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/profile/dto/ProfileRequest.java +++ b/src/main/java/life/mosu/mosuserver/presentation/profile/dto/ProfileRequest.java @@ -1,38 +1,36 @@ package life.mosu.mosuserver.presentation.profile.dto; +import com.fasterxml.jackson.annotation.JsonFormat; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Pattern; +import java.time.LocalDate; 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.ProfileJpaEntity; +import life.mosu.mosuserver.global.annotation.PhoneNumberPattern; import life.mosu.mosuserver.global.exception.CustomRuntimeException; import life.mosu.mosuserver.global.exception.ErrorCode; -import java.time.LocalDate; - public record ProfileRequest( - @NotBlank(message = "이름은 필수입니다.") - String userName, - - @NotNull(message = "생년월일은 필수입니다.") - LocalDate birth, - - @NotBlank(message = "성별은 필수입니다.") - String gender, - - @NotBlank(message = "휴대폰 번호는 필수입니다.") - @Pattern( - regexp = "^01[016789]\\d{7,8}$", - message = "휴대폰 번호 형식이 올바르지 않습니다. 예: 01012345678" - ) - String phoneNumber, - - String email, - Education education, - SchoolInfoRequest schoolInfo, - Grade grade + @NotBlank(message = "이름은 필수입니다.") + String userName, + + @JsonFormat(pattern = "yyyy-MM-dd") + @NotNull(message = "생년월일은 필수입니다.") + LocalDate birth, + + @NotBlank(message = "성별은 필수입니다.") + String gender, + + @NotBlank(message = "휴대폰 번호는 필수입니다.") + @PhoneNumberPattern + String phoneNumber, + + String email, + Education education, + SchoolInfoRequest schoolInfo, + Grade grade ) { public Gender validatedGender() { @@ -45,15 +43,15 @@ public Gender validatedGender() { public ProfileJpaEntity toEntity(Long userId) { return ProfileJpaEntity.builder() - .userId(userId) - .userName(userName) - .birth(birth) - .gender(validatedGender()) - .phoneNumber(phoneNumber) - .email(email) - .education(education) - .schoolInfo(schoolInfo != null ? schoolInfo.toEntity() : null) - .grade(grade) - .build(); + .userId(userId) + .userName(userName) + .birth(birth) + .gender(validatedGender()) + .phoneNumber(phoneNumber) + .email(email) + .education(education) + .schoolInfo(schoolInfo != null ? schoolInfo.toEntity() : null) + .grade(grade) + .build(); } } From 27de6176ebcc722661b1897400000cc325e1dfdf Mon Sep 17 00:00:00 2001 From: chominju Date: Wed, 9 Jul 2025 00:50:19 +0900 Subject: [PATCH 12/17] =?UTF-8?q?MOSU-39=20chore:=20response=20subject=20-?= =?UTF-8?q?>=20string=20=ED=83=80=EC=9E=85=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EB=B0=8F=20=EB=B3=80=EA=B2=BD=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ApplicationSchoolService.java | 12 ++++-- .../dto/ApplicationSchoolResponse.java | 38 ++++++++++--------- .../dto/AdmissionTicketResponse.java | 36 +++++++++--------- 3 files changed, 47 insertions(+), 39 deletions(-) diff --git a/src/main/java/life/mosu/mosuserver/application/applicationschool/ApplicationSchoolService.java b/src/main/java/life/mosu/mosuserver/application/applicationschool/ApplicationSchoolService.java index 33ce20f8..0c36b3d3 100644 --- a/src/main/java/life/mosu/mosuserver/application/applicationschool/ApplicationSchoolService.java +++ b/src/main/java/life/mosu/mosuserver/application/applicationschool/ApplicationSchoolService.java @@ -1,11 +1,14 @@ package life.mosu.mosuserver.application.applicationschool; import java.time.Duration; +import java.util.Set; +import java.util.stream.Collectors; import life.mosu.mosuserver.domain.application.AdmissionTicketImageJpaEntity; import life.mosu.mosuserver.domain.application.AdmissionTicketImageJpaRepository; import life.mosu.mosuserver.domain.application.ApplicationJpaEntity; import life.mosu.mosuserver.domain.application.ApplicationJpaRepository; import life.mosu.mosuserver.domain.application.ApplicationSchoolJpaRepository; +import life.mosu.mosuserver.domain.application.Subject; import life.mosu.mosuserver.domain.applicationschool.ApplicationSchoolJpaEntity; import life.mosu.mosuserver.domain.profile.ProfileJpaEntity; import life.mosu.mosuserver.domain.profile.ProfileJpaRepository; @@ -23,7 +26,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.bind.annotation.RequestParam; @Service @RequiredArgsConstructor @@ -41,7 +43,6 @@ public class ApplicationSchoolService { @Transactional public ApplicationSchoolResponse updateSubjects( - @RequestParam Long userId, Long applicationSchoolId, SubjectUpdateRequest request ) { @@ -56,7 +57,6 @@ public ApplicationSchoolResponse updateSubjects( @Transactional public void cancelApplicationSchool( - @RequestParam Long userId, Long applicationSchoolId, RefundRequest request ) { @@ -104,12 +104,16 @@ public AdmissionTicketResponse getAdmissionTicket(Long userId, Long applicationS AdmissionTicketImageJpaEntity admissionTicketImage = admissionTicketImageJpaRepository.findByApplicationId( application.getId()); + Set subjectNames = applicationSchool.getSubjects().stream() + .map(Subject::getSubjectName) + .collect(Collectors.toSet()); + return AdmissionTicketResponse.of( getAdmissionTicketImageUrl(admissionTicketImage), profile.getUserName(), profile.getBirth(), applicationSchool.getExaminationNumber(), - applicationSchool.getSubjects(), + subjectNames, applicationSchool.getSchoolName() ); diff --git a/src/main/java/life/mosu/mosuserver/presentation/application/dto/ApplicationSchoolResponse.java b/src/main/java/life/mosu/mosuserver/presentation/application/dto/ApplicationSchoolResponse.java index a2d60a27..746f2bc0 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/application/dto/ApplicationSchoolResponse.java +++ b/src/main/java/life/mosu/mosuserver/presentation/application/dto/ApplicationSchoolResponse.java @@ -1,32 +1,36 @@ package life.mosu.mosuserver.presentation.application.dto; +import java.time.LocalDate; +import java.util.Set; +import java.util.stream.Collectors; import life.mosu.mosuserver.domain.application.Lunch; import life.mosu.mosuserver.domain.application.Subject; import life.mosu.mosuserver.domain.applicationschool.ApplicationSchoolJpaEntity; import life.mosu.mosuserver.domain.school.Area; -import java.time.LocalDate; -import java.util.Set; - public record ApplicationSchoolResponse( - Long applicationSchoolId, - Area area, - LocalDate examDate, - String schoolName, - Lunch lunch, - String examinationNumber, - Set subjects + Long applicationSchoolId, + Area area, + LocalDate examDate, + String schoolName, + Lunch lunch, + String examinationNumber, + Set subjects ) { public static ApplicationSchoolResponse from(ApplicationSchoolJpaEntity applicationSchool) { + Set subjectNames = applicationSchool.getSubjects().stream() + .map(Subject::getSubjectName) + .collect(Collectors.toSet()); + return new ApplicationSchoolResponse( - applicationSchool.getId(), - applicationSchool.getArea(), - applicationSchool.getExamDate(), - applicationSchool.getSchoolName(), - applicationSchool.getLunch(), - applicationSchool.getExaminationNumber(), - applicationSchool.getSubjects() + applicationSchool.getId(), + applicationSchool.getArea(), + applicationSchool.getExamDate(), + applicationSchool.getSchoolName(), + applicationSchool.getLunch(), + applicationSchool.getExaminationNumber(), + subjectNames ); } diff --git a/src/main/java/life/mosu/mosuserver/presentation/applicationschool/dto/AdmissionTicketResponse.java b/src/main/java/life/mosu/mosuserver/presentation/applicationschool/dto/AdmissionTicketResponse.java index 619b76c5..20d94c98 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/applicationschool/dto/AdmissionTicketResponse.java +++ b/src/main/java/life/mosu/mosuserver/presentation/applicationschool/dto/AdmissionTicketResponse.java @@ -1,34 +1,34 @@ package life.mosu.mosuserver.presentation.applicationschool.dto; -import life.mosu.mosuserver.domain.application.Subject; - import java.time.LocalDate; import java.util.Set; public record AdmissionTicketResponse( - String admissionTicketImageUrl, - String userName, - LocalDate birth, - String examinationNumber, - Set subjects, - String schoolName -) { - - public static AdmissionTicketResponse of( String admissionTicketImageUrl, String userName, LocalDate birth, String examinationNumber, - Set subjects, + Set subjects, String schoolName +) { + + public static AdmissionTicketResponse of( + String admissionTicketImageUrl, + String userName, + LocalDate birth, + String examinationNumber, + Set subjects, + String schoolName ) { return new AdmissionTicketResponse( - admissionTicketImageUrl, - userName, - birth, - examinationNumber, - subjects, - schoolName + admissionTicketImageUrl, + userName, + birth, + examinationNumber, + subjects, + schoolName ); } + + } From a21f3b0bc290ca99da1c31f318e7003feb28e77e Mon Sep 17 00:00:00 2001 From: chominju Date: Wed, 9 Jul 2025 00:51:01 +0900 Subject: [PATCH 13/17] =?UTF-8?q?MOSU-39=20chore:=20@UserId=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/ApplicationController.java | 20 ++++++---- .../ApplicationSchoolController.java | 40 ++++++++++++------- .../profile/ProfileController.java | 3 +- 3 files changed, 39 insertions(+), 24 deletions(-) diff --git a/src/main/java/life/mosu/mosuserver/presentation/application/ApplicationController.java b/src/main/java/life/mosu/mosuserver/presentation/application/ApplicationController.java index 89f7a111..19c0ab0d 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/application/ApplicationController.java +++ b/src/main/java/life/mosu/mosuserver/presentation/application/ApplicationController.java @@ -1,16 +1,21 @@ package life.mosu.mosuserver.presentation.application; import jakarta.validation.Valid; +import java.util.List; import life.mosu.mosuserver.application.application.ApplicationService; +import life.mosu.mosuserver.global.annotation.UserId; import life.mosu.mosuserver.global.util.ApiResponseWrapper; import life.mosu.mosuserver.presentation.application.dto.ApplicationRequest; import life.mosu.mosuserver.presentation.application.dto.ApplicationResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.*; - -import java.util.List; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; @Slf4j @RestController @@ -22,9 +27,10 @@ public class ApplicationController { //신청 @PostMapping + @PreAuthorize("isAuthenticated() and hasRole('USER')") public ApiResponseWrapper apply( - @RequestParam Long userId, - @Valid @RequestBody ApplicationRequest request + @UserId Long userId, + @Valid @RequestBody ApplicationRequest request ) { ApplicationResponse response = applicationService.apply(userId, request); return ApiResponseWrapper.success(HttpStatus.OK, "신청 성공", response); @@ -32,8 +38,8 @@ public ApiResponseWrapper apply( //전체 신청 내역 조회 @GetMapping - public ApiResponseWrapper> getAll( - @RequestParam Long userId + public ApiResponseWrapper> getApplications( + @UserId Long userId ) { List responses = applicationService.getApplications(userId); return ApiResponseWrapper.success(HttpStatus.OK, "신청 내역 조회 성공", responses); diff --git a/src/main/java/life/mosu/mosuserver/presentation/applicationschool/ApplicationSchoolController.java b/src/main/java/life/mosu/mosuserver/presentation/applicationschool/ApplicationSchoolController.java index 77958c81..26a36cce 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/applicationschool/ApplicationSchoolController.java +++ b/src/main/java/life/mosu/mosuserver/presentation/applicationschool/ApplicationSchoolController.java @@ -1,6 +1,7 @@ package life.mosu.mosuserver.presentation.applicationschool; import life.mosu.mosuserver.application.applicationschool.ApplicationSchoolService; +import life.mosu.mosuserver.global.annotation.UserId; import life.mosu.mosuserver.global.util.ApiResponseWrapper; import life.mosu.mosuserver.presentation.application.dto.ApplicationSchoolResponse; import life.mosu.mosuserver.presentation.applicationschool.dto.AdmissionTicketResponse; @@ -9,7 +10,13 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; @Slf4j @RestController @@ -21,39 +28,42 @@ public class ApplicationSchoolController { @DeleteMapping("/{applicationSchoolId}/cancel") public ApiResponseWrapper cancelApplicationSchool( - @PathVariable("applicationSchoolId") Long applicationSchoolId, - @RequestParam Long userId, - @RequestBody RefundRequest request + @PathVariable("applicationSchoolId") Long applicationSchoolId, + @UserId Long userId, + @RequestBody RefundRequest request ) { - applicationSchoolService.cancelApplicationSchool(userId, applicationSchoolId, request); + applicationSchoolService.cancelApplicationSchool(applicationSchoolId, request); return ApiResponseWrapper.success(HttpStatus.OK, "신청 학교 취소 및 환불 처리 완료"); } @PutMapping("/{applicationSchoolId}/subjects") public ApiResponseWrapper updateSubjects( - @PathVariable("applicationSchoolId") Long applicationSchoolId, - @RequestParam Long userId, - @RequestBody SubjectUpdateRequest request + @PathVariable("applicationSchoolId") Long applicationSchoolId, + @UserId Long userId, + @RequestBody SubjectUpdateRequest request ) { - ApplicationSchoolResponse response = applicationSchoolService.updateSubjects(userId, applicationSchoolId, request); + ApplicationSchoolResponse response = applicationSchoolService.updateSubjects( + applicationSchoolId, request); return ApiResponseWrapper.success(HttpStatus.OK, "신청 과목 수정 성공", response); } @GetMapping("/{applicationSchoolId}") public ApiResponseWrapper getDetail( - @RequestParam Long userId, - @PathVariable("applicationSchoolId") Long applicationSchoolId + @UserId Long userId, + @PathVariable("applicationSchoolId") Long applicationSchoolId ) { - ApplicationSchoolResponse response = applicationSchoolService.getApplicationSchool(applicationSchoolId); + ApplicationSchoolResponse response = applicationSchoolService.getApplicationSchool( + applicationSchoolId); return ApiResponseWrapper.success(HttpStatus.OK, "신청 상세 조회 성공", response); } @GetMapping("/{applicationSchoolId}/admissionticket") public ApiResponseWrapper getAdmissionTicket( - @RequestParam Long userId, - @PathVariable("applicationSchoolId") Long applicationSchoolId + @UserId Long userId, + @PathVariable("applicationSchoolId") Long applicationSchoolId ) { - AdmissionTicketResponse response = applicationSchoolService.getAdmissionTicket(userId, applicationSchoolId); + AdmissionTicketResponse response = applicationSchoolService.getAdmissionTicket(userId, + applicationSchoolId); return ApiResponseWrapper.success(HttpStatus.OK, "수험표 조회 성공", response); } } \ No newline at end of file diff --git a/src/main/java/life/mosu/mosuserver/presentation/profile/ProfileController.java b/src/main/java/life/mosu/mosuserver/presentation/profile/ProfileController.java index 84faf60e..01e0ff9b 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/profile/ProfileController.java +++ b/src/main/java/life/mosu/mosuserver/presentation/profile/ProfileController.java @@ -38,7 +38,6 @@ public ApiResponseWrapper create( @PutMapping @PreAuthorize("isAuthenticated()") -// @PreAuthorize("isAuthenticated() and hasRole('USER')") public ApiResponseWrapper update( @UserId Long userId, @Valid @RequestBody EditProfileRequest request @@ -49,7 +48,7 @@ public ApiResponseWrapper update( @GetMapping public ApiResponseWrapper getProfile( - @RequestParam Long userId) { + @UserId Long userId) { ProfileDetailResponse response = profileService.getProfile(userId); return ApiResponseWrapper.success(HttpStatus.OK, "프로필 조회 성공", response); } From 3794e2b44313fcd8678d0cc7afc46e1f7ecdf7e6 Mon Sep 17 00:00:00 2001 From: chominju Date: Wed, 9 Jul 2025 00:51:18 +0900 Subject: [PATCH 14/17] =?UTF-8?q?MOSU-39=20chore:=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 ++- src/main/resources/application.yml | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 68fafbc2..b9ef67ee 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,5 @@ out/ ### VS Code ### .vscode/ -docker-compose/.env \ No newline at end of file +docker-compose/.env +docker-compose/.env.local \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 8775232e..32afafd0 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -41,8 +41,7 @@ spring: data: redis: host: ${REDIS_HOST} - port: ${REDIS_EXPORTER} - password: ${REDIS_PASSWORD} + port: ${VELKEY_PORT} management: endpoints: From 946350806757666165cec43fe8cd10665a9c4b89 Mon Sep 17 00:00:00 2001 From: chominju Date: Wed, 9 Jul 2025 00:51:39 +0900 Subject: [PATCH 15/17] =?UTF-8?q?MOSU-39=20feat:=20SchoolJpaRepository=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mosu/mosuserver/domain/school/SchoolJpaRepository.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/main/java/life/mosu/mosuserver/domain/school/SchoolJpaRepository.java diff --git a/src/main/java/life/mosu/mosuserver/domain/school/SchoolJpaRepository.java b/src/main/java/life/mosu/mosuserver/domain/school/SchoolJpaRepository.java new file mode 100644 index 00000000..a034cbf6 --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/domain/school/SchoolJpaRepository.java @@ -0,0 +1,7 @@ +package life.mosu.mosuserver.domain.school; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface SchoolJpaRepository extends JpaRepository { + +} From b6a25ad370f68fcb92ed6cb97ee6cb467159b148 Mon Sep 17 00:00:00 2001 From: chominju Date: Wed, 9 Jul 2025 00:51:56 +0900 Subject: [PATCH 16/17] =?UTF-8?q?MOSU-39=20feat:=20SchoolLunchResponse=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/admin/dto/SchoolLunchResponse.java | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/main/java/life/mosu/mosuserver/presentation/admin/dto/SchoolLunchResponse.java diff --git a/src/main/java/life/mosu/mosuserver/presentation/admin/dto/SchoolLunchResponse.java b/src/main/java/life/mosu/mosuserver/presentation/admin/dto/SchoolLunchResponse.java new file mode 100644 index 00000000..b7c988dc --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/presentation/admin/dto/SchoolLunchResponse.java @@ -0,0 +1,8 @@ +package life.mosu.mosuserver.presentation.admin.dto; + +public record SchoolLunchResponse( + String schoolName, + Long count +) { + +} From 11ff2d14e3897b45606a08f07e106332fadb1113 Mon Sep 17 00:00:00 2001 From: chominju Date: Wed, 9 Jul 2025 00:52:28 +0900 Subject: [PATCH 17/17] =?UTF-8?q?MOSU-39=20chore:=20userRole=20length=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mosuserver/domain/user/UserJpaEntity.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/main/java/life/mosu/mosuserver/domain/user/UserJpaEntity.java b/src/main/java/life/mosu/mosuserver/domain/user/UserJpaEntity.java index 57a995d8..a31e8b61 100644 --- a/src/main/java/life/mosu/mosuserver/domain/user/UserJpaEntity.java +++ b/src/main/java/life/mosu/mosuserver/domain/user/UserJpaEntity.java @@ -1,6 +1,13 @@ package life.mosu.mosuserver.domain.user; -import jakarta.persistence.*; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import life.mosu.mosuserver.domain.base.BaseTimeEntity; import lombok.AccessLevel; import lombok.Builder; @@ -24,15 +31,15 @@ public class UserJpaEntity extends BaseTimeEntity { @Column(name = "password") private String password; - @Column + @Column(name = "user_role", nullable = false, length = 50) @Enumerated(EnumType.STRING) private UserRole userRole; @Builder public UserJpaEntity( - final String loginId, - final String password, - final UserRole userRole + final String loginId, + final String password, + final UserRole userRole ) { this.loginId = loginId; this.password = password;