Skip to content

Commit

Permalink
Merge pull request #66 from dnd-side-project/Feature/#63
Browse files Browse the repository at this point in the history
모임 재참여 api, 스냅 조회 api 개발
  • Loading branch information
abc5259 authored Aug 23, 2024
2 parents e618861 + a6e2eb5 commit 28131c0
Show file tree
Hide file tree
Showing 30 changed files with 568 additions and 16 deletions.
2 changes: 2 additions & 0 deletions src/docs/asciidoc/auth/auth.adoc
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
[[인증-API]]
== 인증 API

include::login.adoc[]

include::refresh-token.adoc[]
26 changes: 26 additions & 0 deletions src/docs/asciidoc/auth/login.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
=== 모임 재참여
==== 성공
Request
include::{snippets}/auth-controller-test/login/http-request.adoc[]
include::{snippets}/auth-controller-test/login/path-parameters.adoc[]
Request Cookie
include::{snippets}/auth-controller-test/login/request-cookies.adoc[]
Request Body
include::{snippets}/auth-controller-test/login/request-body.adoc[]
include::{snippets}/auth-controller-test/login/request-fields.adoc[]

Response
include::{snippets}/auth-controller-test/login/http-response.adoc[]
Response Cookie
include::{snippets}/auth-controller-test/login/response-cookies.adoc[]


==== 실패
===== `요청 쿠키로 access token이 없는경우`
include::{snippets}/auth-controller-test/login_no_access_token/response-body.adoc[]

===== `모임의 참여자가 아닌 사용자가 로그인을 시도하는 경우`
include::{snippets}/auth-controller-test/login_no_participation_meeting/response-body.adoc[]

===== `모임의 비밀번호가 틀린 경우`
include::{snippets}/auth-controller-test/login_incorrect_password/response-body.adoc[]
22 changes: 22 additions & 0 deletions src/docs/asciidoc/snap/find-snap-by-id.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
=== snap id로 snap 상세 조회

==== 성공

===== Request
include::{snippets}/snap-controller-test/find-snap-by-id/http-request.adoc[]
===== Query Parameter
include::{snippets}/snap-controller-test/find-snap-by-id/path-parameters.adoc[]
===== Request Cookie
include::{snippets}/snap-controller-test/find-snap-by-id/request-cookies.adoc[]

===== Response
SIMPLE 타입의 snap 일떄
include::{snippets}/snap-controller-test/find-snap-by-id/http-response.adoc[]

RANDOM_MISSION 타입의 snap 일떄
include::{snippets}/snap-controller-test/find-random-mission-snap-by-id/http-response.adoc[]

MEETING_MISSION 타입의 snap 일떄
include::{snippets}/snap-controller-test/find-meeting-mission-snap-by-id/http-response.adoc[]

include::{snippets}/snap-controller-test/find-snap-by-id/response-fields.adoc[]
4 changes: 3 additions & 1 deletion src/docs/asciidoc/snap/snap.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ include::find-random-mission-snap-in-meeting.adoc[]

include::find-meeting-mission-snap-in-meeting.adoc[]

include::find-participant-snaps-in-meeting.adoc[]
include::find-participant-snaps-in-meeting.adoc[]

include::find-snap-by-id.adoc[]
13 changes: 5 additions & 8 deletions src/main/java/com/dnd/snappy/common/logger/MDCFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,17 @@
@Slf4j
@Component
public class MDCFilter implements Filter {
private static final String MDC_CORRELATION_ID_KEY = "correlationId";
private final String REQUEST_ID = "request_id";

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
try {
MDC.put(MDC_CORRELATION_ID_KEY, generateCorrelationId());
chain.doFilter(request, response);
} finally {
MDC.clear();
}
MDC.put(REQUEST_ID, generateRequestId());
chain.doFilter(request, response);
MDC.clear();
}

private String generateCorrelationId() {
private String generateRequestId() {
return UUID.randomUUID()
.toString()
.substring(0, 8);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,6 @@ private String getHeaderAndValue(ContentCachingRequestWrapper requestWrapper) {
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
String path = request.getRequestURI();
return path.contains("/api/docs");
return path.contains("/docs") || path.contains("/favicon.ico");
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package com.dnd.snappy.controller.v1.auth;

import com.dnd.snappy.common.dto.ResponseDto;
import com.dnd.snappy.controller.v1.auth.request.LoginRequest;
import com.dnd.snappy.controller.v1.auth.resolver.AuthInfo;
import com.dnd.snappy.controller.v1.auth.resolver.AuthPrincipal;
import com.dnd.snappy.controller.v1.auth.resolver.RefreshAuthInfo;
import com.dnd.snappy.controller.v1.auth.resolver.RefreshAuthPrincipal;
import com.dnd.snappy.domain.auth.service.AuthCookieManager;
import com.dnd.snappy.domain.auth.service.AuthService;
import com.dnd.snappy.domain.token.service.TokenType;
import jakarta.validation.Valid;
import java.time.Duration;
import java.time.LocalDateTime;
import lombok.RequiredArgsConstructor;
Expand All @@ -14,6 +18,7 @@
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

Expand Down Expand Up @@ -45,4 +50,26 @@ public ResponseEntity<?> reissueToken(
null
));
}

@PostMapping("/meetings/{meetingId}/auth/login")
public ResponseEntity<?> login(
@PathVariable Long meetingId,
@AuthPrincipal AuthInfo authInfo,
@Valid @RequestBody LoginRequest loginRequest
) {
var response = authService.login(meetingId, authInfo.participantId(), loginRequest.password());

Duration duration = Duration.between(LocalDateTime.now(), response.meetingExpiredDate());
String accessTokenCookie = authCookieManager.createTokenCookie(TokenType.ACCESS_TOKEN, response.accessToken(), meetingId, duration);
String refreshTokenCookie = authCookieManager.createTokenCookie(TokenType.REFRESH_TOKEN, response.refreshToken(), meetingId, duration);

return ResponseEntity.ok()
.header(HttpHeaders.SET_COOKIE, accessTokenCookie)
.header(HttpHeaders.SET_COOKIE, refreshTokenCookie)
.body(new ResponseDto<>(
HttpStatus.OK.value(),
null,
null
));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.dnd.snappy.controller.v1.auth.interceptor;

import com.dnd.snappy.common.error.exception.BusinessException;
import com.dnd.snappy.domain.auth.dto.response.TokenInfo;
import com.dnd.snappy.domain.auth.exception.AuthErrorCode;
import com.dnd.snappy.domain.auth.service.JwtTokenStrategy;
import com.dnd.snappy.domain.auth.service.PathVariableExtractor;
import com.dnd.snappy.domain.participant.repository.ParticipantRepository;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

@Component
@RequiredArgsConstructor
public class LoginInterceptor implements HandlerInterceptor {

private final PathVariableExtractor pathVariableExtractor;
private final JwtTokenStrategy jwtTokenStrategy;
private final ParticipantRepository participantRepository;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {

final Long meetingId = pathVariableExtractor.extractMeetingId(request);
TokenInfo tokenInfo = jwtTokenStrategy.loginProcess(request);
validationParticipantInMeeting(tokenInfo.payload(), meetingId);
return true;
}

private void validationParticipantInMeeting(Long participantId, Long meetingId) {
if(!participantRepository.existsByIdAndMeetingId(participantId, meetingId)) {
throw new BusinessException(AuthErrorCode.FORBIDDEN);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.dnd.snappy.controller.v1.auth.interceptor.config;


import com.dnd.snappy.controller.v1.auth.interceptor.LoginInterceptor;
import com.dnd.snappy.controller.v1.auth.interceptor.MeetingParticipationInterceptor;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
Expand All @@ -12,6 +13,7 @@
public class InterceptorConfig implements WebMvcConfigurer {

private final MeetingParticipationInterceptor meetingParticipationInterceptor;
private final LoginInterceptor loginInterceptor;

@Override
public void addInterceptors(InterceptorRegistry registry) {
Expand All @@ -23,6 +25,11 @@ public void addInterceptors(InterceptorRegistry registry) {
.excludePathPatterns("/api/**/meetings/{meetingId}/validate-leader-key")
.excludePathPatterns("/api/**/meetings/{meetingId}/share")
.excludePathPatterns("/api/**/meetings/{meetingId}/tokens/refresh")
.excludePathPatterns("/api/v1/meetings/{meetingId}/participants/check-nickname");
.excludePathPatterns("/api/**/meetings/{meetingId}/participants/check-nickname")
.excludePathPatterns("/api/**/meetings/{meetingId}/auth/login");

registry.addInterceptor(loginInterceptor)
.addPathPatterns("/api/**/meetings/{meetingId}/auth/login");

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.dnd.snappy.controller.v1.auth.request;

import jakarta.validation.constraints.NotBlank;

public record LoginRequest(
@NotBlank(message = "비밀번호는 필수입니다.")
String password
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public boolean supportsParameter(MethodParameter parameter) {
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
TokenInfo tokenInfo = jwtTokenStrategy.process(request, TokenType.ACCESS_TOKEN);
TokenInfo tokenInfo = jwtTokenStrategy.loginProcess(request);
return new AuthInfo(tokenInfo.payload());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.dnd.snappy.controller.v1.snap.request.CursorBasedRequest;
import com.dnd.snappy.domain.common.dto.response.CursorBasedResponseDto;
import com.dnd.snappy.domain.snap.dto.response.CreateSnapResponseDto;
import com.dnd.snappy.domain.snap.dto.response.SnapDetailResponseDto;
import com.dnd.snappy.domain.snap.dto.response.SnapResponseDto;
import com.dnd.snappy.domain.snap.service.MeetingMissionSnapService;
import com.dnd.snappy.domain.snap.service.RandomMissionSnapService;
Expand Down Expand Up @@ -106,4 +107,12 @@ public ResponseEntity<ResponseDto<CursorBasedResponseDto<List<SnapResponseDto>>>
var data = snapService.findParticipantSnapsInMeeting(cursorBasedRequest.toCursorBasedRequestDto(), meetingId, authInfo.participantId());
return ResponseDto.ok(data);
}

@GetMapping("/{snapId}")
public ResponseEntity<ResponseDto<SnapDetailResponseDto>> findSnapById(
@PathVariable("snapId") Long snapId
) {
var data = snapService.findSnapById(snapId);
return ResponseDto.ok(data);
}
}
15 changes: 15 additions & 0 deletions src/main/java/com/dnd/snappy/domain/auth/service/AuthService.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.dnd.snappy.domain.auth.service;

import static com.dnd.snappy.domain.meeting.exception.MeetingErrorCode.MEETING_INVALIDATE_PASSWORD;

import com.dnd.snappy.common.error.exception.BusinessException;
import com.dnd.snappy.domain.auth.dto.response.ReissueTokenResponseDto;
import com.dnd.snappy.domain.auth.exception.AuthErrorCode;
Expand Down Expand Up @@ -34,4 +36,17 @@ public ReissueTokenResponseDto reissueTokens(Long meetingId, Long participantId,
meeting.getExpiredDate()
);
}

public ReissueTokenResponseDto login(Long meetingId, Long participantId, String password) {
Meeting meeting = meetingService.findByMeetingIdOrThrow(meetingId);
if(!meeting.isCorrectPassword(password)) {
throw new BusinessException(MEETING_INVALIDATE_PASSWORD);
}
Tokens tokens = tokenService.createTokens(participantId);
return new ReissueTokenResponseDto(
tokens.accessToken(),
tokens.refreshToken(),
meeting.getExpiredDate()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,10 @@ public TokenInfo process(HttpServletRequest request, TokenType tokenType) {
Long payload = tokenProvider.extractPayload(token);
return new TokenInfo(token, payload);
}

public TokenInfo loginProcess(HttpServletRequest request) {
String token = jwtTokenExtractor.extractToken(request, TokenType.ACCESS_TOKEN);
Long payload = tokenProvider.extractPayloadIgnoringExpiration(token);
return new TokenInfo(token, payload);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.dnd.snappy.domain.snap.dto.response;

public record MissionDetailResponseDto(
Long missionId,
String content
) {
public MissionDetailResponseDto(Integer missionId, String content) {
this(Long.valueOf(missionId), content);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.dnd.snappy.domain.snap.dto.response;

public record ParticipantDetailResponseDto(
Long participantId,
String nickname
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.dnd.snappy.domain.snap.dto.response;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude;
import java.time.LocalDateTime;

public record SnapDetailResponseDto(
Long snapId,
String snapUrl,
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm", timezone = "Asia/Seoul")
LocalDateTime shootDate,
String type,
ParticipantDetailResponseDto photographer,
@JsonInclude(JsonInclude.Include.NON_NULL)
MissionDetailResponseDto mission
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,12 @@ public static MeetingMissionSnap create(String snapUrl, LocalDateTime shootDate,
participant.addShootCount();
return new MeetingMissionSnap(snapUrl, shootDate, meeting, participant, mission);
}

public Long getMissionId() {
return mission.getId();
}

public String getMissionContent() {
return mission.getContent();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,12 @@ public static RandomMissionSnap create(String snapUrl, LocalDateTime shootDate,
participant.addShootCount();
return new RandomMissionSnap(snapUrl, shootDate, meeting, participant, randomMission);
}

public Integer getMissionId() {
return randomMission.getId();
}

public String getMissionContent() {
return randomMission.getContent();
}
}
8 changes: 8 additions & 0 deletions src/main/java/com/dnd/snappy/domain/snap/entity/Snap.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,12 @@ protected Snap(String snapUrl, LocalDateTime shootDate, Meeting meeting, Partici
this.meeting = meeting;
this.participant = participant;
}

public Long getPhotographerId() {
return participant.getId();
}

public String getPhotographerNickname() {
return participant.getNickname();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
@Getter
@AllArgsConstructor
public enum SnapErrorCode implements ErrorCodeInterface {
NOT_FOUND_SNAP(HttpStatus.NOT_FOUND, "NOT_FOUND_SNAP","snap을 찾을 수 없습니다.")
;
private final HttpStatus status;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,12 @@ List<SnapResponseDto> findParticipantSnapsInMeetingByCursorId(
Pageable pageable
);

@Query("""
select s
from Snap s
join fetch s.participant
where s.id = :snapId
""")
Optional<Snap> findSnapByIdWithParticipant(@Param("snapId") Long snapId);

}
Loading

0 comments on commit 28131c0

Please sign in to comment.