Skip to content

Commit

Permalink
[BE] feat: 방 신청 구현(#32) (#58)
Browse files Browse the repository at this point in the history
* feat: Room 엔티티내 외래키 제거

* feat: 예외 타입 추가, 동적 메시지 추가

* feat: RoomResponse 생성 부분 TODO 해결

* feat: RoomController RequestMapping 으로 그룹화

* feat: 방에 참가하는 기능 구현

* feat: 인증 기능 구현

* feat: 방에 참가하는 기능 요청-응답 기능 구현

* feat: 문서 관련 변수 설정

* feat: LoginMemberArgumentResolver WebConfig 에 추가

* feat: Logging 레벨 수정, 명세 추가

* style: 개행 수정
  • Loading branch information
youngsu5582 authored Jul 18, 2024
1 parent 759ebf2 commit 7ff48c9
Show file tree
Hide file tree
Showing 23 changed files with 468 additions and 20 deletions.
13 changes: 13 additions & 0 deletions backend/src/main/java/corea/WebConfig.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
package corea;

import corea.auth.resolver.LoginMemberArgumentResolver;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {

private final LoginMemberArgumentResolver loginMemberArgumentResolver;

@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
Expand All @@ -15,4 +23,9 @@ public void addCorsMappings(CorsRegistry registry) {
.allowCredentials(true)
.maxAge(3000);
}

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(loginMemberArgumentResolver);
}
}
14 changes: 14 additions & 0 deletions backend/src/main/java/corea/auth/RequestHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package corea.auth;

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;

@Component
public class RequestHandler {

private static final String AUTHORIZATION_HEADER = "Authorization";

public String extract(HttpServletRequest request) {
return request.getHeader(AUTHORIZATION_HEADER);
}
}
14 changes: 14 additions & 0 deletions backend/src/main/java/corea/auth/annotation/LoginUser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package corea.auth.annotation;

import io.swagger.v3.oas.annotations.Hidden;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Hidden()
public @interface LoginUser {
}
34 changes: 34 additions & 0 deletions backend/src/main/java/corea/auth/domain/AuthInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package corea.auth.domain;

import corea.member.domain.Member;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

import java.util.Objects;

@RequiredArgsConstructor
@Getter
public class AuthInfo {

private final Long id;

private final String name;

private final String email;

public static AuthInfo from(Member member){
return new AuthInfo(member.getId(), member.getUserName(), member.getEmail());
}

@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (!(o instanceof AuthInfo authInfo)) return false;
return Objects.equals(id, authInfo.id);
}

@Override
public int hashCode() {
return Objects.hashCode(id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package corea.auth.resolver;

import corea.auth.RequestHandler;
import corea.auth.annotation.LoginUser;
import corea.auth.domain.AuthInfo;
import corea.exception.CoreaException;
import corea.exception.ExceptionType;
import corea.member.domain.Member;
import corea.member.repository.MemberRepository;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

@Component
@RequiredArgsConstructor
public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver {

private final RequestHandler requestHandler;
private final MemberRepository memberRepository;

@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(LoginUser.class);
}

@Override
public AuthInfo resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) {
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
Member member = memberRepository.findByEmail(requestHandler.extract(request))
.orElseThrow(()-> new CoreaException(ExceptionType.AUTHORIZATION_ERROR));
return AuthInfo.from(member);
}
}
20 changes: 17 additions & 3 deletions backend/src/main/java/corea/exception/CoreaException.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
package corea.exception;

public class CoreaException extends Exception {
import org.springframework.http.HttpStatus;

public class CoreaException extends RuntimeException {

private final ExceptionType exceptionType;

public CoreaException(ExceptionType exceptionType) {
super(exceptionType.getMessage());
this.exceptionType = exceptionType;
}

public CoreaException(ExceptionType exceptionType, String message) {
super(message);
this.exceptionType = exceptionType;
}

Expand All @@ -12,7 +21,12 @@ public CoreaException(ExceptionType exceptionType, Throwable cause) {
this.exceptionType = exceptionType;
}

public ExceptionType getExceptionType() {
return exceptionType;
public HttpStatus getHttpStatus() {
return exceptionType.getHttpStatus();
}

@Override
public String getMessage() {
return exceptionType.getMessage();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package corea.exception;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@Slf4j
@ControllerAdvice
public class ExceptionResponseHandler {

@ExceptionHandler(CoreaException.class)
public ResponseEntity<ErrorResponse> handleCoreaException(final CoreaException e) {
log.debug("Corea exception [statusCode = {}, errorMessage = {}, cause = {}]", e.getHttpStatus(), e.getMessage(), e.getCause());
return ResponseEntity.status(e.getHttpStatus())
.body(new ErrorResponse(e.getMessage()));
}

@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(final Exception e) {
log.debug("Server exception [errorMessage = {}, cause = {}]", e.getMessage(), e.getCause());
return ResponseEntity.internalServerError()
.body(new ErrorResponse(e.getMessage()));
}
}
4 changes: 4 additions & 0 deletions backend/src/main/java/corea/exception/ExceptionType.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
import org.springframework.http.HttpStatus;

public enum ExceptionType {

NOT_FOUND_ERROR(HttpStatus.NOT_FOUND,"해당하는 값이 없습니다."),
AUTHORIZATION_ERROR(HttpStatus.UNAUTHORIZED,"인증에 실패했습니다."),
ALREADY_APPLY(HttpStatus.BAD_REQUEST,"해당 방에 이미 참여했습니다"),
SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR,"서버에 문제가 발생했습니다.");
private final HttpStatus httpStatus;
private final String message;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package corea.matching.controller;

import corea.auth.annotation.LoginUser;
import corea.auth.domain.AuthInfo;
import corea.matching.dto.ParticipationRequest;
import corea.matching.service.ParticipationService;
import lombok.RequiredArgsConstructor;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/participate")
@RequiredArgsConstructor
public class ParticipateController implements ParticipationControllerSpecification {

private final ParticipationService participationService;

@PostMapping("/{id}")
public ResponseEntity<Void> participate(@PathVariable long id, @LoginUser AuthInfo authInfo) {
participationService.participate(new ParticipationRequest(id, authInfo.getId()));
return ResponseEntity.ok()
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package corea.matching.controller;

import corea.auth.domain.AuthInfo;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import org.springframework.http.ResponseEntity;

public interface ParticipationControllerSpecification {

@ApiResponses(
value = {
@ApiResponse(responseCode = "404", content = @Content(mediaType = "application/json", examples = {
@ExampleObject(name = "해당하는 방이 없는 경우", value = """
{
"message": "1에 해당하는 방 없습니다."
}
""")
})),
}
)
ResponseEntity<Void> participate(long id, AuthInfo authInfo);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,8 @@
import java.util.List;

public interface ParticipationRepository extends JpaRepository<Participation, Long> {

List<Participation> findAllByRoomId(long roomId);

boolean existsByRoomIdAndMemberId(long roomId, long memberId);
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package corea.matching.service;

import corea.exception.CoreaException;
import corea.exception.ExceptionType;
import corea.matching.domain.Participation;
import corea.matching.dto.ParticipationRequest;
import corea.matching.dto.ParticipationResponse;
import corea.member.repository.MemberRepository;
import corea.matching.repository.ParticipationRepository;
import corea.member.repository.MemberRepository;
import corea.room.repository.RoomRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
Expand All @@ -28,10 +30,13 @@ public ParticipationResponse participate(final ParticipationRequest request) {

private void validateIdExist(final long roomId, final long memberId) {
if (!roomRepository.existsById(roomId)) {
throw new IllegalArgumentException(String.format("해당 Id의 방이 없어 참여할 수 없습니다. 입력된 방 Id=%d", roomId));
throw new CoreaException(ExceptionType.NOT_FOUND_ERROR, String.format("%d에 해당하는 방이 없습니다.", roomId));
}
if (!memberRepository.existsById(memberId)) {
throw new IllegalArgumentException(String.format("해당 Id의 멤버가 없어 방에 참여할 수 없습니다. 입력된 멤버 Id=%d", memberId));
throw new CoreaException(ExceptionType.NOT_FOUND_ERROR, String.format("%d에 해당하는 멤버가 없습니다.", memberId));
}
if (participationRepository.existsByRoomIdAndMemberId(roomId, memberId)) {
throw new CoreaException(ExceptionType.ALREADY_APPLY);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,23 @@
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/rooms")
@RequiredArgsConstructor
public class RoomController {

private final RoomService roomService;

@GetMapping("/rooms/{id}")
@GetMapping("/{id}")
public ResponseEntity<RoomResponse> room(@PathVariable final long id) {
final RoomResponse response = roomService.findOne(id);
return ResponseEntity.ok(response);
}

@GetMapping("/rooms")
@GetMapping
public ResponseEntity<RoomResponses> rooms() {
final RoomResponses response = roomService.findAll();
return ResponseEntity.ok(response);
Expand Down
1 change: 1 addition & 0 deletions backend/src/main/java/corea/room/domain/Room.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public class Room {
private int limitedParticipantsSize;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "manager_id",foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
private Member manager;

private LocalDateTime recruitmentDeadline;
Expand Down
9 changes: 6 additions & 3 deletions backend/src/main/java/corea/room/dto/RoomResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ public record RoomResponse(
LocalDateTime reviewDeadline
) {

//TODO 해당 객체를 사용한다면 반영
public static RoomResponse of(final Room room, final String author) {
return null;
public static RoomResponse of(final Room room) {
return new RoomResponse(
room.getId(), room.getTitle(), room.getContent(), room.getManager().getEmail(),
room.getRepositoryLink(), room.getThumbnailLink(), room.getMatchingSize(), List.of(room.getKeyword()),
room.getCurrentParticipantsSize(), room.getLimitedParticipantsSize(), room.getRecruitmentDeadline(), room.getReviewDeadline()
);
}
}
10 changes: 1 addition & 9 deletions backend/src/main/java/corea/room/service/RoomService.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package corea.room.service;

import corea.member.domain.Member;
import corea.room.domain.Room;
import corea.room.dto.RoomCreateRequest;
import corea.room.dto.RoomResponse;
Expand Down Expand Up @@ -42,15 +41,8 @@ public RoomResponses findAll() {
.collect(collectingAndThen(toList(), RoomResponses::new));
}

//TODO 해당 객체를 사용한다면 반영
private RoomResponse toRoomResponse(final Room room) {
final Member member = null;
return RoomResponse.of(room, member.getEmail());
}

private Member getMember(final long memberId) {
return memberRepository.findById(memberId)
.orElseThrow(() -> new IllegalArgumentException(String.format("해당 Id의 멤버가 없습니다. 입력된 Id=%d", memberId)));
return RoomResponse.of(room);
}

private Room getRoom(final long roomId) {
Expand Down
Loading

0 comments on commit 7ff48c9

Please sign in to comment.