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] 테스트 서버 배포 #768

Merged
merged 39 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
1cf5010
refactor: 링크 삭제 시 액세스코드 검증 추가
yechop Sep 26, 2024
8d480a2
test: 테스트용 fakeServer 구현
yechop Sep 26, 2024
e621da5
test: fakeServer 테스트 추가
yechop Sep 26, 2024
37fa843
test: OpenGraphServiceTest 테스트 개선
yechop Sep 26, 2024
7a5c14a
test: 레퍼런스링크와 오픈그래프 테스트 개선
yechop Sep 26, 2024
5800cef
Merge branch 'BE/dev' into BE/feature/#586-ref-delete
yechop Oct 4, 2024
a19ac5b
Merge branch 'BE/dev' into BE/feature/#586-ref-delete
yechop Oct 4, 2024
f97f2c8
refactor: 페어룸으로 레퍼런스링크 찾는 메서드
yechop Oct 4, 2024
ca5658c
feat: github OAuth 간소화 (#731)
koust6u Oct 8, 2024
32f5152
Merge branch 'BE/test' into BE/dev
koust6u Oct 8, 2024
2d4fbee
[BE] CORS 추가 (#733)
koust6u Oct 8, 2024
e9a9982
Merge branch 'BE/test' into BE/dev
koust6u Oct 8, 2024
ed4697f
Merge branch 'BE/dev' into BE/feature/#586-ref-delete
yechop Oct 8, 2024
844ff95
Merge pull request #593 from woowacourse-teams/BE/feature/#586-ref-de…
yechop Oct 8, 2024
8ccde6d
refactor: 페어룸 존재 여부 확인 API 변경
reddevilmidzy Oct 8, 2024
b21ab9c
Merge branch 'BE/dev' into BE/feature/#716-pair-room-exist
reddevilmidzy Oct 8, 2024
94e2d11
refactor: 메서드 이름 변경
reddevilmidzy Oct 8, 2024
f4cf732
Merge remote-tracking branch 'origin/BE/feature/#716-pair-room-exist'…
reddevilmidzy Oct 8, 2024
ccf3f65
Merge pull request #735 from woowacourse-teams/BE/feature/#716-pair-r…
reddevilmidzy Oct 8, 2024
514324d
Merge branch 'BE/test' into BE/dev
reddevilmidzy Oct 8, 2024
ba925b6
Revert "[BE] CORS 추가 (#733)"
koust6u Oct 8, 2024
05db3e2
Revert "feat: github OAuth 간소화 (#731) (#732)"
koust6u Oct 8, 2024
4211629
Merge pull request #740 from woowacourse-teams/BE/test
reddevilmidzy Oct 8, 2024
533573c
fix: DB 커넥션 고갈 문제 수정
koust6u Oct 9, 2024
977ee52
Merge branch 'production' into BE/dev
koust6u Oct 9, 2024
12043f3
feat: 페어룸 테이블에 미션 링크 추가
yechop Oct 11, 2024
40b669d
fix: nginx 설정 해더 추가 (#758)
koust6u Oct 11, 2024
b1db266
[BE] 페어룸 삭제 기능 구현 (#753)
kelly6bf Oct 11, 2024
490bd0e
Merge branch 'BE/dev' into BE/feature/#754-mission-repo
yechop Oct 11, 2024
e2bc7e4
test: 생성자에 missionUrl 추가
yechop Oct 11, 2024
60a4818
refactor: sse connection을 통해 데이터를 전송할 떄 발생하는 예외에 대한 로그 삭제
JiHyeonL Oct 11, 2024
ace06d1
Merge pull request #756 from woowacourse-teams/BE/feature/#754-missio…
yechop Oct 11, 2024
e86313b
Merge branch 'BE/dev' into BE/feature/#761-remove-sse-log
koust6u Oct 12, 2024
6ccc9c0
Merge pull request #763 from woowacourse-teams/BE/feature/#761-remove…
JiHyeonL Oct 12, 2024
0b9d37c
feat: Rest Client 예외 처리 (#767)
koust6u Oct 14, 2024
7e3d30d
[BE] 회원 탈퇴 API 추가 (#760)
reddevilmidzy Oct 14, 2024
fcc5e35
feat: https://coduo.site 추가 (#448)
reddevilmidzy Oct 14, 2024
4473ffb
[BE] 리뷰 피드백 쓰레드로 보내기 수정 (#766)
koust6u Oct 14, 2024
08bb2ca
Merge branch 'BE/test' into BE/dev
koust6u Oct 14, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/review_submit_notification.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ jobs:
- name: Send Slack notification
uses: slackapi/[email protected]
with:
channel-id: ${{secrets.SLACK_FE_REVIEW_CHANNEL}}
channel-id: ${{secrets.SLACK_BE_REVIEW_CHANNEL}}
payload: |
{
"text": "리뷰가 도착했어요✉️",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.servers.Server;

@Configuration
public class SwaggerConfig {
Expand All @@ -18,6 +19,7 @@ public OpenAPI openAPI() {
return new OpenAPI()
.info(apiInfo())
.components(securitySchemeComponents())
.addServersItem(serverItem())
.addSecurityItem(securityRequirement());
}

Expand All @@ -39,6 +41,12 @@ private Components securitySchemeComponents() {
return new Components().addSecuritySchemes(HttpHeaders.AUTHORIZATION, bearerAuth);
}

private Server serverItem() {
return new Server()
.url("https://coduo.site")
.description("클라우드에 배포된 서버");
}

private SecurityRequirement securityRequirement() {
return new SecurityRequirement().addList(HttpHeaders.AUTHORIZATION);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package site.coduo.member.client;

import org.springframework.http.HttpStatusCode;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestClient.ResponseSpec.ErrorHandler;

import lombok.extern.slf4j.Slf4j;
import site.coduo.member.client.dto.GithubUserRequest;
import site.coduo.member.client.dto.GithubUserResponse;
import site.coduo.member.exception.ExternalApiCallException;

@Slf4j
@Component
Expand All @@ -30,13 +33,29 @@ public GithubApiClient() {
.build();
}

GithubApiClient(final RestClient restClient) {
this.client = restClient;
}

public GithubUserResponse getUser(final GithubUserRequest request) {

return client.get()
.uri("/user")
.accept()
.headers(httpHeaders -> httpHeaders.addAll(request.getHeaders()))
.retrieve()
.onStatus(HttpStatusCode::isError, getErrorHandler())
.body(GithubUserResponse.class);
}

private ErrorHandler getErrorHandler() {
return (request, response) -> {
if (response.getStatusCode().is4xxClientError()) {
throw new ExternalApiCallException("Github API 호출에 실패했습니다.");
}
if (response.getStatusCode().is5xxServerError()) {
throw new ExternalApiCallException("Github API 호출 과정 중 서버 내부에서 오류가 발생했습니다.");
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@

import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.MediaType;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestClient.ResponseSpec.ErrorHandler;

import site.coduo.member.client.dto.TokenRequest;
import site.coduo.member.client.dto.TokenResponse;
import site.coduo.member.exception.ExternalApiCallException;
import site.coduo.member.infrastructure.http.Basic;

@Component
Expand Down Expand Up @@ -50,6 +53,7 @@ public TokenResponse grant(final TokenRequest request) {
.header(HttpHeaders.AUTHORIZATION, new Basic(oAuthClientId, oAuthClientSecret).getValue())
.body(request.toQueryParams())
.retrieve()
.onStatus(HttpStatusCode::isError, getErrorHandler())
.body(TokenResponse.class);
}

Expand All @@ -60,4 +64,15 @@ public String getOAuthClientId() {
public String getOAuthRedirectUri() {
return oAuthRedirectUri;
}

private ErrorHandler getErrorHandler() {
return (request, response) -> {
if (response.getStatusCode().is4xxClientError()) {
throw new ExternalApiCallException("Github OAuth API 호출에 실패했습니다.");
}
if (response.getStatusCode().is5xxServerError()) {
throw new ExternalApiCallException("Github OAuth 과정 중 서버 내부에서 오류가 발생했습니다.");
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

Expand All @@ -19,11 +20,17 @@ public class MemberController implements MemberControllerDocs {
private final MemberService memberService;

@GetMapping("/member")
public ResponseEntity<MemberReadResponse> getMember(
@CookieValue(SIGN_IN_COOKIE_NAME) final String token
) {
public ResponseEntity<MemberReadResponse> getMember(@CookieValue(SIGN_IN_COOKIE_NAME) final String token) {
final MemberReadResponse response = memberService.findMemberNameByCredential(token);

return ResponseEntity.ok(response);
}

@DeleteMapping("/member")
public ResponseEntity<Void> deleteMember(@CookieValue(SIGN_IN_COOKIE_NAME) final String token) {
memberService.deleteMember(token);

return ResponseEntity.noContent()
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import lombok.extern.slf4j.Slf4j;
import site.coduo.common.controller.response.ApiErrorResponse;
import site.coduo.member.controller.error.MemberApiError;
import site.coduo.member.exception.ExternalApiCallException;
import site.coduo.member.exception.MemberNotFoundException;

@Slf4j
Expand All @@ -23,4 +24,12 @@ public ResponseEntity<ApiErrorResponse> handlerMemberNotFoundException(final Mem
return ResponseEntity.status(MemberApiError.MEMBER_NOT_FOUND_ERROR.getHttpStatus())
.body(new ApiErrorResponse(MemberApiError.MEMBER_NOT_FOUND_ERROR.getMessage()));
}

@ExceptionHandler(ExternalApiCallException.class)
public ResponseEntity<ApiErrorResponse> handlerExternalApiCallFailureException(final ExternalApiCallException e) {
log.error(e.getMessage());

return ResponseEntity.status(MemberApiError.API_CALL_FAILURE_ERROR.getHttpStatus())
.body(new ApiErrorResponse(MemberApiError.MEMBER_NOT_FOUND_ERROR.getMessage()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,14 @@ ResponseEntity<MemberReadResponse> getMember(
schema = @Schema(type = "string")
)
String token);

@ApiResponse(responseCode = "204", description = "회원 정보를 삭제한다.",
content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))
ResponseEntity<Void> deleteMember(
@Parameter(
in = ParameterIn.COOKIE,
name = "coduo_whoami",
description = "사용자가 인증에 성공하면 서버에서 발급하는 쿠키",
schema = @Schema(type = "string")
) String token);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
public enum MemberApiError {

AUTHENTICATION_ERROR(HttpStatus.UNAUTHORIZED, "인증되지 않은 접근입니다."),
MEMBER_NOT_FOUND_ERROR(HttpStatus.NOT_FOUND, "존재하지 않는 회원입니다.");
MEMBER_NOT_FOUND_ERROR(HttpStatus.NOT_FOUND, "존재하지 않는 회원입니다."),
API_CALL_FAILURE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "외부 API와 상호작용 중 실패했습니다.");

private final HttpStatus httpStatus;
private final String message;
Expand Down
19 changes: 18 additions & 1 deletion backend/src/main/java/site/coduo/member/domain/Member.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package site.coduo.member.domain;

import java.time.LocalDateTime;
import java.util.Objects;

import jakarta.persistence.Column;
Expand All @@ -9,6 +10,8 @@
import jakarta.persistence.Id;
import jakarta.persistence.Table;

import org.springframework.format.annotation.DateTimeFormat;

import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
Expand Down Expand Up @@ -41,14 +44,19 @@ public class Member extends BaseTimeEntity {
@Column(name = "USER_NAME")
private String username;

@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
@Column(name = "DELETED_AT")
private LocalDateTime deletedAt;

@Builder
private Member(final String accessToken, final String loginId, final String userId, final String profileImage,
final String username) {
final String username, final LocalDateTime deletedAt) {
this.accessToken = accessToken;
this.loginId = loginId;
this.userId = userId;
this.profileImage = profileImage;
this.username = username;
this.deletedAt = deletedAt;
}

public void update(final Member other) {
Expand All @@ -57,6 +65,15 @@ public void update(final Member other) {
this.userId = other.userId;
this.profileImage = other.profileImage;
this.username = other.username;
this.deletedAt = other.deletedAt;
}

public void delete() {
this.deletedAt = LocalDateTime.now();
}

public boolean isDeleted() {
return deletedAt != null;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,33 @@
package site.coduo.member.domain.repository;

import java.util.List;
import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;

import site.coduo.member.domain.Member;
import site.coduo.member.exception.MemberNotFoundException;

public interface MemberRepository extends JpaRepository<Member, Long> {

Optional<Member> findByUserId(String userId);

List<Member> findByDeletedAtIsNull();

@Override
default List<Member> findAll() {
return findByDeletedAtIsNull();
}

default Member fetchByUserId(final String userId) {
final Member member = findByUserId(userId)
.orElseThrow(() -> new MemberNotFoundException(String.format("%s는 찾을 수 없는 회원 아이디입니다.", userId)));
if (member.isDeleted()) {
throw new MemberNotFoundException(String.format("%s는 삭제된 회원입니다.", userId));
}
return member;
}

boolean existsByUserId(String userId);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package site.coduo.member.exception;

public class ExternalApiCallException extends MemberException {

public ExternalApiCallException(final String message) {
super(message);
}
}
16 changes: 11 additions & 5 deletions backend/src/main/java/site/coduo/member/service/MemberService.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import site.coduo.member.client.dto.GithubUserResponse;
import site.coduo.member.domain.Member;
import site.coduo.member.domain.repository.MemberRepository;
import site.coduo.member.exception.MemberNotFoundException;
import site.coduo.member.infrastructure.http.Bearer;
import site.coduo.member.infrastructure.security.JwtProvider;
import site.coduo.member.service.dto.member.MemberReadResponse;
Expand All @@ -36,15 +35,22 @@ public void createMember(final String username, final String encryptedAccessToke

public MemberReadResponse findMemberNameByCredential(final String token) {
final String userId = jwtProvider.extractSubject(token);
final Member member = memberRepository.findByUserId(userId)
.orElseThrow(() -> new MemberNotFoundException(String.format("%s는 찾을 수 없는 회원 아이디입니다.", userId)));
final Member member = memberRepository.fetchByUserId(userId);

return new MemberReadResponse(member.getUsername());
}

public Member findMemberByCredential(final String token) {
final String userId = jwtProvider.extractSubject(token);
return memberRepository.findByUserId(userId)
.orElseThrow(() -> new MemberNotFoundException(String.format("%s는 찾을 수 없는 회원 아이디입니다.", userId)));

return memberRepository.fetchByUserId(userId);
}

@Transactional
public void deleteMember(final String token) {
final String userId = jwtProvider.extractSubject(token);
final Member member = memberRepository.fetchByUserId(userId);

member.delete();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand Down Expand Up @@ -92,4 +93,10 @@ public ResponseEntity<PairRoomExistResponse> pairRoomExists(@RequestParam("acces

return ResponseEntity.ok(response);
}

@DeleteMapping("/pair-room/{accessCode}")
public ResponseEntity<Void> deletePairRoom(@PathVariable("accessCode") final String accessCode) {
pairRoomService.deletePairRoom(accessCode);
return ResponseEntity.noContent().build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ ResponseEntity<PairRoomReadResponse> getPairRoom(
@ApiResponse(responseCode = "201", description = "페어룸 저장 성공", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE,
schema = @Schema(implementation = PairRoomCreateResponse.class)))
ResponseEntity<PairRoomCreateResponse> createPairRoom(
@Parameter(description = "페어 프로그래밍에 참여하는 드라이버 이름, 내비게이터 이름, 타이머 시간, 타이머 남은 시간", required = true)
@Parameter(description = "페어 프로그래밍에 참여하는 드라이버 이름, 내비게이터 이름, 타이머 시간, 타이머 남은 시간, 미션 리포지토리 링크", required = true)
PairRoomCreateRequest pairRoomCreateRequest,
@Parameter(description = "로그인 유저 토큰")
String token
Expand Down Expand Up @@ -75,4 +75,10 @@ ResponseEntity<List<PairRoomMemberResponse>> getPairRooms(
schema = @Schema(implementation = PairRoomExistResponse.class)))
ResponseEntity<PairRoomExistResponse> pairRoomExists(String accessCode);

@Operation(summary = "페어룸을 삭제한다.")
@ApiResponse(responseCode = "204", description = "페어룸 삭제 성공")
ResponseEntity<Void> deletePairRoom(
@Parameter(description = "페어룸 접근 코드", required = true)
String accessCode
);
}
13 changes: 13 additions & 0 deletions backend/src/main/java/site/coduo/pairroom/domain/MissionUrl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package site.coduo.pairroom.domain;

import lombok.Getter;

@Getter
public class MissionUrl {

private final String value;

public MissionUrl(final String value) {
this.value = value;
}
}
Loading