Skip to content

Commit

Permalink
Merge branch 'dev/be' into refactor/802-zap-review-git-action
Browse files Browse the repository at this point in the history
  • Loading branch information
kyum-q committed Oct 17, 2024
2 parents 158d8e3 + 7535f77 commit e8d68fb
Show file tree
Hide file tree
Showing 82 changed files with 689 additions and 309 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/backend_ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,7 @@ jobs:
mysql version: ${{ secrets.MYSQL_VERSION }}
mysql database: ${{ secrets.MYSQL_DATABASE }}
mysql root password: ${{ secrets.MYSQL_PASSWORD }}

- name: DB 설정 파일 가져오기
working-directory: ./backend/src/main/resources
run: echo "${{ secrets.APPLICATION_DB_YAML }}" > application-db.yml


- name: gradle 캐싱
uses: gradle/actions/setup-gradle@v4

Expand All @@ -36,6 +32,10 @@ jobs:
java-version: 17
distribution: temurin

- name: 환경변수 주입
run: ${{ secrets.APPLICATION_YML }}
working-directory: ./backend/src/test/resources

- name: 테스트 코드 실행
run: ./gradlew test
working-directory: ./backend
2 changes: 1 addition & 1 deletion backend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ out/
.DS_Store

### YAML ###
application-db.yml
src/test/resources/application.yml

### compose ###
docker/app/*.jar
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ public Member resolveArgument(
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory
) {
AuthenticationPrinciple parameterAnnotation = parameter.getParameterAnnotation(AuthenticationPrinciple.class);
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
if (!parameterAnnotation.required() && !credentialManager.hasCredential(request)) {
return null;
}
String credential = credentialManager.getCredential(request);
return credentialProvider.extractMember(credential);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthenticationPrinciple {
boolean required() default true;
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public interface SpringDocAuthController {
})
@ApiErrorResponse(status = HttpStatus.UNAUTHORIZED, instance = "/login", errorCases = {
@ErrorCase(description = "아이디 불일치", exampleMessage = "존재하지 않는 아이디 moly 입니다."),
@ErrorCase(description = "비밀번호 불일치", exampleMessage = "로그인에 실패하였습니다. 아이디 또는 비밀번호를 확인해주세요."),
@ErrorCase(description = "비밀번호 불일치", exampleMessage = "로그인에 실패하였습니다. 비밀번호를 확인해주세요."),
})
ResponseEntity<LoginResponse> login(LoginRequest request, HttpServletResponse response);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import codezap.global.exception.CodeZapException;
import codezap.global.exception.ErrorCode;

@Component
public class SHA2PasswordEncryptor implements PasswordEncryptor {
Expand All @@ -17,7 +17,7 @@ public SHA2PasswordEncryptor() {
try {
digest = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new CodeZapException(HttpStatus.INTERNAL_SERVER_ERROR, "암호화 알고리즘이 잘못 명시되었습니다.");
throw new CodeZapException(ErrorCode.INTERNAL_SERVER_ERROR, "암호화 알고리즘이 잘못 명시되었습니다.");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
import jakarta.servlet.http.HttpServletResponse;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.stereotype.Component;

import codezap.global.exception.CodeZapException;
import codezap.global.exception.ErrorCode;

@Component
public class CookieCredentialManager implements CredentialManager {
Expand All @@ -29,15 +29,25 @@ public String getCredential(final HttpServletRequest httpServletRequest) {

private void checkCookieExist(final Cookie[] cookies) {
if (cookies == null) {
throw new CodeZapException(HttpStatus.UNAUTHORIZED, "쿠키가 없어서 회원 정보를 찾을 수 없습니다. 다시 로그인해주세요.");
throw new CodeZapException(ErrorCode.UNAUTHORIZED_USER, "쿠키가 없어서 회원 정보를 찾을 수 없습니다. 다시 로그인해주세요.");
}
}

@Override
public boolean hasCredential(final HttpServletRequest httpServletRequest) {
Cookie[] cookies = httpServletRequest.getCookies();
if (cookies == null) {
return false;
}
return Arrays.stream(cookies)
.anyMatch(cookie -> cookie.getName().equals(CREDENTIAL_COOKIE_NAME));
}

private Cookie extractTokenCookie(final Cookie[] cookies) {
return Arrays.stream(cookies)
.filter(cookie -> cookie.getName().equals(CREDENTIAL_COOKIE_NAME))
.findFirst()
.orElseThrow(() -> new CodeZapException(HttpStatus.UNAUTHORIZED,
.orElseThrow(() -> new CodeZapException(ErrorCode.UNAUTHORIZED_USER,
"인증에 대한 쿠키가 없어서 회원 정보를 찾을 수 없습니다. 다시 로그인해주세요."));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ public interface CredentialManager {

String getCredential(HttpServletRequest httpServletRequest);

boolean hasCredential(HttpServletRequest httpServletRequest);

void setCredential(HttpServletResponse httpServletResponse, String credential);

void removeCredential(HttpServletResponse httpServletResponse);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
import java.nio.charset.StandardCharsets;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import codezap.auth.provider.CredentialProvider;
import codezap.global.exception.CodeZapException;
import codezap.global.exception.ErrorCode;
import codezap.member.domain.Member;
import codezap.member.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
Expand All @@ -33,7 +33,7 @@ public Member extractMember(String credential) {

private void checkMatchPassword(Member member, String password) {
if (!member.matchPassword(password)) {
throw new CodeZapException(HttpStatus.UNAUTHORIZED, "아이디 또는 비밀번호가 일치하지 않습니다.");
throw new CodeZapException(ErrorCode.UNAUTHORIZED_PASSWORD, "비밀번호가 일치하지 않습니다.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
import java.nio.charset.StandardCharsets;
import java.util.Base64;

import org.springframework.http.HttpStatus;

import codezap.global.exception.CodeZapException;
import codezap.global.exception.ErrorCode;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;

Expand All @@ -26,13 +25,13 @@ private static String decodeBase64(String base64Credentials) {
byte[] credDecoded = Base64.getDecoder().decode(base64Credentials);
return new String(credDecoded, StandardCharsets.UTF_8);
} catch (IllegalArgumentException e) {
throw new CodeZapException(HttpStatus.UNAUTHORIZED, "잘못된 Base64 인코딩입니다.");
throw new CodeZapException(ErrorCode.UNAUTHORIZED_USER, "잘못된 Base64 인코딩입니다.");
}
}

private static void validateBasicAuth(String[] values) {
if (values.length != BASIC_AUTH_LENGTH || values[0].isEmpty() || values[1].isEmpty()) {
throw new CodeZapException(HttpStatus.UNAUTHORIZED, "인증 정보가 올바르지 않습니다. 다시 로그인 해주세요.");
throw new CodeZapException(ErrorCode.UNAUTHORIZED_USER, "인증 정보가 올바르지 않습니다. 다시 로그인 해주세요.");
}
}
}
4 changes: 2 additions & 2 deletions backend/src/main/java/codezap/auth/service/AuthService.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package codezap.auth.service;

import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;

import codezap.auth.dto.LoginAndCredentialDto;
Expand All @@ -9,6 +8,7 @@
import codezap.auth.encryption.PasswordEncryptor;
import codezap.auth.provider.CredentialProvider;
import codezap.global.exception.CodeZapException;
import codezap.global.exception.ErrorCode;
import codezap.member.domain.Member;
import codezap.member.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -37,7 +37,7 @@ private void validateCorrectPassword(Member member, String password) {
String salt = member.getSalt();
String encryptedPassword = passwordEncryptor.encrypt(password, salt);
if (!member.matchPassword(encryptedPassword)) {
throw new CodeZapException(HttpStatus.UNAUTHORIZED, "로그인에 실패하였습니다. 아이디 또는 비밀번호를 확인해주세요.");
throw new CodeZapException(ErrorCode.UNAUTHORIZED_PASSWORD, "로그인에 실패하였습니다. 비밀번호를 확인해주세요.");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public interface SpringDocCategoryController {
@ApiErrorResponse(status = HttpStatus.BAD_REQUEST, instance = "/categories", errorCases = {
@ErrorCase(description = "모든 필드 중 null인 값이 있는 경우", exampleMessage = "카테고리 이름이 null 입니다."),
@ErrorCase(description = "카테고리 이름이 15자를 초과한 경우", exampleMessage = "카테고리 이름은 최대 15자까지 입력 가능합니다."),
})
@ApiErrorResponse(status = HttpStatus.CONFLICT, instance = "/categories", errorCases = {
@ErrorCase(description = "동일한 이름의 카테고리가 존재하는 경우", exampleMessage = "이름이 Spring 인 카테고리가 이미 존재합니다."),
})
ResponseEntity<CreateCategoryResponse> createCategory(
Expand All @@ -49,7 +51,11 @@ ResponseEntity<CreateCategoryResponse> createCategory(
@ApiResponse(responseCode = "200", description = "카테고리 수정 성공")
@ApiErrorResponse(status = HttpStatus.BAD_REQUEST, instance = "/categories/1", errorCases = {
@ErrorCase(description = "카테고리 이름이 15자를 초과한 경우", exampleMessage = "카테고리 이름은 최대 15자까지 입력 가능합니다."),
})
@ApiErrorResponse(status = HttpStatus.NOT_FOUND, instance = "/categories/1", errorCases = {
@ErrorCase(description = "해당하는 id 값인 카테고리가 없는 경우", exampleMessage = "식별자 1에 해당하는 카테고리가 존재하지 않습니다."),
})
@ApiErrorResponse(status = HttpStatus.CONFLICT, instance = "/categories", errorCases = {
@ErrorCase(description = "동일한 이름의 카테고리가 존재하는 경우", exampleMessage = "이름이 Spring 인 카테고리가 이미 존재합니다."),
})
@ApiErrorResponse(status = HttpStatus.FORBIDDEN, instance = "/categories/1", errorCases = {
Expand Down
5 changes: 2 additions & 3 deletions backend/src/main/java/codezap/category/domain/Category.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;

import org.springframework.http.HttpStatus;

import codezap.global.auditing.BaseTimeEntity;
import codezap.global.exception.CodeZapException;
import codezap.global.exception.ErrorCode;
import codezap.member.domain.Member;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
Expand Down Expand Up @@ -67,7 +66,7 @@ public void updateName(String name) {

public void validateAuthorization(Member member) {
if (!getMember().equals(member)) {
throw new CodeZapException(HttpStatus.UNAUTHORIZED, "해당 카테고리를 수정 또는 삭제할 권한이 없는 유저입니다.");
throw new CodeZapException(ErrorCode.FORBIDDEN_ACCESS, "해당 카테고리를 수정 또는 삭제할 권한이 없는 유저입니다.");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@
import java.util.List;

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

import codezap.category.domain.Category;
import codezap.global.exception.CodeZapException;
import codezap.global.exception.ErrorCode;
import codezap.member.domain.Member;

@SuppressWarnings("unused")
public interface CategoryJpaRepository extends CategoryRepository, JpaRepository<Category, Long> {

default Category fetchById(Long id) {
return findById(id).orElseThrow(
() -> new CodeZapException(HttpStatus.NOT_FOUND, "식별자 " + id + "에 해당하는 카테고리가 존재하지 않습니다."));
() -> new CodeZapException(ErrorCode.RESOURCE_NOT_FOUND, "식별자 " + id + "에 해당하는 카테고리가 존재하지 않습니다."));
}

List<Category> findAllByMemberIdOrderById(Long memberId);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package codezap.category.service;

import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -11,6 +10,7 @@
import codezap.category.dto.response.FindAllCategoriesResponse;
import codezap.category.repository.CategoryRepository;
import codezap.global.exception.CodeZapException;
import codezap.global.exception.ErrorCode;
import codezap.member.domain.Member;
import codezap.template.repository.TemplateRepository;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -52,7 +52,7 @@ public void update(Member member, Long id, UpdateCategoryRequest updateCategoryR

private void validateDuplicatedCategory(String categoryName, Member member) {
if (categoryRepository.existsByNameAndMember(categoryName, member)) {
throw new CodeZapException(HttpStatus.CONFLICT, "이름이 " + categoryName + "인 카테고리가 이미 존재합니다.");
throw new CodeZapException(ErrorCode.DUPLICATE_CATEGORY, "이름이 " + categoryName + "인 카테고리가 이미 존재합니다.");
}
}

Expand All @@ -62,10 +62,10 @@ public void deleteById(Member member, Long id) {
category.validateAuthorization(member);

if (templateRepository.existsByCategoryId(id)) {
throw new CodeZapException(HttpStatus.BAD_REQUEST, "템플릿이 존재하는 카테고리는 삭제할 수 없습니다.");
throw new CodeZapException(ErrorCode.CATEGORY_HAS_TEMPLATES, "템플릿이 존재하는 카테고리는 삭제할 수 없습니다.");
}
if (category.isDefault()) {
throw new CodeZapException(HttpStatus.BAD_REQUEST, "기본 카테고리는 삭제할 수 없습니다.");
throw new CodeZapException(ErrorCode.DEFAULT_CATEGORY, "기본 카테고리는 삭제할 수 없습니다.");
}
categoryRepository.deleteById(id);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
package codezap.global.exception;

import org.springframework.http.HttpStatusCode;
import org.springframework.http.ProblemDetail;

import lombok.Getter;

@Getter
public class CodeZapException extends RuntimeException {

private final HttpStatusCode httpStatusCode;
private final ErrorCode errorCode;

public CodeZapException(HttpStatusCode httpStatusCode, String message) {
public CodeZapException(ErrorCode errorCode, String message) {
super(message);
this.httpStatusCode = httpStatusCode;
this.errorCode = errorCode;
}

public ProblemDetail toProblemDetail() {
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(
errorCode.getHttpStatus(),
getMessage());
return GlobalExceptionHandler.setProperties(problemDetail, errorCode.getCode());
}
}
31 changes: 31 additions & 0 deletions backend/src/main/java/codezap/global/exception/ErrorCode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package codezap.global.exception;

import org.springframework.http.HttpStatus;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum ErrorCode {

SPRING_GLOBAL_EXCEPTION(1000, HttpStatus.BAD_REQUEST),

INVALID_REQUEST(1101, HttpStatus.BAD_REQUEST),
CATEGORY_HAS_TEMPLATES(1102, HttpStatus.BAD_REQUEST),
DEFAULT_CATEGORY(1103, HttpStatus.BAD_REQUEST),

RESOURCE_NOT_FOUND(1201, HttpStatus.NOT_FOUND),
DUPLICATE_ID(1202, HttpStatus.CONFLICT),
DUPLICATE_CATEGORY(1203, HttpStatus.CONFLICT),

UNAUTHORIZED_USER(1301, HttpStatus.UNAUTHORIZED),
UNAUTHORIZED_ID(1302, HttpStatus.UNAUTHORIZED),
UNAUTHORIZED_PASSWORD(1303, HttpStatus.UNAUTHORIZED),
FORBIDDEN_ACCESS(1304, HttpStatus.FORBIDDEN),

INTERNAL_SERVER_ERROR(2000, HttpStatus.INTERNAL_SERVER_ERROR);

private final int code;
private final HttpStatus httpStatus;
}
Loading

0 comments on commit e8d68fb

Please sign in to comment.