diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java index 697fad220..0d127b76f 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/AgendaProfileController.java @@ -25,21 +25,29 @@ import gg.agenda.api.user.agendaprofile.controller.response.CurrentAttendAgendaListResDto; import gg.agenda.api.user.agendaprofile.service.AgendaProfileFindService; import gg.agenda.api.user.agendaprofile.service.AgendaProfileService; +import gg.agenda.api.user.agendaprofile.service.IntraProfileUtils; +import gg.agenda.api.user.agendaprofile.service.intraprofile.IntraProfile; +import gg.agenda.api.user.ticket.service.TicketService; import gg.auth.UserDto; import gg.auth.argumentresolver.Login; +import gg.data.agenda.AgendaProfile; import gg.data.agenda.AgendaTeamProfile; import gg.data.user.type.RoleType; import gg.utils.dto.PageRequestDto; import gg.utils.dto.PageResponseDto; import io.swagger.v3.oas.annotations.Parameter; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +@Slf4j @RestController @RequiredArgsConstructor @RequestMapping("/agenda/profile") public class AgendaProfileController { private final AgendaProfileFindService agendaProfileFindService; private final AgendaProfileService agendaProfileService; + private final TicketService ticketService; + private final IntraProfileUtils intraProfileUtils; /** * AgendaProfile 상세 조회 API @@ -49,9 +57,12 @@ public class AgendaProfileController { @GetMapping public ResponseEntity myAgendaProfileDetails( @Login @Parameter(hidden = true) UserDto user) { - AgendaProfileDetailsResDto agendaProfileDetails = agendaProfileFindService.detailsAgendaProfile( - user.getIntraId()); - return ResponseEntity.status(HttpStatus.OK).body(agendaProfileDetails); + AgendaProfile profile = agendaProfileFindService.findAgendaProfileByIntraId(user.getIntraId()); + int ticketCount = ticketService.findTicketList(profile).size(); + IntraProfile intraProfile = intraProfileUtils.getIntraProfile(); + AgendaProfileDetailsResDto agendaProfileDetails = AgendaProfileDetailsResDto.toDto( + profile, ticketCount, intraProfile); + return ResponseEntity.ok(agendaProfileDetails); } /** diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/AgendaProfileDetailsResDto.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/AgendaProfileDetailsResDto.java index 024c41fa2..fe479063c 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/AgendaProfileDetailsResDto.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/controller/response/AgendaProfileDetailsResDto.java @@ -1,8 +1,10 @@ package gg.agenda.api.user.agendaprofile.controller.response; +import gg.agenda.api.user.agendaprofile.service.intraprofile.IntraProfile; import gg.data.agenda.AgendaProfile; import gg.data.agenda.type.Coalition; import gg.data.agenda.type.Location; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -16,13 +18,29 @@ public class AgendaProfileDetailsResDto { private Coalition userCoalition; private Location userLocation; private int ticketCount; + private IntraProfile intraProfile; - public AgendaProfileDetailsResDto(String intraId, AgendaProfile entity, int ticketCount) { - this.userIntraId = intraId; - this.userContent = entity.getContent(); - this.userGithub = entity.getGithubUrl(); - this.userCoalition = entity.getCoalition(); - this.userLocation = entity.getLocation(); + @Builder + public AgendaProfileDetailsResDto(String userIntraId, String userContent, String userGithub, + Coalition userCoalition, Location userLocation, int ticketCount, IntraProfile intraProfile) { + this.userIntraId = userIntraId; + this.userContent = userContent; + this.userGithub = userGithub; + this.userCoalition = userCoalition; + this.userLocation = userLocation; this.ticketCount = ticketCount; + this.intraProfile = intraProfile; + } + + public static AgendaProfileDetailsResDto toDto(AgendaProfile profile, int ticketCount, IntraProfile intraProfile) { + return AgendaProfileDetailsResDto.builder() + .userIntraId(profile.getIntraId()) + .userContent(profile.getContent()) + .userGithub(profile.getGithubUrl()) + .userCoalition(profile.getCoalition()) + .userLocation(profile.getLocation()) + .ticketCount(ticketCount) + .intraProfile(intraProfile) + .build(); } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java index 67c4416c4..41b546b91 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileFindService.java @@ -10,8 +10,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import gg.agenda.api.user.agendaprofile.controller.response.AgendaProfileDetailsResDto; -import gg.agenda.api.user.agendaprofile.controller.response.AttendedAgendaListResDto; import gg.agenda.api.user.agendaprofile.controller.response.CurrentAttendAgendaListResDto; import gg.data.agenda.Agenda; import gg.data.agenda.AgendaProfile; @@ -21,35 +19,23 @@ import gg.repo.agenda.AgendaProfileRepository; import gg.repo.agenda.AgendaRepository; import gg.repo.agenda.AgendaTeamProfileRepository; -import gg.repo.agenda.AgendaTeamRepository; -import gg.repo.agenda.TicketRepository; -import gg.repo.user.UserRepository; import gg.utils.exception.custom.NotExistException; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +@Slf4j @Service @RequiredArgsConstructor public class AgendaProfileFindService { private final AgendaProfileRepository agendaProfileRepository; - private final TicketRepository ticketRepository; private final AgendaTeamProfileRepository agendaTeamProfileRepository; private final AgendaRepository agendaRepository; - /** - * AgendaProfile 상세 정보를 조회하는 메서드 - * @param intraId 로그인한 유저의 id - * @return AgendaProfileDetailsResDto 객체 - */ @Transactional(readOnly = true) - public AgendaProfileDetailsResDto detailsAgendaProfile(String intraId) { - AgendaProfile agendaProfile = agendaProfileRepository.findByIntraId(intraId) + public AgendaProfile findAgendaProfileByIntraId(String intraId) { + return agendaProfileRepository.findByIntraId(intraId) .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); - - int ticketCount = ticketRepository.findByAgendaProfileIdAndIsUsedFalseAndIsApprovedTrue(agendaProfile.getId()) - .size(); - - return new AgendaProfileDetailsResDto(intraId, agendaProfile, ticketCount); } /** diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileService.java index 2f30b1d8a..6fffc7363 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/AgendaProfileService.java @@ -35,10 +35,4 @@ public void modifyAgendaProfile(Long userId, AgendaProfileChangeReqDto reqDto) { agendaProfile.updateProfile(reqDto.getUserContent(), reqDto.getUserGithub()); agendaProfileRepository.save(agendaProfile); } - - @Transactional(readOnly = true) - public AgendaProfile getAgendaProfile(Long userId) { - return agendaProfileRepository.findByUserId(userId) - .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); - } } diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/IntraProfileUtils.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/IntraProfileUtils.java new file mode 100644 index 000000000..890d2980f --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/IntraProfileUtils.java @@ -0,0 +1,68 @@ +package gg.agenda.api.user.agendaprofile.service; + +import static gg.utils.exception.ErrorCode.*; + +import java.util.List; +import java.util.Objects; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.stereotype.Component; + +import gg.agenda.api.user.agendaprofile.service.intraprofile.IntraAchievement; +import gg.agenda.api.user.agendaprofile.service.intraprofile.IntraImage; +import gg.agenda.api.user.agendaprofile.service.intraprofile.IntraProfile; +import gg.agenda.api.user.agendaprofile.service.intraprofile.IntraProfileResponse; +import gg.auth.FortyTwoAuthUtil; +import gg.utils.exception.custom.AuthenticationException; +import gg.utils.external.ApiUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component +@RequiredArgsConstructor +public class IntraProfileUtils { + private static final String INTRA_PROFILE_URL = "https://api.intra.42.fr/v2/me"; + + private final FortyTwoAuthUtil fortyTwoAuthUtil; + + private final ApiUtil apiUtil; + + public IntraProfile getIntraProfile() { + IntraProfileResponse intraProfileResponse = requestIntraProfile(); + intraProfileResponseValidation(intraProfileResponse); + IntraImage intraImage = intraProfileResponse.getImage(); + List intraAchievements = intraProfileResponse.getAchievements(); + return new IntraProfile(intraImage.getLink(), intraAchievements); + } + + private IntraProfileResponse requestIntraProfile() { + try { + String accessToken = fortyTwoAuthUtil.getAccessToken(); + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(accessToken); + return apiUtil.apiCall(INTRA_PROFILE_URL, IntraProfileResponse.class, headers, HttpMethod.GET); + } catch (Exception e) { + String accessToken = fortyTwoAuthUtil.refreshAccessToken(); + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(accessToken); + return apiUtil.apiCall(INTRA_PROFILE_URL, IntraProfileResponse.class, headers, HttpMethod.GET); + } + } + + private void intraProfileResponseValidation(IntraProfileResponse intraProfileResponse) { + if (Objects.isNull(intraProfileResponse)) { + throw new AuthenticationException(AUTH_NOT_FOUND); + } + if (Objects.isNull(intraProfileResponse.getImage())) { + throw new AuthenticationException(AUTH_NOT_FOUND); + } + if (Objects.isNull(intraProfileResponse.getAchievements())) { + throw new AuthenticationException(AUTH_NOT_FOUND); + } + if (Objects.isNull(intraProfileResponse.getImage().getLink())) { + throw new AuthenticationException(AUTH_NOT_FOUND); + } + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraAchievement.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraAchievement.java new file mode 100644 index 000000000..ade777add --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraAchievement.java @@ -0,0 +1,53 @@ +package gg.agenda.api.user.agendaprofile.service.intraprofile; + +import java.net.URL; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class IntraAchievement { + + private static final String IMAGE_URL = "https://cdn.intra.42.fr"; + + private Long id; + + private String name; + + private String description; + + private String tier; + + private String kind; + + private boolean visible; + + private String image; + + @JsonProperty("nbr_of_success") + private String nbrOfSuccess; + + @JsonProperty("users_url") + private URL usersUrl; + + @Builder + public IntraAchievement(Long id, String name, String description, String tier, String kind, boolean visible, + String image, String nbrOfSuccess, URL usersUrl) { + this.id = id; + this.name = name; + this.description = description; + this.tier = tier; + this.kind = kind; + this.visible = visible; + this.image = image; + this.nbrOfSuccess = nbrOfSuccess; + this.usersUrl = usersUrl; + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraImage.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraImage.java new file mode 100644 index 000000000..1f17a0a2f --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraImage.java @@ -0,0 +1,23 @@ +package gg.agenda.api.user.agendaprofile.service.intraprofile; + +import java.net.URL; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class IntraImage { + + private URL link; + + private IntraImageVersion versions; + + @Builder + public IntraImage(URL link, IntraImageVersion versions) { + this.link = link; + this.versions = versions; + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraImageVersion.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraImageVersion.java new file mode 100644 index 000000000..f5b00d2ce --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraImageVersion.java @@ -0,0 +1,29 @@ +package gg.agenda.api.user.agendaprofile.service.intraprofile; + +import java.net.URL; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class IntraImageVersion { + + private URL large; + + private URL medium; + + private URL small; + + private URL micro; + + @Builder + public IntraImageVersion(URL large, URL medium, URL small, URL micro) { + this.large = large; + this.medium = medium; + this.small = small; + this.micro = micro; + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraProfile.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraProfile.java new file mode 100644 index 000000000..6ed2b7862 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraProfile.java @@ -0,0 +1,24 @@ +package gg.agenda.api.user.agendaprofile.service.intraprofile; + +import java.net.URL; +import java.util.List; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class IntraProfile { + + private URL imageUrl; + + private List achievements; + + @Builder + public IntraProfile(URL imageUrl, List achievements) { + this.imageUrl = imageUrl; + this.achievements = achievements; + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraProfileResponse.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraProfileResponse.java new file mode 100644 index 000000000..8031e3561 --- /dev/null +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/agendaprofile/service/intraprofile/IntraProfileResponse.java @@ -0,0 +1,23 @@ +package gg.agenda.api.user.agendaprofile.service.intraprofile; + +import java.util.List; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class IntraProfileResponse { + + IntraImage image; + + List achievements; + + @Builder + public IntraProfileResponse(IntraImage image, List achievements) { + this.image = image; + this.achievements = achievements; + } +} diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java index 4598c1639..5e30ce829 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/controller/TicketController.java @@ -1,5 +1,7 @@ package gg.agenda.api.user.ticket.controller; +import static gg.utils.exception.ErrorCode.*; + import java.util.List; import java.util.stream.Collectors; @@ -12,9 +14,6 @@ import org.springframework.data.domain.Sort; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PatchMapping; @@ -22,15 +21,18 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import gg.agenda.api.user.agendaprofile.service.AgendaProfileFindService; import gg.agenda.api.user.ticket.controller.response.TicketCountResDto; import gg.agenda.api.user.ticket.controller.response.TicketHistoryResDto; import gg.agenda.api.user.ticket.service.TicketService; import gg.auth.UserDto; import gg.auth.argumentresolver.Login; +import gg.data.agenda.AgendaProfile; import gg.data.agenda.Ticket; import gg.utils.cookie.CookieUtil; import gg.utils.dto.PageRequestDto; import gg.utils.dto.PageResponseDto; +import gg.utils.exception.custom.AuthenticationException; import gg.utils.exception.user.TokenNotValidException; import io.swagger.v3.oas.annotations.Parameter; import lombok.RequiredArgsConstructor; @@ -41,6 +43,7 @@ public class TicketController { private final CookieUtil cookieUtil; private final TicketService ticketService; + private final AgendaProfileFindService agendaProfileFindService; /** * 티켓 설정 추가 @@ -59,7 +62,8 @@ public ResponseEntity ticketSetupAdd(@Parameter(hidden = true) @Login User */ @GetMapping public ResponseEntity ticketCountFind(@Parameter(hidden = true) @Login UserDto user) { - List tickets = ticketService.findTicketList(user); + AgendaProfile profile = agendaProfileFindService.findAgendaProfileByIntraId(user.getIntraId()); + List tickets = ticketService.findTicketList(profile); long approvedCount = tickets.stream() .filter(Ticket::getIsApproved) .count(); @@ -75,16 +79,14 @@ public ResponseEntity ticketCountFind(@Parameter(hidden = tru @PatchMapping public ResponseEntity ticketApproveModify(@Parameter(hidden = true) @Login UserDto user, HttpServletResponse response) { - SecurityContext context = SecurityContextHolder.getContext(); - Authentication authentication = context.getAuthentication(); - try { - ticketService.modifyTicketApprove(user, authentication); + AgendaProfile profile = agendaProfileFindService.findAgendaProfileByIntraId(user.getIntraId()); + ticketService.modifyTicketApprove(profile); + return ResponseEntity.noContent().build(); } catch (TokenNotValidException e) { cookieUtil.deleteCookie(response, "refresh_token"); - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + throw new AuthenticationException(REFRESH_TOKEN_EXPIRED); } - return ResponseEntity.noContent().build(); } /** diff --git a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java index 4efec89ab..371d86b73 100644 --- a/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java +++ b/gg-agenda-api/src/main/java/gg/agenda/api/user/ticket/service/TicketService.java @@ -12,15 +12,12 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; -import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.client.HttpClientErrorException; -import gg.agenda.api.user.agendaprofile.service.AgendaProfileService; +import gg.agenda.api.user.agendaprofile.service.AgendaProfileFindService; import gg.agenda.api.user.ticket.controller.response.TicketHistoryResDto; import gg.auth.FortyTwoAuthUtil; import gg.auth.UserDto; @@ -44,7 +41,7 @@ public class TicketService { private final FortyTwoAuthUtil fortyTwoAuthUtil; private final TicketRepository ticketRepository; private final AgendaRepository agendaRepository; - private final AgendaProfileService agendaProfileService; + private final AgendaProfileFindService agendaProfileFindService; private final AgendaProfileRepository agendaProfileRepository; @Value("https://api.intra.42.fr/v2/users/{id}/correction_point_historics?sort=-id") @@ -75,30 +72,22 @@ public void addTicketSetup(UserDto user) { /** * 티켓 수 조회 - * @param user 사용자 정보 + * @param profile AgendaProfile * @return 티켓 수 */ @Transactional(readOnly = true) - public List findTicketList(UserDto user) { - AgendaProfile profile = agendaProfileRepository.findByUserId(user.getId()) - .orElseThrow(() -> new NotExistException(AGENDA_PROFILE_NOT_FOUND)); - return ticketRepository.findByAgendaProfileAndIsUsedFalse(profile); + public List findTicketList(AgendaProfile profile) { + return ticketRepository.findByAgendaProfileAndIsUsedFalseAndIsApprovedTrue(profile); } /** * 티켓 승인/거절 - * @param user 사용자 정보 + * @param profile 사용자 정보 */ @Transactional - public void modifyTicketApprove(UserDto user, Authentication authentication) { - AgendaProfile profile = agendaProfileService.getAgendaProfile(user.getId()); + public void modifyTicketApprove(AgendaProfile profile) { Ticket setUpTicket = getSetUpTicket(profile); - - OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken)authentication; - OAuth2AuthorizedClient oAuth2AuthorizedClient = fortyTwoAuthUtil.getOAuth2AuthorizedClient(oauthToken); - - List> pointHistory = getPointHistory(profile, oAuth2AuthorizedClient, authentication); - + List> pointHistory = getPointHistory(profile); processTicketApproval(profile, setUpTicket, pointHistory); } @@ -115,22 +104,19 @@ public Ticket getSetUpTicket(AgendaProfile profile) { /** * 포인트 이력 조회 * @param profile AgendaProfile - * @param client OAuth2AuthorizedClient - * @param authentication Authentication * @return 포인트 이력 */ - private List> getPointHistory(AgendaProfile profile, OAuth2AuthorizedClient client, - Authentication authentication) { + private List> getPointHistory(AgendaProfile profile) { String url = pointHistoryUrl.replace("{id}", profile.getFortyTwoId().toString()); ParameterizedTypeReference>> responseType = new ParameterizedTypeReference<>() { }; - try { - return apiUtil.callApiWithAccessToken(url, client.getAccessToken().getTokenValue(), responseType); + String accessToken = fortyTwoAuthUtil.getAccessToken(); + return apiUtil.callApiWithAccessToken(url, accessToken, responseType); } catch (HttpClientErrorException e) { if (e.getStatusCode() == HttpStatus.UNAUTHORIZED) { - client = fortyTwoAuthUtil.refreshAccessToken(client, authentication); - return apiUtil.callApiWithAccessToken(url, client.getAccessToken().getTokenValue(), responseType); + String accessToken = fortyTwoAuthUtil.refreshAccessToken(); + return apiUtil.callApiWithAccessToken(url, accessToken, responseType); } throw e; } diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java index 8e80d6bd0..477028add 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/agendaprofile/AgendaProfileControllerTest.java @@ -5,6 +5,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import java.net.URL; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -17,8 +18,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; @@ -31,6 +34,8 @@ import gg.agenda.api.user.agendaprofile.controller.response.AgendaProfileInfoDetailsResDto; import gg.agenda.api.user.agendaprofile.controller.response.AttendedAgendaListResDto; import gg.agenda.api.user.agendaprofile.controller.response.CurrentAttendAgendaListResDto; +import gg.agenda.api.user.agendaprofile.service.IntraProfileUtils; +import gg.agenda.api.user.agendaprofile.service.intraprofile.IntraProfile; import gg.data.agenda.Agenda; import gg.data.agenda.AgendaProfile; import gg.data.agenda.AgendaTeam; @@ -59,6 +64,10 @@ public class AgendaProfileControllerTest { private AgendaMockData agendaMockData; @Autowired private AgendaProfileRepository agendaProfileRepository; + + @MockBean + private IntraProfileUtils intraProfileUtils; + User user; String accessToken; AgendaProfile agendaProfile; @@ -77,6 +86,9 @@ void beforeEach() { @DisplayName("로그인된 유저에 해당하는 Agenda profile를 상세 조회합니다.") void test() throws Exception { //given + URL url = new URL("http://localhost:8080"); + IntraProfile intraProfile = new IntraProfile(url, List.of()); + Mockito.when(intraProfileUtils.getIntraProfile()).thenReturn(intraProfile); AgendaProfile agendaProfile = agendaMockData.createAgendaProfile(user, SEOUL); agendaMockData.createTicket(agendaProfile); // when diff --git a/gg-agenda-api/src/test/java/gg/agenda/api/user/ticket/TicketControllerTest.java b/gg-agenda-api/src/test/java/gg/agenda/api/user/ticket/TicketControllerTest.java index f42d21372..dcfcb2c59 100644 --- a/gg-agenda-api/src/test/java/gg/agenda/api/user/ticket/TicketControllerTest.java +++ b/gg-agenda-api/src/test/java/gg/agenda/api/user/ticket/TicketControllerTest.java @@ -146,6 +146,8 @@ void findTicketCountSuccess() throws Exception { //given ticketFixture.createTicket(seoulUserAgendaProfile); ticketFixture.createTicket(seoulUserAgendaProfile); + ticketFixture.createNotApporveTicket(seoulUserAgendaProfile); + //when String res = mockMvc.perform( get("/agenda/ticket") diff --git a/gg-auth/src/main/java/gg/auth/FortyTwoAuthUtil.java b/gg-auth/src/main/java/gg/auth/FortyTwoAuthUtil.java index 22a614fc0..e0f851f4d 100644 --- a/gg-auth/src/main/java/gg/auth/FortyTwoAuthUtil.java +++ b/gg-auth/src/main/java/gg/auth/FortyTwoAuthUtil.java @@ -5,11 +5,14 @@ import java.time.Instant; import java.util.List; import java.util.Map; +import java.util.Objects; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; @@ -19,7 +22,6 @@ import org.springframework.stereotype.Component; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import org.springframework.web.client.RestClientException; import gg.utils.exception.ErrorCode; import gg.utils.exception.custom.NotExistException; @@ -32,72 +34,88 @@ public class FortyTwoAuthUtil { private final ApiUtil apiUtil; private final OAuth2AuthorizedClientService authorizedClientService; - /** - * OAuth2AuthorizedClient 조회 - * @param oauthToken OAuth2AuthenticationToken - * @return OAuth2AuthorizedClient - */ - public OAuth2AuthorizedClient getOAuth2AuthorizedClient(OAuth2AuthenticationToken oauthToken) { - String registrationId = oauthToken.getAuthorizedClientRegistrationId(); - OAuth2AuthorizedClient client = authorizedClientService.loadAuthorizedClient(registrationId, - oauthToken.getName()); - if (client.getRefreshToken() == null) { - throw new NotExistException(AUTH_NOT_FOUND); - } - return client; + public String getAccessToken() { + Authentication authentication = getAuthenticationFromContext(); + OAuth2AuthorizedClient client = getClientFromAuthentication(authentication); + return client.getAccessToken().getTokenValue(); } /** * 토큰 갱신 - * @param client OAuth2AuthorizedClient - * @param authentication Authentication * @return 갱신된 OAuth2AuthorizedClient */ - public OAuth2AuthorizedClient refreshAccessToken(OAuth2AuthorizedClient client, Authentication authentication) { - try { - ClientRegistration registration = client.getClientRegistration(); - if (client.getRefreshToken() == null) { - throw new NotExistException(AUTH_NOT_FOUND); - } - - String tokenUri = registration.getProviderDetails().getTokenUri(); - MultiValueMap params = new LinkedMultiValueMap<>(); - params.add("grant_type", "refresh_token"); - params.add("refresh_token", client.getRefreshToken().getTokenValue()); - params.add("client_id", registration.getClientId()); - params.add("client_secret", registration.getClientSecret()); - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - - List> responseBody = apiUtil.apiCall(tokenUri, List.class, headers, params, - HttpMethod.POST); - if (responseBody == null || responseBody.isEmpty()) { - throw new NotExistException(ErrorCode.AUTH_NOT_FOUND); - } - Map map = responseBody.get(0); - - OAuth2AccessToken newAccessToken = new OAuth2AccessToken( - OAuth2AccessToken.TokenType.BEARER, - (String)map.get("access_token"), - Instant.now(), - Instant.now().plusSeconds((Integer)map.get("expires_in")) - ); - - OAuth2RefreshToken newRefreshToken = new OAuth2RefreshToken( - (String)map.get("refresh_token"), - Instant.now() - ); - - OAuth2AuthorizedClient newClient = new OAuth2AuthorizedClient( - registration, client.getPrincipalName(), newAccessToken, newRefreshToken); - - String principalName = authentication.getName(); - authorizedClientService.removeAuthorizedClient(registration.getRegistrationId(), principalName); - authorizedClientService.saveAuthorizedClient(newClient, authentication); - - return newClient; - } catch (RestClientException e) { + public String refreshAccessToken() { + Authentication authentication = getAuthenticationFromContext(); + OAuth2AuthorizedClient client = getClientFromAuthentication(authentication); + ClientRegistration registration = client.getClientRegistration(); + + OAuth2AuthorizedClient newClient = requestNewClient(client, registration); + + authorizedClientService.removeAuthorizedClient( + registration.getRegistrationId(), client.getPrincipalName()); + authorizedClientService.saveAuthorizedClient(newClient, authentication); + + return newClient.getAccessToken().getTokenValue(); + } + + private Authentication getAuthenticationFromContext() { + SecurityContext context = SecurityContextHolder.getContext(); + return context.getAuthentication(); + } + + private OAuth2AuthorizedClient getClientFromAuthentication(Authentication authentication) { + OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken)authentication; + String registrationId = oauthToken.getAuthorizedClientRegistrationId(); + return authorizedClientService.loadAuthorizedClient(registrationId, oauthToken.getName()); + } + + private OAuth2AuthorizedClient requestNewClient(OAuth2AuthorizedClient client, ClientRegistration registration) { + if (Objects.isNull(client.getRefreshToken())) { throw new NotExistException(AUTH_NOT_FOUND); } + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("grant_type", "refresh_token"); + params.add("refresh_token", client.getRefreshToken().getTokenValue()); + params.add("client_id", registration.getClientId()); + params.add("client_secret", registration.getClientSecret()); + + List> responseBody = apiUtil.apiCall( + registration.getProviderDetails().getTokenUri(), + List.class, + headers, + params, + HttpMethod.POST + ); + if (Objects.isNull(responseBody) || responseBody.isEmpty()) { + throw new NotExistException(ErrorCode.AUTH_NOT_FOUND); + } + return createNewClientFromApiResponse(responseBody.get(0), client); + } + + private OAuth2AuthorizedClient createNewClientFromApiResponse( + Map response, OAuth2AuthorizedClient client) { + + OAuth2AccessToken newAccessToken = new OAuth2AccessToken( + OAuth2AccessToken.TokenType.BEARER, + (String)response.get("access_token"), + Instant.now(), + Instant.now().plusSeconds((Integer)response.get("expires_in")) + ); + + OAuth2RefreshToken newRefreshToken = new OAuth2RefreshToken( + (String)response.get("refresh_token"), + Instant.now() + ); + + return new OAuth2AuthorizedClient( + client.getClientRegistration(), + client.getPrincipalName(), + newAccessToken, + newRefreshToken + ); } } diff --git a/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java b/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java index 0c35fffec..3c9c9b957 100644 --- a/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java +++ b/gg-repo/src/main/java/gg/repo/agenda/TicketRepository.java @@ -14,13 +14,11 @@ public interface TicketRepository extends JpaRepository { Optional findFirstByAgendaProfileAndIsApprovedTrueAndIsUsedFalseOrderByCreatedAtAsc( AgendaProfile agendaProfile); - List findByAgendaProfileIdAndIsUsedFalseAndIsApprovedTrue(Long agendaProfileId); - Optional findByAgendaProfileAndIsApprovedFalse(AgendaProfile agendaProfile); Optional findByAgendaProfileId(Long agendaProfileId); Page findByAgendaProfileId(Long agendaProfileId, Pageable pageable); - List findByAgendaProfileAndIsUsedFalse(AgendaProfile agendaProfile); + List findByAgendaProfileAndIsUsedFalseAndIsApprovedTrue(AgendaProfile agendaProfile); } diff --git a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java index 59374b432..0d3f14e08 100644 --- a/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java +++ b/gg-utils/src/main/java/gg/utils/exception/ErrorCode.java @@ -129,6 +129,7 @@ public enum ErrorCode { UNREADABLE_HTTP_MESSAGE(400, "CM008", "유효하지 않은 HTTP 메시지입니다."), CONFLICT(409, "CM009", "CONFLICT"), FORBIDDEN(403, "CM010", "접근이 금지된 요청입니다."), + REFRESH_TOKEN_EXPIRED(401, "CM011", "토큰이 만료되었습니다. 다시 로그인해주세요."), //Feedback FEEDBACK_NOT_FOUND(404, "FB100", "FB NOT FOUND"), diff --git a/gg-utils/src/main/java/gg/utils/exception/custom/AuthenticationException.java b/gg-utils/src/main/java/gg/utils/exception/custom/AuthenticationException.java index 6fb9ac499..107d3b307 100644 --- a/gg-utils/src/main/java/gg/utils/exception/custom/AuthenticationException.java +++ b/gg-utils/src/main/java/gg/utils/exception/custom/AuthenticationException.java @@ -8,4 +8,8 @@ public class AuthenticationException extends CustomRuntimeException { public AuthenticationException(String message, ErrorCode errorCode) { super(message, errorCode); } + + public AuthenticationException(ErrorCode errorCode) { + super(errorCode.getMessage(), errorCode); + } }