diff --git a/backend/src/main/java/sw_css/admin/auth/api/AdminAuthController.java b/backend/src/main/java/sw_css/admin/auth/api/AdminAuthController.java index b6e30916..682b4de5 100644 --- a/backend/src/main/java/sw_css/admin/auth/api/AdminAuthController.java +++ b/backend/src/main/java/sw_css/admin/auth/api/AdminAuthController.java @@ -16,8 +16,8 @@ import sw_css.admin.auth.application.dto.request.DeleteFacultyRequest; import sw_css.admin.auth.application.dto.request.RegisterFacultyRequest; import sw_css.member.domain.FacultyMember; -import sw_css.utils.annotation.Admin; -import sw_css.utils.annotation.SuperAdmin; +import sw_css.utils.annotation.AdminInterface; +import sw_css.utils.annotation.SuperAdminInterface; @Validated @RequestMapping("/admin/auth") @@ -29,7 +29,7 @@ public class AdminAuthController { @PostMapping public ResponseEntity registerFaculty( - @Admin FacultyMember facultyMember, + @AdminInterface FacultyMember facultyMember, @RequestBody @Valid RegisterFacultyRequest request) { Long memberId = adminAuthCommandService.registerFaculty(request); return ResponseEntity.created(URI.create("/members/" + memberId)).build(); @@ -37,7 +37,7 @@ public ResponseEntity registerFaculty( @PostMapping("/files") public ResponseEntity registerFaculties( - @Admin FacultyMember facultyMember, + @AdminInterface FacultyMember facultyMember, @RequestPart(value = "file") final MultipartFile file) { adminAuthCommandService.registerFaculties(file); return ResponseEntity.created(URI.create("/admin/faculties")).build(); @@ -45,7 +45,7 @@ public ResponseEntity registerFaculties( @DeleteMapping() public ResponseEntity deleteFaculty( - @SuperAdmin FacultyMember facultyMember, + @SuperAdminInterface FacultyMember facultyMember, @RequestBody @Valid DeleteFacultyRequest request) { adminAuthCommandService.deleteFaculty(request.member_id()); return ResponseEntity.noContent().build(); diff --git a/backend/src/main/java/sw_css/admin/milestone/api/MilestoneHistoryAdminController.java b/backend/src/main/java/sw_css/admin/milestone/api/MilestoneHistoryAdminController.java index 72314e33..059b002c 100644 --- a/backend/src/main/java/sw_css/admin/milestone/api/MilestoneHistoryAdminController.java +++ b/backend/src/main/java/sw_css/admin/milestone/api/MilestoneHistoryAdminController.java @@ -26,6 +26,8 @@ import sw_css.admin.milestone.application.dto.request.MilestoneHistoryRejectRequest; import sw_css.admin.milestone.application.dto.response.MilestoneHistoryResponse; import sw_css.admin.milestone.application.dto.response.MilestoneScoreResponse; +import sw_css.member.domain.FacultyMember; +import sw_css.utils.annotation.AdminInterface; @Validated @RequestMapping("/admin/milestones/histories") @@ -35,10 +37,9 @@ public class MilestoneHistoryAdminController { private final MilestoneHistoryAdminCommandService milestoneHistoryAdminCommandService; private final MilestoneHistoryAdminQueryService milestoneHistoryAdminQueryService; - // TODO 관리자만 호출할 수 있도록 권한 설정 - @GetMapping public ResponseEntity> findAllMilestoneHistory( + @AdminInterface FacultyMember facultyMember, @RequestParam(value = "field", required = false) final Integer field, @RequestParam(value = "keyword", required = false) final String keyword, final Pageable pageable) { @@ -47,6 +48,7 @@ public ResponseEntity> findAllMilestoneHistory( @GetMapping("/files") public ResponseEntity downloadAllMilestoneHistoryExcelFile( + @AdminInterface FacultyMember facultyMember, @RequestParam(value = "field", required = false) final Integer field, @RequestParam(value = "keyword", required = false) final String keyword ) { @@ -62,38 +64,44 @@ public ResponseEntity downloadAllMilestoneHistoryExcelFile( @GetMapping("/{historyId}") public ResponseEntity findAllMilestoneHistory( + @AdminInterface FacultyMember facultyMember, @PathVariable("historyId") final Long historyId) { return ResponseEntity.ok(milestoneHistoryAdminQueryService.findMilestoneHistory(historyId)); } @PostMapping public ResponseEntity registerMilestoneHistoriesInBatches( + @AdminInterface FacultyMember facultyMember, @RequestPart(value = "file") final MultipartFile file) { milestoneHistoryAdminCommandService.registerMilestoneHistoriesInBatches(file); return ResponseEntity.created(URI.create("/milestones/histories")).build(); } @PatchMapping("/{historyId}/approve") - public ResponseEntity approveMilestoneHistory(@PathVariable("historyId") final Long historyId) { + public ResponseEntity approveMilestoneHistory( + @AdminInterface FacultyMember facultyMember, @PathVariable("historyId") final Long historyId) { milestoneHistoryAdminCommandService.approveMilestoneHistory(historyId); return ResponseEntity.noContent().build(); } @PatchMapping("/{historyId}/reject") - public ResponseEntity approveMilestoneHistory(@PathVariable("historyId") final Long historyId, - @RequestBody @Valid final MilestoneHistoryRejectRequest request) { + public ResponseEntity approveMilestoneHistory( + @AdminInterface FacultyMember facultyMember, @PathVariable("historyId") final Long historyId, + @RequestBody @Valid final MilestoneHistoryRejectRequest request) { milestoneHistoryAdminCommandService.rejectMilestoneHistory(historyId, request); return ResponseEntity.noContent().build(); } @PatchMapping("/{historyId}/cancel") - public ResponseEntity cancelMilestoneHistory(@PathVariable("historyId") final Long historyId) { + public ResponseEntity cancelMilestoneHistory( + @AdminInterface FacultyMember facultyMember, @PathVariable("historyId") final Long historyId) { milestoneHistoryAdminCommandService.cancelMilestoneHistory(historyId); return ResponseEntity.noContent().build(); } @GetMapping("/scores") public ResponseEntity> findAllMilestoneHistoryScores( + @AdminInterface FacultyMember facultyMember, @RequestParam(value = "start_date") final String startDate, @RequestParam(value = "end_date") final String endDate, final Pageable pageable) { @@ -103,6 +111,7 @@ public ResponseEntity> findAllMilestoneHistoryScore @GetMapping("/scores/files") public ResponseEntity downloadMilestoneHistoryScoreExcelFile( + @AdminInterface FacultyMember facultyMember, @RequestParam(value = "start_date") final String startDate, @RequestParam(value = "end_date") final String endDate) { String filename = "마일스톤_점수_현황.xlsx"; diff --git a/backend/src/main/java/sw_css/config/WebConfig.java b/backend/src/main/java/sw_css/config/WebConfig.java index 24d10212..5dd094ff 100644 --- a/backend/src/main/java/sw_css/config/WebConfig.java +++ b/backend/src/main/java/sw_css/config/WebConfig.java @@ -6,20 +6,23 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import sw_css.utils.JwtToken.AdminArgumentResolver; -import sw_css.utils.JwtToken.JwtAuthorizationArgumentResolver; +import sw_css.utils.JwtToken.MemberArgumentResolver; +import sw_css.utils.JwtToken.StudentArgumentResolver; import sw_css.utils.JwtToken.SuperAdminArgumentResolver; @Configuration @RequiredArgsConstructor public class WebConfig implements WebMvcConfigurer { - private final JwtAuthorizationArgumentResolver jwtAuthorizationArgumentResolver; + private final MemberArgumentResolver memberArgumentResolver; + private final StudentArgumentResolver studentArgumentResolver; private final AdminArgumentResolver adminArgumentResolver; private final SuperAdminArgumentResolver superAdminArgumentResolver; @Override public void addArgumentResolvers(List resolvers) { - resolvers.add(jwtAuthorizationArgumentResolver); + resolvers.add(memberArgumentResolver); + resolvers.add(studentArgumentResolver); resolvers.add(adminArgumentResolver); resolvers.add(superAdminArgumentResolver); } diff --git a/backend/src/main/java/sw_css/member/api/MemberController.java b/backend/src/main/java/sw_css/member/api/MemberController.java index 3a56e4ec..a95e7567 100644 --- a/backend/src/main/java/sw_css/member/api/MemberController.java +++ b/backend/src/main/java/sw_css/member/api/MemberController.java @@ -14,7 +14,7 @@ import sw_css.member.application.dto.request.ChangePasswordRequest; import sw_css.member.application.dto.response.StudentMemberResponse; import sw_css.member.domain.Member; -import sw_css.utils.annotation.JwtAuthorization; +import sw_css.utils.annotation.MemberInterface; @Validated @RequestMapping("/members") @@ -29,7 +29,7 @@ public ResponseEntity findStudent(@PathVariable final Lon } @PatchMapping("/change-password") - public ResponseEntity changeMemberPassword(@JwtAuthorization Member me, + public ResponseEntity changeMemberPassword(@MemberInterface Member me, @RequestBody @Valid ChangePasswordRequest request) { memberQueryService.changePassword(me, request.oldPassword(), request.newPassword()); return ResponseEntity.noContent().build(); diff --git a/backend/src/main/java/sw_css/milestone/api/MilestoneHistoryController.java b/backend/src/main/java/sw_css/milestone/api/MilestoneHistoryController.java index 18810afa..e57bee11 100644 --- a/backend/src/main/java/sw_css/milestone/api/MilestoneHistoryController.java +++ b/backend/src/main/java/sw_css/milestone/api/MilestoneHistoryController.java @@ -18,6 +18,8 @@ import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; +import sw_css.member.domain.Member; +import sw_css.member.domain.StudentMember; import sw_css.milestone.application.MilestoneHistoryCommandService; import sw_css.milestone.application.MilestoneHistoryQueryService; import sw_css.milestone.application.dto.request.MilestoneHistoryCreateRequest; @@ -25,6 +27,8 @@ import sw_css.milestone.application.dto.response.MilestoneScoreOfStudentResponse; import sw_css.milestone.domain.MilestoneHistorySortCriteria; import sw_css.milestone.domain.MilestoneStatus; +import sw_css.utils.annotation.MemberInterface; +import sw_css.utils.annotation.StudentInterface; @Validated @RequestMapping("/milestones/histories") @@ -34,27 +38,27 @@ public class MilestoneHistoryController { private final MilestoneHistoryCommandService milestoneHistoryCommandService; private final MilestoneHistoryQueryService milestoneHistoryQueryService; - // TODO 학생만 호출할 수 있도록 권한 설정 @PostMapping public ResponseEntity registerMilestoneHistory( + @StudentInterface StudentMember student, @RequestPart(value = "file", required = false) final MultipartFile file, @RequestPart(value = "request") @Valid final MilestoneHistoryCreateRequest request) { - final Long registeredMilestoneHistoryId = milestoneHistoryCommandService.registerMilestoneHistory(file, + final Long registeredMilestoneHistoryId = milestoneHistoryCommandService.registerMilestoneHistory(student, file, request); return ResponseEntity.created(URI.create("/milestones/histories/" + registeredMilestoneHistoryId)).build(); } - // TODO 학생만 호출할 수 있도록 권한 설정 @DeleteMapping("/{historyId}") - public ResponseEntity deleteMilestoneHistory(@PathVariable("historyId") final Long historyId) { - milestoneHistoryCommandService.deleteMilestoneHistory(historyId); + public ResponseEntity deleteMilestoneHistory( + @StudentInterface StudentMember student, @PathVariable("historyId") final Long historyId) { + milestoneHistoryCommandService.deleteMilestoneHistory(student, historyId); return ResponseEntity.noContent().build(); } - // TODO 학생 본인 혹은 관리자만 호출할 수 있도록 권한 설정 @GetMapping("/members/{memberId}") public ResponseEntity> findAllMilestoneHistories( final Pageable pageable, + @MemberInterface Member me, @PathVariable("memberId") final Long memberId, @RequestParam(value = "start_date", required = false) final String startDate, @RequestParam(value = "end_date", required = false) final String endDate, @@ -67,10 +71,9 @@ public ResponseEntity> findAllMilestoneH sortDirection, pageable)); } - - // TODO 학생 본인 혹은 관리자만 호출할 수 있도록 권한 설정 @GetMapping("/scores/members/{memberId}") public ResponseEntity> findAllMilestoneHistoryScores( + @MemberInterface Member me, @PathVariable("memberId") final Long memberId, @RequestParam(value = "start_date") final String startDate, @RequestParam(value = "end_date") final String endDate) { diff --git a/backend/src/main/java/sw_css/milestone/application/MilestoneHistoryCommandService.java b/backend/src/main/java/sw_css/milestone/application/MilestoneHistoryCommandService.java index e4183583..65fb1658 100644 --- a/backend/src/main/java/sw_css/milestone/application/MilestoneHistoryCommandService.java +++ b/backend/src/main/java/sw_css/milestone/application/MilestoneHistoryCommandService.java @@ -13,9 +13,6 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import sw_css.member.domain.StudentMember; -import sw_css.member.domain.repository.StudentMemberRepository; -import sw_css.member.exception.MemberException; -import sw_css.member.exception.MemberExceptionType; import sw_css.milestone.application.dto.request.MilestoneHistoryCreateRequest; import sw_css.milestone.domain.Milestone; import sw_css.milestone.domain.MilestoneHistory; @@ -35,19 +32,15 @@ public class MilestoneHistoryCommandService { private String filePathPrefix; // TODO 테스트 작성 - private final StudentMemberRepository studentMemberRepository; private final MilestoneRepository milestoneRepository; private final MilestoneHistoryRepository milestoneHistoryRepository; - public Long registerMilestoneHistory(final MultipartFile file, final MilestoneHistoryCreateRequest request) { + public Long registerMilestoneHistory(final StudentMember student, final MultipartFile file, + final MilestoneHistoryCreateRequest request) { validateFileType(file); final Milestone milestone = milestoneRepository.findById(request.milestoneId()) .orElseThrow(() -> new MilestoneException(MilestoneExceptionType.NOT_FOUND_MILESTONE)); - // TODO 요청자의 학번을 불러오는 로직 추가 - final StudentMember student = studentMemberRepository.findById(202055558L).orElseThrow( - () -> new MemberException(MemberExceptionType.NOT_FOUND_STUDENT) - ); final String newFilePath = generateFilePath(file); final MilestoneHistory newMilestoneHistory = new MilestoneHistory(milestone, student, request.description(), @@ -100,11 +93,13 @@ private void uploadFile(final MultipartFile file, final String newFilePath) { } } - public void deleteMilestoneHistory(final Long historyId) { + public void deleteMilestoneHistory(final StudentMember student, final Long historyId) { final MilestoneHistory history = milestoneHistoryRepository.findById(historyId) .orElseThrow( () -> new MilestoneHistoryException(MilestoneHistoryExceptionType.NOT_FOUND_MILESTONE_HISTORY)); - + if (!history.getStudentId().equals(student.getId())) { + throw new MilestoneHistoryException(MilestoneHistoryExceptionType.REMOVE_NOT_ALLOWED); + } history.delete(); } } diff --git a/backend/src/main/java/sw_css/milestone/exception/MilestoneHistoryExceptionType.java b/backend/src/main/java/sw_css/milestone/exception/MilestoneHistoryExceptionType.java index 715cac6a..01a22e33 100644 --- a/backend/src/main/java/sw_css/milestone/exception/MilestoneHistoryExceptionType.java +++ b/backend/src/main/java/sw_css/milestone/exception/MilestoneHistoryExceptionType.java @@ -4,6 +4,7 @@ import sw_css.base.BaseExceptionType; public enum MilestoneHistoryExceptionType implements BaseExceptionType { + REMOVE_NOT_ALLOWED(HttpStatus.FORBIDDEN, "해당 마일스톤에 대한 삭제 권한이 없습니다."), NOT_FOUND_MILESTONE_HISTORY(HttpStatus.NOT_FOUND, "해당하는 마일스톤 실적이 존재하지 않습니다."), ALREADY_PROCESSED(HttpStatus.BAD_REQUEST, "해당 마일스톤 실적은 이미 처리된 상태입니다."), NOT_PROCESSED(HttpStatus.BAD_REQUEST, "해당 마일스톤 실적은 처리되지 않은 상태입니다."), diff --git a/backend/src/main/java/sw_css/utils/JwtToken/AdminArgumentResolver.java b/backend/src/main/java/sw_css/utils/JwtToken/AdminArgumentResolver.java index d8b8d909..bd9ab9fa 100644 --- a/backend/src/main/java/sw_css/utils/JwtToken/AdminArgumentResolver.java +++ b/backend/src/main/java/sw_css/utils/JwtToken/AdminArgumentResolver.java @@ -15,7 +15,7 @@ import sw_css.member.exception.MemberExceptionType; import sw_css.utils.JwtToken.exception.JwtTokenException; import sw_css.utils.JwtToken.exception.JwtTokenExceptionType; -import sw_css.utils.annotation.Admin; +import sw_css.utils.annotation.AdminInterface; @Component @RequiredArgsConstructor @@ -26,7 +26,7 @@ public class AdminArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter parameter) { - return parameter.hasParameterAnnotation(Admin.class); + return parameter.hasParameterAnnotation(AdminInterface.class); } @Override diff --git a/backend/src/main/java/sw_css/utils/JwtToken/JwtAuthorizationArgumentResolver.java b/backend/src/main/java/sw_css/utils/JwtToken/MemberArgumentResolver.java similarity index 90% rename from backend/src/main/java/sw_css/utils/JwtToken/JwtAuthorizationArgumentResolver.java rename to backend/src/main/java/sw_css/utils/JwtToken/MemberArgumentResolver.java index 81d69b93..3ee4d0bd 100644 --- a/backend/src/main/java/sw_css/utils/JwtToken/JwtAuthorizationArgumentResolver.java +++ b/backend/src/main/java/sw_css/utils/JwtToken/MemberArgumentResolver.java @@ -13,18 +13,18 @@ import sw_css.member.exception.MemberExceptionType; import sw_css.utils.JwtToken.exception.JwtTokenException; import sw_css.utils.JwtToken.exception.JwtTokenExceptionType; -import sw_css.utils.annotation.JwtAuthorization; +import sw_css.utils.annotation.MemberInterface; @Component @RequiredArgsConstructor -public class JwtAuthorizationArgumentResolver implements HandlerMethodArgumentResolver { +public class MemberArgumentResolver implements HandlerMethodArgumentResolver { private final MemberRepository memberRepository; private final JwtTokenProvider jwtTokenProvider; @Override public boolean supportsParameter(MethodParameter parameter) { - return parameter.hasParameterAnnotation(JwtAuthorization.class); + return parameter.hasParameterAnnotation(MemberInterface.class); } @Override diff --git a/backend/src/main/java/sw_css/utils/JwtToken/StudentArgumentResolver.java b/backend/src/main/java/sw_css/utils/JwtToken/StudentArgumentResolver.java new file mode 100644 index 00000000..948ea00d --- /dev/null +++ b/backend/src/main/java/sw_css/utils/JwtToken/StudentArgumentResolver.java @@ -0,0 +1,55 @@ +package sw_css.utils.JwtToken; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.core.MethodParameter; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; +import sw_css.member.domain.Member; +import sw_css.member.domain.repository.MemberRepository; +import sw_css.member.domain.repository.StudentMemberRepository; +import sw_css.member.exception.MemberException; +import sw_css.member.exception.MemberExceptionType; +import sw_css.utils.JwtToken.exception.JwtTokenException; +import sw_css.utils.JwtToken.exception.JwtTokenExceptionType; +import sw_css.utils.annotation.StudentInterface; + +@Component +@RequiredArgsConstructor +public class StudentArgumentResolver implements HandlerMethodArgumentResolver { + private final MemberRepository memberRepository; + private final JwtTokenProvider jwtTokenProvider; + private final StudentMemberRepository studentMemberRepository; + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.hasParameterAnnotation(StudentInterface.class); + } + + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + HttpServletRequest httpServletRequest = webRequest.getNativeRequest(HttpServletRequest.class); + + if (httpServletRequest == null) { + throw new JwtTokenException(JwtTokenExceptionType.JWT_TOKEN_INACCESSIBLE); + } + + String token = httpServletRequest.getHeader("Authorization"); + if (token == null || token.trim().isEmpty()) { + throw new JwtTokenException(JwtTokenExceptionType.JWT_TOKEN_EMPTY); + } + + jwtTokenProvider.validateToken(token); + + long userId = jwtTokenProvider.getUserId(token); + Member member = memberRepository.findById(userId) + .orElseThrow(() -> new MemberException(MemberExceptionType.MEMBER_NOT_FOUND)); + + return studentMemberRepository.findByMemberId(member.getId()) + .orElseThrow(() -> new JwtTokenException(JwtTokenExceptionType.JWT_TOKEN_INACCESSIBLE)); + } +} diff --git a/backend/src/main/java/sw_css/utils/JwtToken/SuperAdminArgumentResolver.java b/backend/src/main/java/sw_css/utils/JwtToken/SuperAdminArgumentResolver.java index 31dc9905..593db4ff 100644 --- a/backend/src/main/java/sw_css/utils/JwtToken/SuperAdminArgumentResolver.java +++ b/backend/src/main/java/sw_css/utils/JwtToken/SuperAdminArgumentResolver.java @@ -15,7 +15,7 @@ import sw_css.member.exception.MemberExceptionType; import sw_css.utils.JwtToken.exception.JwtTokenException; import sw_css.utils.JwtToken.exception.JwtTokenExceptionType; -import sw_css.utils.annotation.Admin; +import sw_css.utils.annotation.SuperAdminInterface; @Component @RequiredArgsConstructor @@ -26,7 +26,7 @@ public class SuperAdminArgumentResolver implements HandlerMethodArgumentResolver @Override public boolean supportsParameter(MethodParameter parameter) { - return parameter.hasParameterAnnotation(Admin.class); + return parameter.hasParameterAnnotation(SuperAdminInterface.class); } @Override diff --git a/backend/src/main/java/sw_css/utils/annotation/Admin.java b/backend/src/main/java/sw_css/utils/annotation/AdminInterface.java similarity index 89% rename from backend/src/main/java/sw_css/utils/annotation/Admin.java rename to backend/src/main/java/sw_css/utils/annotation/AdminInterface.java index 0d149928..7646a1b5 100644 --- a/backend/src/main/java/sw_css/utils/annotation/Admin.java +++ b/backend/src/main/java/sw_css/utils/annotation/AdminInterface.java @@ -7,6 +7,6 @@ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) -public @interface Admin { +public @interface AdminInterface { boolean required() default true; } diff --git a/backend/src/main/java/sw_css/utils/annotation/JwtAuthorization.java b/backend/src/main/java/sw_css/utils/annotation/MemberInterface.java similarity index 89% rename from backend/src/main/java/sw_css/utils/annotation/JwtAuthorization.java rename to backend/src/main/java/sw_css/utils/annotation/MemberInterface.java index 6a886fb5..5eb9a495 100644 --- a/backend/src/main/java/sw_css/utils/annotation/JwtAuthorization.java +++ b/backend/src/main/java/sw_css/utils/annotation/MemberInterface.java @@ -7,7 +7,7 @@ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) -public @interface JwtAuthorization { +public @interface MemberInterface { boolean required() default true; } diff --git a/backend/src/main/java/sw_css/utils/annotation/SuperAdmin.java b/backend/src/main/java/sw_css/utils/annotation/StudentInterface.java similarity index 89% rename from backend/src/main/java/sw_css/utils/annotation/SuperAdmin.java rename to backend/src/main/java/sw_css/utils/annotation/StudentInterface.java index c13cfb3a..0fe2ff6e 100644 --- a/backend/src/main/java/sw_css/utils/annotation/SuperAdmin.java +++ b/backend/src/main/java/sw_css/utils/annotation/StudentInterface.java @@ -7,6 +7,6 @@ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) -public @interface SuperAdmin { +public @interface StudentInterface { boolean required() default true; } diff --git a/backend/src/main/java/sw_css/utils/annotation/SuperAdminInterface.java b/backend/src/main/java/sw_css/utils/annotation/SuperAdminInterface.java new file mode 100644 index 00000000..548c807f --- /dev/null +++ b/backend/src/main/java/sw_css/utils/annotation/SuperAdminInterface.java @@ -0,0 +1,12 @@ +package sw_css.utils.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface SuperAdminInterface { + boolean required() default true; +} diff --git a/backend/src/main/resources/test-data.sql b/backend/src/main/resources/test-data.sql index 82e6aca9..63754c40 100644 --- a/backend/src/main/resources/test-data.sql +++ b/backend/src/main/resources/test-data.sql @@ -11,7 +11,7 @@ values ( 'ddang@pusan.ac.kr', '이다은', '$2a$10$YyiOL/E5WjKrZPkB6eQSK.PwZtAO. , false); insert into student_member (id, member_id, major_id, minor_id, double_major_id, career, career_detail) -values (202055555, 3, 1, null, null, 'GRADUATE_SCHOOL', 'IT 기업 개발자'); +values (202055555, 2, 1, null, null, 'GRADUATE_SCHOOL', 'IT 기업 개발자'); ## milestone histories INSERT INTO sw_css.milestone_history (id, milestone_id, student_id, description, file_url, status, reject_reason, count, diff --git a/backend/src/test/java/sw_css/restdocs/RestDocsTest.java b/backend/src/test/java/sw_css/restdocs/RestDocsTest.java index 3b35c8e2..9b382b26 100644 --- a/backend/src/test/java/sw_css/restdocs/RestDocsTest.java +++ b/backend/src/test/java/sw_css/restdocs/RestDocsTest.java @@ -31,7 +31,8 @@ import sw_css.milestone.application.MilestoneHistoryQueryService; import sw_css.milestone.application.MilestoneQueryService; import sw_css.utils.JwtToken.AdminArgumentResolver; -import sw_css.utils.JwtToken.JwtAuthorizationArgumentResolver; +import sw_css.utils.JwtToken.MemberArgumentResolver; +import sw_css.utils.JwtToken.StudentArgumentResolver; import sw_css.utils.JwtToken.SuperAdminArgumentResolver; @Import(RestDocsConfiguration.class) @@ -84,7 +85,10 @@ public abstract class RestDocsTest extends ApiTestHelper { protected MemberRepository memberRepository; @MockBean - protected JwtAuthorizationArgumentResolver jwtAuthorizationArgumentResolver; + protected MemberArgumentResolver memberArgumentResolver; + + @MockBean + protected StudentArgumentResolver studentArgumentResolver; @MockBean protected AdminArgumentResolver adminArgumentResolver; diff --git a/backend/src/test/java/sw_css/restdocs/docs/MilestoneHistoryApiDocsTest.java b/backend/src/test/java/sw_css/restdocs/docs/MilestoneHistoryApiDocsTest.java index fdf4f2c4..cdd5201d 100644 --- a/backend/src/test/java/sw_css/restdocs/docs/MilestoneHistoryApiDocsTest.java +++ b/backend/src/test/java/sw_css/restdocs/docs/MilestoneHistoryApiDocsTest.java @@ -22,6 +22,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; +import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.mock.web.MockMultipartFile; import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; @@ -59,21 +60,28 @@ public void registerMilestoneHistory() throws Exception { partWithName("file").description("증빙 자료 파일(jpg, jpeg, png, pdf)") ); + final StudentMember student = new StudentMember(202055500L, + new Member(1L, "abc@naver.com", "홍길동", "password", "010-0000-0000", false), + new Major(1L, new College(1L, "인문대학"), "사회학과"), null, null, CareerType.EMPLOYMENT_COMPANY, + "IT 사기업 개발자로 취업"); final MockMultipartFile file = new MockMultipartFile("file", "test.png", "multipart/form-data", "example".getBytes()); final MilestoneHistoryCreateRequest request = new MilestoneHistoryCreateRequest(1L, "대회 수상했습니다.", 3, LocalDate.parse("2024-06-05")); final MockMultipartFile requestFile = new MockMultipartFile("request", null, "application/json", objectMapper.writeValueAsString(request).getBytes()); + final String token = "Bearer AccessToken"; + // when - when(milestoneHistoryCommandService.registerMilestoneHistory(file, request)).thenReturn(1L); + when(milestoneHistoryCommandService.registerMilestoneHistory(student, file, request)).thenReturn(1L); // then mockMvc.perform(multipart("/milestones/histories") .file(file) .file(requestFile) .contentType(MediaType.MULTIPART_MIXED) - .content(objectMapper.writeValueAsString(request))) + .content(objectMapper.writeValueAsString(request)) + .header(HttpHeaders.AUTHORIZATION, token)) .andExpect(status().isCreated()) .andDo(document("milestone-history-create", requestPartsSnippet)); } @@ -85,13 +93,19 @@ public void deleteMilestoneHistory() throws Exception { final PathParametersSnippet pathParameters = pathParameters( parameterWithName("historyId").description("마일스톤 실적의 id") ); + final StudentMember student = new StudentMember(202055500L, + new Member(1L, "abc@naver.com", "홍길동", "password", "010-0000-0000", false), + new Major(1L, new College(1L, "인문대학"), "사회학과"), null, null, CareerType.EMPLOYMENT_COMPANY, + "IT 사기업 개발자로 취업"); final Long historyId = 1L; + final String token = "Bearer AccessToken"; // when - doNothing().when(milestoneHistoryCommandService).deleteMilestoneHistory(historyId); + doNothing().when(milestoneHistoryCommandService).deleteMilestoneHistory(student, historyId); // then - mockMvc.perform(RestDocumentationRequestBuilders.delete("/milestones/histories/{historyId}", historyId)) + mockMvc.perform(RestDocumentationRequestBuilders.delete("/milestones/histories/{historyId}", historyId) + .header(HttpHeaders.AUTHORIZATION, token)) .andExpect(status().isNoContent()) .andDo(document("milestone-history-delete", pathParameters)); } @@ -164,6 +178,7 @@ void findAllMilestoneHistories() throws Exception { final Long memberId = 1L; final String startDate = "2024-06-01"; final String endDate = "2024-06-08"; + final String token = "Bearer AccessToken"; //when when(milestoneHistoryQueryService.findAllMilestoneHistories(eq(memberId), eq(startDate), eq(endDate), any(), @@ -172,7 +187,8 @@ void findAllMilestoneHistories() throws Exception { //then mockMvc.perform(RestDocumentationRequestBuilders.get("/milestones/histories/members/{memberId}", memberId) .param("start_date", startDate) - .param("end_date", endDate)) + .param("end_date", endDate) + .header(HttpHeaders.AUTHORIZATION, token)) .andExpect(status().isOk()) .andDo(document("milestone-history-of-student-find-all", pathParameters, queryParameters, responseBodySnippet)); @@ -207,6 +223,7 @@ void findAllMilestoneHistoryScores() throws Exception { final Long memberId = 1L; final String startDate = "2024-06-01"; final String endDate = "2024-06-08"; + final String token = "Bearer AccessToken"; //when when(milestoneHistoryQueryService.findAllMilestoneHistoryScores(memberId, startDate, endDate)).thenReturn( @@ -216,7 +233,8 @@ void findAllMilestoneHistoryScores() throws Exception { mockMvc.perform( RestDocumentationRequestBuilders.get("/milestones/histories/scores/members/{memberId}", memberId) .param("start_date", startDate) - .param("end_date", endDate)) + .param("end_date", endDate) + .header(HttpHeaders.AUTHORIZATION, token)) .andExpect(status().isOk()) .andDo(document("milestone-history-score-of-student-find-all", pathParameters, queryParameters, responseBodySnippet)); diff --git a/backend/src/test/java/sw_css/restdocs/docs/admin/MilestoneHistoryAdminApiDocsTest.java b/backend/src/test/java/sw_css/restdocs/docs/admin/MilestoneHistoryAdminApiDocsTest.java index d6e293b6..59c11a43 100644 --- a/backend/src/test/java/sw_css/restdocs/docs/admin/MilestoneHistoryAdminApiDocsTest.java +++ b/backend/src/test/java/sw_css/restdocs/docs/admin/MilestoneHistoryAdminApiDocsTest.java @@ -28,6 +28,7 @@ import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.mock.web.MockMultipartFile; import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; @@ -65,13 +66,15 @@ public void approveMilestoneHistory() throws Exception { parameterWithName("historyId").description("마일스톤 실적의 id") ); final Long historyId = 1L; + final String token = "Bearer AccessToken"; // when doNothing().when(milestoneHistoryAdminCommandService).approveMilestoneHistory(historyId); // then mockMvc.perform( - RestDocumentationRequestBuilders.patch("/admin/milestones/histories/{historyId}/approve", historyId)) + RestDocumentationRequestBuilders.patch("/admin/milestones/histories/{historyId}/approve", historyId) + .header(HttpHeaders.AUTHORIZATION, token)) .andExpect(status().isNoContent()) .andDo(document("milestone-history-approve", pathParameters)); } @@ -89,6 +92,7 @@ public void rejectMilestoneHistory() throws Exception { final MilestoneHistoryRejectRequest request = new MilestoneHistoryRejectRequest("증빙자료 불충분"); final Long historyId = 1L; + final String token = "Bearer AccessToken"; // when doNothing().when(milestoneHistoryAdminCommandService).rejectMilestoneHistory(historyId, request); @@ -97,7 +101,8 @@ public void rejectMilestoneHistory() throws Exception { mockMvc.perform( RestDocumentationRequestBuilders.patch("/admin/milestones/histories/{historyId}/reject", historyId) .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request))) + .content(objectMapper.writeValueAsString(request)) + .header(HttpHeaders.AUTHORIZATION, token)) .andExpect(status().isNoContent()) .andDo(document("milestone-history-reject", pathParameters, requestBodySnippet)); } @@ -110,13 +115,15 @@ public void cancelMilestoneHistory() throws Exception { parameterWithName("historyId").description("마일스톤 실적의 id") ); final Long historyId = 1L; + final String token = "Bearer AccessToken"; // when doNothing().when(milestoneHistoryAdminCommandService).cancelMilestoneHistory(historyId); // then mockMvc.perform( - RestDocumentationRequestBuilders.patch("/admin/milestones/histories/{historyId}/cancel", historyId)) + RestDocumentationRequestBuilders.patch("/admin/milestones/histories/{historyId}/cancel", historyId) + .header(HttpHeaders.AUTHORIZATION, token)) .andExpect(status().isNoContent()) .andDo(document("milestone-history-cancel", pathParameters)); } @@ -163,13 +170,15 @@ void findMilestoneHistory() throws Exception { final MilestoneHistoryResponse response = MilestoneHistoryResponse.from(history); final Long historyId = 1L; + final String token = "Bearer AccessToken"; //when when(milestoneHistoryAdminQueryService.findMilestoneHistory(historyId)).thenReturn(response); //then mockMvc.perform( - RestDocumentationRequestBuilders.get("/admin/milestones/histories/" + historyId)) + RestDocumentationRequestBuilders.get("/admin/milestones/histories/" + historyId) + .header(HttpHeaders.AUTHORIZATION, token)) .andExpect(status().isOk()) .andDo(document("milestone-history-find", responseBodySnippet)); @@ -249,13 +258,15 @@ void findAllMilestoneHistories() throws Exception { )); final Page response = MilestoneHistoryResponse.from(milestones, pageable); + final String token = "Bearer AccessToken"; //when when(milestoneHistoryAdminQueryService.findAllMilestoneHistories(any(), any(), any())).thenReturn(response); //then mockMvc.perform( - RestDocumentationRequestBuilders.get("/admin/milestones/histories")) + RestDocumentationRequestBuilders.get("/admin/milestones/histories") + .header(HttpHeaders.AUTHORIZATION, token)) .andExpect(status().isOk()) .andDo(document("milestone-history-find-all", queryParameters, responseBodySnippet)); @@ -272,6 +283,7 @@ public void downloadMilestoneHistoryExcelFile() throws Exception { ); final String field = "1"; final String keyword = "202055558"; + final String token = "Bearer AccessToken"; // when when(milestoneHistoryAdminQueryService.downloadMilestoneHistory(any(), any())).thenReturn(response); @@ -279,7 +291,8 @@ public void downloadMilestoneHistoryExcelFile() throws Exception { // then mockMvc.perform(RestDocumentationRequestBuilders.get("/admin/milestones/histories/files") .param("field", field) - .param("keyword", keyword)) + .param("keyword", keyword) + .header(HttpHeaders.AUTHORIZATION, token)) .andExpect(status().isOk()) .andDo(document("download-history-file", queryParameters)); } @@ -294,6 +307,7 @@ void registerMilestoneHistoriesInBatches() throws Exception { final MockMultipartFile request = new MockMultipartFile("file", "test.xls", "multipart/form-data", "example".getBytes()); + final String token = "Bearer AccessToken"; // when doNothing().when(milestoneHistoryAdminCommandService).registerMilestoneHistoriesInBatches(any()); @@ -302,7 +316,8 @@ void registerMilestoneHistoriesInBatches() throws Exception { mockMvc.perform(multipart("/admin/milestones/histories").file(request) .contentType(MediaType.MULTIPART_FORM_DATA) .accept(MediaType.APPLICATION_JSON) - .characterEncoding("UTF-8")) + .characterEncoding("UTF-8") + .header(HttpHeaders.AUTHORIZATION, token)) .andExpect(status().isCreated()) .andDo(document("milestone-history-create-in-batch", requestPartsSnippet)); } @@ -372,6 +387,7 @@ void findAllMilestoneHistoryScores() throws Exception { ACTIVITY, 100, null), 50))))), PageRequest.of(0, 10), 3); final String startDate = "2024-06-01"; final String endDate = "2024-06-08"; + final String token = "Bearer AccessToken"; //when when(milestoneHistoryAdminQueryService.findAllMilestoneHistoryScores(eq(startDate), eq(endDate), @@ -383,7 +399,8 @@ void findAllMilestoneHistoryScores() throws Exception { .param("start_date", startDate) .param("end_date", endDate) .param("page", "0") - .param("size", "10")) + .param("size", "10") + .header(HttpHeaders.AUTHORIZATION, token)) .andExpect(status().isOk()) .andDo(document("milestone-history-score-find-all", queryParameters, responseBodySnippet)); @@ -400,6 +417,7 @@ public void downloadMilestoneHistoryScoreExcelFile() throws Exception { ); final String startDate = "2024-06-01"; final String endDate = "2024-06-08"; + final String token = "Bearer AccessToken"; // when when(milestoneHistoryAdminQueryService.downloadMilestoneHistoryScore(any(), any())).thenReturn(response); @@ -407,7 +425,8 @@ public void downloadMilestoneHistoryScoreExcelFile() throws Exception { // then mockMvc.perform(RestDocumentationRequestBuilders.get("/admin/milestones/histories/scores/files") .param("start_date", startDate) - .param("end_date", endDate)) + .param("end_date", endDate) + .header(HttpHeaders.AUTHORIZATION, token)) .andExpect(status().isOk()) .andDo(document("download-score-file", queryParameters)); } diff --git a/frontend/src/app/admin/faculty/register/page.tsx b/frontend/src/app/admin/faculty/register/page.tsx index 69e03430..239172a2 100644 --- a/frontend/src/app/admin/faculty/register/page.tsx +++ b/frontend/src/app/admin/faculty/register/page.tsx @@ -60,7 +60,7 @@ const Page = () => { const handlePasswordCopyButtonClick = async () => { try { - await navigator.clipboard.writeText(process.env.FACULTY_PASSWORD ?? ''); + await navigator.clipboard.writeText(process.env.NEXT_PUBLIC_FACULTY_PASSWORD ?? ''); toast.info('비밀번호가 복사되었습니다.'); } catch (e) { toast.error('복사에 실패했습니다.'); @@ -88,7 +88,7 @@ const Page = () => {
  • 임시비밀번호는{' '} - {process.env.FACULTY_PASSWORD} + {process.env.NEXT_PUBLIC_FACULTY_PASSWORD} 입니다.
  • @@ -121,7 +121,7 @@ const Page = () => {
  • 임시비밀번호는{' '} - {process.env.FACULTY_PASSWORD} + {process.env.NEXT_PUBLIC_FACULTY_PASSWORD} 입니다.
  • diff --git a/frontend/src/app/admin/milestone/list/[slug]/page.tsx b/frontend/src/app/admin/milestone/list/[slug]/page.tsx index cd6ebbe6..65d3073b 100644 --- a/frontend/src/app/admin/milestone/list/[slug]/page.tsx +++ b/frontend/src/app/admin/milestone/list/[slug]/page.tsx @@ -8,6 +8,8 @@ import { convertMilestoneHistoryStatus } from '@/lib/utils/utils'; import FilePreview from './components/FilePreview'; import MilestoneHistoryStatusChangeButton from './components/MilestoneHistoryStatusChangeButton'; import { notFound } from 'next/navigation'; +import { AuthSliceState } from '@/store/auth.slice'; +import { getAuthFromCookie } from '@/lib/utils/auth'; interface MilestoneHistoryDetailPageProps { params: { @@ -16,9 +18,10 @@ interface MilestoneHistoryDetailPageProps { } const Page = async ({ params: { slug } }: MilestoneHistoryDetailPageProps) => { + const auth: AuthSliceState = getAuthFromCookie(); let history; try { - history = await getMilestoneHistory(slug); + history = await getMilestoneHistory(slug, auth.token); } catch (e) { // TODO: server api error handling... } diff --git a/frontend/src/app/admin/milestone/list/page.tsx b/frontend/src/app/admin/milestone/list/page.tsx index b07d1af8..af521a30 100644 --- a/frontend/src/app/admin/milestone/list/page.tsx +++ b/frontend/src/app/admin/milestone/list/page.tsx @@ -8,16 +8,20 @@ import { getMilestoneHistories } from '@/lib/api/server.api'; import MilestoneHistoryTable from './components/MilestoneHistoryTable'; import MilestoneHistoryExcelFileDownloadButton from './components/MilestoneHistoryTable/MilestoneHistoryExcelFileDownloadButton.tsx'; +import { AuthSliceState } from '@/store/auth.slice'; +import { getAuthFromCookie } from '@/lib/utils/auth'; const Page = async ({ searchParams }: { searchParams?: { [key: string]: string | undefined } }) => { const headersList = headers(); const pathname = headersList.get('x-pathname') || ''; + const auth: AuthSliceState = getAuthFromCookie(); + const page = searchParams?.page ? parseInt(searchParams.page, 10) : 1; const field = searchParams?.field ? parseInt(searchParams.field, 10) : 0; const keyword = searchParams?.keyword ? searchParams.keyword : ''; - const milestoneHistories = await getMilestoneHistories(field, keyword, page - 1); + const milestoneHistories = await getMilestoneHistories(auth.token, field, keyword, page - 1); return (
    diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index a94e7084..3aa9572c 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -18,7 +18,7 @@ export const metadata: Metadata = { const RootLayout = ({ children }: Readonly<{ children: React.ReactNode }>) => ( - + diff --git a/frontend/src/lib/api/server.api.ts b/frontend/src/lib/api/server.api.ts index c11d309a..771d5e74 100644 --- a/frontend/src/lib/api/server.api.ts +++ b/frontend/src/lib/api/server.api.ts @@ -40,9 +40,16 @@ export async function getMilestoneHistoriesOfStudent( .catch((err) => Promise.reject(err)); } -export async function getMilestoneHistories(field?: number, keyword?: string, page: number = 0, size: number = 10) { +export async function getMilestoneHistories( + token: string, + field?: number, + keyword?: string, + page: number = 0, + size: number = 10, +) { return await server .get('/admin/milestones/histories', { + headers: { Authorization: token }, params: removeEmptyField({ field, keyword, @@ -54,9 +61,9 @@ export async function getMilestoneHistories(field?: number, keyword?: string, pa .catch((err) => Promise.reject(err)); } -export async function getMilestoneHistory(historyId: number) { +export async function getMilestoneHistory(historyId: number, token: string) { return await server - .get(`/admin/milestones/histories/${historyId}`) + .get(`/admin/milestones/histories/${historyId}`, { headers: { Authorization: token } }) .then((res) => res.data) .catch((err) => Promise.reject(err)); } diff --git a/frontend/src/lib/hooks/useAdminApi.ts b/frontend/src/lib/hooks/useAdminApi.ts index 32df64e7..b99837f0 100644 --- a/frontend/src/lib/hooks/useAdminApi.ts +++ b/frontend/src/lib/hooks/useAdminApi.ts @@ -4,14 +4,19 @@ import { client } from '@/lib/api/client.axios'; import { useAxiosMutation, useAxiosQuery } from '@/lib/hooks/useAxios'; import { MilestoneScoreOfStudentPageableDto } from '@/types/common.dto'; import { BusinessError } from '@/types/error'; +import { useAppSelector } from './redux'; +import { headerInfos } from '@/data/clientCategory'; +import { stat } from 'fs'; export function useMilestoneHistoryExcelFileQuery(field: number | null, keyword: string | null) { + const auth = useAppSelector((state) => state.auth).value; return useAxiosQuery({ queryKey: QueryKeys.MILESTONE_HISTORY_EXCEL(field, keyword), queryFn: async (): Promise => { const response = await client.get(`/admin/milestones/histories/files`, { params: { keyword, field }, responseType: 'blob', + headers: { Authorization: auth.token }, }); if (response?.status !== 200) { return null; @@ -26,31 +31,38 @@ export const useMilestoneScoresQuery = ( endDate: string, page: number | undefined, size: number | undefined, -) => - useAxiosQuery({ - queryKey: QueryKeys.MILESTONE_SCORES(startDate, endDate, page, size), - queryFn: async (): Promise => { - try { - const response = await client.get('/admin/milestones/histories/scores', { - params: { start_date: startDate, end_date: endDate, page: page ?? 0, size: size ?? 10 }, - }); - return response.data; - } catch (error) { - if (error instanceof BusinessError) { - return null; +) => { + const auth = useAppSelector((state) => state.auth).value; + return ( + useAxiosQuery({ + queryKey: QueryKeys.MILESTONE_SCORES(startDate, endDate, page, size), + queryFn: async (): Promise => { + try { + const response = await client.get('/admin/milestones/histories/scores', { + params: { start_date: startDate, end_date: endDate, page: page ?? 0, size: size ?? 10 }, + headers: { Authorization: auth.token }, + }); + return response.data; + } catch (error) { + if (error instanceof BusinessError) { + return null; + } + throw error; } - throw error; - } - }, - }) ?? []; + }, + }) ?? [] + ); +}; export function useMilestoneHistoryScoreExcelFileQuery(startDate: string, endDate: string) { + const auth = useAppSelector((state) => state.auth).value; return useAxiosQuery({ queryKey: QueryKeys.MILESTONE_HISTORY_SCORE_EXCEL(startDate, endDate), queryFn: async (): Promise => { const response = await client.get(`/admin/milestones/histories/scores/files`, { params: { start_date: startDate, end_date: endDate }, responseType: 'blob', + headers: { Authorization: auth.token }, }); if (response?.status !== 200) { return null; @@ -60,34 +72,63 @@ export function useMilestoneHistoryScoreExcelFileQuery(startDate: string, endDat }); } -export const useMilestoneHistoryStatusApproveMutation = () => - useAxiosMutation({ +export const useMilestoneHistoryStatusApproveMutation = () => { + const auth = useAppSelector((state) => state.auth).value; + + return useAxiosMutation({ mutationFn: async (historyId: number) => { - await client.patch(`/admin/milestones/histories/${historyId}/approve`); + await client.patch( + `/admin/milestones/histories/${historyId}/approve`, + {}, + { + headers: { Authorization: auth.token }, + }, + ); }, }); +}; -export const useMilestoneHistoryStatusRejectMutation = () => - useAxiosMutation({ +export const useMilestoneHistoryStatusRejectMutation = () => { + const auth = useAppSelector((state) => state.auth).value; + return useAxiosMutation({ mutationFn: async ({ historyId, rejectReason }: { historyId: number; rejectReason: string }) => { - await client.patch(`/admin/milestones/histories/${historyId}/reject`, { data: { rejectReason } }); + await client.patch( + `/admin/milestones/histories/${historyId}/reject`, + { + reason: rejectReason, + }, + { + headers: { Authorization: auth.token }, + }, + ); }, }); +}; -export const useMilestoneHistoryStatusCancelMutation = () => - useAxiosMutation({ +export const useMilestoneHistoryStatusCancelMutation = () => { + const auth = useAppSelector((state) => state.auth).value; + return useAxiosMutation({ mutationFn: async (historyId: number) => { - await client.patch(`/admin/milestones/histories/${historyId}/cancel`); + await client.patch( + `/admin/milestones/histories/${historyId}/cancel`, + {}, + { + headers: { Authorization: auth.token }, + }, + ); }, }); +}; -export const useRegisterHistoryInBatchMutation = () => - useAxiosMutation({ +export const useRegisterHistoryInBatchMutation = () => { + const auth = useAppSelector((state) => state.auth).value; + return useAxiosMutation({ mutationFn: async (file?: File) => { const formdata = new FormData(); formdata.append('file', file!); await client.post('/admin/milestones/histories', formdata, { - headers: { 'Content-Type': 'multipart/form-data' }, + headers: { 'Content-Type': 'multipart/form-data', Authorization: auth.token }, }); }, }); +}; diff --git a/frontend/src/lib/hooks/useApi.ts b/frontend/src/lib/hooks/useApi.ts index 5dca7881..91756e81 100644 --- a/frontend/src/lib/hooks/useApi.ts +++ b/frontend/src/lib/hooks/useApi.ts @@ -5,7 +5,6 @@ import { MilestoneHistoryStatus } from '@/data/milestone'; import { QueryKeys } from '@/data/queryKey'; import { client } from '@/lib/api/client.axios'; import { useAxiosMutation, useAxiosQuery } from '@/lib/hooks/useAxios'; -import { mockHackathonTeamPageableData } from '@/mocks/hackathon'; import { CollegeDto, HackathonTeamCreateDto, @@ -20,52 +19,38 @@ import { BusinessError } from '@/types/error'; import { MilestoneHistorySortCriteria, SortDirection } from '@/types/milestone'; import { github } from '../api/github.axios'; import { convertNumToCareer, removeEmptyField } from '../utils/utils'; +import { useAppSelector } from './redux'; export const useCollegeQuery = () => useAxiosQuery({ queryKey: QueryKeys.COLLEGES, - queryFn: async (): Promise => { - try { - const response = await client.get('/colleges'); - return response.data; - } catch (error) { - if (error instanceof BusinessError) { - return null; - } - throw error; - } - }, - }); - -export const useMajorQuery = (collegeId: number) => - useAxiosQuery({ - queryKey: QueryKeys.COLLEGES, - queryFn: async (): Promise => { - try { - const response = await client.get(`/colleges/${collegeId}/majors`); - return response.data; - } catch (error) { - if (error instanceof BusinessError) { - return null; - } - throw error; - } - }, + queryFn: async (): Promise => + await client + .get('/colleges') + .then((res) => res.data) + .catch((err) => Promise.reject(err)), }); -export const useMilestoneScoresOfStudentQuery = (memberId: number, startDate: string, endDate: string) => - useAxiosQuery({ +export const useMilestoneScoresOfStudentQuery = (memberId: number, startDate: string, endDate: string) => { + const auth = useAppSelector((state) => state.auth).value; + return useAxiosQuery({ queryKey: QueryKeys.MILESTONE_SCORES_OF_STUDENT(memberId, startDate, endDate), queryFn: async (): Promise => { - const response = await client.get(`/milestones/histories/scores/members/${memberId}`, { - params: removeEmptyField({ - start_date: startDate, - end_date: endDate, - }), - }); - return response?.data; + return await client + .get(`/milestones/histories/scores/members/${memberId}`, { + params: removeEmptyField({ + start_date: startDate, + end_date: endDate, + }), + headers: { + Authorization: auth.token, + }, + }) + .then((res) => res.data) + .catch((err) => Promise.reject(err)); }, }); +}; export const useMilestoneHistoriesOfStudentQuery = ( memberId: number, @@ -76,8 +61,9 @@ export const useMilestoneHistoriesOfStudentQuery = ( sortDirection: SortDirection | undefined = undefined, page: number = 0, size: number = 10, -) => - useAxiosQuery({ +) => { + const auth = useAppSelector((state) => state.auth).value; + return useAxiosQuery({ queryKey: QueryKeys.MILESTONE_HISTORIES_OF_STUDENT( memberId, startDate, @@ -89,28 +75,33 @@ export const useMilestoneHistoriesOfStudentQuery = ( size, ), queryFn: async (): Promise => { - const response = await client.get(`/milestones/histories/members/${memberId}`, { - params: removeEmptyField({ - start_date: startDate, - end_date: endDate, - filter, - sort_by: sortBy, - sort_direction: sortDirection, - page, - size, - }), - }); - return response?.data; + return await client + .get(`/milestones/histories/members/${memberId}`, { + params: removeEmptyField({ + start_date: startDate, + end_date: endDate, + filter, + sort_by: sortBy, + sort_direction: sortDirection, + page, + size, + }), + headers: { Authorization: auth.token }, + }) + .then((res) => res.data) + .catch((err) => Promise.reject(err)); }, }); +}; export function useMilestoneQuery() { return useAxiosQuery({ queryKey: QueryKeys.MILESTONES, - queryFn: async (): Promise => { - const response = await client.get('/milestones'); - return response?.data; - }, + queryFn: async (): Promise => + await client + .get('/milestones') + .then((res) => res.data) + .catch((err) => Promise.reject(err)), }); } @@ -118,42 +109,39 @@ export function useStudentMemberQuery(memberId: number, options?: { enabled?: bo return useAxiosQuery({ ...options, queryKey: QueryKeys.STUDENT(memberId), - queryFn: async (): Promise => { - const response = await client.get(`/members/${memberId}`); - return response.data; - }, + queryFn: async (): Promise => await client.get(`/members/${memberId}`), }); } export function useStudentMemberMutation() { return useAxiosMutation({ - mutationFn: async (memberId: number): Promise => { - const response = await client.get(`/members/${memberId}`); - return response?.data; - }, + mutationFn: async (memberId: number): Promise => + await client + .get(`/members/${memberId}`) + .then((res) => res.data) + .catch((err) => Promise.reject(err)), }); } export function useStudentMembersQuery() { return useAxiosQuery({ queryKey: QueryKeys.STUDENTS, - queryFn: async (): Promise => { - const response = await client.get('/admin/members'); - return response.data; - }, + queryFn: async (): Promise => + await client + .get('/admin/members') + .then((res) => res.data) + .catch((err) => Promise.reject(err)), }); } export function useFileQuery(fileName: string | null) { return useAxiosQuery({ queryKey: QueryKeys.FILE(fileName), - queryFn: async (): Promise => { - const response = await client.get(`/files/${fileName}`, { responseType: 'blob' }); - if (response?.status !== 200) { - return null; - } - return response?.data; - }, + queryFn: async (): Promise => + await client + .get(`/files/${fileName}`, { responseType: 'blob' }) + .then((res) => res.data) + .catch((err) => Promise.reject(err)), }); } @@ -166,12 +154,11 @@ export function useHackathonTeamsQuery( return useAxiosQuery({ ...options, queryKey: QueryKeys.HACKATHON_TEAMS(hackathonId, page, size), - queryFn: async (): Promise => { - // TODO : API 구현 - //const response = await client.get(`/hackathons/${hackathonId}/teams`); - //return response?.data; - return mockHackathonTeamPageableData; - }, + queryFn: async (): Promise => + await client + .get(`/hackathons/${hackathonId}/teams`) + .then((res) => res.data) + .catch((err) => Promise.reject(err)), }); } @@ -202,25 +189,35 @@ export function useGithubReadmeQuery(owner: string, repo: string, options?: { en } export function useMilestoneHistoryCreateMutation() { + const auth = useAppSelector((state) => state.auth).value; + return useAxiosMutation({ mutationFn: async ({ milestoneId, description, count, file, activatedAt }: MilestoneHistoryCreateDto) => { - const formdata = new FormData(); - formdata.append('file', file!); + const formData = new FormData(); + formData.append('file', file!); const blob = new Blob([JSON.stringify({ milestoneId, description, count, activatedAt })], { type: 'application/json', }); - formdata.append('request', blob); - await client.post('/milestones/histories', formdata, { - headers: { 'Content-Type': 'multipart/form-data' }, - }); + formData.append('request', blob); + + return await client + .post('/milestones/histories', formData, { + headers: { 'Content-Type': 'multipart/form-data', Authorization: auth.token }, + }) + .then((res) => res.data) + .catch((err) => Promise.reject(err)); }, }); } export function useMilestoneHistoryDeleteMutation() { + const auth = useAppSelector((state) => state.auth).value; return useAxiosMutation({ mutationFn: async (id: number) => { - await client.delete(`/milestones/histories/${id}`); + return await client + .delete(`/milestones/histories/${id}`, { headers: { Authorization: auth.token } }) + .then((res) => res.data) + .catch((err) => Promise.reject(err)); }, }); } @@ -228,15 +225,19 @@ export function useMilestoneHistoryDeleteMutation() { export function useRegisterTeamMutation() { return useAxiosMutation({ mutationFn: async ({ hackathonId, image, name, work, githubUrl, members, password }: HackathonTeamCreateDto) => { - const formdata = new FormData(); - formdata.append('image', image!); + const formData = new FormData(); + formData.append('image', image!); const blob = new Blob([JSON.stringify({ name, work, githubUrl, members, password })], { type: 'application/json', }); - formdata.append('request', blob); - await client.post(`/hackathons/${hackathonId}/teams`, formdata, { - headers: { 'Content-Type': 'multipart/form-data' }, - }); + formData.append('request', blob); + + return await client + .post(`/hackathons/${hackathonId}/teams`, formData, { + headers: { 'Content-Type': 'multipart/form-data' }, + }) + .then((res) => res.data) + .catch((err) => Promise.reject(err)); }, }); } @@ -257,7 +258,8 @@ export function useSignUpMutation() { career_detail: userInfo.careerDetail, auth_code: userInfo.authCode, }; - await client + + return await client .post(`/sign-up`, data) .then((res) => res.data) .catch((err) => Promise.reject(err)); diff --git a/frontend/src/lib/utils/utils.tsx b/frontend/src/lib/utils/utils.tsx index 7368cf20..760c2efa 100644 --- a/frontend/src/lib/utils/utils.tsx +++ b/frontend/src/lib/utils/utils.tsx @@ -74,7 +74,7 @@ export const convertCareerToStr = (enumValue: string) => { }; export const appendDashPhoneNumber = (value: string): string => { - const formattedValue = value.replace(/(\d{3})(\d{3})(\d{4})/, '$1-$2-$3'); + const formattedValue = value?.replace(/(\d{3})(\d{3})(\d{4})/, '$1-$2-$3'); return formattedValue; };