Skip to content

Commit

Permalink
Merge branch 'BE/feature/#770' into BE/feature/#772-pair_room_completed
Browse files Browse the repository at this point in the history
  • Loading branch information
koust6u authored Oct 16, 2024
2 parents f77a872 + 2e88fda commit cd08465
Show file tree
Hide file tree
Showing 22 changed files with 449 additions and 53 deletions.
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.AuthorizationException;
import site.coduo.member.exception.ExternalApiCallException;
import site.coduo.member.exception.MemberNotFoundException;

Expand All @@ -25,6 +26,14 @@ public ResponseEntity<ApiErrorResponse> handlerMemberNotFoundException(final Mem
.body(new ApiErrorResponse(MemberApiError.MEMBER_NOT_FOUND_ERROR.getMessage()));
}

@ExceptionHandler(AuthorizationException.class)
public ResponseEntity<ApiErrorResponse> handlerAuthorizationException(final AuthorizationException e) {
log.warn(e.getMessage());

return ResponseEntity.status(MemberApiError.AUTHORIZATION_ERROR.getHttpStatus())
.body(new ApiErrorResponse(MemberApiError.AUTHENTICATION_ERROR.getMessage()));
}

@ExceptionHandler(ExternalApiCallException.class)
public ResponseEntity<ApiErrorResponse> handlerExternalApiCallFailureException(final ExternalApiCallException e) {
log.error(e.getMessage());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
public enum MemberApiError {

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

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

public class AuthorizationException extends MemberException {

public AuthorizationException(final String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ public String getMissionUrl() {
return missionUrl.getValue();
}

public boolean isDeleted() {
return status == PairRoomStatus.DELETED;
}

public boolean isSameAccessCode(final AccessCode code) {
return accessCode.equals(code);
}

@Override
public boolean equals(final Object o) {
if (this == o) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import site.coduo.pairroom.domain.accesscode.AccessCode;
import site.coduo.pairroom.domain.accesscode.UUIDAccessCodeGenerator;
import site.coduo.pairroom.exception.DeletePairRoomException;
import site.coduo.pairroom.exception.InvalidAccessCodeException;
import site.coduo.pairroom.repository.PairRoomEntity;
import site.coduo.pairroom.repository.PairRoomMemberEntity;
import site.coduo.pairroom.repository.PairRoomMemberRepository;
Expand Down Expand Up @@ -130,5 +131,22 @@ public void completePairRoom(final String accessCode) {
final PairRoomEntity pairRoomEntity = pairRoomRepository.fetchByAccessCode(accessCode);
checkDeletePairRoom(pairRoomEntity);
pairRoomEntity.updateStatus(PairRoomStatus.COMPLETED);

public boolean isParticipant(final String token, final String accessCode) {
final Member member = memberService.findMemberByCredential(token);

final List<PairRoomMemberEntity> pairRooms = pairRoomMemberRepository.findByMember(member);
return pairRooms.stream()
.map(PairRoomMemberEntity::getPairRoom)
.filter(pairRoomEntity -> !pairRoomEntity.isDelete())
.map(PairRoomEntity::toDomain)
.anyMatch(pairRoom -> pairRoom.isSameAccessCode(new AccessCode(accessCode)));
}

public void validateNotDeleted(final String accessCode) {
final PairRoom pairRoom = pairRoomRepository.fetchByAccessCode(accessCode).toDomain();
if (pairRoom.isDeleted()) {
throw new InvalidAccessCodeException("이미 삭제되어 접근이 불가능한 엑세스 코드입니다.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ public ResponseEntity<ApiErrorResponse> handleDuplicateTimestampException(final
public ResponseEntity<ApiErrorResponse> handleNotFoundScheduledFutureException(
final NotFoundScheduledFutureException e) {
log.warn(e.getMessage());
return ResponseEntity.status(SseApiError.TIMER_STOP_FAILED.getHttpStatus())
.body(new ApiErrorResponse(SseApiError.TIMER_STOP_FAILED.getMessage()));
return ResponseEntity.status(SseApiError.SCHEDULER_NOT_FOUND.getHttpStatus())
.body(new ApiErrorResponse(SseApiError.SCHEDULER_NOT_FOUND.getMessage()));
}

@ExceptionHandler(NotFoundSseConnectionException.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public enum SseApiError {
SYNC_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "동기화에 실패하였습니다."),
TIMER_START_FAILED(HttpStatus.BAD_REQUEST, "타이머 실행에 실패하였습니다."),
TIMER_STOP_FAILED(HttpStatus.BAD_REQUEST, "타이머 중지에 실패하였습니다."),
SCHEDULER_NOT_FOUND(HttpStatus.NOT_FOUND, "스케줄러를 찾을 수 없습니다."),
CONNECTION_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "SSE 연결에 실패했습니다."),
CONNECTION_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 페어룸의 SSE 연결을 찾을 수 없습니다."),
CONNECTION_DUPLICATED(HttpStatus.BAD_REQUEST, "해당 페어룸에 이미 연결된 SSE 연결이 존재합니다.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,6 @@ public boolean hasNoStreams(final String key) {
if (registry.containsKey(key)) {
return registry.get(key).isEmpty();
}
throw new NotFoundSseConnectionException("SSE 커넥션을 찾을 수 없습니다.");
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ public void release(final String key) {
registry.remove(key);
}

public void clear(final String key) {
if (!registry.containsKey(key)) {
return;
}
registry.get(key)
.cancel(false);
registry.remove(key);
}

public boolean has(final String key) {
return registry.containsKey(key);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import site.coduo.member.exception.AuthorizationException;
import site.coduo.pairroom.service.PairRoomService;
import site.coduo.timer.domain.Timer;
import site.coduo.timer.repository.TimerRepository;
import site.coduo.timer.service.TimestampRegistry;
Expand All @@ -19,23 +21,21 @@
@RequiredArgsConstructor
@Component
public class SchedulerService {

public static final Duration DELAY_SECOND = Duration.of(1, ChronoUnit.SECONDS);

private final ThreadPoolTaskScheduler taskScheduler;
private final SchedulerRegistry schedulerRegistry;
private final TimestampRegistry timestampRegistry;
private final TimerRepository timerRepository;
private final SseService sseService;
private final PairRoomService pairRoomService;

public void start(final String key) {
pairRoomService.validateNotDeleted(key);
if (schedulerRegistry.isActive(key)) {
return;
}
sseService.broadcast(key, "timer", "start");
if (isInitial(key)) {
final Timer timer = timerRepository.fetchTimerByAccessCode(key)
.toDomain();
final Timer timer = timerRepository.fetchTimerByAccessCode(key).toDomain();
scheduling(key, timer);
timestampRegistry.register(key, timer);
return;
Expand All @@ -49,6 +49,7 @@ private boolean isInitial(final String key) {
}

private void scheduling(final String key, final Timer timer) {
sseService.broadcast(key, "timer", "start");
final Trigger trigger = new PeriodicTrigger(DELAY_SECOND);
final ScheduledFuture<?> schedule = taskScheduler.schedule(() -> runTimer(key, timer), trigger);
schedulerRegistry.register(key, schedule);
Expand Down Expand Up @@ -80,4 +81,14 @@ private void stop(final String key, final Timer timer) {
final Timer initalTimer = new Timer(timer.getAccessCode(), timer.getDuration(), timer.getDuration());
timestampRegistry.register(key, initalTimer);
}

public void detach(final String key, final String accessToken) {
if (!pairRoomService.isParticipant(accessToken, key)) {
throw new AuthorizationException("인증되지 않은 타이머 비활성화 접근입니다.");
}
sseService.broadcast(key, "timer", "disconnect");
schedulerRegistry.clear(key);
sseService.disconnectAll(key);
timestampRegistry.clear(key);
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package site.coduo.timer.controller;

import static site.coduo.common.config.web.filter.SignInCookieFilter.SIGN_IN_COOKIE_NAME;

import jakarta.validation.Valid;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CookieValue;
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 @@ -62,4 +65,15 @@ public ResponseEntity<TimerReadResponse> getTimer(

return ResponseEntity.ok(response);
}

@PatchMapping("/{accessCode}/timer/disable")
public ResponseEntity<Void> disableTimer(
@PathVariable("accessCode") final String accessCode,
@CookieValue(name = SIGN_IN_COOKIE_NAME) final String signInToken
) {
schedulerService.detach(accessCode, signInToken);

return ResponseEntity.ok()
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,12 @@ ResponseEntity<Void> updateTimer(
ResponseEntity<TimerReadResponse> getTimer(
String accessCode
);

@Operation(summary = "타이머 기능(시작/종료/변경)을 비활성화(SSE disconnect, 스케줄러 삭제, 시간 정보 삭제) 한다.")
@ApiResponse(responseCode = "200", description = "페어룸 히스토리 조회 성공/ SSE - event: timer, data: disconnect")
@ApiResponse(responseCode = "4xx", description = "페어룸 히스토리 생성 실패")
ResponseEntity<Void> disableTimer(
String accessCode,
String signInToken
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ public void release(final String key) {
registry.remove(key);
}

public void clear(final String key) {
if (!registry.containsKey(key)) {
return;
}
registry.remove(key);
}

public boolean has(final String key) {
return registry.containsKey(key);
}
Expand Down
1 change: 0 additions & 1 deletion backend/src/main/resources/application-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ spring:
username: ${DB_USERNAME}
password: ${DB_PASSWORD}


jpa:
database: mysql
hibernate:
Expand Down
20 changes: 10 additions & 10 deletions backend/src/test/java/site/coduo/acceptance/AuthAcceptanceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@ class AuthAcceptanceTest extends AcceptanceFixture {
@Autowired
private JwtProvider jwtProvider;

static Member createMember() {
return Member.builder()
.username("test user")
.userId(FakeGithubApiClient.USER_ID)
.loginId(FakeGithubApiClient.LOGIN_ID)
.accessToken(FakeGithubOAuthClient.ACCESS_TOKEN.getCredential())
.profileImage(FakeGithubApiClient.PROFILE_IMAGE)
.build();
}

@Test
@DisplayName("로그인 검증 & 로그인 토큰을 발급한다.")
void verify_login_and_publish_login_token() {
Expand Down Expand Up @@ -140,14 +150,4 @@ void no_access_token() {
.statusCode(HttpStatus.SC_UNAUTHORIZED);
}

private Member createMember() {
return Member.builder()
.username("test user")
.userId(FakeGithubApiClient.USER_ID)
.loginId(FakeGithubApiClient.LOGIN_ID)
.accessToken(FakeGithubOAuthClient.ACCESS_TOKEN.getCredential())
.profileImage(FakeGithubApiClient.PROFILE_IMAGE)
.build();
}

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package site.coduo.acceptance;

import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.startsWith;

import static site.coduo.common.config.web.filter.AccessTokenCookieFilter.TEMPORARY_ACCESS_TOKEN_COOKIE_NAME;

Expand Down Expand Up @@ -53,7 +53,7 @@ void request_to_github_authorization_end_point() {
.assertThat()
.statusCode(HttpStatus.SC_OK)
.body("endpoint",
is("https://www.github.com/login/oauth/authorize?client_id=test&state=randomNumber&redirect_uri=http://test.test"));
startsWith("https://www.github.com/login/oauth/authorize?client_id="));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,22 @@ static PairRoomCreateResponse createPairRoom(final PairRoomCreateRequest request
.as(PairRoomCreateResponse.class);
}

static PairRoomCreateResponse createPairRoom(final PairRoomCreateRequest request, final String loginCookie) {
return RestAssured
.given()
.cookie("coduo_whoami", loginCookie)
.contentType(MediaType.APPLICATION_JSON_VALUE)
.accept(MediaType.APPLICATION_JSON_VALUE)
.body(request)

.when()
.post("/api/pair-room")

.then()
.extract()
.as(PairRoomCreateResponse.class);
}

@Test
@DisplayName("페어룸 요청 시 정보를 반환한다.")
void show_pair_room() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
class SseAcceptanceTest extends AcceptanceFixture {

static void createConnect(final String accessCode) {

RestAssured
.given()

Expand Down
Loading

0 comments on commit cd08465

Please sign in to comment.