Skip to content

Commit

Permalink
Be/feat/#158 be 무드 조회 api 구현 (#160)
Browse files Browse the repository at this point in the history
  • Loading branch information
sejeongsong authored Nov 16, 2023
2 parents 58e6d3f + 481e83e commit 59ce448
Show file tree
Hide file tree
Showing 43 changed files with 1,386 additions and 330 deletions.
90 changes: 87 additions & 3 deletions be/src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,22 @@ operation::updateFeed[snippets='http-request']
operation::deleteFeed[snippets='http-request']

[[auth]]
== 보안
== 인증/인가

=== 로그인

==== 성공

operation::login_success[snippets='http-request,http-response']

=== 로그아웃
==== 가입되지 않은 이메일일 때

operation::login_failedByUnregisteredEmail[snippets='http-response']

==== 비밀번호가 틀렸을 때

operation::login_failedByWrongPassword[snippets='http-response']

operation::logout_success[snippets='http-request,http-response']

[[comment]]
== 댓글
Expand Down Expand Up @@ -138,3 +145,80 @@ operation::fetchComments_failed_by_feed_id_not_exists[snippets='http-response']
==== 성공

operation::signupMember_success[snippets='http-request,http-response']

==== 입력값이 잘못됐을 때

operation::signupMember_failedByMultipleInvalidInput[snippets='http-response']

==== 이미 가입된 이메일일 때

operation::signupMember_failedByDuplicateEmail[snippets='http-response']

==== 이미 가입된 닉네임일 때

operation::signupMember_failedByDuplicateNickname[snippets='http-response']

==== 재입력한 패스워드가 다를 때

operation::signupMember_failedByReconfirmPasswordUnmatch[snippets='http-response']

=== 회원 프로필 조회

==== 성공

operation::fetchMemberProfile_success[snippets='http-request,http-response']

==== 존재하지 않는 회원 id일 때

operation::fetchMemberProfile_failedByIdNotFound[snippets='http-response']


[[mood]]
== 무드

=== 무드 조회

==== 성공

operation::fetchSliceMood_success[snippets='http-request,http-response']

==== 성공 - 페이징

operation::fetchSliceMood_whenPageAndSizeExists_success[snippets='http-request,http-response']

=== 무드 랜덤 조회

==== 성공

operation::fetchRandomMood_success[snippets='http-request,http-response']

==== 성공 - DB에 무드가 없을 때

operation::fetchRandomMood_whenMoodNotExists_success[snippets='http-response']

==== 요청의 쿼리 파라미터에 count가 없을 때

operation::fetchRandomMood_failedByCountNull[snippets='http-request,http-response']

=== 무드 추가

==== 성공

operation::registerMood_success[snippets='http-request,http-response']

==== 이미 존재하는 무드일 때

operation::registerMood_failedByDuplicateName[snippets='http-response']

==== 요청으로 받은 무드가 null일 때

operation::registerMood_failedByNullName[snippets='http-response']

=== 개별 무드 조회

==== 성공

operation::findMoodyById_success[snippets='http-request,http-response']



48 changes: 48 additions & 0 deletions be/src/main/java/com/foodymoody/be/auth/util/ClaimUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.foodymoody.be.auth.util;

import com.foodymoody.be.common.exception.ClaimNotFoundException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.IncorrectClaimException;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.RequiredTypeException;
import io.jsonwebtoken.security.Keys;
import java.util.Map;
import java.util.Objects;
import javax.crypto.SecretKey;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class ClaimUtil {

private String secret;
private SecretKey secretKey;

public ClaimUtil(@Value("${jwt.token.secret}") String secret){
this.secret = secret;
this.secretKey = Keys.hmacShaKeyFor(secret.getBytes());
}

public Map<String, Object> createClaim(String key, String value) {
return Map.of(key, value);
}

public <T> T getClaim(Claims claims, String key, Class<T> type) {
try {
T claim = claims.get(key, type);
if (Objects.isNull(claim)) {
throw new ClaimNotFoundException();
}
return claim;
} catch (RequiredTypeException | ClassCastException e) {
throw new IncorrectClaimException(null, claims, key);
}
}

public Claims extractClaims(String token) {
JwtParser parser = Jwts.parserBuilder().setSigningKey(secretKey).build();
return parser.parseClaimsJws(token).getBody();
}

}
44 changes: 10 additions & 34 deletions be/src/main/java/com/foodymoody/be/auth/util/JwtUtil.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
package com.foodymoody.be.auth.util;

import com.foodymoody.be.common.exception.ClaimNotFoundException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.IncorrectClaimException;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.RequiredTypeException;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import java.util.Arrays;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
import javax.crypto.SecretKey;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
Expand All @@ -25,36 +20,37 @@ public class JwtUtil {
private String secret;
private String issuer;
private SecretKey secretKey;
private JwtParser parser;
private ClaimUtil claimUtil;

public JwtUtil(
@Value("${jwt.token.exp.access}") long accessTokenExp,
@Value("${jwt.token.exp.refresh}")long refreshTokenExp,
@Value("${jwt.token.secret}") String secret,
@Value("${jwt.token.issuer}") String issuer) {
@Value("${jwt.token.issuer}") String issuer,
ClaimUtil claimUtil) {
this.accessTokenExp = accessTokenExp;
this.refreshTokenExp = refreshTokenExp;
this.secret = secret;
this.issuer = issuer;
this.secretKey = Keys.hmacShaKeyFor(secret.getBytes());
this.parser = Jwts.parserBuilder().setSigningKey(secretKey).build();
this.claimUtil = claimUtil;
}

public String createAccessToken(Date now, String id, String email) {
Map<String, Object> idClaim = createClaim("id", id);
Map<String, Object> emailClaim = createClaim("email", email);
Map<String, Object> idClaim = claimUtil.createClaim("id", id);
Map<String, Object> emailClaim = claimUtil.createClaim("email", email);
return createToken(now, accessTokenExp, issuer, secretKey, idClaim, emailClaim);
}

public String createRefreshToken(Date now, String id) {
Map<String, Object> idClaim = createClaim("id", id);
Map<String, Object> idClaim = claimUtil.createClaim("id", id);
return createToken(now, refreshTokenExp, issuer, secretKey, idClaim);
}

public Map<String, String> parseAccessToken(String token) {
Claims claims = extractClaims(token);
String id = getClaim(claims, "id", String.class);
String email = getClaim(claims, "email", String.class);
Claims claims = claimUtil.extractClaims(token);
String id = claimUtil.getClaim(claims, "id", String.class);
String email = claimUtil.getClaim(claims, "email", String.class);
return Map.of("id", id, "email", email);
}

Expand All @@ -70,24 +66,4 @@ private String createToken(Date now, long exp, String issuer, SecretKey secretKe
.setExpiration(expiration)
.signWith(secretKey, SignatureAlgorithm.HS256).compact();
}

private Map<String, Object> createClaim(String key, String value) {
return Map.of(key, value);
}

private <T> T getClaim(Claims claims, String key, Class<T> type) {
try {
T claim = claims.get(key, type);
if (Objects.isNull(claim)) {
throw new ClaimNotFoundException();
}
return claim;
} catch (RequiredTypeException | ClassCastException e) {
throw new IncorrectClaimException(null, claims, key);
}
}

private Claims extractClaims(String token) {
return parser.parseClaimsJws(token).getBody();
}
}
2 changes: 1 addition & 1 deletion be/src/main/java/com/foodymoody/be/common/WrappedId.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public class WrappedId implements Serializable {

protected String id;

public WrappedId() {
protected WrappedId() {
}

public WrappedId(String id) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ protected BusinessException(ErrorMessage errorMessage) {
}

public String getCode() {
return errorMessage.getMessage();
return errorMessage.getCode();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.foodymoody.be.common.exception;

public class DuplicateMoodException extends BusinessException {

public DuplicateMoodException() {
super(ErrorMessage.DUPLICATE_MOOD);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@ public enum ErrorMessage {
DUPLICATE_MEMBER_EMAIL("이미 가입된 이메일입니다", "m002"),
DUPLICATE_MEMBER_NICKNAME("이미 존재하는 닉네임입니다", "m003"),
INVALID_CONFIRM_PASSWORD("입력하신 패스워드와 일치하지 않습니다", "m004"),
MEMBER_INCORRECT_PASSWORD("사용자 정보와 패스워드가 일치하지 않습니다", "m005"),
// auth
INVALID_TOKEN("토큰이 유효하지 않습니다", "a001"),
IS_NOT_HTTP_REQUEST("http 요청이 아닙니다", "a002"),
// auth,
UNAUTHORIZED("권한이 없습니다", "a001"),
INVALID_TOKEN("토큰이 유효하지 않습니다", "a002"),
CLAIM_NOT_FOUND("토큰에 해당 클레임이 존재하지 않습니다", "a003"),
UNAUTHORIZED("권한이 없습니다", "a004"),
INVALID_ACCESS_TOKEN("유효하지 않은 액세스 토큰입니다", "a005");
INVALID_ACCESS_TOKEN("유효하지 않은 액세스 토큰입니다", "a004"),
MEMBER_INCORRECT_PASSWORD("사용자 정보와 패스워드가 일치하지 않습니다", "a005"),
// mood
DUPLICATE_MOOD("이미 존재하는 무드입니다", "o001");

private final String message;
private final String code;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import static com.foodymoody.be.common.exception.ErrorMessage.INVALID_INPUT_VALUE;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.NOT_FOUND;
import static org.springframework.http.HttpStatus.UNAUTHORIZED;

import java.util.Map;
import java.util.stream.Collectors;
Expand All @@ -10,6 +12,7 @@
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingRequestValueException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
Expand Down Expand Up @@ -42,11 +45,32 @@ public ErrorResponse handleHttpMessageNotReadableException(HttpMessageNotReadabl

@ResponseStatus(value = BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorResponse handleIllegalArgumentException(HttpMessageNotReadableException e) {
public ErrorResponse handleIllegalArgumentException(IllegalArgumentException e) {
log.error("handleIllegalArgumentException", e);
return new ErrorResponse(e.getMessage(), INVALID_INPUT_VALUE.getCode());
}

@ResponseStatus(value = BAD_REQUEST)
@ExceptionHandler(MissingRequestValueException.class)
public ErrorResponse handleIllegalArgumentException(MissingRequestValueException e) {
log.error("handleMissingRequestValueExceptionException", e);
return new ErrorResponse(INVALID_INPUT_VALUE.getMessage(), INVALID_INPUT_VALUE.getCode());
}

@ResponseStatus(value = UNAUTHORIZED)
@ExceptionHandler(UnauthorizedException.class)
public ErrorResponse handleUnauthorizedException(UnauthorizedException e) {
log.error("handleUnauthorizedExceptionException", e);
return new ErrorResponse(e.getMessage(), e.getCode());
}

@ResponseStatus(value = NOT_FOUND)
@ExceptionHandler(ResourceNotFoundException.class)
public ErrorResponse handleResourceNotFoundException(ResourceNotFoundException e) {
log.error("handleResourceNotFoundExceptionException", e);
return new ErrorResponse(e.getMessage(), e.getCode());
}

private static Map<String, String> getErrors(MethodArgumentNotValidException e) {
return e.getBindingResult()
.getAllErrors()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.foodymoody.be.common.exception;

public class IncorrectMemberPasswordException extends BusinessException {
public class IncorrectMemberPasswordException extends UnauthorizedException {

public IncorrectMemberPasswordException() {
super(ErrorMessage.MEMBER_INCORRECT_PASSWORD);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.foodymoody.be.common.exception;

public class MemberNotFoundException extends BusinessException {
public class MemberNotFoundException extends ResourceNotFoundException {

public MemberNotFoundException() {
super(ErrorMessage.MEMBER_NOT_FOUND);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.foodymoody.be.common.exception;

public abstract class ResourceNotFoundException extends RuntimeException{

private final ErrorMessage errorMessage;

protected ResourceNotFoundException(ErrorMessage errorMessage) {
super(errorMessage.getMessage());
this.errorMessage = errorMessage;
}

public String getCode() {
return errorMessage.getCode();
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package com.foodymoody.be.common.exception;

public class UnauthorizedException extends BusinessException{
public class UnauthorizedException extends RuntimeException{

// TODO 적절한 표준 예외로 리팩토링
public UnauthorizedException() {
super(ErrorMessage.UNAUTHORIZED);
private final ErrorMessage errorMessage;

protected UnauthorizedException(ErrorMessage errorMessage) {
super(errorMessage.getMessage());
this.errorMessage = errorMessage;
}

public String getCode() {
return errorMessage.getCode();
}
}
Loading

0 comments on commit 59ce448

Please sign in to comment.