diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cd094cc..2c6a067 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,7 +2,10 @@ name: gradle build on: pull_request: - branches: [ main ] + branches: + - main + - dev + # 권한 설정 permissions: write-all @@ -25,10 +28,6 @@ jobs: - name: Set up Environment run: echo "${{ secrets.ENV_PROPERTIES_LOCAL }}" > ./.env - # docker로 Test용 mysql 띄우기 - - name: Create Mysql Docker Container - run: sudo docker run -d -p 3306:3306 --env MYSQL_DATABASE="${{ secrets.TEST_DATABASE }}" --env MYSQL_ROOT_PASSWORD="${{ secrets.TEST_DATASOURCE_PASSWORD }}" mysql:8.0.31 - # 빌드시 캐시 적용 - name: Cache Gradle Packages uses: actions/cache@v3 diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index 0626b6e..3b03cc6 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -30,16 +30,12 @@ jobs: - name: Send env file uses: appleboy/scp-action@master with: - username: ${{ secrets.USERNAME }} - host: ${{ secrets.NCP_HOST }} + username: root + host: ${{ secrets.NCP_DEV_IP }} password: ${{ secrets.NCP_PASSWORD }} source: "./.env" target: "/home/ubuntu" - # 도커 MYSQL 이미지 실행 - - name: Create Mysql Docker Container - run: sudo docker run -d -p 3306:3306 --env MYSQL_DATABASE="${{ secrets.TEST_DATABASE }}" --env MYSQL_ROOT_PASSWORD="${{ secrets.TEST_DATASOURCE_PASSWORD }}" mysql:8.0.31 - # 빌드 - name: Build with Gradle run: ./gradlew clean bootJar @@ -55,8 +51,8 @@ jobs: - name: Send docker-compose.yml uses: appleboy/scp-action@master with: - username: ${{ secrets.USERNAME }} - host: ${{ secrets.NCP_HOST }} + username: root + host: ${{ secrets.NCP_DEV_IP }} password: ${{ secrets.NCP_PASSWORD }} port: 22 source: "./.docker/docker-compose.yml" @@ -66,8 +62,8 @@ jobs: - name: Deploy to Dev uses: appleboy/ssh-action@master with: - username: ${{ secrets.USERNAME }} - host: ${{ secrets.NCP_HOST }} + username: root + host: ${{ secrets.NCP_DEV_IP }} password: ${{ secrets.NCP_PASSWORD }} script: | sudo cp /home/ubuntu/.docker/docker-compose.yml /home/ubuntu diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml index b87946b..64946e8 100644 --- a/.github/workflows/deploy-prod.yml +++ b/.github/workflows/deploy-prod.yml @@ -30,16 +30,12 @@ jobs: - name: Send env file uses: appleboy/scp-action@master with: - username: ${{ secrets.USERNAME }} - host: ${{ secrets.NCP_PROD_HOST }} + username: root + host: ${{ secrets.NCP_PROD_IP }} password: ${{ secrets.NCP_PROD_PASSWORD }} source: "./.env" target: "/home/ubuntu" - # 도커 MYSQL 이미지 실행 - - name: Create Mysql Docker Container - run: sudo docker run -d -p 3306:3306 --env MYSQL_DATABASE="${{ secrets.TEST_DATABASE }}" --env MYSQL_ROOT_PASSWORD="${{ secrets.TEST_DATASOURCE_PASSWORD }}" mysql:8.0.31 - # 빌드 - name: Build with Gradle run: ./gradlew clean bootJar @@ -55,19 +51,19 @@ jobs: - name: Send docker-compose.yml uses: appleboy/scp-action@master with: - username: ${{ secrets.USERNAME }} - host: ${{ secrets.NCP_PROD_HOST }} + username: root + host: ${{ secrets.NCP_PROD_IP }} password: ${{ secrets.NCP_PROD_PASSWORD }} port: 22 source: "./.docker/docker-compose.yml" target: "/home/ubuntu/" # 도커 컴포즈 실행 - - name: Deploy to Dev + - name: Deploy to Prod uses: appleboy/ssh-action@master with: - username: ${{ secrets.USERNAME }} - host: ${{ secrets.NCP_PROD_HOST }} + username: root + host: ${{ secrets.NCP_PROD_IP }} password: ${{ secrets.NCP_PROD_PASSWORD }} script: | sudo cp /home/ubuntu/.docker/docker-compose.yml /home/ubuntu diff --git a/src/main/java/com/moneymong/domain/ledger/api/request/UpdateLedgerRequest.java b/src/main/java/com/moneymong/domain/ledger/api/request/UpdateLedgerRequest.java index b4976b6..fee63ce 100644 --- a/src/main/java/com/moneymong/domain/ledger/api/request/UpdateLedgerRequest.java +++ b/src/main/java/com/moneymong/domain/ledger/api/request/UpdateLedgerRequest.java @@ -6,7 +6,6 @@ import java.time.ZonedDateTime; import lombok.Getter; import lombok.NoArgsConstructor; -import org.hibernate.validator.constraints.Length; @Getter @NoArgsConstructor diff --git a/src/main/java/com/moneymong/domain/ledger/entity/LedgerDetail.java b/src/main/java/com/moneymong/domain/ledger/entity/LedgerDetail.java index 731a514..73d4323 100644 --- a/src/main/java/com/moneymong/domain/ledger/entity/LedgerDetail.java +++ b/src/main/java/com/moneymong/domain/ledger/entity/LedgerDetail.java @@ -84,6 +84,18 @@ public void updateBalance(int balance) { this.balance = balance; } + public void updateLedgerDetailInfo( + String storeInfo, + int amount, + String description, + ZonedDateTime paymentDate + ) { + this.storeInfo = storeInfo; + this.amount = amount; + this.description = description; + this.paymentDate = paymentDate; + } + public static LedgerDetail of( final Ledger ledger, final User user, diff --git a/src/main/java/com/moneymong/domain/ledger/repository/LedgerDetailRepository.java b/src/main/java/com/moneymong/domain/ledger/repository/LedgerDetailRepository.java index 18a44d5..4029158 100644 --- a/src/main/java/com/moneymong/domain/ledger/repository/LedgerDetailRepository.java +++ b/src/main/java/com/moneymong/domain/ledger/repository/LedgerDetailRepository.java @@ -4,6 +4,10 @@ import com.moneymong.domain.ledger.entity.LedgerDetail; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; + public interface LedgerDetailRepository extends JpaRepository, LedgerDetailCustom { boolean existsByLedger(Ledger ledger); + + List findAllByLedger(Ledger ledger); } diff --git a/src/main/java/com/moneymong/domain/ledger/repository/impl/LedgerDetailCustomImpl.java b/src/main/java/com/moneymong/domain/ledger/repository/impl/LedgerDetailCustomImpl.java index f3190ea..7389ce1 100644 --- a/src/main/java/com/moneymong/domain/ledger/repository/impl/LedgerDetailCustomImpl.java +++ b/src/main/java/com/moneymong/domain/ledger/repository/impl/LedgerDetailCustomImpl.java @@ -7,7 +7,10 @@ import com.moneymong.domain.ledger.entity.enums.FundType; import com.moneymong.domain.ledger.repository.LedgerDetailCustom; import com.querydsl.jpa.impl.JPAQueryFactory; + +import java.time.LocalTime; import java.time.ZonedDateTime; +import java.time.temporal.TemporalAdjusters; import java.util.List; import java.util.Optional; @@ -62,9 +65,12 @@ public List searchByPeriod( ZonedDateTime to, PageRequest pageable ) { + ZonedDateTime endOfMonth = to.with(TemporalAdjusters.lastDayOfMonth()) + .with(LocalTime.MAX); + return jpaQueryFactory.selectFrom(ledgerDetail) .where(ledgerDetail.ledger.eq(ledger)) - .where(ledgerDetail.paymentDate.between(from, to)) + .where(ledgerDetail.paymentDate.between(from, endOfMonth)) .orderBy(ledgerDetail.paymentDate.desc()) .offset((long) pageable.getPageNumber() * pageable.getPageSize()) .limit(pageable.getPageSize()) @@ -79,10 +85,13 @@ public List searchByPeriodAndFundType( FundType fundType, PageRequest pageable ) { + ZonedDateTime endOfMonth = to.with(TemporalAdjusters.lastDayOfMonth()) + .with(LocalTime.MAX); + return jpaQueryFactory.selectFrom(ledgerDetail) .where(ledgerDetail.ledger.eq(ledger)) .where(ledgerDetail.fundType.eq(fundType)) - .where(ledgerDetail.paymentDate.between(from, to)) + .where(ledgerDetail.paymentDate.between(from, endOfMonth)) .orderBy(ledgerDetail.paymentDate.desc()) .offset((long) pageable.getPageNumber() * pageable.getPageSize()) .limit(pageable.getPageSize()) diff --git a/src/main/java/com/moneymong/domain/ledger/service/manager/LedgerDetailService.java b/src/main/java/com/moneymong/domain/ledger/service/manager/LedgerDetailService.java index 2f0cf84..44ef8b4 100644 --- a/src/main/java/com/moneymong/domain/ledger/service/manager/LedgerDetailService.java +++ b/src/main/java/com/moneymong/domain/ledger/service/manager/LedgerDetailService.java @@ -13,6 +13,7 @@ import com.moneymong.domain.ledger.repository.LedgerDetailRepository; import com.moneymong.domain.ledger.repository.LedgerDocumentRepository; import com.moneymong.domain.ledger.repository.LedgerReceiptRepository; +import com.moneymong.domain.ledger.repository.LedgerRepository; import com.moneymong.domain.ledger.service.mapper.LedgerAssembler; import com.moneymong.domain.ledger.service.reader.LedgerDocumentReader; import com.moneymong.domain.ledger.service.reader.LedgerReceiptReader; @@ -22,8 +23,8 @@ import com.moneymong.global.exception.custom.NotFoundException; import com.moneymong.global.exception.enums.ErrorCode; import java.time.ZonedDateTime; +import java.util.Comparator; import java.util.List; -import java.util.Optional; import com.moneymong.utils.AmountCalculatorByFundType; import lombok.RequiredArgsConstructor; @@ -31,10 +32,13 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import static com.moneymong.domain.ledger.entity.enums.FundType.INCOME; + @Service @Slf4j @RequiredArgsConstructor public class LedgerDetailService { + private final LedgerRepository ledgerRepository; private final LedgerAssembler ledgerAssembler; private final LedgerReceiptReader ledgerReceiptReader; private final LedgerDocumentReader ledgerDocumentReader; @@ -69,51 +73,44 @@ public LedgerDetail createLedgerDetail( int newAmount = AmountCalculatorByFundType.calculate(fundType, amount); - /** - * 가장 오래된 장부 내역인 경우 잔고를 amount 값으로 설정한다. - * 이전 내역이 있는 경우, 가장 가까운 시일에 생성된 장부 내역을 기준으로 잔고를 저장한다. - */ - Optional mostRecentLedgerDetail = ledgerDetailRepository.findMostRecentLedgerDetail(ledger, paymentDate); - - if (mostRecentLedgerDetail.isPresent()) { - LedgerDetail recentDetail = mostRecentLedgerDetail.get(); - - int newBalance = recentDetail.getBalance() + newAmount; - ledgerDetail.updateBalance(newBalance); - }else { - ledgerDetail.updateBalance(newAmount); - } - ledger.updateTotalBalance(newAmount); - ledgerDetailRepository.bulkUpdateLedgerDetailBalance( - ledger, - paymentDate, - newAmount - ); + ledgerDetailRepository.save(ledgerDetail); - return ledgerDetailRepository.save(ledgerDetail); + updateBalance(ledger); + + return ledgerDetail; } @Transactional public LedgerDetailInfoView updateLedgerDetail( User user, - Ledger ledger, LedgerDetail ledgerDetail, UpdateLedgerRequest updateLedgerRequest ) { + long ledgerDetailId = ledgerDetail.getId(); -// LedgerDetail createdLedgerDetail = createLedgerDetail(ledger, -// user, -// updateLedgerRequest.getStoreInfo(), -// ledgerDetail.getFundType(), -// updateLedgerRequest.getAmount(), -// ledger.getTotalBalance(), -// updateLedgerRequest.getDescription(), -// updateLedgerRequest.getPaymentDate() -// ); + Ledger ledger = ledgerDetail.getLedger(); - long ledgerDetailId = ledgerDetail.getId(); + int newAmount = AmountCalculatorByFundType.calculate( + ledgerDetail.getFundType(), + ledgerDetail.getAmount() - updateLedgerRequest.getAmount() + ); + + ledger.updateTotalBalance(-newAmount); + + ledgerDetail.updateLedgerDetailInfo( + updateLedgerRequest.getStoreInfo(), + updateLedgerRequest.getAmount(), + updateLedgerRequest.getDescription(), + updateLedgerRequest.getPaymentDate() + ); + + AgencyUser agencyUser = getAgencyUser(user, ledgerDetail); + + validateStaffUserRole(agencyUser.getAgencyUserRole()); + + updateBalance(ledger); // 2. 장부 상세 내역 조회 List ledgerReceipts = ledgerReceiptReader.getLedgerReceipts(ledgerDetailId); @@ -151,13 +148,9 @@ public void removeLedgerDetail( int newAmount = AmountCalculatorByFundType.calculate(ledgerDetail.getFundType(), ledgerDetail.getAmount()); - ledgerDetailRepository.bulkUpdateLedgerDetailBalance( - ledger, - ledgerDetail.getPaymentDate(), - -newAmount - ); - ledger.updateTotalBalance(-newAmount); + + updateBalance(ledger); } private void validateStaffUserRole(AgencyUserRole userRole) { @@ -183,4 +176,24 @@ private User getUser(Long userId) { .findById(userId) .orElseThrow(() -> new NotFoundException(ErrorCode.USER_NOT_FOUND)); } + + private void updateBalance(Ledger ledger) { + List ledgerDetails = ledgerDetailRepository.findAllByLedger(ledger); + + ledgerDetails.sort(Comparator.comparing(LedgerDetail::getPaymentDate)); + + int previousBalance = 0; + + for (int i = 0; i < ledgerDetails.size(); i++) { + LedgerDetail detail = ledgerDetails.get(i); + + if (detail.getFundType() == INCOME) { + previousBalance += detail.getAmount(); + } else { + previousBalance -= detail.getAmount(); + } + + detail.updateBalance(previousBalance); + } + } } diff --git a/src/main/java/com/moneymong/domain/ledger/service/manager/LedgerService.java b/src/main/java/com/moneymong/domain/ledger/service/manager/LedgerService.java index d5cb6e2..f31a77b 100644 --- a/src/main/java/com/moneymong/domain/ledger/service/manager/LedgerService.java +++ b/src/main/java/com/moneymong/domain/ledger/service/manager/LedgerService.java @@ -17,7 +17,6 @@ import com.moneymong.global.exception.custom.InvalidAccessException; import com.moneymong.global.exception.custom.NotFoundException; import com.moneymong.global.exception.enums.ErrorCode; -import com.moneymong.utils.AmountCalculatorByFundType; import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -100,36 +99,12 @@ public LedgerDetailInfoView updateLedger( final Long ledgerDetailId, final UpdateLedgerRequest updateLedgerRequest ) { - // === 유저 === User user = getUser(userId); - // === 장부 === LedgerDetail ledgerDetail = getLedgerDetail(ledgerDetailId); - Ledger ledger = ledgerDetail.getLedger(); - - // === 소속 === - AgencyUser agencyUser = getAgencyUser(userId, ledger); - - // === 권한 === - validateStaffUserRole(agencyUser.getAgencyUserRole()); - - ledgerDetailService.createLedgerDetail( - ledger, - user, - updateLedgerRequest.getStoreInfo(), - ledgerDetail.getFundType(), - updateLedgerRequest.getAmount(), - ledger.getTotalBalance(), - updateLedgerRequest.getDescription(), - updateLedgerRequest.getPaymentDate() - ); - - ledgerDetailService.removeLedgerDetail(userId, ledgerDetailId); - return ledgerDetailService.updateLedgerDetail( user, - ledger, ledgerDetail, updateLedgerRequest ); diff --git a/src/main/java/com/moneymong/domain/user/api/request/UserDeleteRequest.java b/src/main/java/com/moneymong/domain/user/api/request/UserDeleteRequest.java index 2d71605..68bf2f6 100644 --- a/src/main/java/com/moneymong/domain/user/api/request/UserDeleteRequest.java +++ b/src/main/java/com/moneymong/domain/user/api/request/UserDeleteRequest.java @@ -1,13 +1,14 @@ package com.moneymong.domain.user.api.request; import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; @Getter +@AllArgsConstructor +@NoArgsConstructor public class UserDeleteRequest { @NotBlank private String provider; - - @NotBlank - private String token; } diff --git a/src/main/java/com/moneymong/domain/user/entity/AppleUser.java b/src/main/java/com/moneymong/domain/user/entity/AppleUser.java index 8e30ad0..a92f257 100644 --- a/src/main/java/com/moneymong/domain/user/entity/AppleUser.java +++ b/src/main/java/com/moneymong/domain/user/entity/AppleUser.java @@ -1,13 +1,11 @@ package com.moneymong.domain.user.entity; -import com.moneymong.global.domain.BaseEntity; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import org.hibernate.annotations.SQLDelete; -import org.hibernate.annotations.Where; + import static lombok.AccessLevel.PROTECTED; @@ -17,9 +15,7 @@ @Builder @AllArgsConstructor @NoArgsConstructor(access = PROTECTED) -@Where(clause = "deleted = false") -@SQLDelete(sql = "UPDATE users SET deleted = true where id=?") -public class AppleUser extends BaseEntity { +public class AppleUser { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; diff --git a/src/main/java/com/moneymong/domain/user/repository/AppleUserRepository.java b/src/main/java/com/moneymong/domain/user/repository/AppleUserRepository.java index 4de8c0b..4098c43 100644 --- a/src/main/java/com/moneymong/domain/user/repository/AppleUserRepository.java +++ b/src/main/java/com/moneymong/domain/user/repository/AppleUserRepository.java @@ -3,5 +3,8 @@ import com.moneymong.domain.user.entity.AppleUser; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + public interface AppleUserRepository extends JpaRepository { + Optional findAppleUserByUserId(Long userId); } diff --git a/src/main/java/com/moneymong/domain/user/service/UserFacadeService.java b/src/main/java/com/moneymong/domain/user/service/UserFacadeService.java index c694daa..885d6f7 100644 --- a/src/main/java/com/moneymong/domain/user/service/UserFacadeService.java +++ b/src/main/java/com/moneymong/domain/user/service/UserFacadeService.java @@ -2,7 +2,6 @@ import com.moneymong.domain.agency.service.AgencyUserService; import com.moneymong.domain.user.api.request.LoginRequest; -import com.moneymong.domain.user.api.request.UserDeleteRequest; import com.moneymong.global.security.oauth.dto.AuthUserInfo; import com.moneymong.domain.user.api.response.LoginSuccessResponse; import com.moneymong.global.security.oauth.dto.OAuthUserDataResponse; @@ -40,14 +39,9 @@ public LoginSuccessResponse login(LoginRequest loginRequest) { @Transactional public void delete(Long userId) { + oAuthService.revoke(userId); userService.delete(userId); userUniversityService.delete(userId); agencyUserService.deleteAll(userId); } - - @Transactional - public void revoke(UserDeleteRequest deleteRequest, Long userId) { - oAuthService.revoke(deleteRequest); - delete(userId); - } } diff --git a/src/main/java/com/moneymong/domain/user/service/UserService.java b/src/main/java/com/moneymong/domain/user/service/UserService.java index 24baada..425f9d8 100644 --- a/src/main/java/com/moneymong/domain/user/service/UserService.java +++ b/src/main/java/com/moneymong/domain/user/service/UserService.java @@ -13,12 +13,14 @@ import com.moneymong.global.security.oauth.dto.OAuthUserInfo; import com.moneymong.global.security.token.repository.RefreshTokenRepository; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.UUID; @Service +@Slf4j @RequiredArgsConstructor public class UserService { @@ -65,7 +67,6 @@ public User registerUser(OAuthUserInfo oauthUserInfo) { return newUser; } - @Transactional(readOnly = true) public UserProfileResponse getUserProfile(Long userId) { User user = userRepository.findById(userId) diff --git a/src/main/java/com/moneymong/global/exception/enums/ErrorCode.java b/src/main/java/com/moneymong/global/exception/enums/ErrorCode.java index 6150ff5..c8c3511 100644 --- a/src/main/java/com/moneymong/global/exception/enums/ErrorCode.java +++ b/src/main/java/com/moneymong/global/exception/enums/ErrorCode.java @@ -14,6 +14,7 @@ public enum ErrorCode { // ---- 유저 ---- // USER_NOT_FOUND(MoneymongConstant.NOT_FOUND, "USER-001", "존재하지 않는 회원입니다."), + USER_NOT_FOUND_APPLE(MoneymongConstant.NOT_FOUND, "USER-002", "존재하지 않는 APPLE 유저입니다."), // ---- 대학교 ---- // USER_UNIVERSITY_NOT_FOUND(MoneymongConstant.NOT_FOUND, "UNIVERSITY-001", "회원의 대학 정보가 존재하지 않습니다."), diff --git a/src/main/java/com/moneymong/global/security/oauth/handler/AppleService.java b/src/main/java/com/moneymong/global/security/oauth/handler/AppleService.java index ccb3bd2..a3e4720 100644 --- a/src/main/java/com/moneymong/global/security/oauth/handler/AppleService.java +++ b/src/main/java/com/moneymong/global/security/oauth/handler/AppleService.java @@ -2,6 +2,9 @@ import com.auth0.jwt.JWT; import com.auth0.jwt.interfaces.DecodedJWT; +import com.moneymong.domain.user.entity.AppleUser; +import com.moneymong.domain.user.repository.AppleUserRepository; +import com.moneymong.global.exception.custom.NotFoundException; import com.moneymong.global.exception.enums.ErrorCode; import com.moneymong.global.security.oauth.dto.AppleUserData; import com.moneymong.global.security.oauth.dto.OAuthUserDataRequest; @@ -29,11 +32,10 @@ import java.net.URI; import java.security.PrivateKey; import java.security.Security; -import java.time.ZoneId; import java.time.ZonedDateTime; -import java.util.Base64; -import java.util.Date; -import java.util.Map; +import java.util.*; + +import static com.moneymong.global.exception.enums.ErrorCode.USER_NOT_FOUND_APPLE; @Slf4j @Component @@ -41,6 +43,7 @@ public class AppleService implements OAuthAuthenticationHandler { private final RestTemplate restTemplate; + private final AppleUserRepository appleUserRepository; @Value("${spring.security.oauth2.apple.host}") private String host; @@ -65,7 +68,7 @@ public OAuthProvider getAuthProvider() { @Override public OAuthUserDataResponse getOAuthUserData(OAuthUserDataRequest request) { if (request.getCode() == null) { - return decodePayload(request.getAccessToken(), request.getName()); + return decodePayload(request.getAccessToken(), request.getName(), null); } HttpHeaders httpHeaders = new HttpHeaders(); @@ -98,29 +101,59 @@ public OAuthUserDataResponse getOAuthUserData(OAuthUserDataRequest request) { String idToken = userData.getIdToken(); log.info("[AppleService] refreshToken = {}", refreshToken); - return decodePayload(idToken, request.getName()); + return decodePayload(idToken, request.getName(), refreshToken); } catch (RestClientException e) { - log.info("[AppleService] error message = {}", e.getMessage()); log.warn("[AppleService] failed to get OAuth User Data = {}", request.getAccessToken()); throw new HttpClientException(ErrorCode.HTTP_CLIENT_REQUEST_FAILED); } } @Override - public void unlink(String token) { + public void unlink(Long userId) { + AppleUser appleUser = appleUserRepository.findAppleUserByUserId(userId) + .orElseThrow(() -> new NotFoundException(USER_NOT_FOUND_APPLE)); + + String refreshToken = appleUser.getAppleRefreshToken(); + String clientSecret = createClientSecret(); + + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("client_id", clientId); + params.add("client_secret", clientSecret); + params.add("token", refreshToken); + params.add("token_type_hint", "refresh_token"); + + URI uri = UriComponentsBuilder + .fromUriString(host + "/auth/oauth2/v2/revoke") + .build() + .toUri(); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); + HttpEntity> httpEntity = new HttpEntity<>(params, headers); + + try { + restTemplate.postForEntity(uri, httpEntity, AppleUserData.class); + appleUserRepository.delete(appleUser); + } catch (RestClientException e) { + throw new HttpClientException(ErrorCode.HTTP_CLIENT_REQUEST_FAILED); + } } private String createClientSecret() { - ZonedDateTime expiration = ZonedDateTime.now().plusMinutes(5); + Date expirationDate = Date.from(ZonedDateTime.now().plusDays(30).toInstant()); + + Map jwtHeader = new HashMap<>(); + jwtHeader.put(JwsHeader.KEY_ID, keyId); + jwtHeader.put(JwsHeader.ALGORITHM, "ES256"); return Jwts.builder() - .setHeaderParam(JwsHeader.KEY_ID, keyId) + .setHeaderParams(jwtHeader) .setIssuer(teamId) .setAudience(host) .setSubject(clientId) - .setExpiration(Date.from(expiration.withZoneSameInstant(ZoneId.systemDefault()).toInstant())) - .setIssuedAt(new Date()) + .setExpiration(expirationDate) + .setIssuedAt(new Date(System.currentTimeMillis())) .signWith(getPrivateKey(), SignatureAlgorithm.ES256) .compact(); } @@ -139,19 +172,20 @@ private PrivateKey getPrivateKey() { } } - private OAuthUserDataResponse decodePayload(String idToken, String nickname) { + private OAuthUserDataResponse decodePayload(String idToken, String nickname, String refreshToken) { try { DecodedJWT decoded = JWT.decode(idToken); Map claims = decoded.getClaims(); String providerUid = decoded.getSubject(); - String email = claims.get("email").asString(); + String email = claims.get("sub").asString(); return OAuthUserDataResponse.builder() .provider(getAuthProvider().toString()) .oauthId(providerUid) .email(email) .nickname(nickname) + .appleRefreshToken(refreshToken) .build(); } catch (Exception e) { throw new RuntimeException("Error decoding payload", e); diff --git a/src/main/java/com/moneymong/global/security/oauth/handler/KakaoService.java b/src/main/java/com/moneymong/global/security/oauth/handler/KakaoService.java index ce3d71c..e889907 100644 --- a/src/main/java/com/moneymong/global/security/oauth/handler/KakaoService.java +++ b/src/main/java/com/moneymong/global/security/oauth/handler/KakaoService.java @@ -69,7 +69,7 @@ public OAuthUserDataResponse getOAuthUserData(OAuthUserDataRequest request) { } @Override - public void unlink(String token) { + public void unlink(Long userId) { } } diff --git a/src/main/java/com/moneymong/global/security/oauth/handler/OAuthAuthenticationHandler.java b/src/main/java/com/moneymong/global/security/oauth/handler/OAuthAuthenticationHandler.java index 7e07524..dfee4aa 100644 --- a/src/main/java/com/moneymong/global/security/oauth/handler/OAuthAuthenticationHandler.java +++ b/src/main/java/com/moneymong/global/security/oauth/handler/OAuthAuthenticationHandler.java @@ -9,5 +9,5 @@ public interface OAuthAuthenticationHandler { OAuthUserDataResponse getOAuthUserData(OAuthUserDataRequest request); - void unlink(String token); + void unlink(Long userId); } diff --git a/src/main/java/com/moneymong/global/security/service/OAuthService.java b/src/main/java/com/moneymong/global/security/service/OAuthService.java index a6161a1..11a0cf5 100644 --- a/src/main/java/com/moneymong/global/security/service/OAuthService.java +++ b/src/main/java/com/moneymong/global/security/service/OAuthService.java @@ -1,7 +1,11 @@ package com.moneymong.global.security.service; import com.moneymong.domain.user.api.request.LoginRequest; -import com.moneymong.domain.user.api.request.UserDeleteRequest; + +import com.moneymong.domain.user.entity.User; +import com.moneymong.domain.user.repository.UserRepository; +import com.moneymong.global.exception.custom.NotFoundException; + import com.moneymong.global.security.oauth.dto.OAuthUserDataRequest; import com.moneymong.global.security.oauth.dto.OAuthUserDataResponse; import com.moneymong.global.security.oauth.handler.OAuthAuthenticationHandler; @@ -12,15 +16,19 @@ import java.util.function.Function; import java.util.stream.Collectors; +import static com.moneymong.global.exception.enums.ErrorCode.USER_NOT_FOUND; + @Service public class OAuthService { private final Map oAuthAuthenticationHandlers; + private final UserRepository userRepository; - public OAuthService(List oAuthAuthenticationHandlers) { + public OAuthService(List oAuthAuthenticationHandlers, UserRepository userRepository) { this.oAuthAuthenticationHandlers = oAuthAuthenticationHandlers.stream().collect( Collectors.toConcurrentMap(OAuthAuthenticationHandler::getAuthProvider, Function.identity()) ); + this.userRepository = userRepository; } public OAuthUserDataResponse login(LoginRequest loginRequest) { @@ -37,10 +45,14 @@ public OAuthUserDataResponse login(LoginRequest loginRequest) { return oAuthHandler.getOAuthUserData(request); } - public void revoke(UserDeleteRequest deleteRequest) { - OAuthProvider oAuthProvider = OAuthProvider.get(deleteRequest.getProvider()); + public void revoke(Long userId) { + User user = userRepository + .findById(userId) + .orElseThrow(() -> new NotFoundException(USER_NOT_FOUND)); + + OAuthProvider oAuthProvider = OAuthProvider.get(user.getProvider()); OAuthAuthenticationHandler oAuthHandler = this.oAuthAuthenticationHandlers.get(oAuthProvider); - oAuthHandler.unlink(deleteRequest.getToken()); + oAuthHandler.unlink(userId); } }