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

[BE] docs: GitHub 연동, 자신이 만든 리뷰 그룹 목록 조회 API 문서 작성 #1014

Open
wants to merge 8 commits into
base: develop
Choose a base branch
from
7 changes: 7 additions & 0 deletions backend/src/docs/asciidoc/auth.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
==== 깃허브로 로그인/회원가입

operation::github-auth[snippets="curl-request,request-fields,http-response"]

==== 로그아웃

operation::logout[snippets="curl-request,request-cookies,http-response"]
8 changes: 8 additions & 0 deletions backend/src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,11 @@ include::review-gather.adoc[]
=== 답변 하이라이트

include::highlight-answers.adoc[]

== 인증

include::auth.adoc[]

== 사용자

include::member.adoc[]
3 changes: 3 additions & 0 deletions backend/src/docs/asciidoc/member.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
==== 내 프로필 정보

operation::my-profile[snippets="curl-request,request-cookies,http-response,response-fields"]
4 changes: 4 additions & 0 deletions backend/src/docs/asciidoc/reviewgroup.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ operation::review-group-summary[snippets="curl-request,http-response,response-fi
==== 리뷰 요청 코드, 확인 코드 일치 여부

operation::review-group-check-access[snippets="curl-request,request-fields,http-response,response-cookies"]

==== 자신이 만든 리뷰 그룹 목록 조회

operation::review-group-list[snippets="curl-request,request-cookies,http-response,response-fields"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package reviewme.auth.controller;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import reviewme.auth.service.AuthService;
import reviewme.auth.service.dto.GithubCodeRequest;

@RestController
@RequiredArgsConstructor
public class AuthController {

private final AuthService authService;

@PostMapping("/v2/auth/github")
public ResponseEntity<Void> authWithGithub(
@Valid @RequestBody GithubCodeRequest request,
HttpServletRequest httpRequest
) {
return ResponseEntity.ok().build();
}
Comment on lines +19 to +25
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

산초가 올려준 디스커션의 인증 과정중 아래의 1~3번을 이 메서드에서 모두 하는것인지 궁금해요!(아직 구현이 안해서 헷갈려서 물어봅니당)

  1. auth code를 받음
  2. 그걸로 깃헙에 accessToken을 요청함
  3. 받은 accessToken으로 깃헙에 사용자의 정보를 요청해서 세션을 설정해 응답함

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네 맞아요👍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시 이때 사용자 정보나 로그인 정보같은 것은 안내려줘도 괜찮나요?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Kimprodp
사용자 정보를 따로 넘겨주기보다, 클라이언트와는 JSESSION_ID로만 로그인한 사용자에 대한 통신을 하면 된다 생각해요!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

정보를 1회성으로 내려주는 게 좋을까요, 프론트에서 정보를 확인할 수 있는 API를 뚫어두는 게 좋을까요?


@PostMapping("/v2/auth/logout")
public ResponseEntity<Void> logout(
HttpServletRequest httpRequest
) {
HttpSession session = httpRequest.getSession();
session.invalidate();
nayonsoso marked this conversation as resolved.
Show resolved Hide resolved

ResponseCookie cookie = ResponseCookie.from("JSESSIONID", "")
.path("/")
.maxAge(0)
.secure(true)
.httpOnly(true)
.build();

return ResponseEntity
.noContent()
.header(HttpHeaders.SET_COOKIE, cookie.toString())
.build();
}
nayonsoso marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package reviewme.auth.service;

import org.springframework.stereotype.Service;

@Service
public class AuthService {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package reviewme.auth.service.dto;

import jakarta.validation.constraints.NotBlank;

public record GithubCodeRequest(
@NotBlank(message = "깃허브 임시 코드를 입력해주세요.")
String code) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package reviewme.member.controller;

import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reviewme.member.service.MemberService;
import reviewme.member.service.dto.ProfileResponse;

@RestController
@RequiredArgsConstructor
public class MemberController {

private final MemberService memberService;

@GetMapping("/v2/members/profile/mine")
public ResponseEntity<ProfileResponse> getProfile() {
ProfileResponse response = memberService.getProfile();
return ResponseEntity.ok(response);
}
nayonsoso marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package reviewme.member.service;

import org.springframework.stereotype.Service;
import reviewme.member.service.dto.ProfileResponse;

@Service
public class MemberService {

public ProfileResponse getProfile() {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package reviewme.member.service.dto;

public record ProfileResponse(
String nickname,
String profileImageUrl
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import reviewme.reviewgroup.service.dto.CheckValidAccessRequest;
import reviewme.reviewgroup.service.dto.ReviewGroupCreationRequest;
import reviewme.reviewgroup.service.dto.ReviewGroupCreationResponse;
import reviewme.reviewgroup.service.dto.ReviewGroupListResponse;
import reviewme.reviewgroup.service.dto.ReviewGroupResponse;

@RestController
Expand Down Expand Up @@ -48,4 +49,11 @@ public ResponseEntity<Void> checkGroupAccessCode(
session.setAttribute("reviewRequestCode", request.reviewRequestCode());
return ResponseEntity.noContent().build();
}

@GetMapping("/v2/groups/mine")
public ResponseEntity<ReviewGroupListResponse> getMyReviewGroups() {
// TODO: 세션을 활용한 권한 체계에 따른 추가 조치 필요
ReviewGroupListResponse response = reviewGroupLookupService.getMyReviewGroups();
return ResponseEntity.ok(response);
}
nayonsoso marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import reviewme.reviewgroup.service.dto.ReviewGroupListResponse;
import reviewme.reviewgroup.service.exception.ReviewGroupNotFoundByReviewRequestCodeException;
import reviewme.reviewgroup.domain.ReviewGroup;
import reviewme.reviewgroup.repository.ReviewGroupRepository;
Expand All @@ -21,4 +22,9 @@ public ReviewGroupResponse getReviewGroupSummary(String reviewRequestCode) {

return new ReviewGroupResponse(reviewGroup.getReviewee(), reviewGroup.getProjectName());
}

public ReviewGroupListResponse getMyReviewGroups() {
// TODO: 생성일자 최신순 정렬
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package reviewme.reviewgroup.service.dto;

import java.time.LocalDate;

public record ReviewGroupDetailResponse(
nayonsoso marked this conversation as resolved.
Show resolved Hide resolved
String revieweeName,
String projectName,
LocalDate createdAt,
String reviewRequestCode
) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

프로젝트 목록에서 해당 프로젝트에 리뷰가 몇 개 작성되어있는지도 같이 제공하면 어떨까요?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오~ 괜찮은데요? 이건 프론트 분들이랑 이야기를 해봐야 할 것 같아요!

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package reviewme.reviewgroup.service.dto;

import java.util.List;

public record ReviewGroupListResponse(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

어떤 회원의 그룹 목록인지 확인할 수 있도록 memberId 필드에 추가하는 건 어떻게 생각하시나요?

#1017 (comment)
저희가 작업한 dto와 같은 이유입니다~

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

음 이거에 대해서 다시 생각해봤는데, 불필요한 응답 같다고 다시 생각을 바꾸게 되었어요///😔
#1017 (comment)
여기에 코멘트 달아놨습니다!

boolean isLastPage,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

내가 받은 리뷰와 동일하게 무한스크롤 구현을 생각해서요~ lastReviewId 필드는 필요하지 않을까요?

Copy link
Contributor

@nayonsoso nayonsoso Dec 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사실 일반적인 사용자가 무한 스크롤이 필요할 정도로 많은 리뷰 그룹을 만들 것이라고 생각되진 않는데요..! 🤔
그럼에도 [통일성 / 악의적인 무리한 요청 방지]를 위해서 무스를 추가해줘야 하려나요!
일단 다른 분들의 의견도 들어보겠습니다
@donghoony @Kimprodp

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

무한 스크롤 / 페이징을 여러 군데에서 사용할 것으로 보이는데, 따로 함수로 빼야할 것 같기도 하고.. 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

페이징 적용해서 나쁠건 없다고 생각합니다~
현재 내용은 한번에 보내주는 걸로 설계한 것으로 보이는데 isLastPage 는 다른 용도가 있나요?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그럼 페이징 적용하는 것으로 하겠습니다👍

@Kimprodp
isLastPage 를 내려주면 클라이언트 측에서 '이게 마지막 페이지이구나, 더 이상 요청을 보내면 안되겠구나'를 알 수 있습니다.
따라서 더 이상 로딩이 안되게 하거나 / ui 면에서 차이를 둘 수 있습니다.
이를 위해서 내려주고 있습니다!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아 제가 물어본건 페이징이 안되어있는데 저 필드가 있어서 물어본거였슴당

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Kimprodp
페이징될 수 있게 long lastReviewGroupId 도 추가해줬습니다 👍

List<ReviewGroupDetailResponse> reviewGroups
) {
}
14 changes: 13 additions & 1 deletion backend/src/test/java/reviewme/api/ApiTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,12 @@
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import reviewme.auth.controller.AuthController;
import reviewme.auth.service.AuthService;
import reviewme.highlight.controller.HighlightController;
import reviewme.highlight.service.HighlightService;
import reviewme.member.controller.MemberController;
import reviewme.member.service.MemberService;
import reviewme.review.controller.ReviewController;
import reviewme.review.service.ReviewDetailLookupService;
import reviewme.review.service.ReviewGatheredLookupService;
Expand All @@ -48,7 +52,9 @@
ReviewController.class,
TemplateController.class,
SectionController.class,
HighlightController.class
HighlightController.class,
MemberController.class,
AuthController.class
})
@ExtendWith(RestDocumentationExtension.class)
public abstract class ApiTest {
Expand Down Expand Up @@ -85,6 +91,12 @@ public abstract class ApiTest {
@MockBean
protected HighlightService highlightService;

@MockBean
protected MemberService memberService;

@MockBean
protected AuthService authService;

@MockBean
private ReviewGroupSessionResolver reviewGroupSessionResolver;

Expand Down
61 changes: 61 additions & 0 deletions backend/src/test/java/reviewme/api/AuthApiTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package reviewme.api;

import static org.hamcrest.Matchers.containsString;
import static org.springframework.restdocs.cookies.CookieDocumentation.cookieWithName;
import static org.springframework.restdocs.cookies.CookieDocumentation.requestCookies;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;

import org.junit.jupiter.api.Test;
import org.springframework.restdocs.cookies.CookieDescriptor;
import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler;
import org.springframework.restdocs.payload.FieldDescriptor;

public class AuthApiTest extends ApiTest {

@Test
void 깃허브로_인증한다() {
String request = """
{
"code": "github_auth_code"
}
""";

FieldDescriptor[] requestFieldDescriptors = {
fieldWithPath("code").description("깃허브 임시 인증 코드"),
};

RestDocumentationResultHandler handler = document(
"github-auth",
requestFields(requestFieldDescriptors)
);

givenWithSpec().log().all()
.body(request)
.when().post("/v2/auth/github")
.then().log().all()
.apply(handler)
.statusCode(200);
}

@Test
void 로그아웃한다() {
CookieDescriptor[] cookieDescriptors = {
cookieWithName("JSESSIONID").description("세션 ID")
};

RestDocumentationResultHandler handler = document(
"logout",
requestCookies(cookieDescriptors)
);

givenWithSpec().log().all()
.cookie("JSESSIONID", "SESSION12345678")
.when().post("/v2/auth/logout")
.then().log().all()
.apply(handler)
.statusCode(204)
.header("Set-Cookie", containsString("JSESSIONID=; Path=/; Max-Age=0"));
}
}
45 changes: 45 additions & 0 deletions backend/src/test/java/reviewme/api/MemberApiTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package reviewme.api;

import static org.springframework.restdocs.cookies.CookieDocumentation.cookieWithName;
import static org.springframework.restdocs.cookies.CookieDocumentation.requestCookies;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;

import org.junit.jupiter.api.Test;
import org.mockito.BDDMockito;
import org.springframework.restdocs.cookies.CookieDescriptor;
import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler;
import org.springframework.restdocs.payload.FieldDescriptor;
import reviewme.member.service.dto.ProfileResponse;

public class MemberApiTest extends ApiTest {

@Test
void 내_프로필을_불러온다() {
BDDMockito.given(memberService.getProfile())
.willReturn(new ProfileResponse("donghoony", "https://aru.image"));

CookieDescriptor[] cookieDescriptors = {
cookieWithName("JSESSIONID").description("세션 ID")
};

FieldDescriptor[] responseFieldDescriptors = {
fieldWithPath("nickname").description("닉네임"),
fieldWithPath("profileImageUrl").description("프로필 이미지 URL")
};

RestDocumentationResultHandler handler = document(
"my-profile",
requestCookies(cookieDescriptors),
responseFields(responseFieldDescriptors)
);

givenWithSpec().log().all()
.cookie("JSESSIONID", "SESSION12345678")
.when().get("/v2/members/profile/mine")
.then().log().all()
.apply(handler)
.statusCode(200);
}
}
Loading
Loading