diff --git a/build.gradle b/build.gradle index 41f022be..5a69a7f2 100644 --- a/build.gradle +++ b/build.gradle @@ -22,6 +22,7 @@ repositories { } dependencies { + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-amqp' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-data-redis' @@ -37,6 +38,10 @@ dependencies { testImplementation 'org.springframework.amqp:spring-rabbit-test' testImplementation 'org.springframework.security:spring-security-test' + // https://mvnrepository.com/artifact/com.slack.api/slack-api-client + // 슬랙 api 추가 + implementation group: 'com.slack.api', name: 'slack-api-client', version: '1.29.2' + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' implementation 'io.jsonwebtoken:jjwt-impl:0.11.5' implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' diff --git a/src/main/java/com/postgraduate/domain/mentoring/application/usecase/MentoringApplyUseCase.java b/src/main/java/com/postgraduate/domain/mentoring/application/usecase/MentoringApplyUseCase.java index d65c7d9c..fcc2281c 100644 --- a/src/main/java/com/postgraduate/domain/mentoring/application/usecase/MentoringApplyUseCase.java +++ b/src/main/java/com/postgraduate/domain/mentoring/application/usecase/MentoringApplyUseCase.java @@ -19,12 +19,13 @@ public class MentoringApplyUseCase { private final MentoringSaveService mentoringSaveService; private final SeniorGetService seniorGetService; - public void applyMentoring(User user, MentoringApplyRequest request) { + public Long applyMentoring(User user, MentoringApplyRequest request) { String[] dates = request.date().split(","); if (dates.length != 3) throw new MentoringDateException(); Senior senior = seniorGetService.bySeniorId(request.seniorId()); Mentoring mentoring = MentoringMapper.mapToMentoring(user, senior, request); - mentoringSaveService.save(mentoring); + Mentoring save = mentoringSaveService.save(mentoring); + return save.getMentoringId(); } } diff --git a/src/main/java/com/postgraduate/domain/mentoring/application/usecase/MentoringManageUseCase.java b/src/main/java/com/postgraduate/domain/mentoring/application/usecase/MentoringManageUseCase.java index 224099f7..1d1e75c7 100644 --- a/src/main/java/com/postgraduate/domain/mentoring/application/usecase/MentoringManageUseCase.java +++ b/src/main/java/com/postgraduate/domain/mentoring/application/usecase/MentoringManageUseCase.java @@ -4,6 +4,7 @@ import com.postgraduate.domain.account.domain.service.AccountGetService; import com.postgraduate.domain.mentoring.application.dto.req.MentoringDateRequest; import com.postgraduate.domain.mentoring.domain.entity.Mentoring; +import com.postgraduate.domain.mentoring.domain.service.MentoringDeleteService; import com.postgraduate.domain.mentoring.domain.service.MentoringGetService; import com.postgraduate.domain.mentoring.domain.service.MentoringUpdateService; import com.postgraduate.domain.mentoring.exception.MentoringNotExpectedException; @@ -36,6 +37,7 @@ public class MentoringManageUseCase { private final CheckIsMyMentoringUseCase checkIsMyMentoringUseCase; private final MentoringUpdateService mentoringUpdateService; private final MentoringGetService mentoringGetService; + private final MentoringDeleteService mentoringDeleteService; private final RefuseSaveService refuseSaveService; private final AccountGetService accountGetService; private final SeniorGetService seniorGetService; @@ -79,6 +81,11 @@ public Boolean updateExpected(User user, Long mentoringId, MentoringDateRequest return account.isPresent(); } + public void delete(User user, Long mentoringId) { + Mentoring mentoring = checkIsMyMentoringUseCase.byUser(user, mentoringId); + mentoringDeleteService.deleteMentoring(mentoring); + } + @Scheduled(cron = "0 59 23 * * *", zone = "Asia/Seoul") public void updateAutoCancel() { LocalDateTime now = LocalDateTime.now() diff --git a/src/main/java/com/postgraduate/domain/mentoring/domain/service/MentoringDeleteService.java b/src/main/java/com/postgraduate/domain/mentoring/domain/service/MentoringDeleteService.java new file mode 100644 index 00000000..edb24dd3 --- /dev/null +++ b/src/main/java/com/postgraduate/domain/mentoring/domain/service/MentoringDeleteService.java @@ -0,0 +1,16 @@ +package com.postgraduate.domain.mentoring.domain.service; + +import com.postgraduate.domain.mentoring.domain.entity.Mentoring; +import com.postgraduate.domain.mentoring.domain.repository.MentoringRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class MentoringDeleteService { + private final MentoringRepository mentoringRepository; + + public void deleteMentoring(Mentoring mentoring) { + mentoringRepository.deleteById(mentoring.getMentoringId()); + } +} diff --git a/src/main/java/com/postgraduate/domain/mentoring/presentation/MentoringController.java b/src/main/java/com/postgraduate/domain/mentoring/presentation/MentoringController.java index 2ded304a..f4b1928d 100644 --- a/src/main/java/com/postgraduate/domain/mentoring/presentation/MentoringController.java +++ b/src/main/java/com/postgraduate/domain/mentoring/presentation/MentoringController.java @@ -59,12 +59,20 @@ public ResponseDto getMentoringDetail(@Authentic return ResponseDto.create(MENTORING_FIND.getCode(), GET_MENTORING_DETAIL_INFO.getMessage(), mentoringDetail); } + @DeleteMapping("/me/{mentoringId}") + @Operation(summary = "[대학생] 결제 오류/취소시 멘토링 삭제", description = "대학생이 신청한 멘토링을 삭제합니다. (취소/거절 아닌 삭제)") + public ResponseDto deleteMentoring(@AuthenticationPrincipal User user, + @PathVariable Long mentoringId) { + manageUseCase.delete(user, mentoringId); + return ResponseDto.create(MENTORING_DELETE.getCode(), DELETE_MENTORING.getMessage()); + } + @PostMapping("/applying") @Operation(summary = "[대학생] 멘토링 신청", description = "대학생이 멘토링을 신청합니다.") - public ResponseDto applyMentoring(@AuthenticationPrincipal User user, + public ResponseDto applyMentoring(@AuthenticationPrincipal User user, @RequestBody @Valid MentoringApplyRequest request) { - applyUseCase.applyMentoring(user, request); - return ResponseDto.create(MENTORING_CREATE.getCode(), CREATE_MENTORING.getMessage()); + Long mentoringId = applyUseCase.applyMentoring(user, request); + return ResponseDto.create(MENTORING_CREATE.getCode(), CREATE_MENTORING.getMessage(), mentoringId); } @PatchMapping("/me/{mentoringId}/done") diff --git a/src/main/java/com/postgraduate/domain/mentoring/presentation/constant/MentoringResponseMessage.java b/src/main/java/com/postgraduate/domain/mentoring/presentation/constant/MentoringResponseMessage.java index 72fd8686..e208f907 100644 --- a/src/main/java/com/postgraduate/domain/mentoring/presentation/constant/MentoringResponseMessage.java +++ b/src/main/java/com/postgraduate/domain/mentoring/presentation/constant/MentoringResponseMessage.java @@ -10,6 +10,7 @@ public enum MentoringResponseMessage { GET_MENTORING_DETAIL_INFO("멘토링 상세 조회에 성공하였습니다."), CREATE_MENTORING("멘토링 신청에 성공하였습니다."), UPDATE_MENTORING("멘토링 상태 갱신에 성공하였습니다."), + DELETE_MENTORING("멘토링 삭제에 성공하였습니다."), NOT_FOUND_MENTORING("멘토링이 존재하지 않습니다."), NOT_FOUND_DETAIL("볼 수 없는 신청서 입니다."), diff --git a/src/main/java/com/postgraduate/domain/salary/application/usecase/SalaryManageUseCase.java b/src/main/java/com/postgraduate/domain/salary/application/usecase/SalaryManageUseCase.java index b1a5e683..61030755 100644 --- a/src/main/java/com/postgraduate/domain/salary/application/usecase/SalaryManageUseCase.java +++ b/src/main/java/com/postgraduate/domain/salary/application/usecase/SalaryManageUseCase.java @@ -7,11 +7,13 @@ import com.postgraduate.domain.salary.util.SalaryUtil; import com.postgraduate.domain.senior.domain.entity.Senior; import com.postgraduate.domain.senior.domain.service.SeniorGetService; +import com.postgraduate.global.slack.SlackMessage; import lombok.RequiredArgsConstructor; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.io.IOException; import java.time.LocalDate; import java.util.List; @@ -21,9 +23,11 @@ public class SalaryManageUseCase { private final SalarySaveService salarySaveService; private final SeniorGetService seniorGetService; + private final SlackMessage slackMessage; @Scheduled(cron = "0 0 0 10 * *", zone = "Asia/Seoul") - public void createSalary() { + public void createSalary() throws IOException { + slackMessage.sendSlackSalary(); List seniors = seniorGetService.all(); LocalDate salaryDate = SalaryUtil.getSalaryDate(); seniors.forEach(senior -> { diff --git a/src/main/java/com/postgraduate/domain/salary/domain/service/SalaryGetService.java b/src/main/java/com/postgraduate/domain/salary/domain/service/SalaryGetService.java index 45f27e3e..dbd52e0a 100644 --- a/src/main/java/com/postgraduate/domain/salary/domain/service/SalaryGetService.java +++ b/src/main/java/com/postgraduate/domain/salary/domain/service/SalaryGetService.java @@ -13,6 +13,7 @@ import org.springframework.stereotype.Service; import java.time.LocalDate; +import java.util.List; @Service @RequiredArgsConstructor @@ -31,4 +32,8 @@ public Page findDistinctSeniors(String search, Integer page) { Pageable pageable = PageRequest.of(page - 1, ADMIN_PAGE_SIZE); return salaryRepository.findDistinctBySearchSenior(search, pageable); } + + public List findAll() { + return salaryRepository.findAll(); + } } diff --git a/src/main/java/com/postgraduate/global/config/security/jwt/util/JwtUtils.java b/src/main/java/com/postgraduate/global/config/security/jwt/util/JwtUtils.java index 0645fa63..831c3055 100644 --- a/src/main/java/com/postgraduate/global/config/security/jwt/util/JwtUtils.java +++ b/src/main/java/com/postgraduate/global/config/security/jwt/util/JwtUtils.java @@ -23,6 +23,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; @@ -39,6 +40,8 @@ import static com.postgraduate.global.config.security.jwt.constant.Type.ACCESS; import static com.postgraduate.global.config.security.jwt.constant.Type.REFRESH; +import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.UNAUTHORIZED; @Component @RequiredArgsConstructor @@ -57,7 +60,6 @@ public class JwtUtils { private static final String ROLE = "role"; private static final String TYPE = "type"; private static final String AUTHORIZATION = "Authorization"; - private static final int STATUS = 500; private static final String CONTENT_TYPE = "application/json"; private static final String CHARACTER_ENCODING = "UTF-8"; @@ -111,7 +113,7 @@ private AuthDetails getDetails(HttpServletResponse response, Claims claims) { AuthDetails authDetails = authDetailsService.loadUserByUsername(claims.getSubject()); return authDetails; } catch (UserNotFoundException ex) { - jwtExceptionHandler(response, ex); + jwtExceptionHandler(BAD_REQUEST, response, ex); throw ex; } } @@ -123,10 +125,10 @@ public boolean validateToken(HttpServletResponse response, String token, Type ty throw new InvalidTokenException(); return true; } catch (ApplicationException | SignatureException | UnsupportedJwtException | IllegalArgumentException | MalformedJwtException e) { - jwtExceptionHandler(response, new InvalidTokenException()); + jwtExceptionHandler(BAD_REQUEST, response, new InvalidTokenException()); return false; } catch (ExpiredJwtException e) { - jwtExceptionHandler(response, new TokenExpiredException()); + jwtExceptionHandler(UNAUTHORIZED, response, new TokenExpiredException()); return false; } } @@ -136,8 +138,8 @@ private Claims parseClaims(String token) { return parser.parseClaimsJws(token).getBody(); } - private void jwtExceptionHandler(HttpServletResponse response, ApplicationException ex) { - response.setStatus(STATUS); + private void jwtExceptionHandler(HttpStatus status, HttpServletResponse response, ApplicationException ex) { + response.setStatus(status.value()); response.setContentType(CONTENT_TYPE); response.setCharacterEncoding(CHARACTER_ENCODING); try { diff --git a/src/main/java/com/postgraduate/global/slack/SlackMessage.java b/src/main/java/com/postgraduate/global/slack/SlackMessage.java new file mode 100644 index 00000000..21fb883d --- /dev/null +++ b/src/main/java/com/postgraduate/global/slack/SlackMessage.java @@ -0,0 +1,87 @@ +package com.postgraduate.global.slack; + +import com.postgraduate.domain.salary.domain.entity.Salary; +import com.postgraduate.domain.salary.domain.service.SalaryGetService; +import com.slack.api.Slack; +import com.slack.api.model.Attachment; +import com.slack.api.model.Field; +import com.slack.api.webhook.Payload; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.time.LocalDate; +import java.util.List; + +@Slf4j +@Component +@RequiredArgsConstructor +public class SlackMessage { + private final Slack slackClient = Slack.getInstance(); + private final SalaryGetService salaryGetService; + + @Value("${slack.url}") + private String webHookUrl; + + public void sendSlackSalary() throws IOException{ + List salaries = salaryGetService.findAll(); + List attachments = salaries.stream() + .filter(salary -> salary.getTotalAmount() > 0) + .map(salary -> generateSalarySlackAttachment(salary)) + .toList(); + slackClient.send(webHookUrl, Payload.builder() + .text("---" + LocalDate.now() + "에 정산할 목록 ---") + .attachments(attachments) + .build()); + + Attachment attachment = generateSalarySlackAttachment(salaries); + slackClient.send(webHookUrl, Payload.builder() + .attachments(List.of(attachment)) + .build()); + } + + //attach 생성 -> Field를 리스트로 담자 + private Attachment generateSalarySlackAttachment(Salary salary) { + return Attachment.builder() + .color("#36a64f") + .fields(generateSalaryFields(salary)) + .build(); + } + + private Attachment generateSalarySlackAttachment(List salaries) { + return Attachment.builder() + .color("#1900ff") + .fields(generateTotalField(salaries)) + .build(); + } + + private List generateSalaryFields(Salary salary) { + return List.of( + generateSlackField( + "선배 닉네임 : " + salary.getSenior().getUser().getNickName(), + "금액 : " + salary.getTotalAmount() + )); + } + + private List generateTotalField(List salaries) { + Integer totalAmount = salaries.stream() + .map(Salary::getTotalAmount) + .reduce(0, Integer::sum); + + return List.of( + generateSlackField( + "총 정산 금액", String.valueOf(totalAmount) + "원" + )); + } + + // Field 생성 메서드 + private Field generateSlackField(String title, String value) { + return Field.builder() + .title(title) + .value(value) + .valueShortEnough(false) + .build(); + } +} \ No newline at end of file diff --git a/src/test/java/com/postgraduate/domain/mentoring/application/usecase/MentoringSeniorInfoUseCaseTest.java b/src/test/java/com/postgraduate/domain/mentoring/application/usecase/MentoringSeniorInfoUseCaseTest.java index 10e371c2..0a467b53 100644 --- a/src/test/java/com/postgraduate/domain/mentoring/application/usecase/MentoringSeniorInfoUseCaseTest.java +++ b/src/test/java/com/postgraduate/domain/mentoring/application/usecase/MentoringSeniorInfoUseCaseTest.java @@ -185,9 +185,9 @@ void getSeniorDone() { Mentoring mentoring2 = new Mentoring(2L, user, senior, "A", "b", "a", 40, WAITING, LocalDateTime.now(), LocalDateTime.now()); Mentoring mentoring3 = new Mentoring(3L, user, senior, "A", "b", "a", 40, WAITING, LocalDateTime.now(), LocalDateTime.now()); List mentorings = List.of(mentoring1, mentoring2, mentoring3); - Payment payment1 = new Payment(1l, mentoring1, salary, 10000, "1", "1", LocalDateTime.now(), LocalDateTime.now(), Status.DONE); - Payment payment2 = new Payment(2l, mentoring2, salary, 10000, "1", "1", LocalDateTime.now(), LocalDateTime.now(), Status.DONE); - Payment payment3 = new Payment(3l, mentoring3, salary, 10000, "1", "1", LocalDateTime.now(), LocalDateTime.now(), Status.DONE); + Payment payment1 = new Payment(1l, mentoring1, salary, 10000, "1", "1", "a", LocalDateTime.now(), LocalDateTime.now(), Status.DONE); + Payment payment2 = new Payment(2l, mentoring2, salary, 10000, "1", "1", "a", LocalDateTime.now(), LocalDateTime.now(), Status.DONE); + Payment payment3 = new Payment(3l, mentoring3, salary, 10000, "1", "1", "a", LocalDateTime.now(), LocalDateTime.now(), Status.DONE); DoneSeniorMentoringInfo done1 = MentoringMapper.mapToSeniorDoneInfo(mentoring1, payment1); DoneSeniorMentoringInfo done2 = MentoringMapper.mapToSeniorDoneInfo(mentoring2, payment2); DoneSeniorMentoringInfo done3 = MentoringMapper.mapToSeniorDoneInfo(mentoring3, payment3); diff --git a/src/test/java/com/postgraduate/domain/salary/application/usecase/SalaryInfoUseCaseTest.java b/src/test/java/com/postgraduate/domain/salary/application/usecase/SalaryInfoUseCaseTest.java index f4b7eca1..4f5f2dd1 100644 --- a/src/test/java/com/postgraduate/domain/salary/application/usecase/SalaryInfoUseCaseTest.java +++ b/src/test/java/com/postgraduate/domain/salary/application/usecase/SalaryInfoUseCaseTest.java @@ -103,9 +103,9 @@ void getSalaryDetail() { , 40, DONE , LocalDateTime.now(), LocalDateTime.now()); Salary salary = mock(Salary.class); - Payment payment1 = new Payment(1L, mentoring1, salary, 1000, "a", "a", LocalDateTime.now(), LocalDateTime.now(), Status.DONE); - Payment payment2 = new Payment(2L, mentoring2, salary, 1000, "a", "a", LocalDateTime.now(), LocalDateTime.now(), Status.DONE); - Payment payment3 = new Payment(3L, mentoring3, salary, 1000, "a", "a", LocalDateTime.now(), LocalDateTime.now(), Status.DONE); + Payment payment1 = new Payment(1L, mentoring1, salary, 1000, "a", "a", "a", LocalDateTime.now(), LocalDateTime.now(), Status.DONE); + Payment payment2 = new Payment(2L, mentoring2, salary, 1000, "a", "a", "a", LocalDateTime.now(), LocalDateTime.now(), Status.DONE); + Payment payment3 = new Payment(3L, mentoring3, salary, 1000, "a", "a", "a", LocalDateTime.now(), LocalDateTime.now(), Status.DONE); List payments = List.of(payment1, payment2, payment3); given(seniorGetService.byUser(user))