diff --git a/src/main/java/com/gdschongik/gdsc/domain/member/api/CommonMemberController.java b/src/main/java/com/gdschongik/gdsc/domain/member/api/CommonMemberController.java index c35303519..62495f14a 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/member/api/CommonMemberController.java +++ b/src/main/java/com/gdschongik/gdsc/domain/member/api/CommonMemberController.java @@ -1,6 +1,7 @@ package com.gdschongik.gdsc.domain.member.api; import com.gdschongik.gdsc.domain.member.application.CommonMemberService; +import com.gdschongik.gdsc.domain.member.dto.response.MemberAccountInfoResponse; import com.gdschongik.gdsc.domain.member.dto.response.MemberDepartmentResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -34,4 +35,11 @@ public ResponseEntity> searchDepartments( List response = commonMemberService.searchDepartments(departmentName); return ResponseEntity.ok().body(response); } + + @Operation(summary = "내 계정 정보 조회", description = "내 계정 정보를 조회합니다.") + @GetMapping("/me/account-info") + public ResponseEntity getAccountInfo() { + MemberAccountInfoResponse response = commonMemberService.getAccountInfo(); + return ResponseEntity.ok().body(response); + } } diff --git a/src/main/java/com/gdschongik/gdsc/domain/member/application/CommonMemberService.java b/src/main/java/com/gdschongik/gdsc/domain/member/application/CommonMemberService.java index 3ae725a12..fb19ae07c 100644 --- a/src/main/java/com/gdschongik/gdsc/domain/member/application/CommonMemberService.java +++ b/src/main/java/com/gdschongik/gdsc/domain/member/application/CommonMemberService.java @@ -5,10 +5,13 @@ import com.gdschongik.gdsc.domain.member.dao.MemberRepository; import com.gdschongik.gdsc.domain.member.domain.Department; import com.gdschongik.gdsc.domain.member.domain.Member; +import com.gdschongik.gdsc.domain.member.dto.response.MemberAccountInfoResponse; import com.gdschongik.gdsc.domain.member.dto.response.MemberDepartmentResponse; import com.gdschongik.gdsc.domain.membership.dao.MembershipRepository; import com.gdschongik.gdsc.domain.membership.domain.Membership; import com.gdschongik.gdsc.global.exception.CustomException; +import com.gdschongik.gdsc.global.util.MemberUtil; +import com.gdschongik.gdsc.infra.github.client.GithubClient; import java.util.Arrays; import java.util.List; import lombok.RequiredArgsConstructor; @@ -23,6 +26,8 @@ public class CommonMemberService { private final MembershipRepository membershipRepository; private final MemberRepository memberRepository; + private final MemberUtil memberUtil; + private final GithubClient githubClient; @Transactional(readOnly = true) public List getDepartments() { @@ -81,4 +86,11 @@ public void demoteMemberToAssociateByMembership(Long membershipId) { log.info("[CommonMemberService] 준회원 강등 완료: memberId={}", member.getId()); } + + @Transactional(readOnly = true) + public MemberAccountInfoResponse getAccountInfo() { + Member currentMember = memberUtil.getCurrentMember(); + String githubHandle = githubClient.getGithubHandle(currentMember.getOauthId()); + return MemberAccountInfoResponse.of(currentMember, githubHandle); + } } diff --git a/src/main/java/com/gdschongik/gdsc/domain/member/dto/response/MemberAccountInfoResponse.java b/src/main/java/com/gdschongik/gdsc/domain/member/dto/response/MemberAccountInfoResponse.java new file mode 100644 index 000000000..285c44634 --- /dev/null +++ b/src/main/java/com/gdschongik/gdsc/domain/member/dto/response/MemberAccountInfoResponse.java @@ -0,0 +1,9 @@ +package com.gdschongik.gdsc.domain.member.dto.response; + +import com.gdschongik.gdsc.domain.member.domain.Member; + +public record MemberAccountInfoResponse(String name, String githubHandle) { + public static MemberAccountInfoResponse of(Member member, String githubHandle) { + return new MemberAccountInfoResponse(member.getName(), githubHandle); + } +} diff --git a/src/main/java/com/gdschongik/gdsc/global/common/constant/GithubConstant.java b/src/main/java/com/gdschongik/gdsc/global/common/constant/GithubConstant.java index d524cb9f4..2ffd6b0c7 100644 --- a/src/main/java/com/gdschongik/gdsc/global/common/constant/GithubConstant.java +++ b/src/main/java/com/gdschongik/gdsc/global/common/constant/GithubConstant.java @@ -4,6 +4,7 @@ public class GithubConstant { public static final String GITHUB_DOMAIN = "github.com/"; public static final String GITHUB_ASSIGNMENT_PATH = "week%d/WIL.md"; + public static final String GITHUB_USER_API_URL = "https://api.github.com/user/%s"; private GithubConstant() {} } diff --git a/src/main/java/com/gdschongik/gdsc/global/exception/ErrorCode.java b/src/main/java/com/gdschongik/gdsc/global/exception/ErrorCode.java index 7982912ab..2220ddc33 100644 --- a/src/main/java/com/gdschongik/gdsc/global/exception/ErrorCode.java +++ b/src/main/java/com/gdschongik/gdsc/global/exception/ErrorCode.java @@ -167,6 +167,7 @@ public enum ErrorCode { GITHUB_CONTENT_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 파일입니다."), GITHUB_FILE_READ_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "깃허브 파일 읽기에 실패했습니다."), GITHUB_COMMIT_DATE_FETCH_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "깃허브 커밋 날짜 조회에 실패했습니다."), + GITHUB_USER_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 깃허브 유저입니다."), ; private final HttpStatus status; private final String message; diff --git a/src/main/java/com/gdschongik/gdsc/infra/github/GithubUserRequest.java b/src/main/java/com/gdschongik/gdsc/infra/github/GithubUserRequest.java new file mode 100644 index 000000000..c42c7266c --- /dev/null +++ b/src/main/java/com/gdschongik/gdsc/infra/github/GithubUserRequest.java @@ -0,0 +1,58 @@ +package com.gdschongik.gdsc.infra.github; + +import static com.gdschongik.gdsc.global.common.constant.GithubConstant.*; + +import com.gdschongik.gdsc.global.exception.CustomException; +import com.gdschongik.gdsc.global.exception.ErrorCode; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.List; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.kohsuke.github.connector.GitHubConnectorRequest; + +@RequiredArgsConstructor +public class GithubUserRequest implements GitHubConnectorRequest { + + private final String oauthId; + + @Override + public String method() { + return "GET"; + } + + @Override + public Map> allHeaders() { + return Map.of(); + } + + @Override + public String header(String s) { + return ""; + } + + @Override + public String contentType() { + return ""; + } + + @Override + public InputStream body() { + return null; + } + + @Override + public URL url() { + try { + return new URL(GITHUB_USER_API_URL.formatted(oauthId)); + } catch (MalformedURLException e) { + throw new CustomException(ErrorCode.INTERNAL_SERVER_ERROR); + } + } + + @Override + public boolean hasBody() { + return false; + } +} diff --git a/src/main/java/com/gdschongik/gdsc/infra/github/client/GithubClient.java b/src/main/java/com/gdschongik/gdsc/infra/github/client/GithubClient.java index 869a2a6ec..b7eb73fb5 100644 --- a/src/main/java/com/gdschongik/gdsc/infra/github/client/GithubClient.java +++ b/src/main/java/com/gdschongik/gdsc/infra/github/client/GithubClient.java @@ -3,19 +3,24 @@ import static com.gdschongik.gdsc.global.common.constant.GithubConstant.*; import static com.gdschongik.gdsc.global.exception.ErrorCode.*; +import com.fasterxml.jackson.databind.ObjectMapper; import com.gdschongik.gdsc.domain.study.domain.AssignmentSubmission; import com.gdschongik.gdsc.domain.study.domain.AssignmentSubmissionFetchExecutor; import com.gdschongik.gdsc.domain.study.domain.AssignmentSubmissionFetcher; import com.gdschongik.gdsc.global.exception.CustomException; +import com.gdschongik.gdsc.infra.github.GithubUserRequest; import java.io.IOException; import java.io.InputStream; import java.time.LocalDateTime; import java.time.ZoneId; +import java.util.Map; import lombok.RequiredArgsConstructor; import org.kohsuke.github.GHCommit; import org.kohsuke.github.GHContent; import org.kohsuke.github.GHRepository; import org.kohsuke.github.GitHub; +import org.kohsuke.github.connector.GitHubConnector; +import org.kohsuke.github.connector.GitHubConnectorResponse; import org.springframework.stereotype.Component; @Component @@ -23,6 +28,7 @@ public class GithubClient { private final GitHub github; + private final GitHubConnector gitHubConnector = GitHubConnector.DEFAULT; public GHRepository getRepository(String ownerRepo) { try { @@ -32,6 +38,16 @@ public GHRepository getRepository(String ownerRepo) { } } + public String getGithubHandle(String oauthId) { + try (GitHubConnectorResponse response = gitHubConnector.send(new GithubUserRequest(oauthId)); + InputStream inputStream = response.bodyStream(); ) { + // api가 login이라는 이름으로 사용자의 github handle을 반환합니다. + return (String) new ObjectMapper().readValue(inputStream, Map.class).get("login"); + } catch (IOException e) { + throw new CustomException(GITHUB_USER_NOT_FOUND); + } + } + /** * 직접 요청을 수행하는 대신, fetcher를 통해 요청을 수행합니다. * 요청 수행 시 발생하는 예외의 경우 과제 채점에 사용되므로, 실제 요청은 채점 로직 내부에서 수행되어야 합니다.