Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/#186 마일스톤 권한 설정 #187

Merged
merged 12 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -29,23 +29,23 @@ public class AdminAuthController {

@PostMapping
public ResponseEntity<Void> registerFaculty(
@Admin FacultyMember facultyMember,
@AdminInterface FacultyMember facultyMember,
@RequestBody @Valid RegisterFacultyRequest request) {
Long memberId = adminAuthCommandService.registerFaculty(request);
return ResponseEntity.created(URI.create("/members/" + memberId)).build();
}

@PostMapping("/files")
public ResponseEntity<Void> registerFaculties(
@Admin FacultyMember facultyMember,
@AdminInterface FacultyMember facultyMember,
@RequestPart(value = "file") final MultipartFile file) {
adminAuthCommandService.registerFaculties(file);
return ResponseEntity.created(URI.create("/admin/faculties")).build();
}

@DeleteMapping()
public ResponseEntity<Void> deleteFaculty(
@SuperAdmin FacultyMember facultyMember,
@SuperAdminInterface FacultyMember facultyMember,
@RequestBody @Valid DeleteFacultyRequest request) {
adminAuthCommandService.deleteFaculty(request.member_id());
return ResponseEntity.noContent().build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -35,10 +37,9 @@ public class MilestoneHistoryAdminController {
private final MilestoneHistoryAdminCommandService milestoneHistoryAdminCommandService;
private final MilestoneHistoryAdminQueryService milestoneHistoryAdminQueryService;

// TODO 관리자만 호출할 수 있도록 권한 설정

@GetMapping
public ResponseEntity<Page<MilestoneHistoryResponse>> findAllMilestoneHistory(
@AdminInterface FacultyMember facultyMember,
@RequestParam(value = "field", required = false) final Integer field,
@RequestParam(value = "keyword", required = false) final String keyword,
final Pageable pageable) {
Expand All @@ -47,6 +48,7 @@ public ResponseEntity<Page<MilestoneHistoryResponse>> findAllMilestoneHistory(

@GetMapping("/files")
public ResponseEntity<byte[]> downloadAllMilestoneHistoryExcelFile(
@AdminInterface FacultyMember facultyMember,
@RequestParam(value = "field", required = false) final Integer field,
@RequestParam(value = "keyword", required = false) final String keyword
) {
Expand All @@ -62,38 +64,44 @@ public ResponseEntity<byte[]> downloadAllMilestoneHistoryExcelFile(

@GetMapping("/{historyId}")
public ResponseEntity<MilestoneHistoryResponse> findAllMilestoneHistory(
@AdminInterface FacultyMember facultyMember,
@PathVariable("historyId") final Long historyId) {
return ResponseEntity.ok(milestoneHistoryAdminQueryService.findMilestoneHistory(historyId));
}

@PostMapping
public ResponseEntity<Void> 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<Void> approveMilestoneHistory(@PathVariable("historyId") final Long historyId) {
public ResponseEntity<Void> approveMilestoneHistory(
@AdminInterface FacultyMember facultyMember, @PathVariable("historyId") final Long historyId) {
milestoneHistoryAdminCommandService.approveMilestoneHistory(historyId);
return ResponseEntity.noContent().build();
}

@PatchMapping("/{historyId}/reject")
public ResponseEntity<Void> approveMilestoneHistory(@PathVariable("historyId") final Long historyId,
@RequestBody @Valid final MilestoneHistoryRejectRequest request) {
public ResponseEntity<Void> 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<Void> cancelMilestoneHistory(@PathVariable("historyId") final Long historyId) {
public ResponseEntity<Void> cancelMilestoneHistory(
@AdminInterface FacultyMember facultyMember, @PathVariable("historyId") final Long historyId) {
milestoneHistoryAdminCommandService.cancelMilestoneHistory(historyId);
return ResponseEntity.noContent().build();
}

@GetMapping("/scores")
public ResponseEntity<Page<MilestoneScoreResponse>> findAllMilestoneHistoryScores(
@AdminInterface FacultyMember facultyMember,
@RequestParam(value = "start_date") final String startDate,
@RequestParam(value = "end_date") final String endDate,
final Pageable pageable) {
Expand All @@ -103,6 +111,7 @@ public ResponseEntity<Page<MilestoneScoreResponse>> findAllMilestoneHistoryScore

@GetMapping("/scores/files")
public ResponseEntity<byte[]> downloadMilestoneHistoryScoreExcelFile(
@AdminInterface FacultyMember facultyMember,
@RequestParam(value = "start_date") final String startDate,
@RequestParam(value = "end_date") final String endDate) {
String filename = "마일스톤_점수_현황.xlsx";
Expand Down
9 changes: 6 additions & 3 deletions backend/src/main/java/sw_css/config/WebConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(jwtAuthorizationArgumentResolver);
resolvers.add(memberArgumentResolver);
resolvers.add(studentArgumentResolver);
resolvers.add(adminArgumentResolver);
resolvers.add(superAdminArgumentResolver);
}
Expand Down
4 changes: 2 additions & 2 deletions backend/src/main/java/sw_css/member/api/MemberController.java
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -29,7 +29,7 @@ public ResponseEntity<StudentMemberResponse> findStudent(@PathVariable final Lon
}

@PatchMapping("/change-password")
public ResponseEntity<Void> changeMemberPassword(@JwtAuthorization Member me,
public ResponseEntity<Void> changeMemberPassword(@MemberInterface Member me,
@RequestBody @Valid ChangePasswordRequest request) {
memberQueryService.changePassword(me, request.oldPassword(), request.newPassword());
return ResponseEntity.noContent().build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,17 @@
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;
import sw_css.milestone.application.dto.response.MilestoneHistoryOfStudentResponse;
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")
Expand All @@ -34,27 +38,27 @@ public class MilestoneHistoryController {
private final MilestoneHistoryCommandService milestoneHistoryCommandService;
private final MilestoneHistoryQueryService milestoneHistoryQueryService;

// TODO 학생만 호출할 수 있도록 권한 설정
@PostMapping
public ResponseEntity<Void> 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<Void> deleteMilestoneHistory(@PathVariable("historyId") final Long historyId) {
milestoneHistoryCommandService.deleteMilestoneHistory(historyId);
public ResponseEntity<Void> deleteMilestoneHistory(
@StudentInterface StudentMember student, @PathVariable("historyId") final Long historyId) {
milestoneHistoryCommandService.deleteMilestoneHistory(student, historyId);
return ResponseEntity.noContent().build();
}

// TODO 학생 본인 혹은 관리자만 호출할 수 있도록 권한 설정
@GetMapping("/members/{memberId}")
public ResponseEntity<Page<MilestoneHistoryOfStudentResponse>> 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,
Expand All @@ -67,10 +71,9 @@ public ResponseEntity<Page<MilestoneHistoryOfStudentResponse>> findAllMilestoneH
sortDirection, pageable));
}


// TODO 학생 본인 혹은 관리자만 호출할 수 있도록 권한 설정
@GetMapping("/scores/members/{memberId}")
public ResponseEntity<List<MilestoneScoreOfStudentResponse>> findAllMilestoneHistoryScores(
@MemberInterface Member me,
@PathVariable("memberId") final Long memberId,
@RequestParam(value = "start_date") final String startDate,
@RequestParam(value = "end_date") final String endDate) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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(),
Expand Down Expand Up @@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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, "해당 마일스톤 실적은 처리되지 않은 상태입니다."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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));
}
}
Loading