From 96e1287cb31af1664095350ff36840df20b1cc80 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Fri, 19 Jan 2024 16:23:00 +0900 Subject: [PATCH 01/88] =?UTF-8?q?feat:=20spring-boot-starter-security=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/build.gradle b/backend/build.gradle index fbb4f6632..e12fd2604 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -56,6 +56,8 @@ dependencies { implementation 'org.flywaydb:flyway-core' implementation 'org.flywaydb:flyway-mysql' + + implementation 'org.springframework.boot:spring-boot-starter-security' } test { From d149c761b8a7f0bfeea6e9705fad7a90e54e8112 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Fri, 19 Jan 2024 16:24:16 +0900 Subject: [PATCH 02/88] =?UTF-8?q?chore:=20Flyway=20AdminMember=20=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EB=B8=94=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../db/migration/V9__Create_table_admin_member.sql | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 backend/src/main/resources/db/migration/V9__Create_table_admin_member.sql diff --git a/backend/src/main/resources/db/migration/V9__Create_table_admin_member.sql b/backend/src/main/resources/db/migration/V9__Create_table_admin_member.sql new file mode 100644 index 000000000..be0c71755 --- /dev/null +++ b/backend/src/main/resources/db/migration/V9__Create_table_admin_member.sql @@ -0,0 +1,12 @@ + +CREATE TABLE IF NOT EXISTS admin_member ( + id BIGINT NOT NULL AUTO_INCREMENT, + user_name VARCHAR(20) NOT NULL, + password VARCHAR(64) NOT NULL, + last_login_date DATETIME(6) NOT NULL, + admin_type ENUM ('ADMIN','MASTER'), + status ENUM ('ACTIVE','DELETED','DORMANT'), + created_at DATETIME(6), + modified_at DATETIME(6), + PRIMARY KEY (id) + ) engine=InnoDB; From 8cea490ce13f207655b2041bdd0751fddbb841cd Mon Sep 17 00:00:00 2001 From: LJW25 Date: Fri, 19 Jan 2024 16:24:56 +0900 Subject: [PATCH 03/88] =?UTF-8?q?feat:=20AdminAuth=20=EC=96=B4=EB=85=B8?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EC=85=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/hanglog/auth/AdminAuth.java | 12 ++++++++++ .../java/hanglog/auth/domain/Accessor.java | 23 +++++++++++++++---- .../java/hanglog/auth/domain/Authority.java | 2 +- 3 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 backend/src/main/java/hanglog/auth/AdminAuth.java diff --git a/backend/src/main/java/hanglog/auth/AdminAuth.java b/backend/src/main/java/hanglog/auth/AdminAuth.java new file mode 100644 index 000000000..59dd53eae --- /dev/null +++ b/backend/src/main/java/hanglog/auth/AdminAuth.java @@ -0,0 +1,12 @@ +package hanglog.auth; + +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Target(PARAMETER) +@Retention(RUNTIME) +public @interface AdminAuth { +} diff --git a/backend/src/main/java/hanglog/auth/domain/Accessor.java b/backend/src/main/java/hanglog/auth/domain/Accessor.java index c61e49ae0..9be37a0c1 100644 --- a/backend/src/main/java/hanglog/auth/domain/Accessor.java +++ b/backend/src/main/java/hanglog/auth/domain/Accessor.java @@ -1,7 +1,5 @@ package hanglog.auth.domain; -import static hanglog.auth.domain.Authority.MEMBER; - import lombok.Getter; @Getter @@ -20,10 +18,27 @@ public static Accessor guest() { } public static Accessor member(final Long memberId) { - return new Accessor(memberId, MEMBER); + return new Accessor(memberId, Authority.MEMBER); + } + + public static Accessor admin(final Long memberId) { + return new Accessor(memberId, Authority.ADMIN); + } + + public static Accessor master(final Long memberId) { + return new Accessor(memberId, Authority.MASTER); } + public boolean isMember() { - return MEMBER.equals(authority); + return Authority.MEMBER.equals(authority); + } + + public boolean isAdmin() { + return Authority.ADMIN.equals(authority) || Authority.MASTER.equals(authority); + } + + public boolean isMaster() { + return Authority.MASTER.equals(authority); } } diff --git a/backend/src/main/java/hanglog/auth/domain/Authority.java b/backend/src/main/java/hanglog/auth/domain/Authority.java index 4d2148882..305a859a9 100644 --- a/backend/src/main/java/hanglog/auth/domain/Authority.java +++ b/backend/src/main/java/hanglog/auth/domain/Authority.java @@ -1,5 +1,5 @@ package hanglog.auth.domain; public enum Authority { - GUEST, MEMBER + GUEST, MEMBER, ADMIN, MASTER } From d256694a4264ebe130e2c27c31e9137cc6ac2efe Mon Sep 17 00:00:00 2001 From: LJW25 Date: Fri, 19 Jan 2024 16:28:11 +0900 Subject: [PATCH 04/88] =?UTF-8?q?feat:=20AdminLoginArgumentResolver=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/AdminLoginArgumentResolver.java | 87 +++++++++++++++++++ .../admin/AdminLoginResolverConfig.java | 19 ++++ .../java/hanglog/global/ControllerTest.java | 6 +- 3 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 backend/src/main/java/hanglog/admin/AdminLoginArgumentResolver.java create mode 100644 backend/src/main/java/hanglog/admin/AdminLoginResolverConfig.java diff --git a/backend/src/main/java/hanglog/admin/AdminLoginArgumentResolver.java b/backend/src/main/java/hanglog/admin/AdminLoginArgumentResolver.java new file mode 100644 index 000000000..5032d9649 --- /dev/null +++ b/backend/src/main/java/hanglog/admin/AdminLoginArgumentResolver.java @@ -0,0 +1,87 @@ +package hanglog.admin; + +import static hanglog.global.exception.ExceptionCode.INVALID_REQUEST; +import static hanglog.global.exception.ExceptionCode.NOT_FOUND_REFRESH_TOKEN; +import static org.springframework.http.HttpHeaders.AUTHORIZATION; + +import hanglog.admin.domain.repository.AdminMemberRepository; +import hanglog.admin.domain.type.AdminType; +import hanglog.auth.AdminAuth; +import hanglog.auth.domain.Accessor; +import hanglog.global.exception.BadRequestException; +import hanglog.global.exception.RefreshTokenException; +import hanglog.login.domain.MemberTokens; +import hanglog.login.domain.repository.RefreshTokenRepository; +import hanglog.login.infrastructure.BearerAuthorizationExtractor; +import hanglog.login.infrastructure.JwtProvider; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import java.util.Arrays; +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; + +@RequiredArgsConstructor +@Component +public class AdminLoginArgumentResolver implements HandlerMethodArgumentResolver { + + private static final String REFRESH_TOKEN = "refresh-token"; + + private final JwtProvider jwtProvider; + + private final BearerAuthorizationExtractor extractor; + + private final RefreshTokenRepository refreshTokenRepository; + + private final AdminMemberRepository adminMemberRepository; + + @Override + public boolean supportsParameter(final MethodParameter parameter) { + return parameter.withContainingClass(Long.class) + .hasParameterAnnotation(AdminAuth.class); + } + + @Override + public Accessor resolveArgument( + final MethodParameter parameter, + final ModelAndViewContainer mavContainer, + final NativeWebRequest webRequest, + final WebDataBinderFactory binderFactory + ) { + final HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); + if (request == null) { + throw new BadRequestException(INVALID_REQUEST); + } + + final String refreshToken = extractRefreshToken(request.getCookies()); + final String accessToken = extractor.extractAccessToken(webRequest.getHeader(AUTHORIZATION)); + jwtProvider.validateTokens(new MemberTokens(refreshToken, accessToken)); + + final Long memberId = Long.valueOf(jwtProvider.getSubject(accessToken)); + + if (adminMemberRepository.existsAdminMemberByIdAndAdminType(memberId, AdminType.MASTER)) { + return Accessor.master(memberId); + } + return Accessor.admin(memberId); + } + + private String extractRefreshToken(final Cookie... cookies) { + if (cookies == null) { + throw new RefreshTokenException(NOT_FOUND_REFRESH_TOKEN); + } + return Arrays.stream(cookies) + .filter(this::isValidRefreshToken) + .findFirst() + .orElseThrow(() -> new RefreshTokenException(NOT_FOUND_REFRESH_TOKEN)) + .getValue(); + } + + private boolean isValidRefreshToken(final Cookie cookie) { + return REFRESH_TOKEN.equals(cookie.getName()) && + refreshTokenRepository.existsById(cookie.getValue()); + } +} diff --git a/backend/src/main/java/hanglog/admin/AdminLoginResolverConfig.java b/backend/src/main/java/hanglog/admin/AdminLoginResolverConfig.java new file mode 100644 index 000000000..6e9c5f464 --- /dev/null +++ b/backend/src/main/java/hanglog/admin/AdminLoginResolverConfig.java @@ -0,0 +1,19 @@ +package hanglog.admin; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +@RequiredArgsConstructor +public class AdminLoginResolverConfig implements WebMvcConfigurer { + + private final AdminLoginArgumentResolver adminLoginArgumentResolver; + + @Override + public void addArgumentResolvers(final List resolvers) { + resolvers.add(adminLoginArgumentResolver); + } +} diff --git a/backend/src/test/java/hanglog/global/ControllerTest.java b/backend/src/test/java/hanglog/global/ControllerTest.java index 9bde7335e..a38aaddc6 100644 --- a/backend/src/test/java/hanglog/global/ControllerTest.java +++ b/backend/src/test/java/hanglog/global/ControllerTest.java @@ -2,6 +2,7 @@ import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; +import hanglog.admin.AdminLoginArgumentResolver; import hanglog.global.restdocs.RestDocsConfiguration; import hanglog.login.LoginArgumentResolver; import hanglog.login.domain.repository.RefreshTokenRepository; @@ -33,12 +34,15 @@ public abstract class ControllerTest { @Autowired protected LoginArgumentResolver loginArgumentResolver; + @Autowired + protected AdminLoginArgumentResolver adminLoginArgumentResolver; + @MockBean protected JwtProvider jwtProvider; @MockBean protected RefreshTokenRepository refreshTokenRepository; - + @MockBean BearerAuthorizationExtractor bearerExtractor; From 9341f48529fa60bea3e0d633e969ff810348e856 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Fri, 19 Jan 2024 16:28:33 +0900 Subject: [PATCH 05/88] =?UTF-8?q?feat:=20AdminException=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hanglog/global/exception/AdminException.java | 15 +++++++++++++++ .../hanglog/global/exception/ExceptionCode.java | 9 +++++++++ .../global/exception/GlobalExceptionHandler.java | 8 ++++++++ 3 files changed, 32 insertions(+) create mode 100644 backend/src/main/java/hanglog/global/exception/AdminException.java diff --git a/backend/src/main/java/hanglog/global/exception/AdminException.java b/backend/src/main/java/hanglog/global/exception/AdminException.java new file mode 100644 index 000000000..4f2541f3b --- /dev/null +++ b/backend/src/main/java/hanglog/global/exception/AdminException.java @@ -0,0 +1,15 @@ +package hanglog.global.exception; + +import lombok.Getter; + +@Getter +public class AdminException extends RuntimeException { + + private final int code; + private final String message; + + public AdminException(final ExceptionCode exceptionCode) { + this.code = exceptionCode.getCode(); + this.message = exceptionCode.getMessage(); + } +} diff --git a/backend/src/main/java/hanglog/global/exception/ExceptionCode.java b/backend/src/main/java/hanglog/global/exception/ExceptionCode.java index 444f94a34..6d84986ce 100644 --- a/backend/src/main/java/hanglog/global/exception/ExceptionCode.java +++ b/backend/src/main/java/hanglog/global/exception/ExceptionCode.java @@ -56,6 +56,15 @@ public enum ExceptionCode { INVALID_SHARE_CODE(7002, "공유가 허용되지 않은 코드입니다."), FAIL_SHARE_CODE_HASH(7101, "공유 코드를 해싱하는 데 실패했습니다."), + INVALID_USER_NAME(8001, "존재하지 않는 사용자입니다."), + INVALID_PASSWORD(8002, "비밀번호가 일치하지 않습니다."), + NULL_ADMIN_AUTHORITY(8101, "잘못된 관리자 권한입니다."), + DUPLICATED_ADMIN_USERNAME(8102, "중복된 사용자 이름입니다."), + NOT_FOUND_ADMIN_ID(8103, "요청한 ID에 해당하는 관리자를 찾을 수 없습니다."), + INVALID_CURRENT_PASSWORD(8104, "현재 사용중인 비밀번호가 일치하지 않습니다."), + + INVALID_ADMIN_AUTHORITY(8201, "해당 관리자 기능에 대한 접근 권한이 없습니다."), + INVALID_AUTHORIZATION_CODE(9001, "유효하지 않은 인증 코드입니다."), NOT_SUPPORTED_OAUTH_SERVICE(9002, "해당 OAuth 서비스는 제공하지 않습니다."), FAIL_TO_CONVERT_URL_PARAMETER(9003, "Url Parameter 변환 중 오류가 발생했습니다."), diff --git a/backend/src/main/java/hanglog/global/exception/GlobalExceptionHandler.java b/backend/src/main/java/hanglog/global/exception/GlobalExceptionHandler.java index 05da921f3..e37190dd6 100644 --- a/backend/src/main/java/hanglog/global/exception/GlobalExceptionHandler.java +++ b/backend/src/main/java/hanglog/global/exception/GlobalExceptionHandler.java @@ -53,6 +53,14 @@ public ResponseEntity handleAuthException(final AuthException .body(new ExceptionResponse(e.getCode(), e.getMessage())); } + @ExceptionHandler(AdminException.class) + public ResponseEntity handleAdminException(final AdminException e) { + log.warn(e.getMessage(), e); + + return ResponseEntity.badRequest() + .body(new ExceptionResponse(e.getCode(), e.getMessage())); + } + @ExceptionHandler(BadRequestException.class) public ResponseEntity handleBadRequestException(final BadRequestException e) { log.warn(e.getMessage(), e); From e8a0e4b4adfcbf805385fb304f1a9cd72cd88ad6 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Fri, 19 Jan 2024 16:29:00 +0900 Subject: [PATCH 06/88] =?UTF-8?q?feat:=20AdminMember=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hanglog/admin/domain/AdminMember.java | 70 +++++++++++++++++++ .../hanglog/admin/domain/type/AdminType.java | 17 +++++ 2 files changed, 87 insertions(+) create mode 100644 backend/src/main/java/hanglog/admin/domain/AdminMember.java create mode 100644 backend/src/main/java/hanglog/admin/domain/type/AdminType.java diff --git a/backend/src/main/java/hanglog/admin/domain/AdminMember.java b/backend/src/main/java/hanglog/admin/domain/AdminMember.java new file mode 100644 index 000000000..e0085b67f --- /dev/null +++ b/backend/src/main/java/hanglog/admin/domain/AdminMember.java @@ -0,0 +1,70 @@ +package hanglog.admin.domain; + +import static jakarta.persistence.EnumType.STRING; +import static jakarta.persistence.GenerationType.IDENTITY; +import static lombok.AccessLevel.PROTECTED; + +import hanglog.admin.domain.type.AdminType; +import hanglog.member.domain.MemberState; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import java.time.LocalDateTime; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.Where; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; + +@Entity +@Getter +@NoArgsConstructor(access = PROTECTED) +@SQLDelete(sql = "UPDATE admin_member SET status = 'DELETED' WHERE id = ?") +@Where(clause = "status = 'ACTIVE'") +public class AdminMember { + + @Id + @GeneratedValue(strategy = IDENTITY) + private Long id; + + @Column(nullable = false, unique = true, length = 20) + private String userName; + + @Column(nullable = false, length = 64) + private String password; + + @Column(nullable = false) + private LocalDateTime lastLoginDate; + + @Column(nullable = false) + @Enumerated(value = STRING) + private AdminType adminType; + + @Enumerated(value = STRING) + private MemberState status; + + @CreatedDate + @Column(updatable = false) + private LocalDateTime createdAt; + + @LastModifiedDate + private LocalDateTime modifiedAt; + + public AdminMember(final Long id, final String userName, final String password, final AdminType adminType) { + this.id = id; + this.userName = userName; + this.password = password; + this.lastLoginDate = LocalDateTime.now(); + this.adminType = adminType; + this.status = MemberState.ACTIVE; + this.createdAt = LocalDateTime.now(); + this.modifiedAt = LocalDateTime.now(); + } + + public AdminMember(final String userName, final String password, final AdminType adminType) { + this(null, userName, password, adminType); + } +} diff --git a/backend/src/main/java/hanglog/admin/domain/type/AdminType.java b/backend/src/main/java/hanglog/admin/domain/type/AdminType.java new file mode 100644 index 000000000..2c715fbd0 --- /dev/null +++ b/backend/src/main/java/hanglog/admin/domain/type/AdminType.java @@ -0,0 +1,17 @@ +package hanglog.admin.domain.type; + +import static hanglog.global.exception.ExceptionCode.NULL_ADMIN_AUTHORITY; + +import hanglog.global.exception.AdminException; +import java.util.Arrays; + +public enum AdminType { + ADMIN, MASTER; + + public static AdminType getMappedAdminType(final String adminType) { + return Arrays.stream(values()) + .filter(value -> value.name().toUpperCase().equals(adminType)) + .findAny() + .orElseThrow(() -> new AdminException(NULL_ADMIN_AUTHORITY)); + } +} From e834830ea8546689000ab9d8586eac4d2fbbc848 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Fri, 19 Jan 2024 16:31:55 +0900 Subject: [PATCH 07/88] =?UTF-8?q?test:=20AdminLoginControllerTest=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AdminLoginControllerTest.java | 199 ++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 backend/src/test/java/hanglog/admin/presentation/AdminLoginControllerTest.java diff --git a/backend/src/test/java/hanglog/admin/presentation/AdminLoginControllerTest.java b/backend/src/test/java/hanglog/admin/presentation/AdminLoginControllerTest.java new file mode 100644 index 000000000..8af9ae748 --- /dev/null +++ b/backend/src/test/java/hanglog/admin/presentation/AdminLoginControllerTest.java @@ -0,0 +1,199 @@ +package hanglog.admin.presentation; + +import static hanglog.global.restdocs.RestDocsConfiguration.field; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.restdocs.cookies.CookieDocumentation.cookieWithName; +import static org.springframework.restdocs.cookies.CookieDocumentation.requestCookies; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.delete; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import hanglog.admin.domain.repository.AdminMemberRepository; +import hanglog.admin.domain.type.AdminType; +import hanglog.admin.dto.request.AdminMemberCreateRequest.AdminLoginRequest; +import hanglog.admin.service.AdminLoginService; +import hanglog.global.ControllerTest; +import hanglog.login.domain.MemberTokens; +import hanglog.login.dto.AccessTokenResponse; +import jakarta.servlet.http.Cookie; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.ResultActions; + + +@WebMvcTest(AdminLoginController.class) +@MockBean(JpaMetamodelMappingContext.class) +@AutoConfigureRestDocs +class AdminLoginControllerTest extends ControllerTest { + + private final static String REFRESH_TOKEN = "refreshToken"; + private final static String ACCESS_TOKEN = "accessToken"; + private final static String RENEW_ACCESS_TOKEN = "I'mNewAccessToken!"; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private AdminLoginService adminLoginService; + + @MockBean + protected AdminMemberRepository adminMemberRepository; + + @DisplayName("관리자 로그인을 할 수 있다.") + @Test + void login() throws Exception { + // given + final AdminLoginRequest adminLoginRequest = new AdminLoginRequest("username", "password"); + final MemberTokens memberTokens = new MemberTokens(REFRESH_TOKEN, ACCESS_TOKEN); + + when(adminLoginService.login(any(AdminLoginRequest.class))) + .thenReturn(memberTokens); + + final ResultActions resultActions = mockMvc.perform(post("/admin/login") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(adminLoginRequest)) + ); + + // when + final MvcResult mvcResult = resultActions.andExpect(status().isCreated()) + .andDo(restDocs.document( + requestFields( + fieldWithPath("userName") + .type(JsonFieldType.STRING) + .description("사용자 이름") + .attributes(field("constraint", "문자열")), + fieldWithPath("password") + .type(JsonFieldType.STRING) + .description("비밀번호") + .attributes(field("constraint", "문자열")) + ), + responseFields( + fieldWithPath("accessToken") + .type(JsonFieldType.STRING) + .description("access token") + .attributes(field("constraint", "문자열(jwt)")) + ) + )) + .andReturn(); + + final AccessTokenResponse expected = new AccessTokenResponse(memberTokens.getAccessToken()); + + final AccessTokenResponse actual = objectMapper.readValue( + mvcResult.getResponse().getContentAsString(), + AccessTokenResponse.class + ); + + // then + assertThat(actual).usingRecursiveComparison().isEqualTo(expected); + } + + @DisplayName("accessToken 재발급을 통해 관리자 로그인을 연장할 수 있다.") + @Test + void extendLogin() throws Exception { + // given + given(adminMemberRepository.existsAdminMemberByIdAndAdminType(anyLong(), any(AdminType.class))) + .willReturn(false); + + final MemberTokens memberTokens = new MemberTokens(REFRESH_TOKEN, RENEW_ACCESS_TOKEN); + final Cookie cookie = new Cookie("refresh-token", memberTokens.getRefreshToken()); + + when(adminLoginService.renewalAccessToken(REFRESH_TOKEN, ACCESS_TOKEN)) + .thenReturn(RENEW_ACCESS_TOKEN); + + // when + final ResultActions resultActions = mockMvc.perform(post("/admin/token") + .contentType(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, ACCESS_TOKEN) + .cookie(cookie) + ); + + final MvcResult mvcResult = resultActions.andExpect(status().isCreated()) + .andDo(restDocs.document( + requestCookies( + cookieWithName("refresh-token") + .description("갱신 토큰") + ), + requestHeaders( + headerWithName("Authorization") + .description("access token") + .attributes(field("constraint", "문자열(jwt)")) + ), + responseFields( + fieldWithPath("accessToken") + .type(JsonFieldType.STRING) + .description("access token") + .attributes(field("constraint", "문자열(jwt)")) + ) + )) + .andReturn(); + + final AccessTokenResponse expected = new AccessTokenResponse(memberTokens.getAccessToken()); + + final AccessTokenResponse actual = objectMapper.readValue( + mvcResult.getResponse().getContentAsString(), + AccessTokenResponse.class + ); + + // then + assertThat(actual).usingRecursiveComparison().isEqualTo(expected); + } + + @DisplayName("관리자 멤버의 refreshToken을 삭제하고 로그아웃 할 수 있다.") + @Test + void logout() throws Exception { + // given + given(refreshTokenRepository.existsById(any())).willReturn(true); + doNothing().when(jwtProvider).validateTokens(any()); + given(jwtProvider.getSubject(any())).willReturn("1"); + doNothing().when(adminLoginService).removeRefreshToken(anyString()); + given(adminMemberRepository.existsAdminMemberByIdAndAdminType(anyLong(), any(AdminType.class))) + .willReturn(false); + + final MemberTokens memberTokens = new MemberTokens(REFRESH_TOKEN, RENEW_ACCESS_TOKEN); + final Cookie cookie = new Cookie("refresh-token", memberTokens.getRefreshToken()); + + // when + final ResultActions resultActions = mockMvc.perform(delete("/admin/logout") + .header(HttpHeaders.AUTHORIZATION, ACCESS_TOKEN) + .cookie(cookie) + ); + + resultActions.andExpect(status().isNoContent()) + .andDo(restDocs.document( + requestCookies( + cookieWithName("refresh-token") + .description("갱신 토큰") + ), + requestHeaders( + headerWithName("Authorization") + .description("access token") + .attributes(field("constraint", "문자열(jwt)")) + ) + )); + + // then + verify(adminLoginService).removeRefreshToken(anyString()); + } +} From 13719d258bf60dd8fc91cf87412c21b56deb2914 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Fri, 19 Jan 2024 16:32:17 +0900 Subject: [PATCH 08/88] =?UTF-8?q?feat:=20AdminLogin=20API=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/dto/request/AdminLoginRequest.java | 23 +++++++ .../presentation/AdminLoginController.java | 67 +++++++++++++++++++ .../AdminLoginControllerTest.java | 6 +- 3 files changed, 91 insertions(+), 5 deletions(-) create mode 100644 backend/src/main/java/hanglog/admin/dto/request/AdminLoginRequest.java create mode 100644 backend/src/main/java/hanglog/admin/presentation/AdminLoginController.java diff --git a/backend/src/main/java/hanglog/admin/dto/request/AdminLoginRequest.java b/backend/src/main/java/hanglog/admin/dto/request/AdminLoginRequest.java new file mode 100644 index 000000000..8bcbfaf94 --- /dev/null +++ b/backend/src/main/java/hanglog/admin/dto/request/AdminLoginRequest.java @@ -0,0 +1,23 @@ +package hanglog.admin.dto.request; + +import static lombok.AccessLevel.PRIVATE; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@AllArgsConstructor +@NoArgsConstructor(access = PRIVATE) +public class AdminLoginRequest { + + @NotNull(message = "사용자 이름을 입력해주세요.") + @Size(max = 20, message = "사용자 이름은 20자를 초과할 수 없습니다.") + private String userName; + + @NotNull(message = "비밀번호를 입력해주세요.") + @Size(min = 4, max = 20, message = "비밀번호는 4자 이상, 20자 이하여야 합니다.") + private String password; +} diff --git a/backend/src/main/java/hanglog/admin/presentation/AdminLoginController.java b/backend/src/main/java/hanglog/admin/presentation/AdminLoginController.java new file mode 100644 index 000000000..b82d37eed --- /dev/null +++ b/backend/src/main/java/hanglog/admin/presentation/AdminLoginController.java @@ -0,0 +1,67 @@ +package hanglog.admin.presentation; + +import static org.springframework.http.HttpHeaders.SET_COOKIE; +import static org.springframework.http.HttpStatus.CREATED; + +import hanglog.admin.dto.request.AdminLoginRequest; +import hanglog.admin.service.AdminLoginService; +import hanglog.auth.AdminAuth; +import hanglog.auth.domain.Accessor; +import hanglog.login.domain.MemberTokens; +import hanglog.login.dto.AccessTokenResponse; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseCookie; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.CookieValue; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/admin") +public class AdminLoginController { + + private static final int COOKIE_AGE_SECONDS = 604800; + + private final AdminLoginService adminLoginService; + + @PostMapping("/login") + public ResponseEntity login( + @RequestBody @Valid final AdminLoginRequest adminLoginRequest, + final HttpServletResponse response + ) { + final MemberTokens memberTokens = adminLoginService.login(adminLoginRequest); + final ResponseCookie cookie = ResponseCookie.from("refresh-token", memberTokens.getRefreshToken()) + .maxAge(COOKIE_AGE_SECONDS) + .sameSite("None") + .secure(true) + .httpOnly(true) + .path("/") + .build(); + response.addHeader(SET_COOKIE, cookie.toString()); + return ResponseEntity.status(CREATED).body(new AccessTokenResponse(memberTokens.getAccessToken())); + } + + @PostMapping("/token") + public ResponseEntity extendLogin( + @CookieValue("refresh-token") final String refreshToken, + @RequestHeader("Authorization") final String authorizationHeader + ) { + final String renewalRefreshToken = adminLoginService.renewalAccessToken(refreshToken, authorizationHeader); + return ResponseEntity.status(CREATED).body(new AccessTokenResponse(renewalRefreshToken)); + } + + @DeleteMapping("/logout") + public ResponseEntity logout( + @AdminAuth final Accessor accessor, + @CookieValue("refresh-token") final String refreshToken) { + adminLoginService.removeRefreshToken(refreshToken); + return ResponseEntity.noContent().build(); + } +} diff --git a/backend/src/test/java/hanglog/admin/presentation/AdminLoginControllerTest.java b/backend/src/test/java/hanglog/admin/presentation/AdminLoginControllerTest.java index 8af9ae748..3f54f8e1f 100644 --- a/backend/src/test/java/hanglog/admin/presentation/AdminLoginControllerTest.java +++ b/backend/src/test/java/hanglog/admin/presentation/AdminLoginControllerTest.java @@ -21,9 +21,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.fasterxml.jackson.databind.ObjectMapper; -import hanglog.admin.domain.repository.AdminMemberRepository; import hanglog.admin.domain.type.AdminType; -import hanglog.admin.dto.request.AdminMemberCreateRequest.AdminLoginRequest; +import hanglog.admin.dto.request.AdminLoginRequest; import hanglog.admin.service.AdminLoginService; import hanglog.global.ControllerTest; import hanglog.login.domain.MemberTokens; @@ -58,9 +57,6 @@ class AdminLoginControllerTest extends ControllerTest { @MockBean private AdminLoginService adminLoginService; - @MockBean - protected AdminMemberRepository adminMemberRepository; - @DisplayName("관리자 로그인을 할 수 있다.") @Test void login() throws Exception { From b42233c882fcfe298beac35f821ed7c4bd383870 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Fri, 19 Jan 2024 17:04:22 +0900 Subject: [PATCH 09/88] =?UTF-8?q?test:=20AdminServiceTest=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/service/AdminLoginServiceTest.java | 80 +++++++++++++++++++ .../java/hanglog/global/ControllerTest.java | 4 + 2 files changed, 84 insertions(+) create mode 100644 backend/src/test/java/hanglog/admin/service/AdminLoginServiceTest.java diff --git a/backend/src/test/java/hanglog/admin/service/AdminLoginServiceTest.java b/backend/src/test/java/hanglog/admin/service/AdminLoginServiceTest.java new file mode 100644 index 000000000..a9935cb8f --- /dev/null +++ b/backend/src/test/java/hanglog/admin/service/AdminLoginServiceTest.java @@ -0,0 +1,80 @@ +package hanglog.admin.service; + +import static org.assertj.core.api.SoftAssertions.assertSoftly; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +import hanglog.admin.domain.AdminMember; +import hanglog.admin.domain.repository.AdminMemberRepository; +import hanglog.admin.domain.type.AdminType; +import hanglog.admin.dto.request.AdminLoginRequest; +import hanglog.global.exception.AdminException; +import hanglog.login.domain.MemberTokens; +import hanglog.login.domain.repository.RefreshTokenRepository; +import hanglog.login.infrastructure.BearerAuthorizationExtractor; +import hanglog.login.infrastructure.JwtProvider; +import java.util.Optional; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.crypto.password.PasswordEncoder; + +@ExtendWith(MockitoExtension.class) +class AdminLoginServiceTest { + @InjectMocks + private AdminLoginService adminLoginService; + + @Mock + private AdminMemberRepository adminMemberRepository; + + @Mock + private RefreshTokenRepository refreshTokenRepository; + + @Mock + private JwtProvider jwtProvider; + + @Mock + private BearerAuthorizationExtractor bearerExtractor; + + @Mock + private PasswordEncoder passwordEncoder; + + + @Test + public void testLoginSuccess() { + // given + final AdminLoginRequest loginRequest = new AdminLoginRequest("user", "password"); + final AdminMember adminMember = new AdminMember(1L, "user", "password", AdminType.ADMIN); + final MemberTokens memberTokens = new MemberTokens("accessToken", "refreshToken"); + + when(adminMemberRepository.findAdminMemberByUserName(anyString())).thenReturn(Optional.of(adminMember)); + when(passwordEncoder.matches(anyString(), anyString())).thenReturn(true); + when(jwtProvider.generateLoginToken(anyString())).thenReturn(memberTokens); + + // when + final MemberTokens result = adminLoginService.login(loginRequest); + + // then + assertSoftly(softly -> { + softly.assertThat(result).isNotNull(); + softly.assertThat(memberTokens.getAccessToken()).isEqualTo(result.getAccessToken()); + softly.assertThat(memberTokens.getRefreshToken()).isEqualTo(result.getRefreshToken()); + }); + } + + @Test + public void testLoginFailure_InvalidPassword() { + // given + final AdminLoginRequest loginRequest = new AdminLoginRequest("user", "wrongpassword"); + final AdminMember adminMember = new AdminMember("user", "password", AdminType.ADMIN); + + when(adminMemberRepository.findAdminMemberByUserName(anyString())).thenReturn(Optional.of(adminMember)); + when(passwordEncoder.matches(anyString(), anyString())).thenReturn(false); + + // when & then + assertThrows(AdminException.class, () -> adminLoginService.login(loginRequest)); + } +} diff --git a/backend/src/test/java/hanglog/global/ControllerTest.java b/backend/src/test/java/hanglog/global/ControllerTest.java index a38aaddc6..0d262915d 100644 --- a/backend/src/test/java/hanglog/global/ControllerTest.java +++ b/backend/src/test/java/hanglog/global/ControllerTest.java @@ -3,6 +3,7 @@ import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; import hanglog.admin.AdminLoginArgumentResolver; +import hanglog.admin.domain.repository.AdminMemberRepository; import hanglog.global.restdocs.RestDocsConfiguration; import hanglog.login.LoginArgumentResolver; import hanglog.login.domain.repository.RefreshTokenRepository; @@ -43,6 +44,9 @@ public abstract class ControllerTest { @MockBean protected RefreshTokenRepository refreshTokenRepository; + @MockBean + protected AdminMemberRepository adminMemberRepository; + @MockBean BearerAuthorizationExtractor bearerExtractor; From 6c1bf638efe3fb46777d0fecd9e74aeac24b268e Mon Sep 17 00:00:00 2001 From: LJW25 Date: Fri, 19 Jan 2024 17:04:56 +0900 Subject: [PATCH 10/88] =?UTF-8?q?feat:=20AdminService=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/hanglog/admin/SecurityConfig.java | 14 ++++ .../repository/AdminMemberRepository.java | 15 ++++ .../admin/service/AdminLoginService.java | 68 +++++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 backend/src/main/java/hanglog/admin/SecurityConfig.java create mode 100644 backend/src/main/java/hanglog/admin/domain/repository/AdminMemberRepository.java create mode 100644 backend/src/main/java/hanglog/admin/service/AdminLoginService.java diff --git a/backend/src/main/java/hanglog/admin/SecurityConfig.java b/backend/src/main/java/hanglog/admin/SecurityConfig.java new file mode 100644 index 000000000..4beca911f --- /dev/null +++ b/backend/src/main/java/hanglog/admin/SecurityConfig.java @@ -0,0 +1,14 @@ +package hanglog.admin; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +@Configuration +public class SecurityConfig { + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/backend/src/main/java/hanglog/admin/domain/repository/AdminMemberRepository.java b/backend/src/main/java/hanglog/admin/domain/repository/AdminMemberRepository.java new file mode 100644 index 000000000..e09d200ba --- /dev/null +++ b/backend/src/main/java/hanglog/admin/domain/repository/AdminMemberRepository.java @@ -0,0 +1,15 @@ +package hanglog.admin.domain.repository; + +import hanglog.admin.domain.AdminMember; +import hanglog.admin.domain.type.AdminType; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AdminMemberRepository extends JpaRepository { + + Optional findAdminMemberByUserName(String userName); + + Boolean existsAdminMemberByIdAndAdminType(Long id, AdminType adminType); + + Boolean existsAdminMemberByUserName(String userName); +} diff --git a/backend/src/main/java/hanglog/admin/service/AdminLoginService.java b/backend/src/main/java/hanglog/admin/service/AdminLoginService.java new file mode 100644 index 000000000..b78a370a3 --- /dev/null +++ b/backend/src/main/java/hanglog/admin/service/AdminLoginService.java @@ -0,0 +1,68 @@ +package hanglog.admin.service; + +import static hanglog.global.exception.ExceptionCode.FAIL_TO_VALIDATE_TOKEN; +import static hanglog.global.exception.ExceptionCode.INVALID_PASSWORD; +import static hanglog.global.exception.ExceptionCode.INVALID_REFRESH_TOKEN; +import static hanglog.global.exception.ExceptionCode.INVALID_USER_NAME; + +import hanglog.admin.domain.AdminMember; +import hanglog.admin.domain.repository.AdminMemberRepository; +import hanglog.admin.dto.request.AdminLoginRequest; +import hanglog.global.exception.AdminException; +import hanglog.global.exception.AuthException; +import hanglog.login.domain.MemberTokens; +import hanglog.login.domain.RefreshToken; +import hanglog.login.domain.repository.RefreshTokenRepository; +import hanglog.login.infrastructure.BearerAuthorizationExtractor; +import hanglog.login.infrastructure.JwtProvider; +import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional +@RequiredArgsConstructor +public class AdminLoginService { + + private final AdminMemberRepository adminMemberRepository; + private final RefreshTokenRepository refreshTokenRepository; + private final JwtProvider jwtProvider; + private final BearerAuthorizationExtractor bearerExtractor; + private final PasswordEncoder passwordEncoder; + + + public MemberTokens login(final AdminLoginRequest adminLoginRequest) { + final AdminMember adminMember = adminMemberRepository.findAdminMemberByUserName( + adminLoginRequest.getUserName()) + .orElseThrow(() -> new AdminException(INVALID_USER_NAME)); + + if (passwordEncoder.matches(adminLoginRequest.getPassword(), adminMember.getPassword())) { + final MemberTokens memberTokens = jwtProvider.generateLoginToken(adminMember.getId().toString()); + final RefreshToken savedRefreshToken = new RefreshToken(memberTokens.getRefreshToken(), + adminMember.getId()); + refreshTokenRepository.save(savedRefreshToken); + return memberTokens; + } + + throw new AdminException(INVALID_PASSWORD); + } + + + public String renewalAccessToken(final String refreshTokenRequest, final String authorizationHeader) { + final String accessToken = bearerExtractor.extractAccessToken(authorizationHeader); + if (jwtProvider.isValidRefreshAndInvalidAccess(refreshTokenRequest, accessToken)) { + final RefreshToken refreshToken = refreshTokenRepository.findById(refreshTokenRequest) + .orElseThrow(() -> new AuthException(INVALID_REFRESH_TOKEN)); + return jwtProvider.regenerateAccessToken(refreshToken.getMemberId().toString()); + } + if (jwtProvider.isValidRefreshAndValidAccess(refreshTokenRequest, accessToken)) { + return accessToken; + } + throw new AuthException(FAIL_TO_VALIDATE_TOKEN); + } + + public void removeRefreshToken(final String refreshToken) { + refreshTokenRepository.deleteById(refreshToken); + } +} From fa168e0b51141562bef8ff1c2c10b3d18e7d970d Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 13:19:00 +0900 Subject: [PATCH 11/88] =?UTF-8?q?test:=20AdminMemberControllerTest=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AdminMemberControllerTest.java | 168 ++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 backend/src/test/java/hanglog/admin/presentation/AdminMemberControllerTest.java diff --git a/backend/src/test/java/hanglog/admin/presentation/AdminMemberControllerTest.java b/backend/src/test/java/hanglog/admin/presentation/AdminMemberControllerTest.java new file mode 100644 index 000000000..bd0afb2a5 --- /dev/null +++ b/backend/src/test/java/hanglog/admin/presentation/AdminMemberControllerTest.java @@ -0,0 +1,168 @@ +package hanglog.admin.presentation; + +import static hanglog.global.restdocs.RestDocsConfiguration.field; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doNothing; +import static org.springframework.http.HttpHeaders.AUTHORIZATION; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.patch; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import hanglog.admin.dto.request.AdminMemberCreateRequest; +import hanglog.admin.dto.request.PasswordUpdateRequest; +import hanglog.admin.dto.response.AdminMemberResponse; +import hanglog.admin.service.AdminMemberService; +import hanglog.global.ControllerTest; +import hanglog.login.domain.MemberTokens; +import jakarta.servlet.http.Cookie; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext; +import org.springframework.http.MediaType; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.MockMvc; + +@WebMvcTest(AdminMemberController.class) +@MockBean(JpaMetamodelMappingContext.class) +@AutoConfigureRestDocs +class AdminMemberControllerTest extends ControllerTest { + + private static final MemberTokens MEMBER_TOKENS = new MemberTokens("refreshToken", "accessToken"); + private static final Cookie COOKIE = new Cookie("refresh-token", MEMBER_TOKENS.getRefreshToken()); + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private AdminMemberService adminMemberService; + + @BeforeEach + void setUp() { + given(refreshTokenRepository.existsById(any())).willReturn(true); + doNothing().when(jwtProvider).validateTokens(any()); + given(jwtProvider.getSubject(any())).willReturn("1"); + given(adminMemberRepository.existsAdminMemberByIdAndAdminType(any(), any())).willReturn(true); + } + + @DisplayName("관리자 멤버 목록을 조회한다.") + @Test + void getAdminMembers() throws Exception { + // given + final AdminMemberResponse response = new AdminMemberResponse(1L, "adminUser", "MASTER"); + + given(adminMemberService.getAdminMembers()).willReturn(List.of(response)); + + // when & then + mockMvc.perform(get("/admin/members") + .header(AUTHORIZATION, MEMBER_TOKENS.getAccessToken()) + .cookie(COOKIE)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id").value(response.getId())) + .andExpect(jsonPath("$[0].userName").value(response.getUserName())) + .andExpect(jsonPath("$[0].adminType").value(response.getAdminType())) + .andDo(restDocs.document( + responseFields( + fieldWithPath("[].id") + .type(JsonFieldType.NUMBER) + .description("멤버 ID") + .attributes(field("constraint", "양의 정수")), + fieldWithPath("[].userName") + .type(JsonFieldType.STRING) + .description("사용자 이름") + .attributes(field("constraint", "20자 이내의 문자열")), + fieldWithPath("[].adminType") + .type(JsonFieldType.STRING) + .description("관리자 유형") + .attributes(field("constraint", "ADMIN / MASTER")) + ) + )); + } + + @DisplayName("새로운 관리자 멤버를 생성한다.") + @Test + void createAdminMember() throws Exception { + // given + final AdminMemberCreateRequest request = new AdminMemberCreateRequest( + "newAdmin", + "password", + "ADMIN" + ); + given(adminMemberService.createAdminMember(any(AdminMemberCreateRequest.class))).willReturn(2L); + + // when & then + mockMvc.perform(post("/admin/members") + .header(AUTHORIZATION, MEMBER_TOKENS.getAccessToken()) + .cookie(COOKIE) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isCreated()) + .andExpect(header().string("Location", "/admin/members/2")) + .andDo(restDocs.document( + requestFields( + fieldWithPath("userName") + .type(JsonFieldType.STRING) + .description("사용자 이름") + .attributes(field("constraint", "20자 이내의 문자열")), + fieldWithPath("password") + .type(JsonFieldType.STRING) + .description("비밀번호") + .attributes(field("constraint", "4자이상 20자 이하의 문자열")), + fieldWithPath("adminType") + .type(JsonFieldType.STRING) + .description("관리자 유형") + .attributes(field("constraint", "4자이상 20자 이하의 문자열")) + ) + )); + } + + @DisplayName("관리자 멤버의 비밀번호를 업데이트한다.") + @Test + void updatePassword() throws Exception { + // given + final PasswordUpdateRequest request = new PasswordUpdateRequest("oldPassword", "newPassword"); + doNothing().when(adminMemberService).updatePassword(1L, request); + + // when & then + mockMvc.perform(patch("/admin/members/{memberId}/password", 1) + .header(AUTHORIZATION, MEMBER_TOKENS.getAccessToken()) + .cookie(COOKIE) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isNoContent()) + .andDo(restDocs.document( + pathParameters( + parameterWithName("memberId") + .description("관리자 멤버 ID") + ), + requestFields( + fieldWithPath("currentPassword") + .type(JsonFieldType.STRING) + .description("현재 비밀번호") + .attributes(field("constraint", "4자이상 20자 이하의 문자열")), + fieldWithPath("newPassword") + .type(JsonFieldType.STRING) + .description("새 비밀번호") + .attributes(field("constraint", "4자이상 20자 이하의 문자열")) + ) + )); + } +} From 1f04c657c75a0dcc860fca1461715eac73d7a235 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 13:19:41 +0900 Subject: [PATCH 12/88] =?UTF-8?q?feat:=20AdminOnly=20=EC=96=B4=EB=85=B8?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EC=85=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/hanglog/auth/AdminOnly.java | 12 +++++++++ .../java/hanglog/auth/AdminOnlyChecker.java | 26 +++++++++++++++++++ .../main/java/hanglog/auth/MasterOnly.java | 12 +++++++++ .../java/hanglog/auth/MasterOnlyChecker.java | 26 +++++++++++++++++++ 4 files changed, 76 insertions(+) create mode 100644 backend/src/main/java/hanglog/auth/AdminOnly.java create mode 100644 backend/src/main/java/hanglog/auth/AdminOnlyChecker.java create mode 100644 backend/src/main/java/hanglog/auth/MasterOnly.java create mode 100644 backend/src/main/java/hanglog/auth/MasterOnlyChecker.java diff --git a/backend/src/main/java/hanglog/auth/AdminOnly.java b/backend/src/main/java/hanglog/auth/AdminOnly.java new file mode 100644 index 000000000..046f502de --- /dev/null +++ b/backend/src/main/java/hanglog/auth/AdminOnly.java @@ -0,0 +1,12 @@ +package hanglog.auth; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Target(METHOD) +@Retention(RUNTIME) +public @interface AdminOnly { +} diff --git a/backend/src/main/java/hanglog/auth/AdminOnlyChecker.java b/backend/src/main/java/hanglog/auth/AdminOnlyChecker.java new file mode 100644 index 000000000..97db7f8d0 --- /dev/null +++ b/backend/src/main/java/hanglog/auth/AdminOnlyChecker.java @@ -0,0 +1,26 @@ +package hanglog.auth; + +import static hanglog.global.exception.ExceptionCode.INVALID_ADMIN_AUTHORITY; + +import hanglog.auth.domain.Accessor; +import hanglog.global.exception.AdminException; +import java.util.Arrays; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.stereotype.Component; + +@Aspect +@Component +public class AdminOnlyChecker { + + @Before("@annotation(hanglog.auth.AdminOnly)") + public void check(final JoinPoint joinPoint) { + Arrays.stream(joinPoint.getArgs()) + .filter(Accessor.class::isInstance) + .map(Accessor.class::cast) + .filter(Accessor::isAdmin) + .findFirst() + .orElseThrow(() -> new AdminException(INVALID_ADMIN_AUTHORITY)); + } +} diff --git a/backend/src/main/java/hanglog/auth/MasterOnly.java b/backend/src/main/java/hanglog/auth/MasterOnly.java new file mode 100644 index 000000000..3addade5f --- /dev/null +++ b/backend/src/main/java/hanglog/auth/MasterOnly.java @@ -0,0 +1,12 @@ +package hanglog.auth; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Target(METHOD) +@Retention(RUNTIME) +public @interface MasterOnly { +} diff --git a/backend/src/main/java/hanglog/auth/MasterOnlyChecker.java b/backend/src/main/java/hanglog/auth/MasterOnlyChecker.java new file mode 100644 index 000000000..ca7477cc1 --- /dev/null +++ b/backend/src/main/java/hanglog/auth/MasterOnlyChecker.java @@ -0,0 +1,26 @@ +package hanglog.auth; + +import static hanglog.global.exception.ExceptionCode.INVALID_ADMIN_AUTHORITY; + +import hanglog.auth.domain.Accessor; +import hanglog.global.exception.AdminException; +import java.util.Arrays; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.stereotype.Component; + +@Aspect +@Component +public class MasterOnlyChecker { + + @Before("@annotation(hanglog.auth.MasterOnly)") + public void check(final JoinPoint joinPoint) { + Arrays.stream(joinPoint.getArgs()) + .filter(Accessor.class::isInstance) + .map(Accessor.class::cast) + .filter(Accessor::isMaster) + .findFirst() + .orElseThrow(() -> new AdminException(INVALID_ADMIN_AUTHORITY)); + } +} From 319e1f12259c857cd3e5a93665c94c3af4c263f7 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 13:20:22 +0900 Subject: [PATCH 13/88] =?UTF-8?q?feat:=20AdminMember=20API=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/request/AdminMemberCreateRequest.java | 27 +++++++++ .../dto/request/PasswordUpdateRequest.java | 24 ++++++++ .../dto/response/AdminMemberResponse.java | 22 +++++++ .../presentation/AdminMemberController.java | 59 +++++++++++++++++++ 4 files changed, 132 insertions(+) create mode 100644 backend/src/main/java/hanglog/admin/dto/request/AdminMemberCreateRequest.java create mode 100644 backend/src/main/java/hanglog/admin/dto/request/PasswordUpdateRequest.java create mode 100644 backend/src/main/java/hanglog/admin/dto/response/AdminMemberResponse.java create mode 100644 backend/src/main/java/hanglog/admin/presentation/AdminMemberController.java diff --git a/backend/src/main/java/hanglog/admin/dto/request/AdminMemberCreateRequest.java b/backend/src/main/java/hanglog/admin/dto/request/AdminMemberCreateRequest.java new file mode 100644 index 000000000..42830f063 --- /dev/null +++ b/backend/src/main/java/hanglog/admin/dto/request/AdminMemberCreateRequest.java @@ -0,0 +1,27 @@ +package hanglog.admin.dto.request; + + +import static lombok.AccessLevel.PRIVATE; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@AllArgsConstructor +@NoArgsConstructor(access = PRIVATE) +public class AdminMemberCreateRequest { + + @NotNull(message = "사용자 이름을 입력해주세요.") + @Size(max = 20, message = "사용자 이름은 20자를 초과할 수 없습니다.") + private String userName; + + @NotNull(message = "비밀번호를 입력해주세요.") + @Size(min = 4, max = 20, message = "비밀번호는 4자 이상, 20자 이하여야 합니다.") + private String password; + + @NotNull(message = "관리자 권한을 선택해 주세요.") + private String adminType; +} diff --git a/backend/src/main/java/hanglog/admin/dto/request/PasswordUpdateRequest.java b/backend/src/main/java/hanglog/admin/dto/request/PasswordUpdateRequest.java new file mode 100644 index 000000000..ef6289984 --- /dev/null +++ b/backend/src/main/java/hanglog/admin/dto/request/PasswordUpdateRequest.java @@ -0,0 +1,24 @@ +package hanglog.admin.dto.request; + + +import static lombok.AccessLevel.PRIVATE; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@AllArgsConstructor +@NoArgsConstructor(access = PRIVATE) +public class PasswordUpdateRequest { + + @NotNull(message = "기존 비밀번호를 입력해주세요.") + @Size(min = 4, max = 20, message = "비밀번호는 4자 이상, 20자 이하여야 합니다.") + private String currentPassword; + + @NotNull(message = "새로운 비밀번호를 입력해주세요.") + @Size(min = 4, max = 20, message = "비밀번호는 4자 이상, 20자 이하여야 합니다.") + private String newPassword; +} diff --git a/backend/src/main/java/hanglog/admin/dto/response/AdminMemberResponse.java b/backend/src/main/java/hanglog/admin/dto/response/AdminMemberResponse.java new file mode 100644 index 000000000..b00c58ab6 --- /dev/null +++ b/backend/src/main/java/hanglog/admin/dto/response/AdminMemberResponse.java @@ -0,0 +1,22 @@ +package hanglog.admin.dto.response; + +import hanglog.admin.domain.AdminMember; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class AdminMemberResponse { + + private final Long id; + private final String userName; + private final String adminType; + + public static AdminMemberResponse from(final AdminMember adminMember) { + return new AdminMemberResponse( + adminMember.getId(), + adminMember.getUserName(), + adminMember.getAdminType().name() + ); + } +} diff --git a/backend/src/main/java/hanglog/admin/presentation/AdminMemberController.java b/backend/src/main/java/hanglog/admin/presentation/AdminMemberController.java new file mode 100644 index 000000000..407d2b987 --- /dev/null +++ b/backend/src/main/java/hanglog/admin/presentation/AdminMemberController.java @@ -0,0 +1,59 @@ +package hanglog.admin.presentation; + +import hanglog.admin.dto.request.AdminMemberCreateRequest; +import hanglog.admin.dto.request.PasswordUpdateRequest; +import hanglog.admin.dto.response.AdminMemberResponse; +import hanglog.admin.service.AdminMemberService; +import hanglog.auth.AdminAuth; +import hanglog.auth.MasterOnly; +import hanglog.auth.domain.Accessor; +import jakarta.validation.Valid; +import java.net.URI; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +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; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/admin/members") +public class AdminMemberController { + + private final AdminMemberService adminMemberService; + + @GetMapping + @MasterOnly + public ResponseEntity> getAdminMembers( + @AdminAuth final Accessor accessor + ) { + final List adminMembers = adminMemberService.getAdminMembers(); + return ResponseEntity.ok(adminMembers); + } + + @PostMapping + @MasterOnly + public ResponseEntity createAdminMember( + @AdminAuth final Accessor accessor, + @RequestBody @Valid final AdminMemberCreateRequest adminMemberCreateRequest + ) { + final Long memberId = adminMemberService.createAdminMember(adminMemberCreateRequest); + return ResponseEntity.created(URI.create("/admin/members/" + memberId)).build(); + } + + @PatchMapping("/{memberId}/password") + @MasterOnly + public ResponseEntity updatePassword( + @AdminAuth final Accessor accessor, + @PathVariable final Long memberId, + @RequestBody @Valid final PasswordUpdateRequest passwordUpdateRequest + ) { + adminMemberService.updatePassword(memberId, passwordUpdateRequest); + return ResponseEntity.noContent().build(); + } +} From 637f94229102b83a7c5c73dd2078a2d3f9906336 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 14:09:52 +0900 Subject: [PATCH 14/88] =?UTF-8?q?test:=20AdminMemberServiceTest=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/service/AdminMemberServiceTest.java | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 backend/src/test/java/hanglog/admin/service/AdminMemberServiceTest.java diff --git a/backend/src/test/java/hanglog/admin/service/AdminMemberServiceTest.java b/backend/src/test/java/hanglog/admin/service/AdminMemberServiceTest.java new file mode 100644 index 000000000..ee033a258 --- /dev/null +++ b/backend/src/test/java/hanglog/admin/service/AdminMemberServiceTest.java @@ -0,0 +1,92 @@ +package hanglog.admin.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; + +import hanglog.admin.domain.AdminMember; +import hanglog.admin.domain.repository.AdminMemberRepository; +import hanglog.admin.domain.type.AdminType; +import hanglog.admin.dto.request.AdminMemberCreateRequest; +import hanglog.admin.dto.request.PasswordUpdateRequest; +import hanglog.global.exception.AdminException; +import java.util.Optional; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.crypto.password.PasswordEncoder; + +@ExtendWith(MockitoExtension.class) +class AdminMemberServiceTest { + + @InjectMocks + private AdminMemberService adminMemberService; + + @Mock + private AdminMemberRepository adminMemberRepository; + + @Mock + private PasswordEncoder passwordEncoder; + + @DisplayName("관리자 멤버를 생성한 후 관리자 id를 반환한다.") + @Test + void createAdminMember() { + // given + final AdminMemberCreateRequest request = new AdminMemberCreateRequest( + "username", + "password", + "ADMIN" + ); + final AdminMember adminMember = new AdminMember(1L, "username", "password", AdminType.ADMIN); + + given(adminMemberRepository.existsAdminMemberByUserName("username")).willReturn(false); + given(adminMemberRepository.save(any(AdminMember.class))).willReturn(adminMember); + given(passwordEncoder.encode("password")).willReturn(anyString()); + + // when + final Long actualId = adminMemberService.createAdminMember(request); + + // then + assertThat(actualId).isEqualTo(1L); + } + + @DisplayName("관리자 계정의 비밀번호를 변경한다.") + @Test + void updatePassword() { + // given + final PasswordUpdateRequest request = new PasswordUpdateRequest("password", "newPassword"); + final AdminMember adminMember = new AdminMember(1L, "username", "password", AdminType.ADMIN); + + given(adminMemberRepository.findById(anyLong())).willReturn(Optional.of(adminMember)); + given(passwordEncoder.matches(anyString(), anyString())).willReturn(true); + given(passwordEncoder.encode("newPassword")).willReturn(anyString()); + + // when + adminMemberService.updatePassword(adminMember.getId(), request); + + // then + verify(adminMemberRepository).save(any(AdminMember.class)); + } + + @DisplayName("기존 비밀번호가 일치하지 않으면 비밀번호를 변경할 수 없다.") + @Test + void updatePassword_invalidPassword() { + // given + final PasswordUpdateRequest request = new PasswordUpdateRequest("password", "newPassword"); + final AdminMember adminMember = new AdminMember(1L, "username", "password", AdminType.ADMIN); + + given(adminMemberRepository.findById(anyLong())).willReturn(Optional.of(adminMember)); + given(passwordEncoder.matches(anyString(), anyString())).willReturn(false); + + // when & then + assertThatThrownBy(() -> adminMemberService.updatePassword(adminMember.getId(), request)) + .isInstanceOf(AdminException.class); + } +} From 7b1fd0f31d9402dfff436e665709a9872b143457 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 14:10:03 +0900 Subject: [PATCH 15/88] =?UTF-8?q?feat:=20AdminMemberService=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/service/AdminMemberService.java | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 backend/src/main/java/hanglog/admin/service/AdminMemberService.java diff --git a/backend/src/main/java/hanglog/admin/service/AdminMemberService.java b/backend/src/main/java/hanglog/admin/service/AdminMemberService.java new file mode 100644 index 000000000..52c037497 --- /dev/null +++ b/backend/src/main/java/hanglog/admin/service/AdminMemberService.java @@ -0,0 +1,63 @@ +package hanglog.admin.service; + + +import static hanglog.global.exception.ExceptionCode.DUPLICATED_ADMIN_USERNAME; +import static hanglog.global.exception.ExceptionCode.INVALID_CURRENT_PASSWORD; +import static hanglog.global.exception.ExceptionCode.NOT_FOUND_ADMIN_ID; + +import hanglog.admin.domain.AdminMember; +import hanglog.admin.domain.repository.AdminMemberRepository; +import hanglog.admin.domain.type.AdminType; +import hanglog.admin.dto.request.AdminMemberCreateRequest; +import hanglog.admin.dto.request.PasswordUpdateRequest; +import hanglog.admin.dto.response.AdminMemberResponse; +import hanglog.global.exception.AdminException; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional +@RequiredArgsConstructor +public class AdminMemberService { + + private final AdminMemberRepository adminMemberRepository; + private final PasswordEncoder passwordEncoder; + + public List getAdminMembers() { + final List adminMembers = adminMemberRepository.findAll(); + return adminMembers.stream() + .map(AdminMemberResponse::from) + .toList(); + } + + public Long createAdminMember(final AdminMemberCreateRequest request) { + if (adminMemberRepository.existsAdminMemberByUserName(request.getUserName())) { + throw new AdminException(DUPLICATED_ADMIN_USERNAME); + } + + return adminMemberRepository.save(new AdminMember(request.getUserName(), + passwordEncoder.encode(request.getPassword()), + AdminType.getMappedAdminType(request.getAdminType()))) + .getId(); + } + + public void updatePassword(final Long adminMemberId, final PasswordUpdateRequest request) { + final AdminMember adminMember = adminMemberRepository.findById(adminMemberId) + .orElseThrow(() -> new AdminException(NOT_FOUND_ADMIN_ID)); + + if (!passwordEncoder.matches(request.getCurrentPassword(), adminMember.getPassword())) { + throw new AdminException(INVALID_CURRENT_PASSWORD); + } + + final AdminMember updatedAdminMember = new AdminMember( + adminMember.getId(), + adminMember.getUserName(), + passwordEncoder.encode(request.getNewPassword()), + adminMember.getAdminType() + ); + adminMemberRepository.save(updatedAdminMember); + } +} From c6a74afe871d44d40685e23bcc55bfa96f0e4882 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 14:19:15 +0900 Subject: [PATCH 16/88] =?UTF-8?q?chore:=20submodule=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/backend-submodule | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/backend-submodule b/backend/backend-submodule index 66a9148b1..a0ae218a4 160000 --- a/backend/backend-submodule +++ b/backend/backend-submodule @@ -1 +1 @@ -Subproject commit 66a9148b1255e27d726281ff5be0dcbda3b87b7f +Subproject commit a0ae218a4ed50f41310e3eaf09a3ce8ca3d01e50 From 3da90f9e9ce6e15656700b47a90fd193130e1b2a Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 15:21:04 +0900 Subject: [PATCH 17/88] =?UTF-8?q?feat:=20city=20=EC=83=81=EC=84=B8=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../city/dto/response/CityDetailResponse.java | 29 +++++++++++++++++++ .../hanglog/city/service/CityService.java | 9 ++++++ 2 files changed, 38 insertions(+) create mode 100644 backend/src/main/java/hanglog/city/dto/response/CityDetailResponse.java diff --git a/backend/src/main/java/hanglog/city/dto/response/CityDetailResponse.java b/backend/src/main/java/hanglog/city/dto/response/CityDetailResponse.java new file mode 100644 index 000000000..238a4fb8a --- /dev/null +++ b/backend/src/main/java/hanglog/city/dto/response/CityDetailResponse.java @@ -0,0 +1,29 @@ +package hanglog.city.dto.response; + +import static lombok.AccessLevel.PRIVATE; + +import hanglog.city.domain.City; +import java.math.BigDecimal; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor(access = PRIVATE) +public class CityDetailResponse { + + private final Long id; + private final String name; + private final String country; + private final BigDecimal latitude; + private final BigDecimal longitude; + + public static CityDetailResponse of(final City city) { + return new CityDetailResponse( + city.getId(), + city.getName(), + city.getCountry(), + city.getLatitude(), + city.getLongitude() + ); + } +} diff --git a/backend/src/main/java/hanglog/city/service/CityService.java b/backend/src/main/java/hanglog/city/service/CityService.java index 5649c40b8..bb987abe6 100644 --- a/backend/src/main/java/hanglog/city/service/CityService.java +++ b/backend/src/main/java/hanglog/city/service/CityService.java @@ -2,6 +2,7 @@ import hanglog.city.domain.City; import hanglog.city.domain.repository.CityRepository; +import hanglog.city.dto.response.CityDetailResponse; import hanglog.city.dto.response.CityResponse; import java.util.List; import lombok.RequiredArgsConstructor; @@ -22,4 +23,12 @@ public List getAllCities() { .map(CityResponse::withCountry) .toList(); } + + @Transactional(readOnly = true) + public List getAllCitiesDetail() { + final List cities = cityRepository.findAll(); + return cities.stream() + .map(CityDetailResponse::of) + .toList(); + } } From 4808192bdecac3d29db56a631c9de40eeb8f5817 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 15:29:27 +0900 Subject: [PATCH 18/88] =?UTF-8?q?test:=20city=20=EC=83=81=EC=84=B8=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/CityControllerTest.java | 3 +-- .../service/CityServiceTest.java | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) rename backend/src/test/java/hanglog/{trip => city}/presentation/CityControllerTest.java (96%) rename backend/src/test/java/hanglog/{trip => city}/service/CityServiceTest.java (69%) diff --git a/backend/src/test/java/hanglog/trip/presentation/CityControllerTest.java b/backend/src/test/java/hanglog/city/presentation/CityControllerTest.java similarity index 96% rename from backend/src/test/java/hanglog/trip/presentation/CityControllerTest.java rename to backend/src/test/java/hanglog/city/presentation/CityControllerTest.java index e64fa77fe..9b8c295fa 100644 --- a/backend/src/test/java/hanglog/trip/presentation/CityControllerTest.java +++ b/backend/src/test/java/hanglog/city/presentation/CityControllerTest.java @@ -1,4 +1,4 @@ -package hanglog.trip.presentation; +package hanglog.city.presentation; import static hanglog.trip.fixture.CityFixture.LONDON; import static hanglog.trip.fixture.CityFixture.PARIS; @@ -11,7 +11,6 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import hanglog.city.dto.response.CityResponse; -import hanglog.city.presentation.CityController; import hanglog.city.service.CityService; import hanglog.global.ControllerTest; import java.util.List; diff --git a/backend/src/test/java/hanglog/trip/service/CityServiceTest.java b/backend/src/test/java/hanglog/city/service/CityServiceTest.java similarity index 69% rename from backend/src/test/java/hanglog/trip/service/CityServiceTest.java rename to backend/src/test/java/hanglog/city/service/CityServiceTest.java index f813e106e..e19b624a7 100644 --- a/backend/src/test/java/hanglog/trip/service/CityServiceTest.java +++ b/backend/src/test/java/hanglog/city/service/CityServiceTest.java @@ -1,4 +1,4 @@ -package hanglog.trip.service; +package hanglog.city.service; import static hanglog.trip.fixture.CityFixture.LONDON; import static hanglog.trip.fixture.CityFixture.PARIS; @@ -6,8 +6,8 @@ import static org.mockito.BDDMockito.given; import hanglog.city.domain.repository.CityRepository; +import hanglog.city.dto.response.CityDetailResponse; import hanglog.city.dto.response.CityResponse; -import hanglog.city.service.CityService; import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -41,4 +41,19 @@ void getAllCities() { assertThat(actual).usingRecursiveComparison() .isEqualTo(List.of(CityResponse.withCountry(PARIS), CityResponse.withCountry(LONDON))); } + + @DisplayName("모든 도시의 세부 정보를 반환한다.") + @Test + void getAllCitiesDetail() { + // given + given(cityRepository.findAll()) + .willReturn(List.of(PARIS, LONDON)); + + // when + final List actual = cityService.getAllCitiesDetail(); + + // then + assertThat(actual).usingRecursiveComparison() + .isEqualTo(List.of(CityDetailResponse.of(PARIS), CityDetailResponse.of(LONDON))); + } } From 540fd63bd1da0b02f16fb09a7e200f1c2846554c Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 15:37:18 +0900 Subject: [PATCH 19/88] =?UTF-8?q?test:=20city=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hanglog/city/service/CityServiceTest.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/backend/src/test/java/hanglog/city/service/CityServiceTest.java b/backend/src/test/java/hanglog/city/service/CityServiceTest.java index e19b624a7..ead686ecd 100644 --- a/backend/src/test/java/hanglog/city/service/CityServiceTest.java +++ b/backend/src/test/java/hanglog/city/service/CityServiceTest.java @@ -3,11 +3,17 @@ import static hanglog.trip.fixture.CityFixture.LONDON; import static hanglog.trip.fixture.CityFixture.PARIS; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.BDDMockito.given; +import hanglog.city.domain.City; import hanglog.city.domain.repository.CityRepository; +import hanglog.city.dto.request.CityRequest; import hanglog.city.dto.response.CityDetailResponse; import hanglog.city.dto.response.CityResponse; +import hanglog.global.exception.InvalidDomainException; import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -56,4 +62,43 @@ void getAllCitiesDetail() { assertThat(actual).usingRecursiveComparison() .isEqualTo(List.of(CityDetailResponse.of(PARIS), CityDetailResponse.of(LONDON))); } + + @DisplayName("새로운 도시를 추가한다.") + @Test + void save() { + // given + final CityRequest cityRequest = new CityRequest( + PARIS.getName(), + PARIS.getCountry(), + PARIS.getLatitude(), + PARIS.getLongitude() + ); + + given(cityRepository.existsByNameAndCountry(anyString(), anyString())).willReturn(false); + given(cityRepository.save(any(City.class))).willReturn(PARIS); + + // when + final Long actualId = cityService.save(cityRequest); + + // then + assertThat(actualId).isEqualTo(PARIS.getId()); + } + + @DisplayName("도시와 나라가 중복되면 도시를 추가할 수 없다.") + @Test + void save_duplicateFail() { + // given + final CityRequest cityRequest = new CityRequest( + PARIS.getName(), + PARIS.getCountry(), + PARIS.getLatitude(), + PARIS.getLongitude() + ); + + given(cityRepository.existsByNameAndCountry(anyString(), anyString())).willReturn(true); + + // when + assertThatThrownBy(() -> cityService.save(cityRequest)) + .isInstanceOf(InvalidDomainException.class); + } } From 3989c14452db07e30d1f16e1cb8d337d8d952d80 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 15:37:46 +0900 Subject: [PATCH 20/88] =?UTF-8?q?feat:=20city=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/hanglog/city/domain/City.java | 11 +++++++ .../domain/repository/CityRepository.java | 2 ++ .../hanglog/city/dto/request/CityRequest.java | 30 +++++++++++++++++++ .../hanglog/city/service/CityService.java | 12 ++++++++ .../global/exception/ExceptionCode.java | 2 +- 5 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 backend/src/main/java/hanglog/city/dto/request/CityRequest.java diff --git a/backend/src/main/java/hanglog/city/domain/City.java b/backend/src/main/java/hanglog/city/domain/City.java index 544570b66..10abba9b0 100644 --- a/backend/src/main/java/hanglog/city/domain/City.java +++ b/backend/src/main/java/hanglog/city/domain/City.java @@ -3,6 +3,7 @@ import static jakarta.persistence.GenerationType.IDENTITY; import static lombok.AccessLevel.PROTECTED; +import hanglog.city.dto.request.CityRequest; import hanglog.global.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -34,4 +35,14 @@ public class City extends BaseEntity { @Column(nullable = false, precision = 16, scale = 13) private BigDecimal longitude; + + public static City of(final CityRequest cityRequest) { + return new City( + null, + cityRequest.getName(), + cityRequest.getCountry(), + cityRequest.getLatitude(), + cityRequest.getLongitude() + ); + } } diff --git a/backend/src/main/java/hanglog/city/domain/repository/CityRepository.java b/backend/src/main/java/hanglog/city/domain/repository/CityRepository.java index 1f4340b6e..c74c42677 100644 --- a/backend/src/main/java/hanglog/city/domain/repository/CityRepository.java +++ b/backend/src/main/java/hanglog/city/domain/repository/CityRepository.java @@ -22,4 +22,6 @@ public interface CityRepository extends JpaRepository { AND tc.trip.id = :tripId """) List findCitiesByTripId(@Param("tripId") final Long tripId); + + Boolean existsByNameAndCountry(final String name, final String country); } diff --git a/backend/src/main/java/hanglog/city/dto/request/CityRequest.java b/backend/src/main/java/hanglog/city/dto/request/CityRequest.java new file mode 100644 index 000000000..c9337f7a6 --- /dev/null +++ b/backend/src/main/java/hanglog/city/dto/request/CityRequest.java @@ -0,0 +1,30 @@ +package hanglog.city.dto.request; + +import jakarta.validation.constraints.Digits; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.math.BigDecimal; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class CityRequest { + + @NotBlank(message = "도시의 이름을 입력해주세요.") + @Size(max = 20, message = "도시의 이름은 20자를 초과할 수 없습니다.") + private final String name; + + @NotBlank(message = "나라 이름을 입력해주세요.") + @Size(max = 20, message = "나라 이름은 20자를 초과할 수 없습니다.") + private final String country; + + @NotNull(message = "도시의 위도를 입력해주세요.") + @Digits(integer = 3, fraction = 13) + private final BigDecimal latitude; + + @NotNull(message = "도시의 경도를 입력해주세요.") + @Digits(integer = 3, fraction = 13) + private final BigDecimal longitude; +} diff --git a/backend/src/main/java/hanglog/city/service/CityService.java b/backend/src/main/java/hanglog/city/service/CityService.java index bb987abe6..2a237ee9f 100644 --- a/backend/src/main/java/hanglog/city/service/CityService.java +++ b/backend/src/main/java/hanglog/city/service/CityService.java @@ -2,8 +2,11 @@ import hanglog.city.domain.City; import hanglog.city.domain.repository.CityRepository; +import hanglog.city.dto.request.CityRequest; import hanglog.city.dto.response.CityDetailResponse; import hanglog.city.dto.response.CityResponse; +import hanglog.global.exception.ExceptionCode; +import hanglog.global.exception.InvalidDomainException; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -31,4 +34,13 @@ public List getAllCitiesDetail() { .map(CityDetailResponse::of) .toList(); } + + public Long save(final CityRequest cityRequest) { + if (cityRepository.existsByNameAndCountry(cityRequest.getName(), cityRequest.getCountry())) { + throw new InvalidDomainException(ExceptionCode.DUPLICATED_CITY_NAME); + } + + return cityRepository.save(City.of(cityRequest)).getId(); + } + } diff --git a/backend/src/main/java/hanglog/global/exception/ExceptionCode.java b/backend/src/main/java/hanglog/global/exception/ExceptionCode.java index 6d84986ce..01659b8ce 100644 --- a/backend/src/main/java/hanglog/global/exception/ExceptionCode.java +++ b/backend/src/main/java/hanglog/global/exception/ExceptionCode.java @@ -62,8 +62,8 @@ public enum ExceptionCode { DUPLICATED_ADMIN_USERNAME(8102, "중복된 사용자 이름입니다."), NOT_FOUND_ADMIN_ID(8103, "요청한 ID에 해당하는 관리자를 찾을 수 없습니다."), INVALID_CURRENT_PASSWORD(8104, "현재 사용중인 비밀번호가 일치하지 않습니다."), - INVALID_ADMIN_AUTHORITY(8201, "해당 관리자 기능에 대한 접근 권한이 없습니다."), + DUPLICATED_CITY_NAME(8301, "중복된 나라, 도시 이름입니다."), INVALID_AUTHORIZATION_CODE(9001, "유효하지 않은 인증 코드입니다."), NOT_SUPPORTED_OAUTH_SERVICE(9002, "해당 OAuth 서비스는 제공하지 않습니다."), From a41517e74d2828776d9baeda6ddcfa18c08084fe Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 16:16:48 +0900 Subject: [PATCH 21/88] =?UTF-8?q?test:=20city=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hanglog/city/service/CityServiceTest.java | 64 ++++++++++++++++++- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/backend/src/test/java/hanglog/city/service/CityServiceTest.java b/backend/src/test/java/hanglog/city/service/CityServiceTest.java index ead686ecd..0cc614994 100644 --- a/backend/src/test/java/hanglog/city/service/CityServiceTest.java +++ b/backend/src/test/java/hanglog/city/service/CityServiceTest.java @@ -4,7 +4,9 @@ import static hanglog.trip.fixture.CityFixture.PARIS; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.BDDMockito.given; @@ -13,8 +15,9 @@ import hanglog.city.dto.request.CityRequest; import hanglog.city.dto.response.CityDetailResponse; import hanglog.city.dto.response.CityResponse; -import hanglog.global.exception.InvalidDomainException; +import hanglog.global.exception.BadRequestException; import java.util.List; +import java.util.Optional; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -86,7 +89,7 @@ void save() { @DisplayName("도시와 나라가 중복되면 도시를 추가할 수 없다.") @Test - void save_duplicateFail() { + void save_DuplicateFail() { // given final CityRequest cityRequest = new CityRequest( PARIS.getName(), @@ -99,6 +102,61 @@ void save_duplicateFail() { // when assertThatThrownBy(() -> cityService.save(cityRequest)) - .isInstanceOf(InvalidDomainException.class); + .isInstanceOf(BadRequestException.class); + } + + @DisplayName("도시 정보를 수정한다.") + @Test + void update() { + // given + final CityRequest cityRequest = new CityRequest( + "newName", + PARIS.getCountry(), + PARIS.getLatitude(), + PARIS.getLongitude() + ); + + given(cityRepository.findById(anyLong())).willReturn(Optional.of(PARIS)); + given(cityRepository.existsByNameAndCountry(anyString(), anyString())).willReturn(false); + + // when & then + assertDoesNotThrow(() -> cityService.update(PARIS.getId(), cityRequest)); + } + + @DisplayName("수정한 도시의 이름이 다른 도시와 중복되면 예외가 발생한다.") + @Test + void update_DuplicateFail() { + // given + final CityRequest cityRequest = new CityRequest( + "newName", + PARIS.getCountry(), + PARIS.getLatitude(), + PARIS.getLongitude() + ); + + given(cityRepository.findById(anyLong())).willReturn(Optional.of(PARIS)); + given(cityRepository.existsByNameAndCountry(anyString(), anyString())).willReturn(true); + + // when & then + assertThatThrownBy(() -> cityService.update(PARIS.getId(), cityRequest)) + .isInstanceOf(BadRequestException.class); + } + + @DisplayName("ID에 해당하는 도시가 존재하지 않으면 예외가 발생한다.") + @Test + void update_NotFoundFail() { + // given + final CityRequest cityRequest = new CityRequest( + "newName", + PARIS.getCountry(), + PARIS.getLatitude(), + PARIS.getLongitude() + ); + + given(cityRepository.findById(anyLong())).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> cityService.update(PARIS.getId(), cityRequest)) + .isInstanceOf(BadRequestException.class); } } From 700ba71a923a19d38b8cbed1ac42d61cfdd9fec7 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 16:16:58 +0900 Subject: [PATCH 22/88] =?UTF-8?q?feat:=20city=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/hanglog/city/domain/City.java | 11 ++++++++ .../hanglog/city/service/CityService.java | 28 ++++++++++++++++--- .../global/exception/ExceptionCode.java | 1 + 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/hanglog/city/domain/City.java b/backend/src/main/java/hanglog/city/domain/City.java index 10abba9b0..71532b3b6 100644 --- a/backend/src/main/java/hanglog/city/domain/City.java +++ b/backend/src/main/java/hanglog/city/domain/City.java @@ -45,4 +45,15 @@ public static City of(final CityRequest cityRequest) { cityRequest.getLongitude() ); } + + public void update(final CityRequest cityRequest) { + this.name = cityRequest.getName(); + this.country = cityRequest.getCountry(); + this.latitude = cityRequest.getLatitude(); + this.longitude = cityRequest.getLongitude(); + } + + public boolean isSameNameAndCountry(final String name, final String country) { + return this.name.equals(name) && this.country.equals(country); + } } diff --git a/backend/src/main/java/hanglog/city/service/CityService.java b/backend/src/main/java/hanglog/city/service/CityService.java index 2a237ee9f..90c0b29db 100644 --- a/backend/src/main/java/hanglog/city/service/CityService.java +++ b/backend/src/main/java/hanglog/city/service/CityService.java @@ -1,12 +1,14 @@ package hanglog.city.service; +import static hanglog.global.exception.ExceptionCode.DUPLICATED_CITY_NAME; +import static hanglog.global.exception.ExceptionCode.NOT_FOUND_CITY_ID; + import hanglog.city.domain.City; import hanglog.city.domain.repository.CityRepository; import hanglog.city.dto.request.CityRequest; import hanglog.city.dto.response.CityDetailResponse; import hanglog.city.dto.response.CityResponse; -import hanglog.global.exception.ExceptionCode; -import hanglog.global.exception.InvalidDomainException; +import hanglog.global.exception.BadRequestException; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -36,11 +38,29 @@ public List getAllCitiesDetail() { } public Long save(final CityRequest cityRequest) { + validateCityDuplicate(cityRequest); + + return cityRepository.save(City.of(cityRequest)).getId(); + } + + private void validateCityDuplicate(final CityRequest cityRequest) { if (cityRepository.existsByNameAndCountry(cityRequest.getName(), cityRequest.getCountry())) { - throw new InvalidDomainException(ExceptionCode.DUPLICATED_CITY_NAME); + throw new BadRequestException(DUPLICATED_CITY_NAME); } + } - return cityRepository.save(City.of(cityRequest)).getId(); + public void update(final Long id, final CityRequest cityRequest) { + final City city = cityRepository.findById(id) + .orElseThrow(() -> new BadRequestException(NOT_FOUND_CITY_ID)); + + validateCityDuplicate(city, cityRequest); + + city.update(cityRequest); } + private void validateCityDuplicate(final City city, final CityRequest cityRequest) { + if (!city.isSameNameAndCountry(cityRequest.getName(), cityRequest.getCountry())) { + validateCityDuplicate(cityRequest); + } + } } diff --git a/backend/src/main/java/hanglog/global/exception/ExceptionCode.java b/backend/src/main/java/hanglog/global/exception/ExceptionCode.java index 01659b8ce..6c75d5652 100644 --- a/backend/src/main/java/hanglog/global/exception/ExceptionCode.java +++ b/backend/src/main/java/hanglog/global/exception/ExceptionCode.java @@ -64,6 +64,7 @@ public enum ExceptionCode { INVALID_CURRENT_PASSWORD(8104, "현재 사용중인 비밀번호가 일치하지 않습니다."), INVALID_ADMIN_AUTHORITY(8201, "해당 관리자 기능에 대한 접근 권한이 없습니다."), DUPLICATED_CITY_NAME(8301, "중복된 나라, 도시 이름입니다."), + NOT_FOUND_CITY(8302, "요청한 ID에 해당하는 도시를 찾을 수 없습니다."), INVALID_AUTHORIZATION_CODE(9001, "유효하지 않은 인증 코드입니다."), NOT_SUPPORTED_OAUTH_SERVICE(9002, "해당 OAuth 서비스는 제공하지 않습니다."), From e002a8449ab978bf4c6b2bd2cd1dd17e89ff33fc Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 16:35:31 +0900 Subject: [PATCH 23/88] =?UTF-8?q?test:=20city=20=EC=83=81=EC=84=B8=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20API=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/AdminCityControllerTest.java | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 backend/src/test/java/hanglog/admin/presentation/AdminCityControllerTest.java diff --git a/backend/src/test/java/hanglog/admin/presentation/AdminCityControllerTest.java b/backend/src/test/java/hanglog/admin/presentation/AdminCityControllerTest.java new file mode 100644 index 000000000..79934a2cc --- /dev/null +++ b/backend/src/test/java/hanglog/admin/presentation/AdminCityControllerTest.java @@ -0,0 +1,103 @@ +package hanglog.admin.presentation; + +import static hanglog.global.restdocs.RestDocsConfiguration.field; +import static hanglog.trip.fixture.CityFixture.PARIS; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doNothing; +import static org.springframework.http.HttpHeaders.AUTHORIZATION; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import hanglog.city.dto.response.CityDetailResponse; +import hanglog.city.service.CityService; +import hanglog.global.ControllerTest; +import hanglog.login.domain.MemberTokens; +import jakarta.servlet.http.Cookie; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.MockMvc; + + +@WebMvcTest(AdminCityController.class) +@MockBean(JpaMetamodelMappingContext.class) +@AutoConfigureRestDocs +class AdminCityControllerTest extends ControllerTest { + + private static final MemberTokens MEMBER_TOKENS = new MemberTokens("refreshToken", "accessToken"); + private static final Cookie COOKIE = new Cookie("refresh-token", MEMBER_TOKENS.getRefreshToken()); + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private CityService cityService; + + + @BeforeEach + void setUp() { + given(refreshTokenRepository.existsById(any())).willReturn(true); + doNothing().when(jwtProvider).validateTokens(any()); + given(jwtProvider.getSubject(any())).willReturn("1"); + given(adminMemberRepository.existsAdminMemberByIdAndAdminType(any(), any())).willReturn(false); + } + + @DisplayName("도시 상세 목록을 조회한다.") + @Test + void getCitiesDetail() throws Exception { + // given + final CityDetailResponse response = CityDetailResponse.of(PARIS); + + given(cityService.getAllCitiesDetail()).willReturn(List.of(response)); + + // when & then + mockMvc.perform(get("/admin/cities") + .header(AUTHORIZATION, MEMBER_TOKENS.getAccessToken()) + .cookie(COOKIE)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id").value(response.getId())) + .andExpect(jsonPath("$[0].name").value(response.getName())) + .andExpect(jsonPath("$[0].country").value(response.getCountry())) + .andExpect(jsonPath("$[0].latitude").value(response.getLatitude())) + .andExpect(jsonPath("$[0].longitude").value(response.getLongitude())) + .andDo(restDocs.document( + responseFields( + fieldWithPath("[].id") + .type(JsonFieldType.NUMBER) + .description("도시 ID") + .attributes(field("constraint", "양의 정수")), + fieldWithPath("[].name") + .type(JsonFieldType.STRING) + .description("도시 이름") + .attributes(field("constraint", "20자 이내의 문자열")), + fieldWithPath("[].country") + .type(JsonFieldType.STRING) + .description("나라 이름") + .attributes(field("constraint", "20자 이내의 문자열")), + fieldWithPath("[].latitude") + .type(JsonFieldType.NUMBER) + .description("위도") + .attributes(field("constraint", "BigDecimal(3,13)")), + fieldWithPath("[].longitude") + .type(JsonFieldType.NUMBER) + .description("경도") + .attributes(field("constraint", "BigDecimal(3,13)")) + ) + )); + } +} From e80aeddd4cd86bc742ee93354217361689b4e1e0 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 16:35:45 +0900 Subject: [PATCH 24/88] =?UTF-8?q?feat:=20city=20=EC=83=81=EC=84=B8=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20API=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/AdminCityController.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 backend/src/main/java/hanglog/admin/presentation/AdminCityController.java diff --git a/backend/src/main/java/hanglog/admin/presentation/AdminCityController.java b/backend/src/main/java/hanglog/admin/presentation/AdminCityController.java new file mode 100644 index 000000000..241e2680b --- /dev/null +++ b/backend/src/main/java/hanglog/admin/presentation/AdminCityController.java @@ -0,0 +1,31 @@ +package hanglog.admin.presentation; + + +import hanglog.auth.AdminAuth; +import hanglog.auth.AdminOnly; +import hanglog.auth.domain.Accessor; +import hanglog.city.dto.response.CityDetailResponse; +import hanglog.city.service.CityService; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/admin/cities") +public class AdminCityController { + + private final CityService cityService; + + @GetMapping + @AdminOnly + public ResponseEntity> getCitiesDetail( + @AdminAuth final Accessor accessor + ) { + final List citiesDetail = cityService.getAllCitiesDetail(); + return ResponseEntity.ok(citiesDetail); + } +} From a696b33cffa3dc2999db70a73869809e0a2fc33c Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 16:40:25 +0900 Subject: [PATCH 25/88] =?UTF-8?q?feat:=20=EB=8F=84=EC=8B=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/presentation/AdminCityController.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/backend/src/main/java/hanglog/admin/presentation/AdminCityController.java b/backend/src/main/java/hanglog/admin/presentation/AdminCityController.java index 241e2680b..9d94f6927 100644 --- a/backend/src/main/java/hanglog/admin/presentation/AdminCityController.java +++ b/backend/src/main/java/hanglog/admin/presentation/AdminCityController.java @@ -4,12 +4,17 @@ import hanglog.auth.AdminAuth; import hanglog.auth.AdminOnly; import hanglog.auth.domain.Accessor; +import hanglog.city.dto.request.CityRequest; import hanglog.city.dto.response.CityDetailResponse; import hanglog.city.service.CityService; +import jakarta.validation.Valid; +import java.net.URI; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +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; @@ -28,4 +33,14 @@ public ResponseEntity> getCitiesDetail( final List citiesDetail = cityService.getAllCitiesDetail(); return ResponseEntity.ok(citiesDetail); } + + @PostMapping + @AdminOnly + public ResponseEntity createCity( + @AdminAuth final Accessor accessor, + @RequestBody @Valid final CityRequest cityRequest + ) { + final Long cityId = cityService.save(cityRequest); + return ResponseEntity.created(URI.create("/admin/cities" + cityId)).build(); + } } From adacf8c645962d4ce4767ba1e16dd5eadd44bd17 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 16:48:14 +0900 Subject: [PATCH 26/88] =?UTF-8?q?test:=20=EB=8F=84=EC=8B=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20API=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/AdminCityController.java | 2 +- .../presentation/AdminCityControllerTest.java | 48 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/hanglog/admin/presentation/AdminCityController.java b/backend/src/main/java/hanglog/admin/presentation/AdminCityController.java index 9d94f6927..96caa4788 100644 --- a/backend/src/main/java/hanglog/admin/presentation/AdminCityController.java +++ b/backend/src/main/java/hanglog/admin/presentation/AdminCityController.java @@ -41,6 +41,6 @@ public ResponseEntity createCity( @RequestBody @Valid final CityRequest cityRequest ) { final Long cityId = cityService.save(cityRequest); - return ResponseEntity.created(URI.create("/admin/cities" + cityId)).build(); + return ResponseEntity.created(URI.create("/admin/cities/" + cityId)).build(); } } diff --git a/backend/src/test/java/hanglog/admin/presentation/AdminCityControllerTest.java b/backend/src/test/java/hanglog/admin/presentation/AdminCityControllerTest.java index 79934a2cc..eecac915b 100644 --- a/backend/src/test/java/hanglog/admin/presentation/AdminCityControllerTest.java +++ b/backend/src/test/java/hanglog/admin/presentation/AdminCityControllerTest.java @@ -7,17 +7,22 @@ import static org.mockito.Mockito.doNothing; import static org.springframework.http.HttpHeaders.AUTHORIZATION; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.fasterxml.jackson.databind.ObjectMapper; +import hanglog.city.dto.request.CityRequest; import hanglog.city.dto.response.CityDetailResponse; import hanglog.city.service.CityService; import hanglog.global.ControllerTest; import hanglog.login.domain.MemberTokens; import jakarta.servlet.http.Cookie; +import java.math.BigDecimal; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -27,6 +32,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext; +import org.springframework.http.MediaType; import org.springframework.restdocs.payload.JsonFieldType; import org.springframework.test.web.servlet.MockMvc; @@ -100,4 +106,46 @@ void getCitiesDetail() throws Exception { ) )); } + + @DisplayName("새로운 도시를 생성한다.") + @Test + void createCity() throws Exception { + // given + final CityRequest request = new CityRequest( + "name", + "country", + BigDecimal.valueOf(123.12345), + BigDecimal.valueOf(123.12345) + ); + given(cityService.save(any(CityRequest.class))).willReturn(1L); + + // when & then + mockMvc.perform(post("/admin/cities") + .header(AUTHORIZATION, MEMBER_TOKENS.getAccessToken()) + .cookie(COOKIE) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isCreated()) + .andExpect(header().string("Location", "/admin/cities/1")) + .andDo(restDocs.document( + requestFields( + fieldWithPath("name") + .type(JsonFieldType.STRING) + .description("도시 이름") + .attributes(field("constraint", "20자 이내의 문자열")), + fieldWithPath("country") + .type(JsonFieldType.STRING) + .description("나라 이름") + .attributes(field("constraint", "20자 이내의 문자열")), + fieldWithPath("latitude") + .type(JsonFieldType.NUMBER) + .description("위도") + .attributes(field("constraint", "BigDecimal(3,13)")), + fieldWithPath("longitude") + .type(JsonFieldType.NUMBER) + .description("경도") + .attributes(field("constraint", "BigDecimal(3,13)")) + ) + )); + } } From 6eaf012fc3d9bd9c0d114bfa9e19a854809b40e6 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 16:59:22 +0900 Subject: [PATCH 27/88] =?UTF-8?q?test:=20=EB=8F=84=EC=8B=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20API=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/AdminCityControllerTest.java | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/backend/src/test/java/hanglog/admin/presentation/AdminCityControllerTest.java b/backend/src/test/java/hanglog/admin/presentation/AdminCityControllerTest.java index eecac915b..0c3e5bc8d 100644 --- a/backend/src/test/java/hanglog/admin/presentation/AdminCityControllerTest.java +++ b/backend/src/test/java/hanglog/admin/presentation/AdminCityControllerTest.java @@ -8,9 +8,12 @@ import static org.springframework.http.HttpHeaders.AUTHORIZATION; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.put; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -148,4 +151,50 @@ void createCity() throws Exception { ) )); } + + + @DisplayName("도시 정보를 수정한다.") + @Test + void updateCity() throws Exception { + // given + final CityRequest request = new CityRequest( + "newName", + "country", + BigDecimal.valueOf(123.12345), + BigDecimal.valueOf(123.12345) + ); + doNothing().when(cityService).update(1L, request); + + // when & then + mockMvc.perform(put("/admin/cities/{cityId}", 1) + .header(AUTHORIZATION, MEMBER_TOKENS.getAccessToken()) + .cookie(COOKIE) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isNoContent()) + .andDo(restDocs.document( + pathParameters( + parameterWithName("cityId") + .description("도시 ID") + ), + requestFields( + fieldWithPath("name") + .type(JsonFieldType.STRING) + .description("도시 이름") + .attributes(field("constraint", "20자 이내의 문자열")), + fieldWithPath("country") + .type(JsonFieldType.STRING) + .description("나라 이름") + .attributes(field("constraint", "20자 이내의 문자열")), + fieldWithPath("latitude") + .type(JsonFieldType.NUMBER) + .description("위도") + .attributes(field("constraint", "BigDecimal(3,13)")), + fieldWithPath("longitude") + .type(JsonFieldType.NUMBER) + .description("경도") + .attributes(field("constraint", "BigDecimal(3,13)")) + ) + )); + } } From b44580fb72e5ecf093b7c939a69e0c57730fb8e6 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 16:59:32 +0900 Subject: [PATCH 28/88] =?UTF-8?q?feat:=20=EB=8F=84=EC=8B=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/presentation/AdminCityController.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/backend/src/main/java/hanglog/admin/presentation/AdminCityController.java b/backend/src/main/java/hanglog/admin/presentation/AdminCityController.java index 96caa4788..20dfe2194 100644 --- a/backend/src/main/java/hanglog/admin/presentation/AdminCityController.java +++ b/backend/src/main/java/hanglog/admin/presentation/AdminCityController.java @@ -13,7 +13,9 @@ import lombok.RequiredArgsConstructor; 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.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -43,4 +45,15 @@ public ResponseEntity createCity( final Long cityId = cityService.save(cityRequest); return ResponseEntity.created(URI.create("/admin/cities/" + cityId)).build(); } + + @PutMapping("/{cityId}") + @AdminOnly + public ResponseEntity updateCity( + @AdminAuth final Accessor accessor, + @PathVariable final Long cityId, + @RequestBody @Valid final CityRequest cityRequest + ) { + cityService.update(cityId, cityRequest); + return ResponseEntity.noContent().build(); + } } From 9397c14594249da6e9950ee8fb2403f809c6cfd0 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 17:20:22 +0900 Subject: [PATCH 29/88] =?UTF-8?q?test:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=84=B8=EB=B6=80=20=EC=A0=95=EB=B3=B4=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../category/fixture/CategoryFixture.java | 6 ++++++ .../category/service/CategoryServiceTest.java | 20 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/backend/src/test/java/hanglog/category/fixture/CategoryFixture.java b/backend/src/test/java/hanglog/category/fixture/CategoryFixture.java index 439405fc9..6ef8b3a98 100644 --- a/backend/src/test/java/hanglog/category/fixture/CategoryFixture.java +++ b/backend/src/test/java/hanglog/category/fixture/CategoryFixture.java @@ -14,6 +14,12 @@ public class CategoryFixture { new Category(600L, "etc", "기타") ); + public static final List CATEGORIES = List.of( + new Category(100L, "food", "음식"), + new Category(101L, "cafe", "카페"), + new Category(102L, "restaurants", "식당") + ); + public static final Category FOOD = EXPENSE_CATEGORIES.get(0); public static final Category CULTURE = EXPENSE_CATEGORIES.get(1); public static final Category SHOPPING = EXPENSE_CATEGORIES.get(2); diff --git a/backend/src/test/java/hanglog/category/service/CategoryServiceTest.java b/backend/src/test/java/hanglog/category/service/CategoryServiceTest.java index 757c48333..6175c927f 100644 --- a/backend/src/test/java/hanglog/category/service/CategoryServiceTest.java +++ b/backend/src/test/java/hanglog/category/service/CategoryServiceTest.java @@ -1,10 +1,12 @@ package hanglog.category.service; +import static hanglog.category.fixture.CategoryFixture.CATEGORIES; import static hanglog.category.fixture.CategoryFixture.EXPENSE_CATEGORIES; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; import hanglog.category.domain.repository.CategoryRepository; +import hanglog.category.dto.CategoryDetailResponse; import hanglog.category.dto.CategoryResponse; import java.util.List; import org.junit.jupiter.api.DisplayName; @@ -42,4 +44,22 @@ void getExpenseCategories() { // then assertThat(actualResponses).usingRecursiveComparison().isEqualTo(expectResponses); } + + @DisplayName("모든 카테고리의 세부 정보를 반환한다.") + @Test + void getAllCategoriesDetail() { + // given + final List responses = CATEGORIES.stream() + .map(CategoryDetailResponse::of) + .toList(); + + given(categoryRepository.findAll()) + .willReturn(CATEGORIES); + + // when + final List actualResponses = categoryService.getAllCategoriesDetail(); + + // then + assertThat(actualResponses).usingRecursiveComparison().isEqualTo(responses); + } } From 8c78c40df2ee00600b11c41106aa75fa2cb8616a Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 17:20:31 +0900 Subject: [PATCH 30/88] =?UTF-8?q?feat:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=84=B8=EB=B6=80=20=EC=A0=95=EB=B3=B4=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../category/dto/CategoryDetailResponse.java | 22 +++++++++++++++++++ .../category/service/CategoryService.java | 9 ++++++++ 2 files changed, 31 insertions(+) create mode 100644 backend/src/main/java/hanglog/category/dto/CategoryDetailResponse.java diff --git a/backend/src/main/java/hanglog/category/dto/CategoryDetailResponse.java b/backend/src/main/java/hanglog/category/dto/CategoryDetailResponse.java new file mode 100644 index 000000000..936f30e14 --- /dev/null +++ b/backend/src/main/java/hanglog/category/dto/CategoryDetailResponse.java @@ -0,0 +1,22 @@ +package hanglog.category.dto; + +import hanglog.category.domain.Category; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class CategoryDetailResponse { + + private final Long id; + private final String engName; + private final String korName; + + public static CategoryDetailResponse of(final Category category) { + return new CategoryDetailResponse( + category.getId(), + category.getEngName(), + category.getKorName() + ); + } +} diff --git a/backend/src/main/java/hanglog/category/service/CategoryService.java b/backend/src/main/java/hanglog/category/service/CategoryService.java index bb2fecba3..2f9b1b217 100644 --- a/backend/src/main/java/hanglog/category/service/CategoryService.java +++ b/backend/src/main/java/hanglog/category/service/CategoryService.java @@ -2,6 +2,7 @@ import hanglog.category.domain.Category; import hanglog.category.domain.repository.CategoryRepository; +import hanglog.category.dto.CategoryDetailResponse; import hanglog.category.dto.CategoryResponse; import java.util.List; import lombok.RequiredArgsConstructor; @@ -22,4 +23,12 @@ public List getExpenseCategories() { .map(CategoryResponse::of) .toList(); } + + @Transactional(readOnly = true) + public List getAllCategoriesDetail() { + final List expenseCategories = categoryRepository.findAll(); + return expenseCategories.stream() + .map(CategoryDetailResponse::of) + .toList(); + } } From b19a32c70b1282cb9392d009ed0004d07bc6a182 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 17:23:38 +0900 Subject: [PATCH 31/88] =?UTF-8?q?rename:=20category=20response=20dto=20?= =?UTF-8?q?=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../category/dto/{ => response}/CategoryDetailResponse.java | 2 +- .../hanglog/category/dto/{ => response}/CategoryResponse.java | 2 +- .../hanglog/category/presentation/CategoryController.java | 2 +- .../main/java/hanglog/category/service/CategoryService.java | 4 ++-- .../hanglog/expense/dto/response/CategoryExpenseResponse.java | 2 +- .../hanglog/expense/dto/response/ItemExpenseResponse.java | 2 +- .../main/java/hanglog/trip/dto/response/PlaceResponse.java | 2 +- .../hanglog/category/presentation/CategoryControllerTest.java | 2 +- .../java/hanglog/category/service/CategoryServiceTest.java | 4 ++-- .../hanglog/integration/controller/ItemIntegrationTest.java | 2 +- 10 files changed, 12 insertions(+), 12 deletions(-) rename backend/src/main/java/hanglog/category/dto/{ => response}/CategoryDetailResponse.java (92%) rename backend/src/main/java/hanglog/category/dto/{ => response}/CategoryResponse.java (91%) diff --git a/backend/src/main/java/hanglog/category/dto/CategoryDetailResponse.java b/backend/src/main/java/hanglog/category/dto/response/CategoryDetailResponse.java similarity index 92% rename from backend/src/main/java/hanglog/category/dto/CategoryDetailResponse.java rename to backend/src/main/java/hanglog/category/dto/response/CategoryDetailResponse.java index 936f30e14..aa3da294f 100644 --- a/backend/src/main/java/hanglog/category/dto/CategoryDetailResponse.java +++ b/backend/src/main/java/hanglog/category/dto/response/CategoryDetailResponse.java @@ -1,4 +1,4 @@ -package hanglog.category.dto; +package hanglog.category.dto.response; import hanglog.category.domain.Category; import lombok.Getter; diff --git a/backend/src/main/java/hanglog/category/dto/CategoryResponse.java b/backend/src/main/java/hanglog/category/dto/response/CategoryResponse.java similarity index 91% rename from backend/src/main/java/hanglog/category/dto/CategoryResponse.java rename to backend/src/main/java/hanglog/category/dto/response/CategoryResponse.java index 57c10b60c..c6d91f8d3 100644 --- a/backend/src/main/java/hanglog/category/dto/CategoryResponse.java +++ b/backend/src/main/java/hanglog/category/dto/response/CategoryResponse.java @@ -1,4 +1,4 @@ -package hanglog.category.dto; +package hanglog.category.dto.response; import hanglog.category.domain.Category; import lombok.Getter; diff --git a/backend/src/main/java/hanglog/category/presentation/CategoryController.java b/backend/src/main/java/hanglog/category/presentation/CategoryController.java index f9e373d52..ec491c7e0 100644 --- a/backend/src/main/java/hanglog/category/presentation/CategoryController.java +++ b/backend/src/main/java/hanglog/category/presentation/CategoryController.java @@ -1,6 +1,6 @@ package hanglog.category.presentation; -import hanglog.category.dto.CategoryResponse; +import hanglog.category.dto.response.CategoryResponse; import hanglog.category.service.CategoryService; import java.util.List; import lombok.RequiredArgsConstructor; diff --git a/backend/src/main/java/hanglog/category/service/CategoryService.java b/backend/src/main/java/hanglog/category/service/CategoryService.java index 2f9b1b217..ed8e4455b 100644 --- a/backend/src/main/java/hanglog/category/service/CategoryService.java +++ b/backend/src/main/java/hanglog/category/service/CategoryService.java @@ -2,8 +2,8 @@ import hanglog.category.domain.Category; import hanglog.category.domain.repository.CategoryRepository; -import hanglog.category.dto.CategoryDetailResponse; -import hanglog.category.dto.CategoryResponse; +import hanglog.category.dto.response.CategoryDetailResponse; +import hanglog.category.dto.response.CategoryResponse; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; diff --git a/backend/src/main/java/hanglog/expense/dto/response/CategoryExpenseResponse.java b/backend/src/main/java/hanglog/expense/dto/response/CategoryExpenseResponse.java index 528282a0a..6ec9027a5 100644 --- a/backend/src/main/java/hanglog/expense/dto/response/CategoryExpenseResponse.java +++ b/backend/src/main/java/hanglog/expense/dto/response/CategoryExpenseResponse.java @@ -1,7 +1,7 @@ package hanglog.expense.dto.response; -import hanglog.category.dto.CategoryResponse; +import hanglog.category.dto.response.CategoryResponse; import hanglog.expense.domain.CategoryExpense; import java.math.BigDecimal; import lombok.Getter; diff --git a/backend/src/main/java/hanglog/expense/dto/response/ItemExpenseResponse.java b/backend/src/main/java/hanglog/expense/dto/response/ItemExpenseResponse.java index a68a5d9b1..3add61f46 100644 --- a/backend/src/main/java/hanglog/expense/dto/response/ItemExpenseResponse.java +++ b/backend/src/main/java/hanglog/expense/dto/response/ItemExpenseResponse.java @@ -1,6 +1,6 @@ package hanglog.expense.dto.response; -import hanglog.category.dto.CategoryResponse; +import hanglog.category.dto.response.CategoryResponse; import hanglog.expense.domain.Expense; import java.math.BigDecimal; import lombok.Getter; diff --git a/backend/src/main/java/hanglog/trip/dto/response/PlaceResponse.java b/backend/src/main/java/hanglog/trip/dto/response/PlaceResponse.java index 8860528a6..39d11aed6 100644 --- a/backend/src/main/java/hanglog/trip/dto/response/PlaceResponse.java +++ b/backend/src/main/java/hanglog/trip/dto/response/PlaceResponse.java @@ -1,6 +1,6 @@ package hanglog.trip.dto.response; -import hanglog.category.dto.CategoryResponse; +import hanglog.category.dto.response.CategoryResponse; import hanglog.trip.domain.Place; import java.math.BigDecimal; import lombok.Getter; diff --git a/backend/src/test/java/hanglog/category/presentation/CategoryControllerTest.java b/backend/src/test/java/hanglog/category/presentation/CategoryControllerTest.java index 8d9124fc0..36b7b83db 100644 --- a/backend/src/test/java/hanglog/category/presentation/CategoryControllerTest.java +++ b/backend/src/test/java/hanglog/category/presentation/CategoryControllerTest.java @@ -11,7 +11,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import hanglog.category.dto.CategoryResponse; +import hanglog.category.dto.response.CategoryResponse; import hanglog.category.service.CategoryService; import hanglog.global.ControllerTest; import java.util.List; diff --git a/backend/src/test/java/hanglog/category/service/CategoryServiceTest.java b/backend/src/test/java/hanglog/category/service/CategoryServiceTest.java index 6175c927f..c472ba807 100644 --- a/backend/src/test/java/hanglog/category/service/CategoryServiceTest.java +++ b/backend/src/test/java/hanglog/category/service/CategoryServiceTest.java @@ -6,8 +6,8 @@ import static org.mockito.BDDMockito.given; import hanglog.category.domain.repository.CategoryRepository; -import hanglog.category.dto.CategoryDetailResponse; -import hanglog.category.dto.CategoryResponse; +import hanglog.category.dto.response.CategoryDetailResponse; +import hanglog.category.dto.response.CategoryResponse; import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/backend/src/test/java/hanglog/integration/controller/ItemIntegrationTest.java b/backend/src/test/java/hanglog/integration/controller/ItemIntegrationTest.java index 31af4e10b..ff5201cfb 100644 --- a/backend/src/test/java/hanglog/integration/controller/ItemIntegrationTest.java +++ b/backend/src/test/java/hanglog/integration/controller/ItemIntegrationTest.java @@ -13,7 +13,7 @@ import hanglog.category.domain.Category; import hanglog.category.domain.repository.CategoryRepository; -import hanglog.category.dto.CategoryResponse; +import hanglog.category.dto.response.CategoryResponse; import hanglog.currency.domain.type.CurrencyType; import hanglog.expense.dto.response.ItemExpenseResponse; import hanglog.global.exception.BadRequestException; From 0ac1231b9d5358ef8007aabbe270e0253577b600 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 17:35:33 +0900 Subject: [PATCH 32/88] =?UTF-8?q?test:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=B6=94=EA=B0=80=20=EA=B8=B0=EB=8A=A5=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../category/service/CategoryServiceTest.java | 36 +++++++++++++++++++ .../hanglog/city/service/CityServiceTest.java | 2 +- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/backend/src/test/java/hanglog/category/service/CategoryServiceTest.java b/backend/src/test/java/hanglog/category/service/CategoryServiceTest.java index c472ba807..1c6d38487 100644 --- a/backend/src/test/java/hanglog/category/service/CategoryServiceTest.java +++ b/backend/src/test/java/hanglog/category/service/CategoryServiceTest.java @@ -2,12 +2,19 @@ import static hanglog.category.fixture.CategoryFixture.CATEGORIES; import static hanglog.category.fixture.CategoryFixture.EXPENSE_CATEGORIES; +import static hanglog.category.fixture.CategoryFixture.FOOD; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.BDDMockito.given; +import hanglog.category.domain.Category; import hanglog.category.domain.repository.CategoryRepository; +import hanglog.category.dto.request.CategoryRequest; import hanglog.category.dto.response.CategoryDetailResponse; import hanglog.category.dto.response.CategoryResponse; +import hanglog.global.exception.BadRequestException; import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -62,4 +69,33 @@ void getAllCategoriesDetail() { // then assertThat(actualResponses).usingRecursiveComparison().isEqualTo(responses); } + + @DisplayName("새로운 카테고리를 추가한다.") + @Test + void save() { + // given + final CategoryRequest categoryRequest = new CategoryRequest(FOOD.getEngName(), FOOD.getKorName()); + + given(categoryRepository.existsByEngNameAndKorName(anyString(), anyString())).willReturn(false); + given(categoryRepository.save(any(Category.class))).willReturn(FOOD); + + // when + final Long actualId = categoryService.save(categoryRequest); + + // then + assertThat(actualId).isEqualTo(FOOD.getId()); + } + + @DisplayName("중복된 카테고리를 추가할 수 없다.") + @Test + void save_DuplicateFail() { + // given + final CategoryRequest categoryRequest = new CategoryRequest(FOOD.getEngName(), FOOD.getKorName()); + + given(categoryRepository.existsByEngNameAndKorName(anyString(), anyString())).willReturn(true); + + // when & then + assertThatThrownBy(() -> categoryService.save(categoryRequest)) + .isInstanceOf(BadRequestException.class); + } } diff --git a/backend/src/test/java/hanglog/city/service/CityServiceTest.java b/backend/src/test/java/hanglog/city/service/CityServiceTest.java index 0cc614994..77eb9968a 100644 --- a/backend/src/test/java/hanglog/city/service/CityServiceTest.java +++ b/backend/src/test/java/hanglog/city/service/CityServiceTest.java @@ -100,7 +100,7 @@ void save_DuplicateFail() { given(cityRepository.existsByNameAndCountry(anyString(), anyString())).willReturn(true); - // when + // when &then assertThatThrownBy(() -> cityService.save(cityRequest)) .isInstanceOf(BadRequestException.class); } From f887d59db41598cca86557622c16b6ffb7d7720a Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 17:35:55 +0900 Subject: [PATCH 33/88] =?UTF-8?q?feat:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=B6=94=EA=B0=80=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hanglog/category/domain/Category.java | 5 +++++ .../domain/repository/CategoryRepository.java | 2 ++ .../category/dto/request/CategoryRequest.java | 19 +++++++++++++++++++ .../category/service/CategoryService.java | 16 ++++++++++++++++ .../global/exception/ExceptionCode.java | 3 +++ 5 files changed, 45 insertions(+) create mode 100644 backend/src/main/java/hanglog/category/dto/request/CategoryRequest.java diff --git a/backend/src/main/java/hanglog/category/domain/Category.java b/backend/src/main/java/hanglog/category/domain/Category.java index 438e162ab..82cafc851 100644 --- a/backend/src/main/java/hanglog/category/domain/Category.java +++ b/backend/src/main/java/hanglog/category/domain/Category.java @@ -2,6 +2,7 @@ import static lombok.AccessLevel.PROTECTED; +import hanglog.category.dto.request.CategoryRequest; import hanglog.global.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -30,6 +31,10 @@ public class Category extends BaseEntity { @Column(nullable = false, length = 50) private String korName; + public static Category of(final CategoryRequest categoryRequest) { + return new Category(null, categoryRequest.getEngName(), categoryRequest.getKorName()); + } + @Override public boolean equals(final Object o) { if (this == o) { diff --git a/backend/src/main/java/hanglog/category/domain/repository/CategoryRepository.java b/backend/src/main/java/hanglog/category/domain/repository/CategoryRepository.java index 2ed98d9cb..d4685512b 100644 --- a/backend/src/main/java/hanglog/category/domain/repository/CategoryRepository.java +++ b/backend/src/main/java/hanglog/category/domain/repository/CategoryRepository.java @@ -14,4 +14,6 @@ public interface CategoryRepository extends JpaRepository { @Query("SELECT c FROM Category c WHERE c.id = 600") Category findCategoryETC(); + + Boolean existsByEngNameAndKorName(String engName, String korName); } diff --git a/backend/src/main/java/hanglog/category/dto/request/CategoryRequest.java b/backend/src/main/java/hanglog/category/dto/request/CategoryRequest.java new file mode 100644 index 000000000..ae09be647 --- /dev/null +++ b/backend/src/main/java/hanglog/category/dto/request/CategoryRequest.java @@ -0,0 +1,19 @@ +package hanglog.category.dto.request; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class CategoryRequest { + + @NotBlank(message = "영어이름을 입력해주세요.") + @Size(max = 50, message = "영어이름은 50자를 초과할 수 없습니다.") + private final String engName; + + @NotBlank(message = "한글 이을 입력해주세요.") + @Size(max = 50, message = "한글 이름은 50자를 초과할 수 없습니다.") + private final String korName; +} diff --git a/backend/src/main/java/hanglog/category/service/CategoryService.java b/backend/src/main/java/hanglog/category/service/CategoryService.java index ed8e4455b..bcd29d9b4 100644 --- a/backend/src/main/java/hanglog/category/service/CategoryService.java +++ b/backend/src/main/java/hanglog/category/service/CategoryService.java @@ -1,9 +1,13 @@ package hanglog.category.service; +import static hanglog.global.exception.ExceptionCode.DUPLICATED_CATEGORY_NAME; + import hanglog.category.domain.Category; import hanglog.category.domain.repository.CategoryRepository; +import hanglog.category.dto.request.CategoryRequest; import hanglog.category.dto.response.CategoryDetailResponse; import hanglog.category.dto.response.CategoryResponse; +import hanglog.global.exception.BadRequestException; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -31,4 +35,16 @@ public List getAllCategoriesDetail() { .map(CategoryDetailResponse::of) .toList(); } + + public Long save(final CategoryRequest categoryRequest) { + validateCategoryDuplicate(categoryRequest); + + return categoryRepository.save(Category.of(categoryRequest)).getId(); + } + + private void validateCategoryDuplicate(final CategoryRequest categoryRequest) { + if (categoryRepository.existsByEngNameAndKorName(categoryRequest.getEngName(), categoryRequest.getKorName())) { + throw new BadRequestException(DUPLICATED_CATEGORY_NAME); + } + } } diff --git a/backend/src/main/java/hanglog/global/exception/ExceptionCode.java b/backend/src/main/java/hanglog/global/exception/ExceptionCode.java index 6c75d5652..c4c78f4a9 100644 --- a/backend/src/main/java/hanglog/global/exception/ExceptionCode.java +++ b/backend/src/main/java/hanglog/global/exception/ExceptionCode.java @@ -65,6 +65,9 @@ public enum ExceptionCode { INVALID_ADMIN_AUTHORITY(8201, "해당 관리자 기능에 대한 접근 권한이 없습니다."), DUPLICATED_CITY_NAME(8301, "중복된 나라, 도시 이름입니다."), NOT_FOUND_CITY(8302, "요청한 ID에 해당하는 도시를 찾을 수 없습니다."), + DUPLICATED_CATEGORY_NAME(8311, "중복된 카테고리 이름입니다."), + NOT_FOUND_CATEGORY(8312, "요청한 ID에 해당하는 카테고리를 찾을 수 없습니다."), + INVALID_AUTHORIZATION_CODE(9001, "유효하지 않은 인증 코드입니다."), NOT_SUPPORTED_OAUTH_SERVICE(9002, "해당 OAuth 서비스는 제공하지 않습니다."), From 9d668f7d34095bcb2c464db6817e1c87b30c644f Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 17:45:02 +0900 Subject: [PATCH 34/88] =?UTF-8?q?test:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=88=98=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../category/service/CategoryServiceTest.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/backend/src/test/java/hanglog/category/service/CategoryServiceTest.java b/backend/src/test/java/hanglog/category/service/CategoryServiceTest.java index 1c6d38487..ab764b33a 100644 --- a/backend/src/test/java/hanglog/category/service/CategoryServiceTest.java +++ b/backend/src/test/java/hanglog/category/service/CategoryServiceTest.java @@ -5,7 +5,9 @@ import static hanglog.category.fixture.CategoryFixture.FOOD; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.BDDMockito.given; @@ -16,6 +18,7 @@ import hanglog.category.dto.response.CategoryResponse; import hanglog.global.exception.BadRequestException; import java.util.List; +import java.util.Optional; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -98,4 +101,44 @@ void save_DuplicateFail() { assertThatThrownBy(() -> categoryService.save(categoryRequest)) .isInstanceOf(BadRequestException.class); } + + @DisplayName("카테고리 정보를 수정한다.") + @Test + void update() { + // given + final CategoryRequest categoryRequest = new CategoryRequest("newName", FOOD.getKorName()); + + given(categoryRepository.findById(anyLong())).willReturn(Optional.of(FOOD)); + given(categoryRepository.existsByEngNameAndKorName(anyString(), anyString())).willReturn(false); + + // when & then + assertDoesNotThrow(() -> categoryService.update(FOOD.getId(), categoryRequest)); + } + + @DisplayName("수정한 카테고리의 이름이 다른 카테고리와 중복되면 예외가 발생한다.") + @Test + void update_DuplicateFail() { + // given + final CategoryRequest categoryRequest = new CategoryRequest("newName", FOOD.getKorName()); + + given(categoryRepository.findById(anyLong())).willReturn(Optional.of(FOOD)); + given(categoryRepository.existsByEngNameAndKorName(anyString(), anyString())).willReturn(true); + + // when & then + assertThatThrownBy(() -> categoryService.update(FOOD.getId(), categoryRequest)) + .isInstanceOf(BadRequestException.class); + } + + @DisplayName("ID에 해당하는 카테고리가 존재하지 않으면 예외가 발생한다.") + @Test + void update_NotFoundFail() { + // given + final CategoryRequest categoryRequest = new CategoryRequest("newName", FOOD.getKorName()); + + given(categoryRepository.findById(anyLong())).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> categoryService.update(FOOD.getId(), categoryRequest)) + .isInstanceOf(BadRequestException.class); + } } From 281d0cc3b794f51f058875253889f47be3dcc0d4 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 17:45:13 +0900 Subject: [PATCH 35/88] =?UTF-8?q?feat:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=88=98=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/hanglog/category/domain/Category.java | 9 +++++++++ .../category/service/CategoryService.java | 16 ++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/backend/src/main/java/hanglog/category/domain/Category.java b/backend/src/main/java/hanglog/category/domain/Category.java index 82cafc851..8b0874c35 100644 --- a/backend/src/main/java/hanglog/category/domain/Category.java +++ b/backend/src/main/java/hanglog/category/domain/Category.java @@ -35,6 +35,15 @@ public static Category of(final CategoryRequest categoryRequest) { return new Category(null, categoryRequest.getEngName(), categoryRequest.getKorName()); } + public void update(final CategoryRequest categoryRequest) { + this.engName = categoryRequest.getEngName(); + this.korName = categoryRequest.getKorName(); + } + + public boolean isSameNames(final String engName, final String korName) { + return this.engName.equals(engName) && this.korName.equals(korName); + } + @Override public boolean equals(final Object o) { if (this == o) { diff --git a/backend/src/main/java/hanglog/category/service/CategoryService.java b/backend/src/main/java/hanglog/category/service/CategoryService.java index bcd29d9b4..9f8ac1a51 100644 --- a/backend/src/main/java/hanglog/category/service/CategoryService.java +++ b/backend/src/main/java/hanglog/category/service/CategoryService.java @@ -1,6 +1,7 @@ package hanglog.category.service; import static hanglog.global.exception.ExceptionCode.DUPLICATED_CATEGORY_NAME; +import static hanglog.global.exception.ExceptionCode.NOT_FOUND_CATEGORY_ID; import hanglog.category.domain.Category; import hanglog.category.domain.repository.CategoryRepository; @@ -47,4 +48,19 @@ private void validateCategoryDuplicate(final CategoryRequest categoryRequest) { throw new BadRequestException(DUPLICATED_CATEGORY_NAME); } } + + public void update(final Long id, final CategoryRequest categoryRequest) { + final Category category = categoryRepository.findById(id) + .orElseThrow(() -> new BadRequestException(NOT_FOUND_CATEGORY_ID)); + + validateCategoryDuplicate(category, categoryRequest); + + category.update(categoryRequest); + } + + private void validateCategoryDuplicate(final Category category, final CategoryRequest categoryRequest) { + if (!category.isSameNames(categoryRequest.getEngName(), categoryRequest.getKorName())) { + validateCategoryDuplicate(categoryRequest); + } + } } From 98fd96ccf5e7fba04ae38693888ab2982e495f10 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 18:45:24 +0900 Subject: [PATCH 36/88] =?UTF-8?q?test:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=84=B8=EB=B6=80=20=EC=A0=95=EB=B3=B4=20API=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AdminCategoryControllerTest.java | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 backend/src/test/java/hanglog/admin/presentation/AdminCategoryControllerTest.java diff --git a/backend/src/test/java/hanglog/admin/presentation/AdminCategoryControllerTest.java b/backend/src/test/java/hanglog/admin/presentation/AdminCategoryControllerTest.java new file mode 100644 index 000000000..1dc92fadc --- /dev/null +++ b/backend/src/test/java/hanglog/admin/presentation/AdminCategoryControllerTest.java @@ -0,0 +1,92 @@ +package hanglog.admin.presentation; + +import static hanglog.category.fixture.CategoryFixture.FOOD; +import static hanglog.global.restdocs.RestDocsConfiguration.field; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doNothing; +import static org.springframework.http.HttpHeaders.AUTHORIZATION; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import hanglog.category.dto.response.CategoryDetailResponse; +import hanglog.category.service.CategoryService; +import hanglog.global.ControllerTest; +import hanglog.login.domain.MemberTokens; +import jakarta.servlet.http.Cookie; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.MockMvc; + + +@WebMvcTest(AdminCategoryController.class) +@MockBean(JpaMetamodelMappingContext.class) +@AutoConfigureRestDocs +class AdminCategoryControllerTest extends ControllerTest { + + private static final MemberTokens MEMBER_TOKENS = new MemberTokens("refreshToken", "accessToken"); + private static final Cookie COOKIE = new Cookie("refresh-token", MEMBER_TOKENS.getRefreshToken()); + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private CategoryService categoryService; + + @BeforeEach + void setUp() { + given(refreshTokenRepository.existsById(any())).willReturn(true); + doNothing().when(jwtProvider).validateTokens(any()); + given(jwtProvider.getSubject(any())).willReturn("1"); + given(adminMemberRepository.existsAdminMemberByIdAndAdminType(any(), any())).willReturn(false); + } + + @DisplayName("카테고리 상세 목록을 조회한다.") + @Test + void getCategoriesDetail() throws Exception { + // given + final CategoryDetailResponse response = CategoryDetailResponse.of(FOOD); + + given(categoryService.getAllCategoriesDetail()).willReturn(List.of(response)); + + // when & then + mockMvc.perform(get("/admin/categories") + .header(AUTHORIZATION, MEMBER_TOKENS.getAccessToken()) + .cookie(COOKIE)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id").value(response.getId())) + .andExpect(jsonPath("$[0].engName").value(response.getEngName())) + .andExpect(jsonPath("$[0].korName").value(response.getKorName())) + .andDo(restDocs.document( + responseFields( + fieldWithPath("[].id") + .type(JsonFieldType.NUMBER) + .description("카테고리 ID") + .attributes(field("constraint", "양의 정수")), + fieldWithPath("[].engName") + .type(JsonFieldType.STRING) + .description("영어 이름") + .attributes(field("constraint", "50자 이내의 영어 문자열")), + fieldWithPath("[].korName") + .type(JsonFieldType.STRING) + .description("한글 이름") + .attributes(field("constraint", "50자 이내의 한글 문자열")) + ) + )); + } +} From 47e4d08f2228c58e50059d33a81ad4f9aa9912b8 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 18:45:33 +0900 Subject: [PATCH 37/88] =?UTF-8?q?feat:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=84=B8=EB=B6=80=20=EC=A0=95=EB=B3=B4=20API=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/AdminCategoryController.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 backend/src/main/java/hanglog/admin/presentation/AdminCategoryController.java diff --git a/backend/src/main/java/hanglog/admin/presentation/AdminCategoryController.java b/backend/src/main/java/hanglog/admin/presentation/AdminCategoryController.java new file mode 100644 index 000000000..00bd50aed --- /dev/null +++ b/backend/src/main/java/hanglog/admin/presentation/AdminCategoryController.java @@ -0,0 +1,31 @@ +package hanglog.admin.presentation; + + +import hanglog.auth.AdminAuth; +import hanglog.auth.AdminOnly; +import hanglog.auth.domain.Accessor; +import hanglog.category.dto.response.CategoryDetailResponse; +import hanglog.category.service.CategoryService; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/admin/categories") +public class AdminCategoryController { + + private final CategoryService categoryService; + + @GetMapping + @AdminOnly + public ResponseEntity> getCategoriesDetail( + @AdminAuth final Accessor accessor + ) { + final List categoriesDetail = categoryService.getAllCategoriesDetail(); + return ResponseEntity.ok(categoriesDetail); + } +} From afb2e947102b68c8a51779ba55f6b3ce441309ba Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 18:51:10 +0900 Subject: [PATCH 38/88] =?UTF-8?q?test:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=B6=94=EA=B0=80=20API=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AdminCategoryControllerTest.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/backend/src/test/java/hanglog/admin/presentation/AdminCategoryControllerTest.java b/backend/src/test/java/hanglog/admin/presentation/AdminCategoryControllerTest.java index 1dc92fadc..fc136eee5 100644 --- a/backend/src/test/java/hanglog/admin/presentation/AdminCategoryControllerTest.java +++ b/backend/src/test/java/hanglog/admin/presentation/AdminCategoryControllerTest.java @@ -7,12 +7,16 @@ import static org.mockito.Mockito.doNothing; import static org.springframework.http.HttpHeaders.AUTHORIZATION; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.fasterxml.jackson.databind.ObjectMapper; +import hanglog.category.dto.request.CategoryRequest; import hanglog.category.dto.response.CategoryDetailResponse; import hanglog.category.service.CategoryService; import hanglog.global.ControllerTest; @@ -27,6 +31,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext; +import org.springframework.http.MediaType; import org.springframework.restdocs.payload.JsonFieldType; import org.springframework.test.web.servlet.MockMvc; @@ -89,4 +94,35 @@ void getCategoriesDetail() throws Exception { ) )); } + + @DisplayName("새로운 카테고리를 생성한다.") + @Test + void createCategory() throws Exception { + // given + final CategoryRequest request = new CategoryRequest("engName", "korName"); + + given(categoryService.save(any(CategoryRequest.class))).willReturn(1L); + + // when & then + mockMvc.perform(post("/admin/categories") + .header(AUTHORIZATION, MEMBER_TOKENS.getAccessToken()) + .cookie(COOKIE) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isCreated()) + .andExpect(header().string("Location", "/admin/categories/1")) + .andDo(restDocs.document( + requestFields( + fieldWithPath("engName") + .type(JsonFieldType.STRING) + .description("영어 이름") + .attributes(field("constraint", "50자 이내의 영어 문자열")), + fieldWithPath("korName") + .type(JsonFieldType.STRING) + .description("한글 이름") + .attributes(field("constraint", "50자 이내의 한글 문자열")) + ) + )); + } + } From 02d375d607c7149f4be4157fb90465254e391a5b Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 18:51:20 +0900 Subject: [PATCH 39/88] =?UTF-8?q?feat:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=B6=94=EA=B0=80=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/AdminCategoryController.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/backend/src/main/java/hanglog/admin/presentation/AdminCategoryController.java b/backend/src/main/java/hanglog/admin/presentation/AdminCategoryController.java index 00bd50aed..9adac47d4 100644 --- a/backend/src/main/java/hanglog/admin/presentation/AdminCategoryController.java +++ b/backend/src/main/java/hanglog/admin/presentation/AdminCategoryController.java @@ -4,12 +4,17 @@ import hanglog.auth.AdminAuth; import hanglog.auth.AdminOnly; import hanglog.auth.domain.Accessor; +import hanglog.category.dto.request.CategoryRequest; import hanglog.category.dto.response.CategoryDetailResponse; import hanglog.category.service.CategoryService; +import jakarta.validation.Valid; +import java.net.URI; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +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; @@ -28,4 +33,14 @@ public ResponseEntity> getCategoriesDetail( final List categoriesDetail = categoryService.getAllCategoriesDetail(); return ResponseEntity.ok(categoriesDetail); } + + @PostMapping + @AdminOnly + public ResponseEntity createCategory( + @AdminAuth final Accessor accessor, + @RequestBody @Valid final CategoryRequest categoryRequest + ) { + final Long categoryId = categoryService.save(categoryRequest); + return ResponseEntity.created(URI.create("/admin/categories/" + categoryId)).build(); + } } From b98d7f1a32132e51ee61e53e2d69bfb1f2921596 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 18:56:02 +0900 Subject: [PATCH 40/88] =?UTF-8?q?test:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=88=98=EC=A0=95=20API=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AdminCategoryControllerTest.java | 35 +++++++++++++++++++ .../presentation/AdminCityControllerTest.java | 1 - 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/backend/src/test/java/hanglog/admin/presentation/AdminCategoryControllerTest.java b/backend/src/test/java/hanglog/admin/presentation/AdminCategoryControllerTest.java index fc136eee5..4c9a93ff4 100644 --- a/backend/src/test/java/hanglog/admin/presentation/AdminCategoryControllerTest.java +++ b/backend/src/test/java/hanglog/admin/presentation/AdminCategoryControllerTest.java @@ -8,9 +8,12 @@ import static org.springframework.http.HttpHeaders.AUTHORIZATION; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.put; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -125,4 +128,36 @@ void createCategory() throws Exception { )); } + @DisplayName("카테고리 정보를 수정한다.") + @Test + void updateCategory() throws Exception { + // given + final CategoryRequest request = new CategoryRequest("engName", "korName"); + + doNothing().when(categoryService).update(1L, request); + + // when & then + mockMvc.perform(put("/admin/categories/{categoryId}", 1) + .header(AUTHORIZATION, MEMBER_TOKENS.getAccessToken()) + .cookie(COOKIE) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isNoContent()) + .andDo(restDocs.document( + pathParameters( + parameterWithName("categoryId") + .description("카테고리 ID") + ), + requestFields( + fieldWithPath("engName") + .type(JsonFieldType.STRING) + .description("영어 이름") + .attributes(field("constraint", "50자 이내의 영어 문자열")), + fieldWithPath("korName") + .type(JsonFieldType.STRING) + .description("한글 이름") + .attributes(field("constraint", "50자 이내의 한글 문자열")) + ) + )); + } } diff --git a/backend/src/test/java/hanglog/admin/presentation/AdminCityControllerTest.java b/backend/src/test/java/hanglog/admin/presentation/AdminCityControllerTest.java index 0c3e5bc8d..838ba73c0 100644 --- a/backend/src/test/java/hanglog/admin/presentation/AdminCityControllerTest.java +++ b/backend/src/test/java/hanglog/admin/presentation/AdminCityControllerTest.java @@ -152,7 +152,6 @@ void createCity() throws Exception { )); } - @DisplayName("도시 정보를 수정한다.") @Test void updateCity() throws Exception { From 3ae8de857b3e984a6474951e2f65789ab2421d28 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 18:56:13 +0900 Subject: [PATCH 41/88] =?UTF-8?q?feat:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=88=98=EC=A0=95=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/presentation/AdminCategoryController.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/backend/src/main/java/hanglog/admin/presentation/AdminCategoryController.java b/backend/src/main/java/hanglog/admin/presentation/AdminCategoryController.java index 9adac47d4..daaa3f7db 100644 --- a/backend/src/main/java/hanglog/admin/presentation/AdminCategoryController.java +++ b/backend/src/main/java/hanglog/admin/presentation/AdminCategoryController.java @@ -13,7 +13,9 @@ import lombok.RequiredArgsConstructor; 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.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -43,4 +45,15 @@ public ResponseEntity createCategory( final Long categoryId = categoryService.save(categoryRequest); return ResponseEntity.created(URI.create("/admin/categories/" + categoryId)).build(); } + + @PutMapping("/{categoryId}") + @AdminOnly + public ResponseEntity updateCategory( + @AdminAuth final Accessor accessor, + @PathVariable final Long categoryId, + @RequestBody @Valid final CategoryRequest categoryRequest + ) { + categoryService.update(categoryId, categoryRequest); + return ResponseEntity.noContent().build(); + } } From 8e3fe7012c803030f27d0111e730447d0d500f23 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 20:00:08 +0900 Subject: [PATCH 42/88] =?UTF-8?q?test:=20=ED=99=98=EC=9C=A8=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=95=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../currency/fixture/CurrencyFixture.java | 37 +++++++++++++ .../currency/service/CurrencyServiceTest.java | 53 +++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 backend/src/test/java/hanglog/currency/fixture/CurrencyFixture.java create mode 100644 backend/src/test/java/hanglog/currency/service/CurrencyServiceTest.java diff --git a/backend/src/test/java/hanglog/currency/fixture/CurrencyFixture.java b/backend/src/test/java/hanglog/currency/fixture/CurrencyFixture.java new file mode 100644 index 000000000..a4c459007 --- /dev/null +++ b/backend/src/test/java/hanglog/currency/fixture/CurrencyFixture.java @@ -0,0 +1,37 @@ +package hanglog.currency.fixture; + +import hanglog.currency.domain.Currency; +import java.time.LocalDate; + +public class CurrencyFixture { + + public static final Currency CURRENCY_1 = new Currency( + 1L, + LocalDate.of(2023, 7, 1), + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0 + ); + + public static final Currency CURRENCY_2 = new Currency( + 2L, + LocalDate.of(2023, 7, 2), + 2.0, + 2.0, + 2.0, + 2.0, + 2.0, + 2.0, + 2.0, + 2.0, + 2.0, + 2.0 + ); +} diff --git a/backend/src/test/java/hanglog/currency/service/CurrencyServiceTest.java b/backend/src/test/java/hanglog/currency/service/CurrencyServiceTest.java new file mode 100644 index 000000000..636a44fa2 --- /dev/null +++ b/backend/src/test/java/hanglog/currency/service/CurrencyServiceTest.java @@ -0,0 +1,53 @@ +package hanglog.currency.service; + +import static hanglog.currency.fixture.CurrencyFixture.CURRENCY_1; +import static hanglog.currency.fixture.CurrencyFixture.CURRENCY_2; +import static org.assertj.core.api.SoftAssertions.assertSoftly; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; + +import hanglog.currency.domain.repository.CurrencyRepository; +import hanglog.currency.dto.CurrencyListResponse; +import hanglog.currency.dto.CurrencyResponse; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.transaction.annotation.Transactional; + +@ExtendWith(MockitoExtension.class) +@Transactional +class CurrencyServiceTest { + + @InjectMocks + private CurrencyService currencyService; + + @Mock + private CurrencyRepository currencyRepository; + + @DisplayName("환율 리스트를 페이지로 조회한다.") + @Test + void getCurrenciesByPage() { + // given + final Pageable pageable = PageRequest.of(0, 10); + + given(currencyRepository.findCurrenciesByPageable(any())).willReturn(List.of(CURRENCY_1, CURRENCY_2)); + given(currencyRepository.count()).willReturn(2L); + + // when + final CurrencyListResponse actual = currencyService.getCurrenciesByPage(pageable); + + // then + assertSoftly(softly -> { + softly.assertThat(actual.getCurrencies()).size().isEqualTo(2); + softly.assertThat(actual.getCurrencies()).usingRecursiveComparison() + .isEqualTo(List.of(CurrencyResponse.of(CURRENCY_1), CurrencyResponse.of(CURRENCY_2))); + softly.assertThat(actual.getLastPageIndex()).isEqualTo(1L); + }); + } +} From 29c05aa33df707145a10e05b9bce4f75522b3f1b Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 20:00:23 +0900 Subject: [PATCH 43/88] =?UTF-8?q?feat:=20=ED=99=98=EC=9C=A8=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=95=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/repository/CurrencyRepository.java | 4 ++ .../currency/dto/CurrencyListResponse.java | 14 ++++++ .../currency/dto/CurrencyResponse.java | 43 +++++++++++++++++++ .../currency/service/CurrencyService.java | 23 ++++++++++ 4 files changed, 84 insertions(+) create mode 100644 backend/src/main/java/hanglog/currency/dto/CurrencyListResponse.java create mode 100644 backend/src/main/java/hanglog/currency/dto/CurrencyResponse.java diff --git a/backend/src/main/java/hanglog/currency/domain/repository/CurrencyRepository.java b/backend/src/main/java/hanglog/currency/domain/repository/CurrencyRepository.java index 776e82e61..f2a7ffbfd 100644 --- a/backend/src/main/java/hanglog/currency/domain/repository/CurrencyRepository.java +++ b/backend/src/main/java/hanglog/currency/domain/repository/CurrencyRepository.java @@ -2,7 +2,9 @@ import hanglog.currency.domain.Currency; import java.time.LocalDate; +import java.util.List; import java.util.Optional; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; public interface CurrencyRepository extends JpaRepository { @@ -12,4 +14,6 @@ public interface CurrencyRepository extends JpaRepository { Optional findTopByOrderByDateAsc(); boolean existsByDate(LocalDate date); + + List findCurrenciesByPageable(final Pageable pageable); } diff --git a/backend/src/main/java/hanglog/currency/dto/CurrencyListResponse.java b/backend/src/main/java/hanglog/currency/dto/CurrencyListResponse.java new file mode 100644 index 000000000..0f346a5cc --- /dev/null +++ b/backend/src/main/java/hanglog/currency/dto/CurrencyListResponse.java @@ -0,0 +1,14 @@ +package hanglog.currency.dto; + +import java.util.List; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + + +@Getter +@RequiredArgsConstructor +public class CurrencyListResponse { + + final List currencies; + final Long lastPageIndex; +} diff --git a/backend/src/main/java/hanglog/currency/dto/CurrencyResponse.java b/backend/src/main/java/hanglog/currency/dto/CurrencyResponse.java new file mode 100644 index 000000000..a2428206b --- /dev/null +++ b/backend/src/main/java/hanglog/currency/dto/CurrencyResponse.java @@ -0,0 +1,43 @@ +package hanglog.currency.dto; + +import static lombok.AccessLevel.PRIVATE; + +import hanglog.currency.domain.Currency; +import java.time.LocalDate; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor(access = PRIVATE) +public class CurrencyResponse { + + private final Long id; + private final LocalDate date; + private final Double usd; + private final Double eur; + private final Double gbp; + private final Double jpy; + private final Double cny; + private final Double chf; + private final Double sgd; + private final Double thb; + private final Double hkd; + private final Double krw; + + public static CurrencyResponse of(final Currency currency) { + return new CurrencyResponse( + currency.getId(), + currency.getDate(), + currency.getUsd(), + currency.getEur(), + currency.getGbp(), + currency.getJpy(), + currency.getCny(), + currency.getChf(), + currency.getSgd(), + currency.getThb(), + currency.getHkd(), + currency.getKrw() + ); + } +} diff --git a/backend/src/main/java/hanglog/currency/service/CurrencyService.java b/backend/src/main/java/hanglog/currency/service/CurrencyService.java index 0d696e2b8..369363c04 100644 --- a/backend/src/main/java/hanglog/currency/service/CurrencyService.java +++ b/backend/src/main/java/hanglog/currency/service/CurrencyService.java @@ -19,6 +19,8 @@ import hanglog.currency.domain.Currency; import hanglog.currency.domain.repository.CurrencyRepository; import hanglog.currency.domain.type.CurrencyType; +import hanglog.currency.dto.CurrencyListResponse; +import hanglog.currency.dto.CurrencyResponse; import hanglog.currency.dto.SingleCurrencyResponse; import hanglog.global.exception.BadRequestException; import hanglog.global.exception.InvalidCurrencyDateException; @@ -30,6 +32,7 @@ import java.util.Map; import java.util.Objects; import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.domain.Pageable; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -129,4 +132,24 @@ private Currency createCurrency(final LocalDate date, final Map currencies = currencyRepository.findCurrenciesByPageable(pageable.previousOrFirst()); + final List currencyResponses = currencies.stream() + .map(CurrencyResponse::of) + .toList(); + final Long lastPageIndex = getLastPageIndex(pageable.getPageSize()); + + return new CurrencyListResponse(currencyResponses, lastPageIndex); + } + + private Long getLastPageIndex(final int pageSize) { + final long totalCount = currencyRepository.count(); + final long lastPageIndex = totalCount / pageSize; + if (totalCount % pageSize == 0) { + return lastPageIndex; + } + return lastPageIndex + 1; + } } From e43fa335c2a33971752cdd783f55fac9e1792c71 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 20:31:31 +0900 Subject: [PATCH 44/88] =?UTF-8?q?fix:=20=ED=99=98=EC=9C=A8=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20repository=20=EB=A9=94=EC=86=8C=EB=93=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hanglog/currency/domain/repository/CurrencyRepository.java | 2 +- .../src/main/java/hanglog/currency/service/CurrencyService.java | 2 +- .../test/java/hanglog/currency/service/CurrencyServiceTest.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/hanglog/currency/domain/repository/CurrencyRepository.java b/backend/src/main/java/hanglog/currency/domain/repository/CurrencyRepository.java index f2a7ffbfd..1bb0071bc 100644 --- a/backend/src/main/java/hanglog/currency/domain/repository/CurrencyRepository.java +++ b/backend/src/main/java/hanglog/currency/domain/repository/CurrencyRepository.java @@ -15,5 +15,5 @@ public interface CurrencyRepository extends JpaRepository { boolean existsByDate(LocalDate date); - List findCurrenciesByPageable(final Pageable pageable); + List findBy(final Pageable pageable); } diff --git a/backend/src/main/java/hanglog/currency/service/CurrencyService.java b/backend/src/main/java/hanglog/currency/service/CurrencyService.java index 369363c04..30850c2dc 100644 --- a/backend/src/main/java/hanglog/currency/service/CurrencyService.java +++ b/backend/src/main/java/hanglog/currency/service/CurrencyService.java @@ -135,7 +135,7 @@ private Currency createCurrency(final LocalDate date, final Map currencies = currencyRepository.findCurrenciesByPageable(pageable.previousOrFirst()); + final List currencies = currencyRepository.findBy(pageable.previousOrFirst()); final List currencyResponses = currencies.stream() .map(CurrencyResponse::of) .toList(); diff --git a/backend/src/test/java/hanglog/currency/service/CurrencyServiceTest.java b/backend/src/test/java/hanglog/currency/service/CurrencyServiceTest.java index 636a44fa2..7c685037d 100644 --- a/backend/src/test/java/hanglog/currency/service/CurrencyServiceTest.java +++ b/backend/src/test/java/hanglog/currency/service/CurrencyServiceTest.java @@ -36,7 +36,7 @@ void getCurrenciesByPage() { // given final Pageable pageable = PageRequest.of(0, 10); - given(currencyRepository.findCurrenciesByPageable(any())).willReturn(List.of(CURRENCY_1, CURRENCY_2)); + given(currencyRepository.findBy(any())).willReturn(List.of(CURRENCY_1, CURRENCY_2)); given(currencyRepository.count()).willReturn(2L); // when From 679a1a2b3a6342c94846cdc77642b2cf5659d6ab Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 21:03:23 +0900 Subject: [PATCH 45/88] =?UTF-8?q?test:=20=ED=99=98=EC=9C=A8=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20Api=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/AdminCityControllerTest.java | 1 - .../AdminCurrencyControllerTest.java | 147 ++++++++++++++++++ 2 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 backend/src/test/java/hanglog/admin/presentation/AdminCurrencyControllerTest.java diff --git a/backend/src/test/java/hanglog/admin/presentation/AdminCityControllerTest.java b/backend/src/test/java/hanglog/admin/presentation/AdminCityControllerTest.java index 838ba73c0..66564a04a 100644 --- a/backend/src/test/java/hanglog/admin/presentation/AdminCityControllerTest.java +++ b/backend/src/test/java/hanglog/admin/presentation/AdminCityControllerTest.java @@ -57,7 +57,6 @@ class AdminCityControllerTest extends ControllerTest { @MockBean private CityService cityService; - @BeforeEach void setUp() { given(refreshTokenRepository.existsById(any())).willReturn(true); diff --git a/backend/src/test/java/hanglog/admin/presentation/AdminCurrencyControllerTest.java b/backend/src/test/java/hanglog/admin/presentation/AdminCurrencyControllerTest.java new file mode 100644 index 000000000..e1846f629 --- /dev/null +++ b/backend/src/test/java/hanglog/admin/presentation/AdminCurrencyControllerTest.java @@ -0,0 +1,147 @@ +package hanglog.admin.presentation; + +import static hanglog.currency.fixture.CurrencyFixture.CURRENCY_1; +import static hanglog.currency.fixture.CurrencyFixture.CURRENCY_2; +import static hanglog.global.restdocs.RestDocsConfiguration.field; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doNothing; +import static org.springframework.http.HttpHeaders.AUTHORIZATION; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import hanglog.currency.dto.CurrencyListResponse; +import hanglog.currency.dto.CurrencyResponse; +import hanglog.currency.service.CurrencyService; +import hanglog.global.ControllerTest; +import hanglog.login.domain.MemberTokens; +import jakarta.servlet.http.Cookie; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.MockMvc; + + +@WebMvcTest(AdminCurrencyController.class) +@MockBean(JpaMetamodelMappingContext.class) +@AutoConfigureRestDocs +class AdminCurrencyControllerTest extends ControllerTest { + + + private static final MemberTokens MEMBER_TOKENS = new MemberTokens("refreshToken", "accessToken"); + private static final Cookie COOKIE = new Cookie("refresh-token", MEMBER_TOKENS.getRefreshToken()); + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private CurrencyService currencyService; + + @BeforeEach + void setUp() { + given(refreshTokenRepository.existsById(any())).willReturn(true); + doNothing().when(jwtProvider).validateTokens(any()); + given(jwtProvider.getSubject(any())).willReturn("1"); + given(adminMemberRepository.existsAdminMemberByIdAndAdminType(any(), any())).willReturn(false); + } + + @DisplayName("도시 상세 목록을 조회한다.") + @Test + void getCitiesDetail() throws Exception { + // given + final CurrencyResponse response1 = CurrencyResponse.of(CURRENCY_1); + final CurrencyResponse response2 = CurrencyResponse.of(CURRENCY_2); + final CurrencyListResponse pageResponse = new CurrencyListResponse(List.of(response1, response2), 1L); + + given(currencyService.getCurrenciesByPage(any())).willReturn(pageResponse); + + // when & then + mockMvc.perform(get("/admin/currencies") + .header(AUTHORIZATION, MEMBER_TOKENS.getAccessToken()) + .cookie(COOKIE)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.currencies.[0].id").value(response1.getId())) + .andExpect(jsonPath("$.currencies.[0].date").value(response1.getDate().toString())) + .andExpect(jsonPath("$.currencies.[0].usd").value(response1.getUsd())) + .andExpect(jsonPath("$.currencies.[0].eur").value(response1.getEur())) + .andExpect(jsonPath("$.currencies.[0].gbp").value(response1.getGbp())) + .andExpect(jsonPath("$.currencies.[0].jpy").value(response1.getJpy())) + .andExpect(jsonPath("$.currencies.[0].cny").value(response1.getCny())) + .andExpect(jsonPath("$.currencies.[0].chf").value(response1.getChf())) + .andExpect(jsonPath("$.currencies.[0].sgd").value(response1.getSgd())) + .andExpect(jsonPath("$.currencies.[0].thb").value(response1.getThb())) + .andExpect(jsonPath("$.currencies.[0].hkd").value(response1.getHkd())) + .andExpect(jsonPath("$.currencies.[0].krw").value(response1.getKrw())) + .andExpect(jsonPath("$.lastPageIndex").value(pageResponse.getLastPageIndex())) + .andDo(restDocs.document( + responseFields( + fieldWithPath("currencies.[].id") + .type(JsonFieldType.NUMBER) + .description("환율 ID") + .attributes(field("constraint", "양의 정수")), + fieldWithPath("currencies.[].date") + .type(JsonFieldType.STRING) + .description("날짜") + .attributes(field("constraint", "YYYY-MM-DD")), + fieldWithPath("currencies.[].usd") + .type(JsonFieldType.NUMBER) + .description("USD") + .attributes(field("constraint", "double")), + fieldWithPath("currencies.[].eur") + .type(JsonFieldType.NUMBER) + .description("EUR") + .attributes(field("constraint", "double")), + fieldWithPath("currencies.[].gbp") + .type(JsonFieldType.NUMBER) + .description("GBP") + .attributes(field("constraint", "double")), + fieldWithPath("currencies.[].jpy") + .type(JsonFieldType.NUMBER) + .description("JPY") + .attributes(field("constraint", "double")), + fieldWithPath("currencies.[].cny") + .type(JsonFieldType.NUMBER) + .description("CNY") + .attributes(field("constraint", "double")), + fieldWithPath("currencies.[].chf") + .type(JsonFieldType.NUMBER) + .description("CHF") + .attributes(field("constraint", "double")), + fieldWithPath("currencies.[].sgd") + .type(JsonFieldType.NUMBER) + .description("SGD") + .attributes(field("constraint", "double")), + fieldWithPath("currencies.[].thb") + .type(JsonFieldType.NUMBER) + .description("THB") + .attributes(field("constraint", "double")), + fieldWithPath("currencies.[].hkd") + .type(JsonFieldType.NUMBER) + .description("HKD") + .attributes(field("constraint", "double")), + fieldWithPath("currencies.[].krw") + .type(JsonFieldType.NUMBER) + .description("KRW") + .attributes(field("constraint", "double")), + fieldWithPath("lastPageIndex") + .type(JsonFieldType.NUMBER) + .description("마지막 페이지 인덱스") + .attributes(field("constraint", "양의 정수")) + ) + )); + } +} From cd51552112c79c44d5718dbac6a1602da749f173 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 21:03:32 +0900 Subject: [PATCH 46/88] =?UTF-8?q?feat:=20=ED=99=98=EC=9C=A8=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20Api=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/AdminCurrencyController.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 backend/src/main/java/hanglog/admin/presentation/AdminCurrencyController.java diff --git a/backend/src/main/java/hanglog/admin/presentation/AdminCurrencyController.java b/backend/src/main/java/hanglog/admin/presentation/AdminCurrencyController.java new file mode 100644 index 000000000..425b74b6b --- /dev/null +++ b/backend/src/main/java/hanglog/admin/presentation/AdminCurrencyController.java @@ -0,0 +1,35 @@ +package hanglog.admin.presentation; + + +import static org.springframework.data.domain.Sort.Direction.DESC; + +import hanglog.auth.AdminAuth; +import hanglog.auth.AdminOnly; +import hanglog.auth.domain.Accessor; +import hanglog.currency.dto.CurrencyListResponse; +import hanglog.currency.service.CurrencyService; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/admin/currencies") +public class AdminCurrencyController { + + private final CurrencyService currencyService; + + @GetMapping + @AdminOnly + public ResponseEntity getCurrencies( + @AdminAuth final Accessor accessor, + @PageableDefault(sort = "date", direction = DESC) final Pageable pageable + ) { + final CurrencyListResponse currencies = currencyService.getCurrenciesByPage(pageable); + return ResponseEntity.ok().body(currencies); + } +} From 3a0752a8865e64a23c08dd59b86f45943052fb65 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 21:08:28 +0900 Subject: [PATCH 47/88] =?UTF-8?q?rename:=20Currency=20dto=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hanglog/admin/presentation/AdminCurrencyController.java | 2 +- .../currency/dto/{ => response}/CurrencyListResponse.java | 2 +- .../currency/dto/{ => response}/CurrencyResponse.java | 2 +- .../currency/dto/{ => response}/SingleCurrencyResponse.java | 2 +- .../main/java/hanglog/currency/service/CurrencyService.java | 6 +++--- .../admin/presentation/AdminCurrencyControllerTest.java | 4 ++-- .../java/hanglog/currency/service/CurrencyServiceTest.java | 4 ++-- 7 files changed, 11 insertions(+), 11 deletions(-) rename backend/src/main/java/hanglog/currency/dto/{ => response}/CurrencyListResponse.java (85%) rename backend/src/main/java/hanglog/currency/dto/{ => response}/CurrencyResponse.java (96%) rename backend/src/main/java/hanglog/currency/dto/{ => response}/SingleCurrencyResponse.java (86%) diff --git a/backend/src/main/java/hanglog/admin/presentation/AdminCurrencyController.java b/backend/src/main/java/hanglog/admin/presentation/AdminCurrencyController.java index 425b74b6b..12ae91c1b 100644 --- a/backend/src/main/java/hanglog/admin/presentation/AdminCurrencyController.java +++ b/backend/src/main/java/hanglog/admin/presentation/AdminCurrencyController.java @@ -6,7 +6,7 @@ import hanglog.auth.AdminAuth; import hanglog.auth.AdminOnly; import hanglog.auth.domain.Accessor; -import hanglog.currency.dto.CurrencyListResponse; +import hanglog.currency.dto.response.CurrencyListResponse; import hanglog.currency.service.CurrencyService; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; diff --git a/backend/src/main/java/hanglog/currency/dto/CurrencyListResponse.java b/backend/src/main/java/hanglog/currency/dto/response/CurrencyListResponse.java similarity index 85% rename from backend/src/main/java/hanglog/currency/dto/CurrencyListResponse.java rename to backend/src/main/java/hanglog/currency/dto/response/CurrencyListResponse.java index 0f346a5cc..1f7ef889b 100644 --- a/backend/src/main/java/hanglog/currency/dto/CurrencyListResponse.java +++ b/backend/src/main/java/hanglog/currency/dto/response/CurrencyListResponse.java @@ -1,4 +1,4 @@ -package hanglog.currency.dto; +package hanglog.currency.dto.response; import java.util.List; import lombok.Getter; diff --git a/backend/src/main/java/hanglog/currency/dto/CurrencyResponse.java b/backend/src/main/java/hanglog/currency/dto/response/CurrencyResponse.java similarity index 96% rename from backend/src/main/java/hanglog/currency/dto/CurrencyResponse.java rename to backend/src/main/java/hanglog/currency/dto/response/CurrencyResponse.java index a2428206b..735369546 100644 --- a/backend/src/main/java/hanglog/currency/dto/CurrencyResponse.java +++ b/backend/src/main/java/hanglog/currency/dto/response/CurrencyResponse.java @@ -1,4 +1,4 @@ -package hanglog.currency.dto; +package hanglog.currency.dto.response; import static lombok.AccessLevel.PRIVATE; diff --git a/backend/src/main/java/hanglog/currency/dto/SingleCurrencyResponse.java b/backend/src/main/java/hanglog/currency/dto/response/SingleCurrencyResponse.java similarity index 86% rename from backend/src/main/java/hanglog/currency/dto/SingleCurrencyResponse.java rename to backend/src/main/java/hanglog/currency/dto/response/SingleCurrencyResponse.java index 03147c57f..086e848d9 100644 --- a/backend/src/main/java/hanglog/currency/dto/SingleCurrencyResponse.java +++ b/backend/src/main/java/hanglog/currency/dto/response/SingleCurrencyResponse.java @@ -1,4 +1,4 @@ -package hanglog.currency.dto; +package hanglog.currency.dto.response; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; diff --git a/backend/src/main/java/hanglog/currency/service/CurrencyService.java b/backend/src/main/java/hanglog/currency/service/CurrencyService.java index 30850c2dc..c646bb868 100644 --- a/backend/src/main/java/hanglog/currency/service/CurrencyService.java +++ b/backend/src/main/java/hanglog/currency/service/CurrencyService.java @@ -19,9 +19,9 @@ import hanglog.currency.domain.Currency; import hanglog.currency.domain.repository.CurrencyRepository; import hanglog.currency.domain.type.CurrencyType; -import hanglog.currency.dto.CurrencyListResponse; -import hanglog.currency.dto.CurrencyResponse; -import hanglog.currency.dto.SingleCurrencyResponse; +import hanglog.currency.dto.response.CurrencyListResponse; +import hanglog.currency.dto.response.CurrencyResponse; +import hanglog.currency.dto.response.SingleCurrencyResponse; import hanglog.global.exception.BadRequestException; import hanglog.global.exception.InvalidCurrencyDateException; import java.time.DayOfWeek; diff --git a/backend/src/test/java/hanglog/admin/presentation/AdminCurrencyControllerTest.java b/backend/src/test/java/hanglog/admin/presentation/AdminCurrencyControllerTest.java index e1846f629..f5f9459bb 100644 --- a/backend/src/test/java/hanglog/admin/presentation/AdminCurrencyControllerTest.java +++ b/backend/src/test/java/hanglog/admin/presentation/AdminCurrencyControllerTest.java @@ -14,8 +14,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.fasterxml.jackson.databind.ObjectMapper; -import hanglog.currency.dto.CurrencyListResponse; -import hanglog.currency.dto.CurrencyResponse; +import hanglog.currency.dto.response.CurrencyListResponse; +import hanglog.currency.dto.response.CurrencyResponse; import hanglog.currency.service.CurrencyService; import hanglog.global.ControllerTest; import hanglog.login.domain.MemberTokens; diff --git a/backend/src/test/java/hanglog/currency/service/CurrencyServiceTest.java b/backend/src/test/java/hanglog/currency/service/CurrencyServiceTest.java index 7c685037d..ca7b8342f 100644 --- a/backend/src/test/java/hanglog/currency/service/CurrencyServiceTest.java +++ b/backend/src/test/java/hanglog/currency/service/CurrencyServiceTest.java @@ -7,8 +7,8 @@ import static org.mockito.BDDMockito.given; import hanglog.currency.domain.repository.CurrencyRepository; -import hanglog.currency.dto.CurrencyListResponse; -import hanglog.currency.dto.CurrencyResponse; +import hanglog.currency.dto.response.CurrencyListResponse; +import hanglog.currency.dto.response.CurrencyResponse; import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; From 161a90465e7213bda4e0218d168b91133dc3d858 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 21:29:30 +0900 Subject: [PATCH 48/88] =?UTF-8?q?test:=20=ED=99=98=EC=9C=A8=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20=EA=B8=B0=EB=8A=A5=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../currency/service/CurrencyServiceTest.java | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/backend/src/test/java/hanglog/currency/service/CurrencyServiceTest.java b/backend/src/test/java/hanglog/currency/service/CurrencyServiceTest.java index ca7b8342f..c3f3d763c 100644 --- a/backend/src/test/java/hanglog/currency/service/CurrencyServiceTest.java +++ b/backend/src/test/java/hanglog/currency/service/CurrencyServiceTest.java @@ -2,13 +2,18 @@ import static hanglog.currency.fixture.CurrencyFixture.CURRENCY_1; import static hanglog.currency.fixture.CurrencyFixture.CURRENCY_2; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.SoftAssertions.assertSoftly; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; +import hanglog.currency.domain.Currency; import hanglog.currency.domain.repository.CurrencyRepository; +import hanglog.currency.dto.request.CurrencyRequest; import hanglog.currency.dto.response.CurrencyListResponse; import hanglog.currency.dto.response.CurrencyResponse; +import hanglog.global.exception.BadRequestException; import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -50,4 +55,57 @@ void getCurrenciesByPage() { softly.assertThat(actual.getLastPageIndex()).isEqualTo(1L); }); } + + @DisplayName("새로운 환율 정보를 추가한다.") + @Test + void save() { + // given + final CurrencyRequest currencyRequest = new CurrencyRequest( + CURRENCY_1.getDate(), + CURRENCY_1.getUsd(), + CURRENCY_1.getEur(), + CURRENCY_1.getGbp(), + CURRENCY_1.getJpy(), + CURRENCY_1.getCny(), + CURRENCY_1.getChf(), + CURRENCY_1.getSgd(), + CURRENCY_1.getThb(), + CURRENCY_1.getHkd(), + CURRENCY_1.getKrw() + ); + + given(currencyRepository.existsByDate(any())).willReturn(false); + given(currencyRepository.save(any(Currency.class))).willReturn(CURRENCY_1); + + // when + final Long actualId = currencyService.save(currencyRequest); + + // then + assertThat(actualId).isEqualTo(CURRENCY_1.getId()); + } + + @DisplayName("중복된 날짜의 환율을 추가할 수 없다.") + @Test + void save_DuplicateDateFail() { + // given + final CurrencyRequest currencyRequest = new CurrencyRequest( + CURRENCY_1.getDate(), + CURRENCY_1.getUsd(), + CURRENCY_1.getEur(), + CURRENCY_1.getGbp(), + CURRENCY_1.getJpy(), + CURRENCY_1.getCny(), + CURRENCY_1.getChf(), + CURRENCY_1.getSgd(), + CURRENCY_1.getThb(), + CURRENCY_1.getHkd(), + CURRENCY_1.getKrw() + ); + + given(currencyRepository.existsByDate(any())).willReturn(true); + + // when &then + assertThatThrownBy(() -> currencyService.save(currencyRequest)) + .isInstanceOf(BadRequestException.class); + } } From 18583b3aec9f2c6c6ffbf44718b1aa3b41fce863 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 21:29:37 +0900 Subject: [PATCH 49/88] =?UTF-8?q?feat:=20=ED=99=98=EC=9C=A8=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hanglog/currency/domain/Currency.java | 18 +++++ .../currency/dto/request/CurrencyRequest.java | 69 +++++++++++++++++++ .../currency/service/CurrencyService.java | 17 ++++- 3 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 backend/src/main/java/hanglog/currency/dto/request/CurrencyRequest.java diff --git a/backend/src/main/java/hanglog/currency/domain/Currency.java b/backend/src/main/java/hanglog/currency/domain/Currency.java index fb1ef0c82..9cc2dde40 100644 --- a/backend/src/main/java/hanglog/currency/domain/Currency.java +++ b/backend/src/main/java/hanglog/currency/domain/Currency.java @@ -3,6 +3,7 @@ import static jakarta.persistence.GenerationType.IDENTITY; import static lombok.AccessLevel.PROTECTED; +import hanglog.currency.dto.request.CurrencyRequest; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; @@ -71,6 +72,23 @@ public Currency( this(null, date, usd, eur, gbp, jpy, cny, chf, sgd, thb, hkd, krw); } + public static Currency of(final CurrencyRequest currencyRequest) { + return new Currency( + null, + currencyRequest.getDate(), + currencyRequest.getUsd(), + currencyRequest.getEur(), + currencyRequest.getGbp(), + currencyRequest.getJpy(), + currencyRequest.getCny(), + currencyRequest.getChf(), + currencyRequest.getSgd(), + currencyRequest.getThb(), + currencyRequest.getHkd(), + currencyRequest.getKrw() + ); + } + public double getUnitRateOfJpy() { return this.jpy / 100; } diff --git a/backend/src/main/java/hanglog/currency/dto/request/CurrencyRequest.java b/backend/src/main/java/hanglog/currency/dto/request/CurrencyRequest.java new file mode 100644 index 000000000..6f7f6685b --- /dev/null +++ b/backend/src/main/java/hanglog/currency/dto/request/CurrencyRequest.java @@ -0,0 +1,69 @@ +package hanglog.currency.dto.request; + + +import jakarta.validation.constraints.DecimalMax; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotNull; +import java.time.LocalDate; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.format.annotation.DateTimeFormat; + +@Getter +@AllArgsConstructor +public class CurrencyRequest { + + @NotNull(message = "날짜를 입력해주세요.") + @DateTimeFormat(pattern = "yyyy-MM-dd") + private final LocalDate date; + + @NotNull(message = "USD 금액을 입력해주세요.") + @DecimalMin(value = "0", message = "USD 금액이 0원보다 작을 수 없습니다.") + @DecimalMax(value = "100000", message = "USD 금액이 10만원보다 클 수 없습니다.") + private final double usd; + + @NotNull(message = "EUR 금액을 입력해주세요.") + @DecimalMin(value = "0", message = "EUR 금액이 0원보다 작을 수 없습니다.") + @DecimalMax(value = "100000", message = "EUR 금액이 10만원보다 클 수 없습니다.") + private final double eur; + + @NotNull(message = "GBP 금액을 입력해주세요.") + @DecimalMin(value = "0", message = "GBP 금액이 0원보다 작을 수 없습니다.") + @DecimalMax(value = "100000", message = "GBP 금액이 10만원보다 클 수 없습니다.") + private final double gbp; + + @NotNull(message = "JPY 금액을 입력해주세요.") + @DecimalMin(value = "0", message = "JPY 금액이 0원보다 작을 수 없습니다.") + @DecimalMax(value = "100000", message = "JPY 금액이 10만원보다 클 수 없습니다.") + private final double jpy; + + @NotNull(message = "CNY 금액을 입력해주세요.") + @DecimalMin(value = "0", message = "CNY 금액이 0원보다 작을 수 없습니다.") + @DecimalMax(value = "100000", message = "CNY 금액이 10만원보다 클 수 없습니다.") + private final double cny; + + @NotNull(message = "CHF 금액을 입력해주세요.") + @DecimalMin(value = "0", message = "CHF 금액이 0원보다 작을 수 없습니다.") + @DecimalMax(value = "100000", message = "CHF 금액이 10만원보다 클 수 없습니다.") + private final double chf; + + @NotNull(message = "SGD 금액을 입력해주세요.") + @DecimalMin(value = "0", message = "SGD 금액이 0원보다 작을 수 없습니다.") + @DecimalMax(value = "100000", message = "SGD 금액이 10만원보다 클 수 없습니다.") + private final double sgd; + + @NotNull(message = "THB 금액을 입력해주세요.") + @DecimalMin(value = "0", message = "THB 금액이 0원보다 작을 수 없습니다.") + @DecimalMax(value = "100000", message = "THB 금액이 10만원보다 클 수 없습니다.") + private final double thb; + + @NotNull(message = "HKD 금액을 입력해주세요.") + @DecimalMin(value = "0", message = "HKD 금액이 0원보다 작을 수 없습니다.") + @DecimalMax(value = "100000", message = "HKD 금액이 10만원보다 클 수 없습니다.") + private final double hkd; + + @NotNull(message = "KRW 금액을 입력해주세요.") + @DecimalMin(value = "0", message = "KRW 금액이 0원보다 작을 수 없습니다.") + @DecimalMax(value = "100000", message = "KRW 금액이 10만원보다 클 수 없습니다.") + private final double krw; +} diff --git a/backend/src/main/java/hanglog/currency/service/CurrencyService.java b/backend/src/main/java/hanglog/currency/service/CurrencyService.java index c646bb868..790121e17 100644 --- a/backend/src/main/java/hanglog/currency/service/CurrencyService.java +++ b/backend/src/main/java/hanglog/currency/service/CurrencyService.java @@ -19,6 +19,7 @@ import hanglog.currency.domain.Currency; import hanglog.currency.domain.repository.CurrencyRepository; import hanglog.currency.domain.type.CurrencyType; +import hanglog.currency.dto.request.CurrencyRequest; import hanglog.currency.dto.response.CurrencyListResponse; import hanglog.currency.dto.response.CurrencyResponse; import hanglog.currency.dto.response.SingleCurrencyResponse; @@ -70,9 +71,7 @@ public void saveYesterdayCurrency() { } public void saveDailyCurrency(final LocalDate date) { - if (currencyRepository.existsByDate(date)) { - throw new BadRequestException(INVALID_DATE_ALREADY_EXIST); - } + validateDate(date); validateWeekend(date); final List singleCurrencyResponses = requestFilteredCurrencyResponses(date); @@ -89,6 +88,12 @@ public void saveDailyCurrency(final LocalDate date) { currencyRepository.save(currency); } + private void validateDate(final LocalDate date) { + if (currencyRepository.existsByDate(date)) { + throw new BadRequestException(INVALID_DATE_ALREADY_EXIST); + } + } + private void validateWeekend(final LocalDate date) { final DayOfWeek dayOfWeek = date.getDayOfWeek(); if (dayOfWeek.equals(SUNDAY) || dayOfWeek.equals(SATURDAY)) { @@ -152,4 +157,10 @@ private Long getLastPageIndex(final int pageSize) { } return lastPageIndex + 1; } + + public Long save(final CurrencyRequest currencyRequest) { + validateDate(currencyRequest.getDate()); + + return currencyRepository.save(Currency.of(currencyRequest)).getId(); + } } From 9614be7edfde9cf1dbf08eb3f1754f0343eeff97 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 21:46:57 +0900 Subject: [PATCH 50/88] =?UTF-8?q?test:=20=ED=99=98=EC=9C=A8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../currency/service/CurrencyServiceTest.java | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/backend/src/test/java/hanglog/currency/service/CurrencyServiceTest.java b/backend/src/test/java/hanglog/currency/service/CurrencyServiceTest.java index c3f3d763c..216422f85 100644 --- a/backend/src/test/java/hanglog/currency/service/CurrencyServiceTest.java +++ b/backend/src/test/java/hanglog/currency/service/CurrencyServiceTest.java @@ -5,7 +5,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.SoftAssertions.assertSoftly; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.BDDMockito.given; import hanglog.currency.domain.Currency; @@ -14,7 +16,9 @@ import hanglog.currency.dto.response.CurrencyListResponse; import hanglog.currency.dto.response.CurrencyResponse; import hanglog.global.exception.BadRequestException; +import java.time.LocalDate; import java.util.List; +import java.util.Optional; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -108,4 +112,79 @@ void save_DuplicateDateFail() { assertThatThrownBy(() -> currencyService.save(currencyRequest)) .isInstanceOf(BadRequestException.class); } + + @DisplayName("환율 정보를 수정한다.") + @Test + void update() { + // given + final CurrencyRequest currencyRequest = new CurrencyRequest( + CURRENCY_1.getDate(), + 99.99, + CURRENCY_1.getEur(), + CURRENCY_1.getGbp(), + CURRENCY_1.getJpy(), + CURRENCY_1.getCny(), + CURRENCY_1.getChf(), + CURRENCY_1.getSgd(), + CURRENCY_1.getThb(), + CURRENCY_1.getHkd(), + CURRENCY_1.getKrw() + ); + + given(currencyRepository.findById(anyLong())).willReturn(Optional.of(CURRENCY_1)); + + // when & then + assertDoesNotThrow(() -> currencyService.update(CURRENCY_1.getId(), currencyRequest)); + } + + @DisplayName("수정한 환율 정보의 날짜가 다른 환율 정보와 중복되면 예외가 발생한다.") + @Test + void update_DuplicateFail() { + // given + final CurrencyRequest currencyRequest = new CurrencyRequest( + LocalDate.of(9999, 1, 1), + 99.99, + CURRENCY_1.getEur(), + CURRENCY_1.getGbp(), + CURRENCY_1.getJpy(), + CURRENCY_1.getCny(), + CURRENCY_1.getChf(), + CURRENCY_1.getSgd(), + CURRENCY_1.getThb(), + CURRENCY_1.getHkd(), + CURRENCY_1.getKrw() + ); + + given(currencyRepository.findById(anyLong())).willReturn(Optional.of(CURRENCY_1)); + given(currencyRepository.existsByDate(any())).willReturn(true); + + // when & then + assertThatThrownBy(() -> currencyService.update(CURRENCY_1.getId(), currencyRequest)) + .isInstanceOf(BadRequestException.class); + } + + @DisplayName("ID에 해당하는 환율 정보가 존재하지 않으면 예외가 발생한다.") + @Test + void update_NotFoundFail() { + // given + final CurrencyRequest currencyRequest = new CurrencyRequest( + CURRENCY_1.getDate(), + 99.99, + CURRENCY_1.getEur(), + CURRENCY_1.getGbp(), + CURRENCY_1.getJpy(), + CURRENCY_1.getCny(), + CURRENCY_1.getChf(), + CURRENCY_1.getSgd(), + CURRENCY_1.getThb(), + CURRENCY_1.getHkd(), + CURRENCY_1.getKrw() + ); + + given(currencyRepository.findById(anyLong())).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> currencyService.update(CURRENCY_1.getId(), currencyRequest)) + .isInstanceOf(BadRequestException.class); + } } From cc6a8b76f3a221dde118b07ba3ce03a299a4fb66 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 21:47:05 +0900 Subject: [PATCH 51/88] =?UTF-8?q?feat:=20=ED=99=98=EC=9C=A8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hanglog/currency/domain/Currency.java | 14 +++++++++++++ .../currency/service/CurrencyService.java | 21 ++++++++++++++++--- .../global/exception/ExceptionCode.java | 1 - 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/hanglog/currency/domain/Currency.java b/backend/src/main/java/hanglog/currency/domain/Currency.java index 9cc2dde40..68f50bcae 100644 --- a/backend/src/main/java/hanglog/currency/domain/Currency.java +++ b/backend/src/main/java/hanglog/currency/domain/Currency.java @@ -89,6 +89,20 @@ public static Currency of(final CurrencyRequest currencyRequest) { ); } + public void update(final CurrencyRequest currencyRequest) { + this.date = currencyRequest.getDate(); + this.usd = currencyRequest.getUsd(); + this.eur = currencyRequest.getEur(); + this.gbp = currencyRequest.getGbp(); + this.jpy = currencyRequest.getJpy(); + this.cny = currencyRequest.getCny(); + this.chf = currencyRequest.getChf(); + this.sgd = currencyRequest.getSgd(); + this.thb = currencyRequest.getThb(); + this.hkd = currencyRequest.getHkd(); + this.krw = currencyRequest.getKrw(); + } + public double getUnitRateOfJpy() { return this.jpy / 100; } diff --git a/backend/src/main/java/hanglog/currency/service/CurrencyService.java b/backend/src/main/java/hanglog/currency/service/CurrencyService.java index 790121e17..dcc7ef403 100644 --- a/backend/src/main/java/hanglog/currency/service/CurrencyService.java +++ b/backend/src/main/java/hanglog/currency/service/CurrencyService.java @@ -71,7 +71,7 @@ public void saveYesterdayCurrency() { } public void saveDailyCurrency(final LocalDate date) { - validateDate(date); + validateDateDuplicate(date); validateWeekend(date); final List singleCurrencyResponses = requestFilteredCurrencyResponses(date); @@ -88,7 +88,7 @@ public void saveDailyCurrency(final LocalDate date) { currencyRepository.save(currency); } - private void validateDate(final LocalDate date) { + private void validateDateDuplicate(final LocalDate date) { if (currencyRepository.existsByDate(date)) { throw new BadRequestException(INVALID_DATE_ALREADY_EXIST); } @@ -159,8 +159,23 @@ private Long getLastPageIndex(final int pageSize) { } public Long save(final CurrencyRequest currencyRequest) { - validateDate(currencyRequest.getDate()); + validateDateDuplicate(currencyRequest.getDate()); return currencyRepository.save(Currency.of(currencyRequest)).getId(); } + + public void update(final Long id, final CurrencyRequest currencyRequest) { + final Currency currency = currencyRepository.findById(id) + .orElseThrow(() -> new BadRequestException(NOT_FOUND_CURRENCY_DATA)); + + validateDateDuplicate(currency, currencyRequest.getDate()); + + currency.update(currencyRequest); + } + + private void validateDateDuplicate(final Currency currency, final LocalDate newDate) { + if (!currency.getDate().equals(newDate)) { + validateDateDuplicate(newDate); + } + } } diff --git a/backend/src/main/java/hanglog/global/exception/ExceptionCode.java b/backend/src/main/java/hanglog/global/exception/ExceptionCode.java index c4c78f4a9..2ca70f2e0 100644 --- a/backend/src/main/java/hanglog/global/exception/ExceptionCode.java +++ b/backend/src/main/java/hanglog/global/exception/ExceptionCode.java @@ -68,7 +68,6 @@ public enum ExceptionCode { DUPLICATED_CATEGORY_NAME(8311, "중복된 카테고리 이름입니다."), NOT_FOUND_CATEGORY(8312, "요청한 ID에 해당하는 카테고리를 찾을 수 없습니다."), - INVALID_AUTHORIZATION_CODE(9001, "유효하지 않은 인증 코드입니다."), NOT_SUPPORTED_OAUTH_SERVICE(9002, "해당 OAuth 서비스는 제공하지 않습니다."), FAIL_TO_CONVERT_URL_PARAMETER(9003, "Url Parameter 변환 중 오류가 발생했습니다."), From 5457d43ae876639ff42473782ecca4aef9caf909 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 21:52:46 +0900 Subject: [PATCH 52/88] =?UTF-8?q?test:=20=ED=99=98=EC=9C=A8=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20API=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AdminCurrencyControllerTest.java | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/backend/src/test/java/hanglog/admin/presentation/AdminCurrencyControllerTest.java b/backend/src/test/java/hanglog/admin/presentation/AdminCurrencyControllerTest.java index f5f9459bb..20e95f615 100644 --- a/backend/src/test/java/hanglog/admin/presentation/AdminCurrencyControllerTest.java +++ b/backend/src/test/java/hanglog/admin/presentation/AdminCurrencyControllerTest.java @@ -8,12 +8,16 @@ import static org.mockito.Mockito.doNothing; import static org.springframework.http.HttpHeaders.AUTHORIZATION; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.fasterxml.jackson.databind.ObjectMapper; +import hanglog.currency.dto.request.CurrencyRequest; import hanglog.currency.dto.response.CurrencyListResponse; import hanglog.currency.dto.response.CurrencyResponse; import hanglog.currency.service.CurrencyService; @@ -29,6 +33,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext; +import org.springframework.http.MediaType; import org.springframework.restdocs.payload.JsonFieldType; import org.springframework.test.web.servlet.MockMvc; @@ -144,4 +149,83 @@ void getCitiesDetail() throws Exception { ) )); } + + + @DisplayName("새로운 환율 정보를 생성한다.") + @Test + void createCurrency() throws Exception { + // given + final CurrencyRequest request = new CurrencyRequest( + CURRENCY_1.getDate(), + CURRENCY_1.getUsd(), + CURRENCY_1.getEur(), + CURRENCY_1.getGbp(), + CURRENCY_1.getJpy(), + CURRENCY_1.getCny(), + CURRENCY_1.getChf(), + CURRENCY_1.getSgd(), + CURRENCY_1.getThb(), + CURRENCY_1.getHkd(), + CURRENCY_1.getKrw() + ); + + given(currencyService.save(any(CurrencyRequest.class))).willReturn(1L); + + // when & then + mockMvc.perform(post("/admin/currencies") + .header(AUTHORIZATION, MEMBER_TOKENS.getAccessToken()) + .cookie(COOKIE) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isCreated()) + .andExpect(header().string("Location", "/admin/currencies/1")) + .andDo(restDocs.document( + requestFields( + fieldWithPath("date") + .type(JsonFieldType.STRING) + .description("날짜") + .attributes(field("constraint", "YYYY-MM-DD")), + fieldWithPath("usd") + .type(JsonFieldType.NUMBER) + .description("USD") + .attributes(field("constraint", "double")), + fieldWithPath("eur") + .type(JsonFieldType.NUMBER) + .description("EUR") + .attributes(field("constraint", "double")), + fieldWithPath("gbp") + .type(JsonFieldType.NUMBER) + .description("GBP") + .attributes(field("constraint", "double")), + fieldWithPath("jpy") + .type(JsonFieldType.NUMBER) + .description("JPY") + .attributes(field("constraint", "double")), + fieldWithPath("cny") + .type(JsonFieldType.NUMBER) + .description("CNY") + .attributes(field("constraint", "double")), + fieldWithPath("chf") + .type(JsonFieldType.NUMBER) + .description("CHF") + .attributes(field("constraint", "double")), + fieldWithPath("sgd") + .type(JsonFieldType.NUMBER) + .description("SGD") + .attributes(field("constraint", "double")), + fieldWithPath("thb") + .type(JsonFieldType.NUMBER) + .description("THB") + .attributes(field("constraint", "double")), + fieldWithPath("hkd") + .type(JsonFieldType.NUMBER) + .description("HKD") + .attributes(field("constraint", "double")), + fieldWithPath("krw") + .type(JsonFieldType.NUMBER) + .description("KRW") + .attributes(field("constraint", "double")) + ) + )); + } } From 238889d9216326e06245ab70bbb39bafb81fa6a2 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 21:52:55 +0900 Subject: [PATCH 53/88] =?UTF-8?q?feat:=20=ED=99=98=EC=9C=A8=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/AdminCurrencyController.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/backend/src/main/java/hanglog/admin/presentation/AdminCurrencyController.java b/backend/src/main/java/hanglog/admin/presentation/AdminCurrencyController.java index 12ae91c1b..8ad9f6b5f 100644 --- a/backend/src/main/java/hanglog/admin/presentation/AdminCurrencyController.java +++ b/backend/src/main/java/hanglog/admin/presentation/AdminCurrencyController.java @@ -6,13 +6,18 @@ import hanglog.auth.AdminAuth; import hanglog.auth.AdminOnly; import hanglog.auth.domain.Accessor; +import hanglog.currency.dto.request.CurrencyRequest; import hanglog.currency.dto.response.CurrencyListResponse; import hanglog.currency.service.CurrencyService; +import jakarta.validation.Valid; +import java.net.URI; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +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; @@ -32,4 +37,14 @@ public ResponseEntity getCurrencies( final CurrencyListResponse currencies = currencyService.getCurrenciesByPage(pageable); return ResponseEntity.ok().body(currencies); } + + @PostMapping + @AdminOnly + public ResponseEntity createCurrency( + @AdminAuth final Accessor accessor, + @RequestBody @Valid final CurrencyRequest currencyRequest + ) { + final Long currencyId = currencyService.save(currencyRequest); + return ResponseEntity.created(URI.create("/admin/currencies/" + currencyId)).build(); + } } From 8fd59f678df156cd9ca438c877c9b66660d2d806 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 21:55:29 +0900 Subject: [PATCH 54/88] =?UTF-8?q?test:=20=ED=99=98=EC=9C=A8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20API=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AdminCurrencyControllerTest.java | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/backend/src/test/java/hanglog/admin/presentation/AdminCurrencyControllerTest.java b/backend/src/test/java/hanglog/admin/presentation/AdminCurrencyControllerTest.java index 20e95f615..bc33bea68 100644 --- a/backend/src/test/java/hanglog/admin/presentation/AdminCurrencyControllerTest.java +++ b/backend/src/test/java/hanglog/admin/presentation/AdminCurrencyControllerTest.java @@ -9,9 +9,12 @@ import static org.springframework.http.HttpHeaders.AUTHORIZATION; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.put; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -228,4 +231,85 @@ void createCurrency() throws Exception { ) )); } + + @DisplayName("환율 정보를 수정한다.") + @Test + void updateCurrency() throws Exception { + // given + final CurrencyRequest request = new CurrencyRequest( + CURRENCY_1.getDate(), + CURRENCY_1.getUsd(), + CURRENCY_1.getEur(), + CURRENCY_1.getGbp(), + CURRENCY_1.getJpy(), + CURRENCY_1.getCny(), + CURRENCY_1.getChf(), + CURRENCY_1.getSgd(), + CURRENCY_1.getThb(), + CURRENCY_1.getHkd(), + CURRENCY_1.getKrw() + ); + + doNothing().when(currencyService).update(1L, request); + + // when & then + mockMvc.perform(put("/admin/currencies/{currencyId}", 1) + .header(AUTHORIZATION, MEMBER_TOKENS.getAccessToken()) + .cookie(COOKIE) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isNoContent()) + .andDo(restDocs.document( + pathParameters( + parameterWithName("currencyId") + .description("환율 ID") + ), + requestFields( + fieldWithPath("date") + .type(JsonFieldType.STRING) + .description("날짜") + .attributes(field("constraint", "YYYY-MM-DD")), + fieldWithPath("usd") + .type(JsonFieldType.NUMBER) + .description("USD") + .attributes(field("constraint", "double")), + fieldWithPath("eur") + .type(JsonFieldType.NUMBER) + .description("EUR") + .attributes(field("constraint", "double")), + fieldWithPath("gbp") + .type(JsonFieldType.NUMBER) + .description("GBP") + .attributes(field("constraint", "double")), + fieldWithPath("jpy") + .type(JsonFieldType.NUMBER) + .description("JPY") + .attributes(field("constraint", "double")), + fieldWithPath("cny") + .type(JsonFieldType.NUMBER) + .description("CNY") + .attributes(field("constraint", "double")), + fieldWithPath("chf") + .type(JsonFieldType.NUMBER) + .description("CHF") + .attributes(field("constraint", "double")), + fieldWithPath("sgd") + .type(JsonFieldType.NUMBER) + .description("SGD") + .attributes(field("constraint", "double")), + fieldWithPath("thb") + .type(JsonFieldType.NUMBER) + .description("THB") + .attributes(field("constraint", "double")), + fieldWithPath("hkd") + .type(JsonFieldType.NUMBER) + .description("HKD") + .attributes(field("constraint", "double")), + fieldWithPath("krw") + .type(JsonFieldType.NUMBER) + .description("KRW") + .attributes(field("constraint", "double")) + ) + )); + } } From b59b43e8ca61574d48b0c514640ee078aaf1755d Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 21:55:39 +0900 Subject: [PATCH 55/88] =?UTF-8?q?feat:=20=ED=99=98=EC=9C=A8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/presentation/AdminCurrencyController.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/backend/src/main/java/hanglog/admin/presentation/AdminCurrencyController.java b/backend/src/main/java/hanglog/admin/presentation/AdminCurrencyController.java index 8ad9f6b5f..f9773638d 100644 --- a/backend/src/main/java/hanglog/admin/presentation/AdminCurrencyController.java +++ b/backend/src/main/java/hanglog/admin/presentation/AdminCurrencyController.java @@ -16,7 +16,9 @@ import org.springframework.data.web.PageableDefault; 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.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -47,4 +49,15 @@ public ResponseEntity createCurrency( final Long currencyId = currencyService.save(currencyRequest); return ResponseEntity.created(URI.create("/admin/currencies/" + currencyId)).build(); } + + @PutMapping("/{currencyId}") + @AdminOnly + public ResponseEntity updateCategory( + @AdminAuth final Accessor accessor, + @PathVariable final Long currencyId, + @RequestBody @Valid final CurrencyRequest currencyRequest + ) { + currencyService.update(currencyId, currencyRequest); + return ResponseEntity.noContent().build(); + } } From beb73512cea8db595ad98ebf41b4b8219b8fb820 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 23:10:55 +0900 Subject: [PATCH 56/88] =?UTF-8?q?test:=20=EA=B3=B5=EB=B0=B1=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hanglog/admin/presentation/AdminCurrencyControllerTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/test/java/hanglog/admin/presentation/AdminCurrencyControllerTest.java b/backend/src/test/java/hanglog/admin/presentation/AdminCurrencyControllerTest.java index bc33bea68..9a3e7d87c 100644 --- a/backend/src/test/java/hanglog/admin/presentation/AdminCurrencyControllerTest.java +++ b/backend/src/test/java/hanglog/admin/presentation/AdminCurrencyControllerTest.java @@ -46,7 +46,6 @@ @AutoConfigureRestDocs class AdminCurrencyControllerTest extends ControllerTest { - private static final MemberTokens MEMBER_TOKENS = new MemberTokens("refreshToken", "accessToken"); private static final Cookie COOKIE = new Cookie("refresh-token", MEMBER_TOKENS.getRefreshToken()); From 47809f6cec230ec7401b25e139edb234f76c3f37 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sat, 20 Jan 2024 23:16:33 +0900 Subject: [PATCH 57/88] =?UTF-8?q?chore:=20submodule=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/backend-submodule | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/backend-submodule b/backend/backend-submodule index a0ae218a4..787a8d416 160000 --- a/backend/backend-submodule +++ b/backend/backend-submodule @@ -1 +1 @@ -Subproject commit a0ae218a4ed50f41310e3eaf09a3ce8ca3d01e50 +Subproject commit 787a8d4164253995c6af33f7b9b880d9ef59b48e From cecb6f6cd49c870f1c773bdeb4d1bbad12897d97 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Mon, 22 Jan 2024 22:54:26 +0900 Subject: [PATCH 58/88] =?UTF-8?q?chore:=20=EB=9D=BC=EC=9D=B4=EB=B8=8C?= =?UTF-8?q?=EB=9F=AC=EB=A6=AC=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/build.gradle b/backend/build.gradle index e12fd2604..fe4ab851a 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -57,7 +57,7 @@ dependencies { implementation 'org.flywaydb:flyway-core' implementation 'org.flywaydb:flyway-mysql' - implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.security:spring-security-crypto:6.2.1' } test { From 5db7c0381c3f5466abf707bad890251b15d09c3c Mon Sep 17 00:00:00 2001 From: LJW25 Date: Mon, 22 Jan 2024 23:14:50 +0900 Subject: [PATCH 59/88] =?UTF-8?q?refactor:=20BCrypt=20=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/build.gradle | 2 +- .../java/hanglog/admin/BCryptPasswordEncoder.java | 15 +++++++++++++++ .../main/java/hanglog/admin/PasswordEncoder.java | 7 +++++++ .../main/java/hanglog/admin/SecurityConfig.java | 2 -- .../hanglog/admin/service/AdminLoginService.java | 2 +- .../hanglog/admin/service/AdminMemberService.java | 2 +- .../admin/service/AdminLoginServiceTest.java | 2 +- .../admin/service/AdminMemberServiceTest.java | 2 +- 8 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 backend/src/main/java/hanglog/admin/BCryptPasswordEncoder.java create mode 100644 backend/src/main/java/hanglog/admin/PasswordEncoder.java diff --git a/backend/build.gradle b/backend/build.gradle index fe4ab851a..b466a8f12 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -57,7 +57,7 @@ dependencies { implementation 'org.flywaydb:flyway-core' implementation 'org.flywaydb:flyway-mysql' - implementation 'org.springframework.security:spring-security-crypto:6.2.1' + implementation group: 'org.mindrot', name: 'jbcrypt', version: '0.3m' } test { diff --git a/backend/src/main/java/hanglog/admin/BCryptPasswordEncoder.java b/backend/src/main/java/hanglog/admin/BCryptPasswordEncoder.java new file mode 100644 index 000000000..548bc9347 --- /dev/null +++ b/backend/src/main/java/hanglog/admin/BCryptPasswordEncoder.java @@ -0,0 +1,15 @@ +package hanglog.admin; + +import org.mindrot.jbcrypt.BCrypt; + +public class BCryptPasswordEncoder implements PasswordEncoder { + @Override + public String encode(final String password) { + return BCrypt.hashpw(password, BCrypt.gensalt()); + } + + @Override + public boolean matches(final String password, final String hashed) { + return BCrypt.checkpw(password, hashed); + } +} diff --git a/backend/src/main/java/hanglog/admin/PasswordEncoder.java b/backend/src/main/java/hanglog/admin/PasswordEncoder.java new file mode 100644 index 000000000..01f39a782 --- /dev/null +++ b/backend/src/main/java/hanglog/admin/PasswordEncoder.java @@ -0,0 +1,7 @@ +package hanglog.admin; + +public interface PasswordEncoder { + String encode(String password); + + boolean matches(String password, String hashed); +} diff --git a/backend/src/main/java/hanglog/admin/SecurityConfig.java b/backend/src/main/java/hanglog/admin/SecurityConfig.java index 4beca911f..10091de3e 100644 --- a/backend/src/main/java/hanglog/admin/SecurityConfig.java +++ b/backend/src/main/java/hanglog/admin/SecurityConfig.java @@ -2,8 +2,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; @Configuration public class SecurityConfig { diff --git a/backend/src/main/java/hanglog/admin/service/AdminLoginService.java b/backend/src/main/java/hanglog/admin/service/AdminLoginService.java index b78a370a3..d484bd834 100644 --- a/backend/src/main/java/hanglog/admin/service/AdminLoginService.java +++ b/backend/src/main/java/hanglog/admin/service/AdminLoginService.java @@ -5,6 +5,7 @@ import static hanglog.global.exception.ExceptionCode.INVALID_REFRESH_TOKEN; import static hanglog.global.exception.ExceptionCode.INVALID_USER_NAME; +import hanglog.admin.PasswordEncoder; import hanglog.admin.domain.AdminMember; import hanglog.admin.domain.repository.AdminMemberRepository; import hanglog.admin.dto.request.AdminLoginRequest; @@ -16,7 +17,6 @@ import hanglog.login.infrastructure.BearerAuthorizationExtractor; import hanglog.login.infrastructure.JwtProvider; import lombok.RequiredArgsConstructor; -import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; diff --git a/backend/src/main/java/hanglog/admin/service/AdminMemberService.java b/backend/src/main/java/hanglog/admin/service/AdminMemberService.java index 52c037497..df4b9742e 100644 --- a/backend/src/main/java/hanglog/admin/service/AdminMemberService.java +++ b/backend/src/main/java/hanglog/admin/service/AdminMemberService.java @@ -5,6 +5,7 @@ import static hanglog.global.exception.ExceptionCode.INVALID_CURRENT_PASSWORD; import static hanglog.global.exception.ExceptionCode.NOT_FOUND_ADMIN_ID; +import hanglog.admin.PasswordEncoder; import hanglog.admin.domain.AdminMember; import hanglog.admin.domain.repository.AdminMemberRepository; import hanglog.admin.domain.type.AdminType; @@ -14,7 +15,6 @@ import hanglog.global.exception.AdminException; import java.util.List; import lombok.RequiredArgsConstructor; -import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; diff --git a/backend/src/test/java/hanglog/admin/service/AdminLoginServiceTest.java b/backend/src/test/java/hanglog/admin/service/AdminLoginServiceTest.java index a9935cb8f..bdbff0c4b 100644 --- a/backend/src/test/java/hanglog/admin/service/AdminLoginServiceTest.java +++ b/backend/src/test/java/hanglog/admin/service/AdminLoginServiceTest.java @@ -5,6 +5,7 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; +import hanglog.admin.PasswordEncoder; import hanglog.admin.domain.AdminMember; import hanglog.admin.domain.repository.AdminMemberRepository; import hanglog.admin.domain.type.AdminType; @@ -20,7 +21,6 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.security.crypto.password.PasswordEncoder; @ExtendWith(MockitoExtension.class) class AdminLoginServiceTest { diff --git a/backend/src/test/java/hanglog/admin/service/AdminMemberServiceTest.java b/backend/src/test/java/hanglog/admin/service/AdminMemberServiceTest.java index ee033a258..7993589ed 100644 --- a/backend/src/test/java/hanglog/admin/service/AdminMemberServiceTest.java +++ b/backend/src/test/java/hanglog/admin/service/AdminMemberServiceTest.java @@ -8,6 +8,7 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.verify; +import hanglog.admin.PasswordEncoder; import hanglog.admin.domain.AdminMember; import hanglog.admin.domain.repository.AdminMemberRepository; import hanglog.admin.domain.type.AdminType; @@ -21,7 +22,6 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.security.crypto.password.PasswordEncoder; @ExtendWith(MockitoExtension.class) class AdminMemberServiceTest { From 6abd773705d925c9f904ae0be304aef20a996a12 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Tue, 23 Jan 2024 13:21:15 +0900 Subject: [PATCH 60/88] =?UTF-8?q?chore:=20=EC=84=9C=EB=B8=8C=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/backend-submodule | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/backend-submodule b/backend/backend-submodule index 787a8d416..cc3201d2f 160000 --- a/backend/backend-submodule +++ b/backend/backend-submodule @@ -1 +1 @@ -Subproject commit 787a8d4164253995c6af33f7b9b880d9ef59b48e +Subproject commit cc3201d2fd8c386bf6af3ee9b7914fc6f725a75d From ca62ca72cfc38d1279d64c9ecc2b4b2c25fe1457 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Tue, 23 Jan 2024 13:58:01 +0900 Subject: [PATCH 61/88] =?UTF-8?q?refactor:=20AdminMemberRepository=20?= =?UTF-8?q?=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=9D=B4=EB=A6=84=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/domain/repository/AdminMemberRepository.java | 6 +++--- .../main/java/hanglog/admin/service/AdminLoginService.java | 2 +- .../main/java/hanglog/admin/service/AdminMemberService.java | 2 +- .../admin/presentation/AdminCategoryControllerTest.java | 2 +- .../hanglog/admin/presentation/AdminCityControllerTest.java | 2 +- .../admin/presentation/AdminCurrencyControllerTest.java | 2 +- .../admin/presentation/AdminLoginControllerTest.java | 4 ++-- .../admin/presentation/AdminMemberControllerTest.java | 2 +- .../java/hanglog/admin/service/AdminLoginServiceTest.java | 4 ++-- .../java/hanglog/admin/service/AdminMemberServiceTest.java | 2 +- 10 files changed, 14 insertions(+), 14 deletions(-) diff --git a/backend/src/main/java/hanglog/admin/domain/repository/AdminMemberRepository.java b/backend/src/main/java/hanglog/admin/domain/repository/AdminMemberRepository.java index e09d200ba..036215500 100644 --- a/backend/src/main/java/hanglog/admin/domain/repository/AdminMemberRepository.java +++ b/backend/src/main/java/hanglog/admin/domain/repository/AdminMemberRepository.java @@ -7,9 +7,9 @@ public interface AdminMemberRepository extends JpaRepository { - Optional findAdminMemberByUserName(String userName); + Optional findByUserName(String userName); - Boolean existsAdminMemberByIdAndAdminType(Long id, AdminType adminType); + Boolean existsByIdAndAdminType(Long id, AdminType adminType); - Boolean existsAdminMemberByUserName(String userName); + Boolean existsByUserName(String userName); } diff --git a/backend/src/main/java/hanglog/admin/service/AdminLoginService.java b/backend/src/main/java/hanglog/admin/service/AdminLoginService.java index d484bd834..9ec81ee3a 100644 --- a/backend/src/main/java/hanglog/admin/service/AdminLoginService.java +++ b/backend/src/main/java/hanglog/admin/service/AdminLoginService.java @@ -33,7 +33,7 @@ public class AdminLoginService { public MemberTokens login(final AdminLoginRequest adminLoginRequest) { - final AdminMember adminMember = adminMemberRepository.findAdminMemberByUserName( + final AdminMember adminMember = adminMemberRepository.findByUserName( adminLoginRequest.getUserName()) .orElseThrow(() -> new AdminException(INVALID_USER_NAME)); diff --git a/backend/src/main/java/hanglog/admin/service/AdminMemberService.java b/backend/src/main/java/hanglog/admin/service/AdminMemberService.java index df4b9742e..8a4976126 100644 --- a/backend/src/main/java/hanglog/admin/service/AdminMemberService.java +++ b/backend/src/main/java/hanglog/admin/service/AdminMemberService.java @@ -34,7 +34,7 @@ public List getAdminMembers() { } public Long createAdminMember(final AdminMemberCreateRequest request) { - if (adminMemberRepository.existsAdminMemberByUserName(request.getUserName())) { + if (adminMemberRepository.existsByUserName(request.getUserName())) { throw new AdminException(DUPLICATED_ADMIN_USERNAME); } diff --git a/backend/src/test/java/hanglog/admin/presentation/AdminCategoryControllerTest.java b/backend/src/test/java/hanglog/admin/presentation/AdminCategoryControllerTest.java index 4c9a93ff4..5b8216287 100644 --- a/backend/src/test/java/hanglog/admin/presentation/AdminCategoryControllerTest.java +++ b/backend/src/test/java/hanglog/admin/presentation/AdminCategoryControllerTest.java @@ -61,7 +61,7 @@ void setUp() { given(refreshTokenRepository.existsById(any())).willReturn(true); doNothing().when(jwtProvider).validateTokens(any()); given(jwtProvider.getSubject(any())).willReturn("1"); - given(adminMemberRepository.existsAdminMemberByIdAndAdminType(any(), any())).willReturn(false); + given(adminMemberRepository.existsByIdAndAdminType(any(), any())).willReturn(false); } @DisplayName("카테고리 상세 목록을 조회한다.") diff --git a/backend/src/test/java/hanglog/admin/presentation/AdminCityControllerTest.java b/backend/src/test/java/hanglog/admin/presentation/AdminCityControllerTest.java index 66564a04a..1a43cf63d 100644 --- a/backend/src/test/java/hanglog/admin/presentation/AdminCityControllerTest.java +++ b/backend/src/test/java/hanglog/admin/presentation/AdminCityControllerTest.java @@ -62,7 +62,7 @@ void setUp() { given(refreshTokenRepository.existsById(any())).willReturn(true); doNothing().when(jwtProvider).validateTokens(any()); given(jwtProvider.getSubject(any())).willReturn("1"); - given(adminMemberRepository.existsAdminMemberByIdAndAdminType(any(), any())).willReturn(false); + given(adminMemberRepository.existsByIdAndAdminType(any(), any())).willReturn(false); } @DisplayName("도시 상세 목록을 조회한다.") diff --git a/backend/src/test/java/hanglog/admin/presentation/AdminCurrencyControllerTest.java b/backend/src/test/java/hanglog/admin/presentation/AdminCurrencyControllerTest.java index 9a3e7d87c..bf4028964 100644 --- a/backend/src/test/java/hanglog/admin/presentation/AdminCurrencyControllerTest.java +++ b/backend/src/test/java/hanglog/admin/presentation/AdminCurrencyControllerTest.java @@ -63,7 +63,7 @@ void setUp() { given(refreshTokenRepository.existsById(any())).willReturn(true); doNothing().when(jwtProvider).validateTokens(any()); given(jwtProvider.getSubject(any())).willReturn("1"); - given(adminMemberRepository.existsAdminMemberByIdAndAdminType(any(), any())).willReturn(false); + given(adminMemberRepository.existsByIdAndAdminType(any(), any())).willReturn(false); } @DisplayName("도시 상세 목록을 조회한다.") diff --git a/backend/src/test/java/hanglog/admin/presentation/AdminLoginControllerTest.java b/backend/src/test/java/hanglog/admin/presentation/AdminLoginControllerTest.java index 3f54f8e1f..15d9183a0 100644 --- a/backend/src/test/java/hanglog/admin/presentation/AdminLoginControllerTest.java +++ b/backend/src/test/java/hanglog/admin/presentation/AdminLoginControllerTest.java @@ -109,7 +109,7 @@ void login() throws Exception { @Test void extendLogin() throws Exception { // given - given(adminMemberRepository.existsAdminMemberByIdAndAdminType(anyLong(), any(AdminType.class))) + given(adminMemberRepository.existsByIdAndAdminType(anyLong(), any(AdminType.class))) .willReturn(false); final MemberTokens memberTokens = new MemberTokens(REFRESH_TOKEN, RENEW_ACCESS_TOKEN); @@ -164,7 +164,7 @@ void logout() throws Exception { doNothing().when(jwtProvider).validateTokens(any()); given(jwtProvider.getSubject(any())).willReturn("1"); doNothing().when(adminLoginService).removeRefreshToken(anyString()); - given(adminMemberRepository.existsAdminMemberByIdAndAdminType(anyLong(), any(AdminType.class))) + given(adminMemberRepository.existsByIdAndAdminType(anyLong(), any(AdminType.class))) .willReturn(false); final MemberTokens memberTokens = new MemberTokens(REFRESH_TOKEN, RENEW_ACCESS_TOKEN); diff --git a/backend/src/test/java/hanglog/admin/presentation/AdminMemberControllerTest.java b/backend/src/test/java/hanglog/admin/presentation/AdminMemberControllerTest.java index bd0afb2a5..647791be9 100644 --- a/backend/src/test/java/hanglog/admin/presentation/AdminMemberControllerTest.java +++ b/backend/src/test/java/hanglog/admin/presentation/AdminMemberControllerTest.java @@ -60,7 +60,7 @@ void setUp() { given(refreshTokenRepository.existsById(any())).willReturn(true); doNothing().when(jwtProvider).validateTokens(any()); given(jwtProvider.getSubject(any())).willReturn("1"); - given(adminMemberRepository.existsAdminMemberByIdAndAdminType(any(), any())).willReturn(true); + given(adminMemberRepository.existsByIdAndAdminType(any(), any())).willReturn(true); } @DisplayName("관리자 멤버 목록을 조회한다.") diff --git a/backend/src/test/java/hanglog/admin/service/AdminLoginServiceTest.java b/backend/src/test/java/hanglog/admin/service/AdminLoginServiceTest.java index bdbff0c4b..36a03a314 100644 --- a/backend/src/test/java/hanglog/admin/service/AdminLoginServiceTest.java +++ b/backend/src/test/java/hanglog/admin/service/AdminLoginServiceTest.java @@ -50,7 +50,7 @@ public void testLoginSuccess() { final AdminMember adminMember = new AdminMember(1L, "user", "password", AdminType.ADMIN); final MemberTokens memberTokens = new MemberTokens("accessToken", "refreshToken"); - when(adminMemberRepository.findAdminMemberByUserName(anyString())).thenReturn(Optional.of(adminMember)); + when(adminMemberRepository.findByUserName(anyString())).thenReturn(Optional.of(adminMember)); when(passwordEncoder.matches(anyString(), anyString())).thenReturn(true); when(jwtProvider.generateLoginToken(anyString())).thenReturn(memberTokens); @@ -71,7 +71,7 @@ public void testLoginFailure_InvalidPassword() { final AdminLoginRequest loginRequest = new AdminLoginRequest("user", "wrongpassword"); final AdminMember adminMember = new AdminMember("user", "password", AdminType.ADMIN); - when(adminMemberRepository.findAdminMemberByUserName(anyString())).thenReturn(Optional.of(adminMember)); + when(adminMemberRepository.findByUserName(anyString())).thenReturn(Optional.of(adminMember)); when(passwordEncoder.matches(anyString(), anyString())).thenReturn(false); // when & then diff --git a/backend/src/test/java/hanglog/admin/service/AdminMemberServiceTest.java b/backend/src/test/java/hanglog/admin/service/AdminMemberServiceTest.java index 7993589ed..388144a5e 100644 --- a/backend/src/test/java/hanglog/admin/service/AdminMemberServiceTest.java +++ b/backend/src/test/java/hanglog/admin/service/AdminMemberServiceTest.java @@ -46,7 +46,7 @@ void createAdminMember() { ); final AdminMember adminMember = new AdminMember(1L, "username", "password", AdminType.ADMIN); - given(adminMemberRepository.existsAdminMemberByUserName("username")).willReturn(false); + given(adminMemberRepository.existsByUserName("username")).willReturn(false); given(adminMemberRepository.save(any(AdminMember.class))).willReturn(adminMember); given(passwordEncoder.encode("password")).willReturn(anyString()); From 6e7849625f8f35af5ea7b9a847d7171e1b41e8a8 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Tue, 23 Jan 2024 13:58:16 +0900 Subject: [PATCH 62/88] =?UTF-8?q?refactor:=20Master=20static=20import=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/hanglog/admin/AdminLoginArgumentResolver.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/hanglog/admin/AdminLoginArgumentResolver.java b/backend/src/main/java/hanglog/admin/AdminLoginArgumentResolver.java index 5032d9649..2ef1e884c 100644 --- a/backend/src/main/java/hanglog/admin/AdminLoginArgumentResolver.java +++ b/backend/src/main/java/hanglog/admin/AdminLoginArgumentResolver.java @@ -1,11 +1,11 @@ package hanglog.admin; +import static hanglog.admin.domain.type.AdminType.MASTER; import static hanglog.global.exception.ExceptionCode.INVALID_REQUEST; import static hanglog.global.exception.ExceptionCode.NOT_FOUND_REFRESH_TOKEN; import static org.springframework.http.HttpHeaders.AUTHORIZATION; import hanglog.admin.domain.repository.AdminMemberRepository; -import hanglog.admin.domain.type.AdminType; import hanglog.auth.AdminAuth; import hanglog.auth.domain.Accessor; import hanglog.global.exception.BadRequestException; @@ -63,7 +63,7 @@ public Accessor resolveArgument( final Long memberId = Long.valueOf(jwtProvider.getSubject(accessToken)); - if (adminMemberRepository.existsAdminMemberByIdAndAdminType(memberId, AdminType.MASTER)) { + if (adminMemberRepository.existsByIdAndAdminType(memberId, MASTER)) { return Accessor.master(memberId); } return Accessor.admin(memberId); From 0edb405225e0ddc7a508c1c9e7999c8ac09b0cc9 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Tue, 23 Jan 2024 14:26:42 +0900 Subject: [PATCH 63/88] =?UTF-8?q?refactor:=20=ED=8C=A8=ED=82=A4=EC=A7=80?= =?UTF-8?q?=EB=AA=85=20=EC=98=A4=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CustomLikeRepositoryImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename backend/src/main/java/hanglog/like/{infrastrcutrue => infrastructrue}/CustomLikeRepositoryImpl.java (97%) diff --git a/backend/src/main/java/hanglog/like/infrastrcutrue/CustomLikeRepositoryImpl.java b/backend/src/main/java/hanglog/like/infrastructrue/CustomLikeRepositoryImpl.java similarity index 97% rename from backend/src/main/java/hanglog/like/infrastrcutrue/CustomLikeRepositoryImpl.java rename to backend/src/main/java/hanglog/like/infrastructrue/CustomLikeRepositoryImpl.java index 98e983850..1005636a8 100644 --- a/backend/src/main/java/hanglog/like/infrastrcutrue/CustomLikeRepositoryImpl.java +++ b/backend/src/main/java/hanglog/like/infrastructrue/CustomLikeRepositoryImpl.java @@ -1,4 +1,4 @@ -package hanglog.like.infrastrcutrue; +package hanglog.like.infrastructrue; import hanglog.like.domain.Likes; import hanglog.like.domain.repository.CustomLikeRepository; From c7d9cb8e4cb8c1ed0034eedcfd17e49abe4d5217 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Tue, 23 Jan 2024 14:27:12 +0900 Subject: [PATCH 64/88] =?UTF-8?q?refactor:=20=ED=8C=A8=ED=82=A4=EC=A7=80?= =?UTF-8?q?=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/main/java/hanglog/admin/SecurityConfig.java | 2 ++ .../admin/{ => infrastructure}/BCryptPasswordEncoder.java | 2 +- .../hanglog/admin/{ => infrastructure}/PasswordEncoder.java | 2 +- .../src/main/java/hanglog/admin/service/AdminLoginService.java | 2 +- .../src/main/java/hanglog/admin/service/AdminMemberService.java | 2 +- .../test/java/hanglog/admin/service/AdminLoginServiceTest.java | 2 +- .../test/java/hanglog/admin/service/AdminMemberServiceTest.java | 2 +- 7 files changed, 8 insertions(+), 6 deletions(-) rename backend/src/main/java/hanglog/admin/{ => infrastructure}/BCryptPasswordEncoder.java (90%) rename backend/src/main/java/hanglog/admin/{ => infrastructure}/PasswordEncoder.java (77%) diff --git a/backend/src/main/java/hanglog/admin/SecurityConfig.java b/backend/src/main/java/hanglog/admin/SecurityConfig.java index 10091de3e..3af3266ef 100644 --- a/backend/src/main/java/hanglog/admin/SecurityConfig.java +++ b/backend/src/main/java/hanglog/admin/SecurityConfig.java @@ -1,5 +1,7 @@ package hanglog.admin; +import hanglog.admin.infrastructure.BCryptPasswordEncoder; +import hanglog.admin.infrastructure.PasswordEncoder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/backend/src/main/java/hanglog/admin/BCryptPasswordEncoder.java b/backend/src/main/java/hanglog/admin/infrastructure/BCryptPasswordEncoder.java similarity index 90% rename from backend/src/main/java/hanglog/admin/BCryptPasswordEncoder.java rename to backend/src/main/java/hanglog/admin/infrastructure/BCryptPasswordEncoder.java index 548bc9347..2399aa4c1 100644 --- a/backend/src/main/java/hanglog/admin/BCryptPasswordEncoder.java +++ b/backend/src/main/java/hanglog/admin/infrastructure/BCryptPasswordEncoder.java @@ -1,4 +1,4 @@ -package hanglog.admin; +package hanglog.admin.infrastructure; import org.mindrot.jbcrypt.BCrypt; diff --git a/backend/src/main/java/hanglog/admin/PasswordEncoder.java b/backend/src/main/java/hanglog/admin/infrastructure/PasswordEncoder.java similarity index 77% rename from backend/src/main/java/hanglog/admin/PasswordEncoder.java rename to backend/src/main/java/hanglog/admin/infrastructure/PasswordEncoder.java index 01f39a782..0efa2043b 100644 --- a/backend/src/main/java/hanglog/admin/PasswordEncoder.java +++ b/backend/src/main/java/hanglog/admin/infrastructure/PasswordEncoder.java @@ -1,4 +1,4 @@ -package hanglog.admin; +package hanglog.admin.infrastructure; public interface PasswordEncoder { String encode(String password); diff --git a/backend/src/main/java/hanglog/admin/service/AdminLoginService.java b/backend/src/main/java/hanglog/admin/service/AdminLoginService.java index 9ec81ee3a..447c1a824 100644 --- a/backend/src/main/java/hanglog/admin/service/AdminLoginService.java +++ b/backend/src/main/java/hanglog/admin/service/AdminLoginService.java @@ -5,10 +5,10 @@ import static hanglog.global.exception.ExceptionCode.INVALID_REFRESH_TOKEN; import static hanglog.global.exception.ExceptionCode.INVALID_USER_NAME; -import hanglog.admin.PasswordEncoder; import hanglog.admin.domain.AdminMember; import hanglog.admin.domain.repository.AdminMemberRepository; import hanglog.admin.dto.request.AdminLoginRequest; +import hanglog.admin.infrastructure.PasswordEncoder; import hanglog.global.exception.AdminException; import hanglog.global.exception.AuthException; import hanglog.login.domain.MemberTokens; diff --git a/backend/src/main/java/hanglog/admin/service/AdminMemberService.java b/backend/src/main/java/hanglog/admin/service/AdminMemberService.java index 8a4976126..1054e832c 100644 --- a/backend/src/main/java/hanglog/admin/service/AdminMemberService.java +++ b/backend/src/main/java/hanglog/admin/service/AdminMemberService.java @@ -5,13 +5,13 @@ import static hanglog.global.exception.ExceptionCode.INVALID_CURRENT_PASSWORD; import static hanglog.global.exception.ExceptionCode.NOT_FOUND_ADMIN_ID; -import hanglog.admin.PasswordEncoder; import hanglog.admin.domain.AdminMember; import hanglog.admin.domain.repository.AdminMemberRepository; import hanglog.admin.domain.type.AdminType; import hanglog.admin.dto.request.AdminMemberCreateRequest; import hanglog.admin.dto.request.PasswordUpdateRequest; import hanglog.admin.dto.response.AdminMemberResponse; +import hanglog.admin.infrastructure.PasswordEncoder; import hanglog.global.exception.AdminException; import java.util.List; import lombok.RequiredArgsConstructor; diff --git a/backend/src/test/java/hanglog/admin/service/AdminLoginServiceTest.java b/backend/src/test/java/hanglog/admin/service/AdminLoginServiceTest.java index 36a03a314..57fa12ed5 100644 --- a/backend/src/test/java/hanglog/admin/service/AdminLoginServiceTest.java +++ b/backend/src/test/java/hanglog/admin/service/AdminLoginServiceTest.java @@ -5,11 +5,11 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; -import hanglog.admin.PasswordEncoder; import hanglog.admin.domain.AdminMember; import hanglog.admin.domain.repository.AdminMemberRepository; import hanglog.admin.domain.type.AdminType; import hanglog.admin.dto.request.AdminLoginRequest; +import hanglog.admin.infrastructure.PasswordEncoder; import hanglog.global.exception.AdminException; import hanglog.login.domain.MemberTokens; import hanglog.login.domain.repository.RefreshTokenRepository; diff --git a/backend/src/test/java/hanglog/admin/service/AdminMemberServiceTest.java b/backend/src/test/java/hanglog/admin/service/AdminMemberServiceTest.java index 388144a5e..97de332e8 100644 --- a/backend/src/test/java/hanglog/admin/service/AdminMemberServiceTest.java +++ b/backend/src/test/java/hanglog/admin/service/AdminMemberServiceTest.java @@ -8,12 +8,12 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.verify; -import hanglog.admin.PasswordEncoder; import hanglog.admin.domain.AdminMember; import hanglog.admin.domain.repository.AdminMemberRepository; import hanglog.admin.domain.type.AdminType; import hanglog.admin.dto.request.AdminMemberCreateRequest; import hanglog.admin.dto.request.PasswordUpdateRequest; +import hanglog.admin.infrastructure.PasswordEncoder; import hanglog.global.exception.AdminException; import java.util.Optional; import org.junit.jupiter.api.DisplayName; From 1bdf8e28f8b68eed75758285113fc7dbafbe74b0 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Tue, 23 Jan 2024 15:09:13 +0900 Subject: [PATCH 65/88] =?UTF-8?q?test:=20AdminMemberService=20=ED=86=B5?= =?UTF-8?q?=ED=95=A9=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/config/SecurityTestConfig.java | 15 ++++ .../AdminMemberServiceIntegrationTest.java | 84 +++++++++++++++++++ backend/src/test/resources/data/truncate.sql | 1 + 3 files changed, 100 insertions(+) create mode 100644 backend/src/test/java/hanglog/global/config/SecurityTestConfig.java create mode 100644 backend/src/test/java/hanglog/integration/service/AdminMemberServiceIntegrationTest.java diff --git a/backend/src/test/java/hanglog/global/config/SecurityTestConfig.java b/backend/src/test/java/hanglog/global/config/SecurityTestConfig.java new file mode 100644 index 000000000..74083a57b --- /dev/null +++ b/backend/src/test/java/hanglog/global/config/SecurityTestConfig.java @@ -0,0 +1,15 @@ +package hanglog.global.config; + +import hanglog.admin.infrastructure.BCryptPasswordEncoder; +import hanglog.admin.infrastructure.PasswordEncoder; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; + +@TestConfiguration +public class SecurityTestConfig { + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/backend/src/test/java/hanglog/integration/service/AdminMemberServiceIntegrationTest.java b/backend/src/test/java/hanglog/integration/service/AdminMemberServiceIntegrationTest.java new file mode 100644 index 000000000..0a8ed51bb --- /dev/null +++ b/backend/src/test/java/hanglog/integration/service/AdminMemberServiceIntegrationTest.java @@ -0,0 +1,84 @@ +package hanglog.integration.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.SoftAssertions.assertSoftly; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import hanglog.admin.dto.request.AdminMemberCreateRequest; +import hanglog.admin.dto.request.PasswordUpdateRequest; +import hanglog.admin.dto.response.AdminMemberResponse; +import hanglog.admin.service.AdminMemberService; +import hanglog.global.config.SecurityTestConfig; +import hanglog.global.exception.AdminException; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.jdbc.Sql; + +@DataJpaTest +@Sql(value = {"classpath:data/truncate.sql"}) +@Import({ + AdminMemberService.class, + SecurityTestConfig.class +}) +public class AdminMemberServiceIntegrationTest { + private static final AdminMemberCreateRequest CREATE_REQUEST = new AdminMemberCreateRequest( + "username", + "password", + "ADMIN" + ); + + @Autowired + private AdminMemberService adminMemberService; + + + @DisplayName("관리자 계정을 생성한다.") + @Test + void createAdminMember() { + // when & then + assertThat(adminMemberService.createAdminMember(CREATE_REQUEST)).isPositive(); + } + + @DisplayName("관리자 계정 목록을 조회한다.") + @Test + void getAdminMembers() { + // given + adminMemberService.createAdminMember(CREATE_REQUEST); + + // when + final List actual = adminMemberService.getAdminMembers(); + + // then + assertSoftly(softly -> { + softly.assertThat(actual).hasSize(1); + softly.assertThat(actual.get(0).getUserName()).isEqualTo(CREATE_REQUEST.getUserName()); + }); + } + + @DisplayName("관리자 계정의 비밀번호를 변경한다.") + @Test + void updatePassword() { + // given + final Long id = adminMemberService.createAdminMember(CREATE_REQUEST); + final PasswordUpdateRequest request = new PasswordUpdateRequest(CREATE_REQUEST.getPassword(), "newPassword"); + + // when & then + assertDoesNotThrow(() -> adminMemberService.updatePassword(id, request)); + } + + @DisplayName("관리자 계정의 기존 비밀번호를 틀리변 비밀번호를 변경할 수 없다.") + @Test + void updatePassword_Fail() { + // given + final Long id = adminMemberService.createAdminMember(CREATE_REQUEST); + final PasswordUpdateRequest request = new PasswordUpdateRequest("wrongPassword", "newPassword"); + + // when & then + assertThatThrownBy(() -> adminMemberService.updatePassword(id, request)) + .isInstanceOf(AdminException.class); + } +} diff --git a/backend/src/test/resources/data/truncate.sql b/backend/src/test/resources/data/truncate.sql index c7fe2de16..54a985c27 100644 --- a/backend/src/test/resources/data/truncate.sql +++ b/backend/src/test/resources/data/truncate.sql @@ -10,4 +10,5 @@ TRUNCATE TABLE currency RESTART IDENTITY; TRUNCATE TABLE city RESTART IDENTITY; TRUNCATE TABLE category RESTART IDENTITY; TRUNCATE TABLE member RESTART IDENTITY; +TRUNCATE TABLE admin_member RESTART IDENTITY; SET referential_integrity TRUE; From 5aa99bd64146b6c217c467c80c47ce31718a73ed Mon Sep 17 00:00:00 2001 From: LJW25 Date: Tue, 23 Jan 2024 15:19:12 +0900 Subject: [PATCH 66/88] =?UTF-8?q?test:=20AdminLoginService=20=ED=86=B5?= =?UTF-8?q?=ED=95=A9=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AdminLoginServiceIntegrationTest.java | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 backend/src/test/java/hanglog/integration/service/AdminLoginServiceIntegrationTest.java diff --git a/backend/src/test/java/hanglog/integration/service/AdminLoginServiceIntegrationTest.java b/backend/src/test/java/hanglog/integration/service/AdminLoginServiceIntegrationTest.java new file mode 100644 index 000000000..99d314dc1 --- /dev/null +++ b/backend/src/test/java/hanglog/integration/service/AdminLoginServiceIntegrationTest.java @@ -0,0 +1,47 @@ +package hanglog.integration.service; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import hanglog.admin.dto.request.AdminLoginRequest; +import hanglog.admin.dto.request.AdminMemberCreateRequest; +import hanglog.admin.service.AdminLoginService; +import hanglog.admin.service.AdminMemberService; +import hanglog.global.exception.AdminException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +public class AdminLoginServiceIntegrationTest extends RedisServiceIntegrationTest { + + @Autowired + private AdminLoginService adminLoginService; + + @Autowired + private AdminMemberService adminMemberService; + + @DisplayName("계정 정보가 올바르면 로그인에 성공한다.") + @Test + public void login() { + // given + final String username = "username"; + final String pw = "password"; + adminMemberService.createAdminMember(new AdminMemberCreateRequest(username, pw, "ADMIN")); + + // when & then + assertDoesNotThrow(() -> adminLoginService.login(new AdminLoginRequest(username, pw))); + } + + @DisplayName("비밀번호가 틀리면 로그인에 실패한다.") + @Test + public void login_fail() { + // given + final String username = "username"; + final String pw = "password"; + adminMemberService.createAdminMember(new AdminMemberCreateRequest(username, pw, "ADMIN")); + + // when & then + assertThatThrownBy(() -> adminLoginService.login(new AdminLoginRequest(username, "wrongPassword"))) + .isInstanceOf(AdminException.class); + } +} From 531eedfb1a2dcb385fdfe91def3bcdf03d2fa567 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Tue, 23 Jan 2024 15:53:03 +0900 Subject: [PATCH 67/88] =?UTF-8?q?refactor:=20Category=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?,=20=EC=88=98=EC=A0=95=20=EC=8B=9C=20id=EB=A5=BC=20=ED=8F=AC?= =?UTF-8?q?=ED=95=A8=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/hanglog/category/domain/Category.java | 3 ++- .../category/dto/request/CategoryRequest.java | 4 ++++ .../category/service/CategoryService.java | 18 +++++++++++++++--- .../global/exception/ExceptionCode.java | 3 ++- .../AdminCategoryControllerTest.java | 12 ++++++++++-- .../category/service/CategoryServiceTest.java | 10 +++++----- 6 files changed, 38 insertions(+), 12 deletions(-) diff --git a/backend/src/main/java/hanglog/category/domain/Category.java b/backend/src/main/java/hanglog/category/domain/Category.java index 8b0874c35..3a4f65623 100644 --- a/backend/src/main/java/hanglog/category/domain/Category.java +++ b/backend/src/main/java/hanglog/category/domain/Category.java @@ -32,10 +32,11 @@ public class Category extends BaseEntity { private String korName; public static Category of(final CategoryRequest categoryRequest) { - return new Category(null, categoryRequest.getEngName(), categoryRequest.getKorName()); + return new Category(categoryRequest.getId(), categoryRequest.getEngName(), categoryRequest.getKorName()); } public void update(final CategoryRequest categoryRequest) { + this.id = categoryRequest.getId(); this.engName = categoryRequest.getEngName(); this.korName = categoryRequest.getKorName(); } diff --git a/backend/src/main/java/hanglog/category/dto/request/CategoryRequest.java b/backend/src/main/java/hanglog/category/dto/request/CategoryRequest.java index ae09be647..6407f8014 100644 --- a/backend/src/main/java/hanglog/category/dto/request/CategoryRequest.java +++ b/backend/src/main/java/hanglog/category/dto/request/CategoryRequest.java @@ -1,6 +1,7 @@ package hanglog.category.dto.request; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import lombok.AllArgsConstructor; import lombok.Getter; @@ -9,6 +10,9 @@ @AllArgsConstructor public class CategoryRequest { + @NotNull(message = "카테고리 ID를 입력해주세요.") + private final Long id; + @NotBlank(message = "영어이름을 입력해주세요.") @Size(max = 50, message = "영어이름은 50자를 초과할 수 없습니다.") private final String engName; diff --git a/backend/src/main/java/hanglog/category/service/CategoryService.java b/backend/src/main/java/hanglog/category/service/CategoryService.java index 9f8ac1a51..743d8088c 100644 --- a/backend/src/main/java/hanglog/category/service/CategoryService.java +++ b/backend/src/main/java/hanglog/category/service/CategoryService.java @@ -1,5 +1,6 @@ package hanglog.category.service; +import static hanglog.global.exception.ExceptionCode.DUPLICATED_CATEGORY_ID; import static hanglog.global.exception.ExceptionCode.DUPLICATED_CATEGORY_NAME; import static hanglog.global.exception.ExceptionCode.NOT_FOUND_CATEGORY_ID; @@ -38,17 +39,25 @@ public List getAllCategoriesDetail() { } public Long save(final CategoryRequest categoryRequest) { - validateCategoryDuplicate(categoryRequest); + validateCategoryDuplicateId(categoryRequest); + validateCategoryDuplicateName(categoryRequest); return categoryRepository.save(Category.of(categoryRequest)).getId(); } - private void validateCategoryDuplicate(final CategoryRequest categoryRequest) { + private void validateCategoryDuplicateId(final CategoryRequest categoryRequest) { + if (categoryRepository.existsById(categoryRequest.getId())) { + throw new BadRequestException(DUPLICATED_CATEGORY_ID); + } + } + + private void validateCategoryDuplicateName(final CategoryRequest categoryRequest) { if (categoryRepository.existsByEngNameAndKorName(categoryRequest.getEngName(), categoryRequest.getKorName())) { throw new BadRequestException(DUPLICATED_CATEGORY_NAME); } } + public void update(final Long id, final CategoryRequest categoryRequest) { final Category category = categoryRepository.findById(id) .orElseThrow(() -> new BadRequestException(NOT_FOUND_CATEGORY_ID)); @@ -59,8 +68,11 @@ public void update(final Long id, final CategoryRequest categoryRequest) { } private void validateCategoryDuplicate(final Category category, final CategoryRequest categoryRequest) { + if (!category.getId().equals(categoryRequest.getId())) { + validateCategoryDuplicateId(categoryRequest); + } if (!category.isSameNames(categoryRequest.getEngName(), categoryRequest.getKorName())) { - validateCategoryDuplicate(categoryRequest); + validateCategoryDuplicateName(categoryRequest); } } } diff --git a/backend/src/main/java/hanglog/global/exception/ExceptionCode.java b/backend/src/main/java/hanglog/global/exception/ExceptionCode.java index 2ca70f2e0..c0912e24a 100644 --- a/backend/src/main/java/hanglog/global/exception/ExceptionCode.java +++ b/backend/src/main/java/hanglog/global/exception/ExceptionCode.java @@ -66,7 +66,8 @@ public enum ExceptionCode { DUPLICATED_CITY_NAME(8301, "중복된 나라, 도시 이름입니다."), NOT_FOUND_CITY(8302, "요청한 ID에 해당하는 도시를 찾을 수 없습니다."), DUPLICATED_CATEGORY_NAME(8311, "중복된 카테고리 이름입니다."), - NOT_FOUND_CATEGORY(8312, "요청한 ID에 해당하는 카테고리를 찾을 수 없습니다."), + DUPLICATED_CATEGORY_ID(8312, "중복된 카테고리 아이디입니다."), + NOT_FOUND_CATEGORY(8313, "요청한 ID에 해당하는 카테고리를 찾을 수 없습니다."), INVALID_AUTHORIZATION_CODE(9001, "유효하지 않은 인증 코드입니다."), NOT_SUPPORTED_OAUTH_SERVICE(9002, "해당 OAuth 서비스는 제공하지 않습니다."), diff --git a/backend/src/test/java/hanglog/admin/presentation/AdminCategoryControllerTest.java b/backend/src/test/java/hanglog/admin/presentation/AdminCategoryControllerTest.java index 5b8216287..bd29393af 100644 --- a/backend/src/test/java/hanglog/admin/presentation/AdminCategoryControllerTest.java +++ b/backend/src/test/java/hanglog/admin/presentation/AdminCategoryControllerTest.java @@ -102,7 +102,7 @@ void getCategoriesDetail() throws Exception { @Test void createCategory() throws Exception { // given - final CategoryRequest request = new CategoryRequest("engName", "korName"); + final CategoryRequest request = new CategoryRequest(1L, "engName", "korName"); given(categoryService.save(any(CategoryRequest.class))).willReturn(1L); @@ -116,6 +116,10 @@ void createCategory() throws Exception { .andExpect(header().string("Location", "/admin/categories/1")) .andDo(restDocs.document( requestFields( + fieldWithPath("id") + .type(JsonFieldType.NUMBER) + .description("카테고리 ID") + .attributes(field("constraint", "양의 정수")), fieldWithPath("engName") .type(JsonFieldType.STRING) .description("영어 이름") @@ -132,7 +136,7 @@ void createCategory() throws Exception { @Test void updateCategory() throws Exception { // given - final CategoryRequest request = new CategoryRequest("engName", "korName"); + final CategoryRequest request = new CategoryRequest(1L, "engName", "korName"); doNothing().when(categoryService).update(1L, request); @@ -149,6 +153,10 @@ void updateCategory() throws Exception { .description("카테고리 ID") ), requestFields( + fieldWithPath("id") + .type(JsonFieldType.NUMBER) + .description("카테고리 ID") + .attributes(field("constraint", "양의 정수")), fieldWithPath("engName") .type(JsonFieldType.STRING) .description("영어 이름") diff --git a/backend/src/test/java/hanglog/category/service/CategoryServiceTest.java b/backend/src/test/java/hanglog/category/service/CategoryServiceTest.java index ab764b33a..88c53daca 100644 --- a/backend/src/test/java/hanglog/category/service/CategoryServiceTest.java +++ b/backend/src/test/java/hanglog/category/service/CategoryServiceTest.java @@ -77,7 +77,7 @@ void getAllCategoriesDetail() { @Test void save() { // given - final CategoryRequest categoryRequest = new CategoryRequest(FOOD.getEngName(), FOOD.getKorName()); + final CategoryRequest categoryRequest = new CategoryRequest(1L, FOOD.getEngName(), FOOD.getKorName()); given(categoryRepository.existsByEngNameAndKorName(anyString(), anyString())).willReturn(false); given(categoryRepository.save(any(Category.class))).willReturn(FOOD); @@ -93,7 +93,7 @@ void save() { @Test void save_DuplicateFail() { // given - final CategoryRequest categoryRequest = new CategoryRequest(FOOD.getEngName(), FOOD.getKorName()); + final CategoryRequest categoryRequest = new CategoryRequest(1L, FOOD.getEngName(), FOOD.getKorName()); given(categoryRepository.existsByEngNameAndKorName(anyString(), anyString())).willReturn(true); @@ -106,7 +106,7 @@ void save_DuplicateFail() { @Test void update() { // given - final CategoryRequest categoryRequest = new CategoryRequest("newName", FOOD.getKorName()); + final CategoryRequest categoryRequest = new CategoryRequest(1L, "newName", FOOD.getKorName()); given(categoryRepository.findById(anyLong())).willReturn(Optional.of(FOOD)); given(categoryRepository.existsByEngNameAndKorName(anyString(), anyString())).willReturn(false); @@ -119,7 +119,7 @@ void update() { @Test void update_DuplicateFail() { // given - final CategoryRequest categoryRequest = new CategoryRequest("newName", FOOD.getKorName()); + final CategoryRequest categoryRequest = new CategoryRequest(1L, "newName", FOOD.getKorName()); given(categoryRepository.findById(anyLong())).willReturn(Optional.of(FOOD)); given(categoryRepository.existsByEngNameAndKorName(anyString(), anyString())).willReturn(true); @@ -133,7 +133,7 @@ void update_DuplicateFail() { @Test void update_NotFoundFail() { // given - final CategoryRequest categoryRequest = new CategoryRequest("newName", FOOD.getKorName()); + final CategoryRequest categoryRequest = new CategoryRequest(1L, "newName", FOOD.getKorName()); given(categoryRepository.findById(anyLong())).willReturn(Optional.empty()); From e4aae46c786c9ebdc145a13cc1259c14fa1cb622 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Tue, 23 Jan 2024 16:09:09 +0900 Subject: [PATCH 68/88] =?UTF-8?q?test:=20CategoryService=20=ED=86=B5?= =?UTF-8?q?=ED=95=A9=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CategoryServiceIntegrationTest.java | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 backend/src/test/java/hanglog/integration/service/CategoryServiceIntegrationTest.java diff --git a/backend/src/test/java/hanglog/integration/service/CategoryServiceIntegrationTest.java b/backend/src/test/java/hanglog/integration/service/CategoryServiceIntegrationTest.java new file mode 100644 index 000000000..55b5f962c --- /dev/null +++ b/backend/src/test/java/hanglog/integration/service/CategoryServiceIntegrationTest.java @@ -0,0 +1,102 @@ +package hanglog.integration.service; + +import static hanglog.integration.IntegrationFixture.ETC_CATEGORY; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import hanglog.category.dto.request.CategoryRequest; +import hanglog.category.service.CategoryService; +import hanglog.global.exception.BadRequestException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.jdbc.Sql; + +@DataJpaTest +@Sql(value = { + "classpath:data/truncate.sql", + "classpath:data/categories.sql" +}) +@Import(CategoryService.class) +public class CategoryServiceIntegrationTest { + + @Autowired + private CategoryService categoryService; + + private static final CategoryRequest CATEGORY_REQUEST = new CategoryRequest(999L, "eng", "한글"); + + @DisplayName("카테고리 목록을 조회한다.") + @Test + void getAllCategoriesDetail() { + // when & then + assertThat(categoryService.getAllCategoriesDetail()).isNotEmpty(); + } + + @DisplayName("새로운 카테고리를 저장한다.") + @Test + void save() { + // when & then + assertDoesNotThrow(() -> categoryService.save(CATEGORY_REQUEST)); + } + + @DisplayName("카테고리 이름을 수정한다.") + @Test + void update_CategoryName() { + // given + final Long id = categoryService.save(CATEGORY_REQUEST); + final CategoryRequest updateRequest = new CategoryRequest(id, "changed", "변경된카테고리"); + + // when & then + assertDoesNotThrow(() -> categoryService.update(id, updateRequest)); + } + + @DisplayName("이미 존재하는 카테고리 이름으로 수정할 수 없다.") + @Test + void update_CategoryNameFail() { + // given + final Long id = categoryService.save(CATEGORY_REQUEST); + final CategoryRequest updateRequest = new CategoryRequest( + id, + ETC_CATEGORY.getEngName(), + ETC_CATEGORY.getKorName() + ); + + // when & then + assertThatThrownBy(() -> categoryService.update(id, updateRequest)) + .isInstanceOf(BadRequestException.class); + } + + @DisplayName("카테고리 ID를 수정한다.") + @Test + void update_CategoryId() { + // given + final Long id = categoryService.save(CATEGORY_REQUEST); + final CategoryRequest updateRequest = new CategoryRequest( + 900L, + CATEGORY_REQUEST.getEngName(), + CATEGORY_REQUEST.getKorName() + ); + + // when & then + assertDoesNotThrow(() -> categoryService.update(id, updateRequest)); + } + + @DisplayName("이미 존재하는 카테고리 ID로 수정할 수 없다.") + @Test + void update_CategoryIdFail() { + // given + final Long id = categoryService.save(CATEGORY_REQUEST); + final CategoryRequest updateRequest = new CategoryRequest( + ETC_CATEGORY.getId(), + CATEGORY_REQUEST.getEngName(), + CATEGORY_REQUEST.getKorName() + ); + + // when & then + assertThatThrownBy(() -> categoryService.update(id, updateRequest)) + .isInstanceOf(BadRequestException.class); + } +} From 531e2b82042e8abfca8ae493f2659c5f6323815e Mon Sep 17 00:00:00 2001 From: LJW25 Date: Tue, 23 Jan 2024 16:28:25 +0900 Subject: [PATCH 69/88] =?UTF-8?q?test:=20CityService=20=ED=86=B5=ED=95=A9?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/CityServiceIntegrationTest.java | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 backend/src/test/java/hanglog/integration/service/CityServiceIntegrationTest.java diff --git a/backend/src/test/java/hanglog/integration/service/CityServiceIntegrationTest.java b/backend/src/test/java/hanglog/integration/service/CityServiceIntegrationTest.java new file mode 100644 index 000000000..c1560a96b --- /dev/null +++ b/backend/src/test/java/hanglog/integration/service/CityServiceIntegrationTest.java @@ -0,0 +1,84 @@ +package hanglog.integration.service; + + +import static hanglog.integration.IntegrationFixture.LONDON; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import hanglog.city.dto.request.CityRequest; +import hanglog.city.service.CityService; +import hanglog.global.exception.BadRequestException; +import java.math.BigDecimal; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.jdbc.Sql; + +@DataJpaTest +@Sql(value = { + "classpath:data/truncate.sql", + "classpath:data/cities.sql" +}) +@Import(CityService.class) +public class CityServiceIntegrationTest { + + @Autowired + private CityService cityService; + + private static final CityRequest CITY_REQUEST = new CityRequest( + "name", + "country", + BigDecimal.valueOf(123.12345), + BigDecimal.valueOf(123.12345) + ); + + @DisplayName("카테고리 목록을 조회한다.") + @Test + void getAllCitiesDetail() { + // when & then + assertThat(cityService.getAllCitiesDetail()).isNotEmpty(); + } + + @DisplayName("새로운 카테고리를 저장한다.") + @Test + void save() { + // when & then + assertDoesNotThrow(() -> cityService.save(CITY_REQUEST)); + } + + @DisplayName("카테고리 이름을 수정한다.") + @Test + void update_CityName() { + // given + final Long id = cityService.save(CITY_REQUEST); + final CityRequest updateRequest = new CityRequest( + "newName", + "newCountry", + CITY_REQUEST.getLatitude(), + CITY_REQUEST.getLongitude() + ); + + // when & then + assertDoesNotThrow(() -> cityService.update(id, updateRequest)); + } + + @DisplayName("이미 존재하는 카테고리 이름으로 수정할 수 없다.") + @Test + void update_CityNameFail() { + // given + final Long id = cityService.save(CITY_REQUEST); + final CityRequest updateRequest = new CityRequest( + LONDON.getName(), + LONDON.getCountry(), + CITY_REQUEST.getLatitude(), + CITY_REQUEST.getLongitude() + ); + + // when & then + assertThatThrownBy(() -> cityService.update(id, updateRequest)) + .isInstanceOf(BadRequestException.class); + } +} From 34cf69177f019052e7ff540fc29e2bb1e4c3f399 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Tue, 23 Jan 2024 17:09:56 +0900 Subject: [PATCH 70/88] =?UTF-8?q?test:=20AdminMember=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=20Controller=20=ED=86=B5=ED=95=A9=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AdminIntegrationTest.java | 65 +++++++++++++++++++ .../AdminMemberIntegrationTest.java | 53 +++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 backend/src/test/java/hanglog/integration/controller/AdminIntegrationTest.java create mode 100644 backend/src/test/java/hanglog/integration/controller/AdminMemberIntegrationTest.java diff --git a/backend/src/test/java/hanglog/integration/controller/AdminIntegrationTest.java b/backend/src/test/java/hanglog/integration/controller/AdminIntegrationTest.java new file mode 100644 index 000000000..9d86a7677 --- /dev/null +++ b/backend/src/test/java/hanglog/integration/controller/AdminIntegrationTest.java @@ -0,0 +1,65 @@ +package hanglog.integration.controller; + + +import hanglog.admin.domain.AdminMember; +import hanglog.admin.domain.repository.AdminMemberRepository; +import hanglog.admin.domain.type.AdminType; +import hanglog.global.config.RedisTestConfig; +import hanglog.login.domain.MemberTokens; +import hanglog.login.domain.RefreshToken; +import hanglog.login.domain.repository.RefreshTokenRepository; +import hanglog.login.infrastructure.JwtProvider; +import io.restassured.RestAssured; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.jdbc.Sql; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@Sql(value = { + "classpath:data/truncate.sql", + "classpath:data/currency.sql", + "classpath:data/cities.sql", + "classpath:data/categories.sql" +}) +@Import({ + RedisTestConfig.class +}) +public abstract class AdminIntegrationTest { + + @LocalServerPort + int port; + + @Autowired + private AdminMemberRepository adminMemberRepository; + + @Autowired + private RefreshTokenRepository refreshTokenRepository; + + @Autowired + private JwtProvider jwtProvider; + + public static String parseUri(final String uri) { + final String[] parts = uri.split("/"); + return parts[parts.length - 1]; + } + + public MemberTokens getAdminMemberTokenBy(final AdminType adminType) { + final AdminMember adminMember = new AdminMember("username", "password", adminType); + adminMemberRepository.save(adminMember); + final Long memberId = adminMember.getId(); + final MemberTokens memberToken = jwtProvider.generateLoginToken(memberId.toString()); + final RefreshToken refreshToken = new RefreshToken(memberToken.getRefreshToken(), memberId); + refreshTokenRepository.save(refreshToken); + + return memberToken; + } + + @BeforeEach + void setPort() { + RestAssured.port = port; + } +} diff --git a/backend/src/test/java/hanglog/integration/controller/AdminMemberIntegrationTest.java b/backend/src/test/java/hanglog/integration/controller/AdminMemberIntegrationTest.java new file mode 100644 index 000000000..6e3e18cf6 --- /dev/null +++ b/backend/src/test/java/hanglog/integration/controller/AdminMemberIntegrationTest.java @@ -0,0 +1,53 @@ +package hanglog.integration.controller; + +import static io.restassured.http.ContentType.JSON; +import static org.assertj.core.api.SoftAssertions.assertSoftly; +import static org.springframework.http.HttpHeaders.AUTHORIZATION; + +import hanglog.admin.domain.type.AdminType; +import hanglog.admin.dto.request.AdminMemberCreateRequest; +import hanglog.login.domain.MemberTokens; +import io.restassured.RestAssured; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatus; + +public class AdminMemberIntegrationTest extends AdminIntegrationTest { + + @DisplayName("마스터 계정은 어드민 계정을 생성할 수 있다.") + @Test + void createAdminMember() { + // given + final MemberTokens masterMemberToken = getAdminMemberTokenBy(AdminType.MASTER); + final AdminMemberCreateRequest createRequest = new AdminMemberCreateRequest("admin", "password", "ADMIN"); + + // when + final ExtractableResponse response = requestCreateAdminMember(masterMemberToken, createRequest); + final Long adminId = Long.parseLong(parseUri(response.header("Location"))); + + // then + assertSoftly( + softly -> { + softly.assertThat(response.statusCode()).isEqualTo(HttpStatus.CREATED.value()); + softly.assertThat(response.header("Location")).isNotBlank(); + softly.assertThat(adminId).isPositive(); + } + ); + } + + public static ExtractableResponse requestCreateAdminMember(final MemberTokens memberTokens, + final AdminMemberCreateRequest createRequest) { + return RestAssured + .given().log().all() + .header(AUTHORIZATION, + "Bearer " + memberTokens.getAccessToken()) + .cookies("refresh-token", memberTokens.getRefreshToken()) + .contentType(JSON) + .body(createRequest) + .when().post("/admin/members") + .then().log().all() + .extract(); + } +} From ebc37a4c70a8494423da2b6632c5b683574cf539 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Tue, 23 Jan 2024 23:07:45 +0900 Subject: [PATCH 71/88] =?UTF-8?q?test:=20AdminCurrency=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1,=EC=A1=B0=ED=9A=8C=20Controller=20=ED=86=B5=ED=95=A9?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AdminCurrencyIntegrationTest.java | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 backend/src/test/java/hanglog/integration/controller/AdminCurrencyIntegrationTest.java diff --git a/backend/src/test/java/hanglog/integration/controller/AdminCurrencyIntegrationTest.java b/backend/src/test/java/hanglog/integration/controller/AdminCurrencyIntegrationTest.java new file mode 100644 index 000000000..fae60d675 --- /dev/null +++ b/backend/src/test/java/hanglog/integration/controller/AdminCurrencyIntegrationTest.java @@ -0,0 +1,111 @@ +package hanglog.integration.controller; + +import static io.restassured.http.ContentType.JSON; +import static org.assertj.core.api.SoftAssertions.assertSoftly; +import static org.springframework.http.HttpHeaders.AUTHORIZATION; + +import hanglog.admin.domain.type.AdminType; +import hanglog.currency.dto.request.CurrencyRequest; +import hanglog.currency.dto.response.CurrencyListResponse; +import hanglog.login.domain.MemberTokens; +import io.restassured.RestAssured; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import java.time.LocalDate; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatus; + +public class AdminCurrencyIntegrationTest extends AdminIntegrationTest { + + private static final CurrencyRequest CURRENCY_REQUEST = new CurrencyRequest( + LocalDate.of(2099, 12, 31), + 1181.1, + 1281.96, + 142.78, + 1019.47, + 162.76, + 1139.06, + 820.17, + 34.89, + 142.78, + 1.0 + ); + + @DisplayName("새로운 환율 정보를 추가할 수 있다.") + @Test + void createCurrency() { + // given + final MemberTokens adminMemberToken = getAdminMemberTokenBy(AdminType.ADMIN); + + // when + final ExtractableResponse response = requestCreateCurrency(adminMemberToken, CURRENCY_REQUEST); + final Long id = Long.parseLong(parseUri(response.header("Location"))); + + // then + assertSoftly( + softly -> { + softly.assertThat(response.statusCode()).isEqualTo(HttpStatus.CREATED.value()); + softly.assertThat(response.header("Location")).isNotBlank(); + softly.assertThat(id).isPositive(); + } + ); + } + + @DisplayName("환율 정보를 페이지로 조회할 수 있다.") + @Test + void getCurrencies() { + // given + final MemberTokens adminMemberToken = getAdminMemberTokenBy(AdminType.ADMIN); + requestCreateCurrency(adminMemberToken, CURRENCY_REQUEST); + + // when + final ExtractableResponse response = requestGetCurrencies(adminMemberToken, 0, 10); + final CurrencyListResponse currencyListResponse = response.as(CurrencyListResponse.class); + // then + assertSoftly( + softly -> { + softly.assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value()); + softly.assertThat(currencyListResponse.getLastPageIndex()).isPositive(); + softly.assertThat(currencyListResponse.getCurrencies()).isNotEmpty(); + softly.assertThat(currencyListResponse.getCurrencies().get(0)) + .usingRecursiveComparison() + .ignoringFields("id") + .isEqualTo(CURRENCY_REQUEST); + } + ); + } + + + public static ExtractableResponse requestCreateCurrency( + final MemberTokens memberTokens, + final CurrencyRequest request + ) { + return RestAssured + .given().log().all() + .header(AUTHORIZATION, + "Bearer " + memberTokens.getAccessToken()) + .cookies("refresh-token", memberTokens.getRefreshToken()) + .contentType(JSON) + .body(request) + .when().post("/admin/currencies") + .then().log().all() + .extract(); + } + + + protected static ExtractableResponse requestGetCurrencies( + final MemberTokens memberTokens, + final int page, + final int size + ) { + return RestAssured + .given().log().all() + .header(AUTHORIZATION, + "Bearer " + memberTokens.getAccessToken()) + .cookies("refresh-token", memberTokens.getRefreshToken()) + .when().get("/admin/currencies?page={page}&size={size}", page, size) + .then().log().all() + .extract(); + } +} From fe4e5ca9ec37420e32d1ec056bba07bc9cc20dc2 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Tue, 23 Jan 2024 23:17:48 +0900 Subject: [PATCH 72/88] =?UTF-8?q?refactor:=20userName=EC=9D=84=20username?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/hanglog/admin/domain/AdminMember.java | 10 +++++----- .../admin/domain/repository/AdminMemberRepository.java | 4 ++-- .../admin/dto/response/AdminMemberResponse.java | 2 +- .../java/hanglog/admin/service/AdminLoginService.java | 2 +- .../java/hanglog/admin/service/AdminMemberService.java | 4 ++-- .../db/migration/V9__Create_table_admin_member.sql | 2 +- .../hanglog/admin/service/AdminLoginServiceTest.java | 4 ++-- .../hanglog/admin/service/AdminMemberServiceTest.java | 2 +- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/backend/src/main/java/hanglog/admin/domain/AdminMember.java b/backend/src/main/java/hanglog/admin/domain/AdminMember.java index e0085b67f..5a2fa751e 100644 --- a/backend/src/main/java/hanglog/admin/domain/AdminMember.java +++ b/backend/src/main/java/hanglog/admin/domain/AdminMember.java @@ -31,7 +31,7 @@ public class AdminMember { private Long id; @Column(nullable = false, unique = true, length = 20) - private String userName; + private String username; @Column(nullable = false, length = 64) private String password; @@ -53,9 +53,9 @@ public class AdminMember { @LastModifiedDate private LocalDateTime modifiedAt; - public AdminMember(final Long id, final String userName, final String password, final AdminType adminType) { + public AdminMember(final Long id, final String username, final String password, final AdminType adminType) { this.id = id; - this.userName = userName; + this.username = username; this.password = password; this.lastLoginDate = LocalDateTime.now(); this.adminType = adminType; @@ -64,7 +64,7 @@ public AdminMember(final Long id, final String userName, final String password, this.modifiedAt = LocalDateTime.now(); } - public AdminMember(final String userName, final String password, final AdminType adminType) { - this(null, userName, password, adminType); + public AdminMember(final String username, final String password, final AdminType adminType) { + this(null, username, password, adminType); } } diff --git a/backend/src/main/java/hanglog/admin/domain/repository/AdminMemberRepository.java b/backend/src/main/java/hanglog/admin/domain/repository/AdminMemberRepository.java index 036215500..1bcdc1ec5 100644 --- a/backend/src/main/java/hanglog/admin/domain/repository/AdminMemberRepository.java +++ b/backend/src/main/java/hanglog/admin/domain/repository/AdminMemberRepository.java @@ -7,9 +7,9 @@ public interface AdminMemberRepository extends JpaRepository { - Optional findByUserName(String userName); + Optional findByUsername(String username); Boolean existsByIdAndAdminType(Long id, AdminType adminType); - Boolean existsByUserName(String userName); + Boolean existsByUsername(String username); } diff --git a/backend/src/main/java/hanglog/admin/dto/response/AdminMemberResponse.java b/backend/src/main/java/hanglog/admin/dto/response/AdminMemberResponse.java index b00c58ab6..880906297 100644 --- a/backend/src/main/java/hanglog/admin/dto/response/AdminMemberResponse.java +++ b/backend/src/main/java/hanglog/admin/dto/response/AdminMemberResponse.java @@ -15,7 +15,7 @@ public class AdminMemberResponse { public static AdminMemberResponse from(final AdminMember adminMember) { return new AdminMemberResponse( adminMember.getId(), - adminMember.getUserName(), + adminMember.getUsername(), adminMember.getAdminType().name() ); } diff --git a/backend/src/main/java/hanglog/admin/service/AdminLoginService.java b/backend/src/main/java/hanglog/admin/service/AdminLoginService.java index 447c1a824..b59a45b9a 100644 --- a/backend/src/main/java/hanglog/admin/service/AdminLoginService.java +++ b/backend/src/main/java/hanglog/admin/service/AdminLoginService.java @@ -33,7 +33,7 @@ public class AdminLoginService { public MemberTokens login(final AdminLoginRequest adminLoginRequest) { - final AdminMember adminMember = adminMemberRepository.findByUserName( + final AdminMember adminMember = adminMemberRepository.findByUsername( adminLoginRequest.getUserName()) .orElseThrow(() -> new AdminException(INVALID_USER_NAME)); diff --git a/backend/src/main/java/hanglog/admin/service/AdminMemberService.java b/backend/src/main/java/hanglog/admin/service/AdminMemberService.java index 1054e832c..acd278c43 100644 --- a/backend/src/main/java/hanglog/admin/service/AdminMemberService.java +++ b/backend/src/main/java/hanglog/admin/service/AdminMemberService.java @@ -34,7 +34,7 @@ public List getAdminMembers() { } public Long createAdminMember(final AdminMemberCreateRequest request) { - if (adminMemberRepository.existsByUserName(request.getUserName())) { + if (adminMemberRepository.existsByUsername(request.getUserName())) { throw new AdminException(DUPLICATED_ADMIN_USERNAME); } @@ -54,7 +54,7 @@ public void updatePassword(final Long adminMemberId, final PasswordUpdateRequest final AdminMember updatedAdminMember = new AdminMember( adminMember.getId(), - adminMember.getUserName(), + adminMember.getUsername(), passwordEncoder.encode(request.getNewPassword()), adminMember.getAdminType() ); diff --git a/backend/src/main/resources/db/migration/V9__Create_table_admin_member.sql b/backend/src/main/resources/db/migration/V9__Create_table_admin_member.sql index be0c71755..093254df6 100644 --- a/backend/src/main/resources/db/migration/V9__Create_table_admin_member.sql +++ b/backend/src/main/resources/db/migration/V9__Create_table_admin_member.sql @@ -1,7 +1,7 @@ CREATE TABLE IF NOT EXISTS admin_member ( id BIGINT NOT NULL AUTO_INCREMENT, - user_name VARCHAR(20) NOT NULL, + username VARCHAR(20) NOT NULL, password VARCHAR(64) NOT NULL, last_login_date DATETIME(6) NOT NULL, admin_type ENUM ('ADMIN','MASTER'), diff --git a/backend/src/test/java/hanglog/admin/service/AdminLoginServiceTest.java b/backend/src/test/java/hanglog/admin/service/AdminLoginServiceTest.java index 57fa12ed5..13cc64dc9 100644 --- a/backend/src/test/java/hanglog/admin/service/AdminLoginServiceTest.java +++ b/backend/src/test/java/hanglog/admin/service/AdminLoginServiceTest.java @@ -50,7 +50,7 @@ public void testLoginSuccess() { final AdminMember adminMember = new AdminMember(1L, "user", "password", AdminType.ADMIN); final MemberTokens memberTokens = new MemberTokens("accessToken", "refreshToken"); - when(adminMemberRepository.findByUserName(anyString())).thenReturn(Optional.of(adminMember)); + when(adminMemberRepository.findByUsername(anyString())).thenReturn(Optional.of(adminMember)); when(passwordEncoder.matches(anyString(), anyString())).thenReturn(true); when(jwtProvider.generateLoginToken(anyString())).thenReturn(memberTokens); @@ -71,7 +71,7 @@ public void testLoginFailure_InvalidPassword() { final AdminLoginRequest loginRequest = new AdminLoginRequest("user", "wrongpassword"); final AdminMember adminMember = new AdminMember("user", "password", AdminType.ADMIN); - when(adminMemberRepository.findByUserName(anyString())).thenReturn(Optional.of(adminMember)); + when(adminMemberRepository.findByUsername(anyString())).thenReturn(Optional.of(adminMember)); when(passwordEncoder.matches(anyString(), anyString())).thenReturn(false); // when & then diff --git a/backend/src/test/java/hanglog/admin/service/AdminMemberServiceTest.java b/backend/src/test/java/hanglog/admin/service/AdminMemberServiceTest.java index 97de332e8..dc5e87f1f 100644 --- a/backend/src/test/java/hanglog/admin/service/AdminMemberServiceTest.java +++ b/backend/src/test/java/hanglog/admin/service/AdminMemberServiceTest.java @@ -46,7 +46,7 @@ void createAdminMember() { ); final AdminMember adminMember = new AdminMember(1L, "username", "password", AdminType.ADMIN); - given(adminMemberRepository.existsByUserName("username")).willReturn(false); + given(adminMemberRepository.existsByUsername("username")).willReturn(false); given(adminMemberRepository.save(any(AdminMember.class))).willReturn(adminMember); given(passwordEncoder.encode("password")).willReturn(anyString()); From d429a5a3a46937ee867096d0ee0d8bba84d1caeb Mon Sep 17 00:00:00 2001 From: LJW25 Date: Tue, 23 Jan 2024 23:22:10 +0900 Subject: [PATCH 73/88] =?UTF-8?q?refactor:=20request=20=EA=B2=80=EC=A6=9D?= =?UTF-8?q?=20=EB=A9=94=EC=84=B8=EC=A7=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../category/dto/request/CategoryRequest.java | 2 +- .../currency/dto/request/CurrencyRequest.java | 60 +++++++++---------- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/backend/src/main/java/hanglog/category/dto/request/CategoryRequest.java b/backend/src/main/java/hanglog/category/dto/request/CategoryRequest.java index 6407f8014..9a04f31fa 100644 --- a/backend/src/main/java/hanglog/category/dto/request/CategoryRequest.java +++ b/backend/src/main/java/hanglog/category/dto/request/CategoryRequest.java @@ -17,7 +17,7 @@ public class CategoryRequest { @Size(max = 50, message = "영어이름은 50자를 초과할 수 없습니다.") private final String engName; - @NotBlank(message = "한글 이을 입력해주세요.") + @NotBlank(message = "한글 이름을 입력해주세요.") @Size(max = 50, message = "한글 이름은 50자를 초과할 수 없습니다.") private final String korName; } diff --git a/backend/src/main/java/hanglog/currency/dto/request/CurrencyRequest.java b/backend/src/main/java/hanglog/currency/dto/request/CurrencyRequest.java index 6f7f6685b..0aef30cce 100644 --- a/backend/src/main/java/hanglog/currency/dto/request/CurrencyRequest.java +++ b/backend/src/main/java/hanglog/currency/dto/request/CurrencyRequest.java @@ -17,53 +17,53 @@ public class CurrencyRequest { @DateTimeFormat(pattern = "yyyy-MM-dd") private final LocalDate date; - @NotNull(message = "USD 금액을 입력해주세요.") - @DecimalMin(value = "0", message = "USD 금액이 0원보다 작을 수 없습니다.") - @DecimalMax(value = "100000", message = "USD 금액이 10만원보다 클 수 없습니다.") + @NotNull(message = "USD 환율을 입력해주세요.") + @DecimalMin(value = "0", message = "USD 환율이 0원보다 작을 수 없습니다.") + @DecimalMax(value = "100000", message = "USD 환율이 10만원보다 클 수 없습니다.") private final double usd; - @NotNull(message = "EUR 금액을 입력해주세요.") - @DecimalMin(value = "0", message = "EUR 금액이 0원보다 작을 수 없습니다.") - @DecimalMax(value = "100000", message = "EUR 금액이 10만원보다 클 수 없습니다.") + @NotNull(message = "EUR 환율을 입력해주세요.") + @DecimalMin(value = "0", message = "EUR 환율이 0원보다 작을 수 없습니다.") + @DecimalMax(value = "100000", message = "EUR 환율이 10만원보다 클 수 없습니다.") private final double eur; - @NotNull(message = "GBP 금액을 입력해주세요.") - @DecimalMin(value = "0", message = "GBP 금액이 0원보다 작을 수 없습니다.") - @DecimalMax(value = "100000", message = "GBP 금액이 10만원보다 클 수 없습니다.") + @NotNull(message = "GBP 환율을 입력해주세요.") + @DecimalMin(value = "0", message = "GBP 환율이 0원보다 작을 수 없습니다.") + @DecimalMax(value = "100000", message = "GBP 환율이 10만원보다 클 수 없습니다.") private final double gbp; - @NotNull(message = "JPY 금액을 입력해주세요.") - @DecimalMin(value = "0", message = "JPY 금액이 0원보다 작을 수 없습니다.") - @DecimalMax(value = "100000", message = "JPY 금액이 10만원보다 클 수 없습니다.") + @NotNull(message = "JPY 환율을 입력해주세요.") + @DecimalMin(value = "0", message = "JPY 환율이 0원보다 작을 수 없습니다.") + @DecimalMax(value = "100000", message = "JPY 환율이 10만원보다 클 수 없습니다.") private final double jpy; - @NotNull(message = "CNY 금액을 입력해주세요.") - @DecimalMin(value = "0", message = "CNY 금액이 0원보다 작을 수 없습니다.") - @DecimalMax(value = "100000", message = "CNY 금액이 10만원보다 클 수 없습니다.") + @NotNull(message = "CNY 환율을 입력해주세요.") + @DecimalMin(value = "0", message = "CNY 환율이 0원보다 작을 수 없습니다.") + @DecimalMax(value = "100000", message = "CNY 환율이 10만원보다 클 수 없습니다.") private final double cny; - @NotNull(message = "CHF 금액을 입력해주세요.") - @DecimalMin(value = "0", message = "CHF 금액이 0원보다 작을 수 없습니다.") - @DecimalMax(value = "100000", message = "CHF 금액이 10만원보다 클 수 없습니다.") + @NotNull(message = "CHF 환율을 입력해주세요.") + @DecimalMin(value = "0", message = "CHF 환율이 0원보다 작을 수 없습니다.") + @DecimalMax(value = "100000", message = "CHF 환율이 10만원보다 클 수 없습니다.") private final double chf; - @NotNull(message = "SGD 금액을 입력해주세요.") - @DecimalMin(value = "0", message = "SGD 금액이 0원보다 작을 수 없습니다.") - @DecimalMax(value = "100000", message = "SGD 금액이 10만원보다 클 수 없습니다.") + @NotNull(message = "SGD 환율을 입력해주세요.") + @DecimalMin(value = "0", message = "SGD 환율이 0원보다 작을 수 없습니다.") + @DecimalMax(value = "100000", message = "SGD 환율이 10만원보다 클 수 없습니다.") private final double sgd; - @NotNull(message = "THB 금액을 입력해주세요.") - @DecimalMin(value = "0", message = "THB 금액이 0원보다 작을 수 없습니다.") - @DecimalMax(value = "100000", message = "THB 금액이 10만원보다 클 수 없습니다.") + @NotNull(message = "THB 환율을 입력해주세요.") + @DecimalMin(value = "0", message = "THB 환율이 0원보다 작을 수 없습니다.") + @DecimalMax(value = "100000", message = "THB 환율이 10만원보다 클 수 없습니다.") private final double thb; - @NotNull(message = "HKD 금액을 입력해주세요.") - @DecimalMin(value = "0", message = "HKD 금액이 0원보다 작을 수 없습니다.") - @DecimalMax(value = "100000", message = "HKD 금액이 10만원보다 클 수 없습니다.") + @NotNull(message = "HKD 환율을 입력해주세요.") + @DecimalMin(value = "0", message = "HKD 환율이 0원보다 작을 수 없습니다.") + @DecimalMax(value = "100000", message = "HKD 환율이 10만원보다 클 수 없습니다.") private final double hkd; - @NotNull(message = "KRW 금액을 입력해주세요.") - @DecimalMin(value = "0", message = "KRW 금액이 0원보다 작을 수 없습니다.") - @DecimalMax(value = "100000", message = "KRW 금액이 10만원보다 클 수 없습니다.") + @NotNull(message = "KRW 환율을 입력해주세요.") + @DecimalMin(value = "0", message = "KRW 환율이 0원보다 작을 수 없습니다.") + @DecimalMax(value = "100000", message = "KRW 환율이 10만원보다 클 수 없습니다.") private final double krw; } From bedc68cc498964e254bc7127022af3f5c177b7b7 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Tue, 23 Jan 2024 23:36:36 +0900 Subject: [PATCH 74/88] =?UTF-8?q?refactor:=20Currency=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=9D=B4=EB=A6=84=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hanglog/currency/domain/repository/CurrencyRepository.java | 2 +- .../src/main/java/hanglog/currency/service/CurrencyService.java | 2 +- .../test/java/hanglog/currency/service/CurrencyServiceTest.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/hanglog/currency/domain/repository/CurrencyRepository.java b/backend/src/main/java/hanglog/currency/domain/repository/CurrencyRepository.java index 1bb0071bc..6bfe9d4ff 100644 --- a/backend/src/main/java/hanglog/currency/domain/repository/CurrencyRepository.java +++ b/backend/src/main/java/hanglog/currency/domain/repository/CurrencyRepository.java @@ -15,5 +15,5 @@ public interface CurrencyRepository extends JpaRepository { boolean existsByDate(LocalDate date); - List findBy(final Pageable pageable); + List findAllBy(final Pageable pageable); } diff --git a/backend/src/main/java/hanglog/currency/service/CurrencyService.java b/backend/src/main/java/hanglog/currency/service/CurrencyService.java index dcc7ef403..4ad9c6d52 100644 --- a/backend/src/main/java/hanglog/currency/service/CurrencyService.java +++ b/backend/src/main/java/hanglog/currency/service/CurrencyService.java @@ -140,7 +140,7 @@ private Currency createCurrency(final LocalDate date, final Map currencies = currencyRepository.findBy(pageable.previousOrFirst()); + final List currencies = currencyRepository.findAllBy(pageable.previousOrFirst()); final List currencyResponses = currencies.stream() .map(CurrencyResponse::of) .toList(); diff --git a/backend/src/test/java/hanglog/currency/service/CurrencyServiceTest.java b/backend/src/test/java/hanglog/currency/service/CurrencyServiceTest.java index 216422f85..b7d1cee33 100644 --- a/backend/src/test/java/hanglog/currency/service/CurrencyServiceTest.java +++ b/backend/src/test/java/hanglog/currency/service/CurrencyServiceTest.java @@ -45,7 +45,7 @@ void getCurrenciesByPage() { // given final Pageable pageable = PageRequest.of(0, 10); - given(currencyRepository.findBy(any())).willReturn(List.of(CURRENCY_1, CURRENCY_2)); + given(currencyRepository.findAllBy(any())).willReturn(List.of(CURRENCY_1, CURRENCY_2)); given(currencyRepository.count()).willReturn(2L); // when From e4b5941c35c22a1abfc281be9b6f9758002e1950 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Tue, 23 Jan 2024 23:39:53 +0900 Subject: [PATCH 75/88] =?UTF-8?q?refactor:=20response=20=EB=B3=80=EC=88=98?= =?UTF-8?q?=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hanglog/admin/presentation/AdminCategoryController.java | 4 ++-- .../java/hanglog/admin/presentation/AdminCityController.java | 4 ++-- .../hanglog/admin/presentation/AdminCurrencyController.java | 4 ++-- .../hanglog/admin/presentation/AdminMemberController.java | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/src/main/java/hanglog/admin/presentation/AdminCategoryController.java b/backend/src/main/java/hanglog/admin/presentation/AdminCategoryController.java index daaa3f7db..7e7538d02 100644 --- a/backend/src/main/java/hanglog/admin/presentation/AdminCategoryController.java +++ b/backend/src/main/java/hanglog/admin/presentation/AdminCategoryController.java @@ -32,8 +32,8 @@ public class AdminCategoryController { public ResponseEntity> getCategoriesDetail( @AdminAuth final Accessor accessor ) { - final List categoriesDetail = categoryService.getAllCategoriesDetail(); - return ResponseEntity.ok(categoriesDetail); + final List categoryDetailResponses = categoryService.getAllCategoriesDetail(); + return ResponseEntity.ok(categoryDetailResponses); } @PostMapping diff --git a/backend/src/main/java/hanglog/admin/presentation/AdminCityController.java b/backend/src/main/java/hanglog/admin/presentation/AdminCityController.java index 20dfe2194..6a225757a 100644 --- a/backend/src/main/java/hanglog/admin/presentation/AdminCityController.java +++ b/backend/src/main/java/hanglog/admin/presentation/AdminCityController.java @@ -32,8 +32,8 @@ public class AdminCityController { public ResponseEntity> getCitiesDetail( @AdminAuth final Accessor accessor ) { - final List citiesDetail = cityService.getAllCitiesDetail(); - return ResponseEntity.ok(citiesDetail); + final List cityDetailResponses = cityService.getAllCitiesDetail(); + return ResponseEntity.ok(cityDetailResponses); } @PostMapping diff --git a/backend/src/main/java/hanglog/admin/presentation/AdminCurrencyController.java b/backend/src/main/java/hanglog/admin/presentation/AdminCurrencyController.java index f9773638d..25b03a858 100644 --- a/backend/src/main/java/hanglog/admin/presentation/AdminCurrencyController.java +++ b/backend/src/main/java/hanglog/admin/presentation/AdminCurrencyController.java @@ -36,8 +36,8 @@ public ResponseEntity getCurrencies( @AdminAuth final Accessor accessor, @PageableDefault(sort = "date", direction = DESC) final Pageable pageable ) { - final CurrencyListResponse currencies = currencyService.getCurrenciesByPage(pageable); - return ResponseEntity.ok().body(currencies); + final CurrencyListResponse currencyListResponse = currencyService.getCurrenciesByPage(pageable); + return ResponseEntity.ok().body(currencyListResponse); } @PostMapping diff --git a/backend/src/main/java/hanglog/admin/presentation/AdminMemberController.java b/backend/src/main/java/hanglog/admin/presentation/AdminMemberController.java index 407d2b987..bf0ec19a9 100644 --- a/backend/src/main/java/hanglog/admin/presentation/AdminMemberController.java +++ b/backend/src/main/java/hanglog/admin/presentation/AdminMemberController.java @@ -32,8 +32,8 @@ public class AdminMemberController { public ResponseEntity> getAdminMembers( @AdminAuth final Accessor accessor ) { - final List adminMembers = adminMemberService.getAdminMembers(); - return ResponseEntity.ok(adminMembers); + final List adminMemberResponses = adminMemberService.getAdminMembers(); + return ResponseEntity.ok(adminMemberResponses); } @PostMapping From e7bece6101d0e9aaa3288d7bb4e7b99e79d679c8 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Tue, 23 Jan 2024 23:46:48 +0900 Subject: [PATCH 76/88] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=83=9D=EC=84=B1=EC=9E=90=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/hanglog/admin/dto/request/AdminLoginRequest.java | 4 ---- .../hanglog/admin/dto/request/AdminMemberCreateRequest.java | 4 ---- .../java/hanglog/admin/dto/request/PasswordUpdateRequest.java | 4 ---- 3 files changed, 12 deletions(-) diff --git a/backend/src/main/java/hanglog/admin/dto/request/AdminLoginRequest.java b/backend/src/main/java/hanglog/admin/dto/request/AdminLoginRequest.java index 8bcbfaf94..65672765b 100644 --- a/backend/src/main/java/hanglog/admin/dto/request/AdminLoginRequest.java +++ b/backend/src/main/java/hanglog/admin/dto/request/AdminLoginRequest.java @@ -1,16 +1,12 @@ package hanglog.admin.dto.request; -import static lombok.AccessLevel.PRIVATE; - import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import lombok.AllArgsConstructor; import lombok.Getter; -import lombok.NoArgsConstructor; @Getter @AllArgsConstructor -@NoArgsConstructor(access = PRIVATE) public class AdminLoginRequest { @NotNull(message = "사용자 이름을 입력해주세요.") diff --git a/backend/src/main/java/hanglog/admin/dto/request/AdminMemberCreateRequest.java b/backend/src/main/java/hanglog/admin/dto/request/AdminMemberCreateRequest.java index 42830f063..2f6274b39 100644 --- a/backend/src/main/java/hanglog/admin/dto/request/AdminMemberCreateRequest.java +++ b/backend/src/main/java/hanglog/admin/dto/request/AdminMemberCreateRequest.java @@ -1,17 +1,13 @@ package hanglog.admin.dto.request; -import static lombok.AccessLevel.PRIVATE; - import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import lombok.AllArgsConstructor; import lombok.Getter; -import lombok.NoArgsConstructor; @Getter @AllArgsConstructor -@NoArgsConstructor(access = PRIVATE) public class AdminMemberCreateRequest { @NotNull(message = "사용자 이름을 입력해주세요.") diff --git a/backend/src/main/java/hanglog/admin/dto/request/PasswordUpdateRequest.java b/backend/src/main/java/hanglog/admin/dto/request/PasswordUpdateRequest.java index ef6289984..0205b35fc 100644 --- a/backend/src/main/java/hanglog/admin/dto/request/PasswordUpdateRequest.java +++ b/backend/src/main/java/hanglog/admin/dto/request/PasswordUpdateRequest.java @@ -1,17 +1,13 @@ package hanglog.admin.dto.request; -import static lombok.AccessLevel.PRIVATE; - import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import lombok.AllArgsConstructor; import lombok.Getter; -import lombok.NoArgsConstructor; @Getter @AllArgsConstructor -@NoArgsConstructor(access = PRIVATE) public class PasswordUpdateRequest { @NotNull(message = "기존 비밀번호를 입력해주세요.") From af24938d4600725eaee006494a5a8b41c80f605a Mon Sep 17 00:00:00 2001 From: LJW25 Date: Tue, 23 Jan 2024 23:59:53 +0900 Subject: [PATCH 77/88] =?UTF-8?q?chore:=20=ED=8C=A8=ED=82=A4=EC=A7=80=20?= =?UTF-8?q?=EB=B2=84=EC=A0=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/build.gradle b/backend/build.gradle index b466a8f12..3d56174fa 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -57,7 +57,7 @@ dependencies { implementation 'org.flywaydb:flyway-core' implementation 'org.flywaydb:flyway-mysql' - implementation group: 'org.mindrot', name: 'jbcrypt', version: '0.3m' + implementation 'org.mindrot:jbcrypt:0.4' } test { From d997a8d9391c94c7fafe373867509910b2546bfb Mon Sep 17 00:00:00 2001 From: LJW25 Date: Wed, 24 Jan 2024 13:52:06 +0900 Subject: [PATCH 78/88] =?UTF-8?q?refactor:=20=EC=BB=A8=EB=B2=A4=EC=85=98?= =?UTF-8?q?=20=EB=A7=9E=EC=B6=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hanglog/admin/dto/response/AdminMemberResponse.java | 2 +- .../admin/presentation/AdminCurrencyController.java | 2 +- .../java/hanglog/admin/service/AdminLoginService.java | 6 ++---- .../java/hanglog/admin/service/AdminMemberService.java | 9 ++++++--- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/backend/src/main/java/hanglog/admin/dto/response/AdminMemberResponse.java b/backend/src/main/java/hanglog/admin/dto/response/AdminMemberResponse.java index 880906297..1a9d9f6b2 100644 --- a/backend/src/main/java/hanglog/admin/dto/response/AdminMemberResponse.java +++ b/backend/src/main/java/hanglog/admin/dto/response/AdminMemberResponse.java @@ -5,7 +5,7 @@ import lombok.RequiredArgsConstructor; @Getter -@RequiredArgsConstructor +@RequiredArgsConstructor() public class AdminMemberResponse { private final Long id; diff --git a/backend/src/main/java/hanglog/admin/presentation/AdminCurrencyController.java b/backend/src/main/java/hanglog/admin/presentation/AdminCurrencyController.java index 25b03a858..c9fbd66bd 100644 --- a/backend/src/main/java/hanglog/admin/presentation/AdminCurrencyController.java +++ b/backend/src/main/java/hanglog/admin/presentation/AdminCurrencyController.java @@ -52,7 +52,7 @@ public ResponseEntity createCurrency( @PutMapping("/{currencyId}") @AdminOnly - public ResponseEntity updateCategory( + public ResponseEntity updateCurrency( @AdminAuth final Accessor accessor, @PathVariable final Long currencyId, @RequestBody @Valid final CurrencyRequest currencyRequest diff --git a/backend/src/main/java/hanglog/admin/service/AdminLoginService.java b/backend/src/main/java/hanglog/admin/service/AdminLoginService.java index b59a45b9a..919e29d4b 100644 --- a/backend/src/main/java/hanglog/admin/service/AdminLoginService.java +++ b/backend/src/main/java/hanglog/admin/service/AdminLoginService.java @@ -31,10 +31,9 @@ public class AdminLoginService { private final BearerAuthorizationExtractor bearerExtractor; private final PasswordEncoder passwordEncoder; - public MemberTokens login(final AdminLoginRequest adminLoginRequest) { - final AdminMember adminMember = adminMemberRepository.findByUsername( - adminLoginRequest.getUserName()) + final AdminMember adminMember = adminMemberRepository + .findByUsername(adminLoginRequest.getUserName()) .orElseThrow(() -> new AdminException(INVALID_USER_NAME)); if (passwordEncoder.matches(adminLoginRequest.getPassword(), adminMember.getPassword())) { @@ -48,7 +47,6 @@ public MemberTokens login(final AdminLoginRequest adminLoginRequest) { throw new AdminException(INVALID_PASSWORD); } - public String renewalAccessToken(final String refreshTokenRequest, final String authorizationHeader) { final String accessToken = bearerExtractor.extractAccessToken(authorizationHeader); if (jwtProvider.isValidRefreshAndInvalidAccess(refreshTokenRequest, accessToken)) { diff --git a/backend/src/main/java/hanglog/admin/service/AdminMemberService.java b/backend/src/main/java/hanglog/admin/service/AdminMemberService.java index acd278c43..d2c581b9e 100644 --- a/backend/src/main/java/hanglog/admin/service/AdminMemberService.java +++ b/backend/src/main/java/hanglog/admin/service/AdminMemberService.java @@ -38,10 +38,13 @@ public Long createAdminMember(final AdminMemberCreateRequest request) { throw new AdminException(DUPLICATED_ADMIN_USERNAME); } - return adminMemberRepository.save(new AdminMember(request.getUserName(), + return adminMemberRepository.save( + new AdminMember( + request.getUserName(), passwordEncoder.encode(request.getPassword()), - AdminType.getMappedAdminType(request.getAdminType()))) - .getId(); + AdminType.getMappedAdminType(request.getAdminType()) + ) + ).getId(); } public void updatePassword(final Long adminMemberId, final PasswordUpdateRequest request) { From 1a122370baf697330067f4a6ddf91f8666e37ddc Mon Sep 17 00:00:00 2001 From: LJW25 Date: Wed, 24 Jan 2024 14:39:45 +0900 Subject: [PATCH 79/88] =?UTF-8?q?refactor:=20response=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=EC=9E=90=20private=20=EC=A0=91=EA=B7=BC=EC=A0=9C=EC=96=B4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/hanglog/admin/dto/response/AdminMemberResponse.java | 4 +++- .../category/dto/response/CategoryDetailResponse.java | 4 +++- .../hanglog/currency/dto/response/CurrencyListResponse.java | 4 ++-- .../admin/presentation/AdminMemberControllerTest.java | 5 ++++- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/backend/src/main/java/hanglog/admin/dto/response/AdminMemberResponse.java b/backend/src/main/java/hanglog/admin/dto/response/AdminMemberResponse.java index 1a9d9f6b2..2e2408a98 100644 --- a/backend/src/main/java/hanglog/admin/dto/response/AdminMemberResponse.java +++ b/backend/src/main/java/hanglog/admin/dto/response/AdminMemberResponse.java @@ -1,11 +1,13 @@ package hanglog.admin.dto.response; +import static lombok.AccessLevel.PRIVATE; + import hanglog.admin.domain.AdminMember; import lombok.Getter; import lombok.RequiredArgsConstructor; @Getter -@RequiredArgsConstructor() +@RequiredArgsConstructor(access = PRIVATE) public class AdminMemberResponse { private final Long id; diff --git a/backend/src/main/java/hanglog/category/dto/response/CategoryDetailResponse.java b/backend/src/main/java/hanglog/category/dto/response/CategoryDetailResponse.java index aa3da294f..0f0f51990 100644 --- a/backend/src/main/java/hanglog/category/dto/response/CategoryDetailResponse.java +++ b/backend/src/main/java/hanglog/category/dto/response/CategoryDetailResponse.java @@ -1,11 +1,13 @@ package hanglog.category.dto.response; +import static lombok.AccessLevel.PRIVATE; + import hanglog.category.domain.Category; import lombok.Getter; import lombok.RequiredArgsConstructor; @Getter -@RequiredArgsConstructor +@RequiredArgsConstructor(access = PRIVATE) public class CategoryDetailResponse { private final Long id; diff --git a/backend/src/main/java/hanglog/currency/dto/response/CurrencyListResponse.java b/backend/src/main/java/hanglog/currency/dto/response/CurrencyListResponse.java index 1f7ef889b..cf1fe94e0 100644 --- a/backend/src/main/java/hanglog/currency/dto/response/CurrencyListResponse.java +++ b/backend/src/main/java/hanglog/currency/dto/response/CurrencyListResponse.java @@ -9,6 +9,6 @@ @RequiredArgsConstructor public class CurrencyListResponse { - final List currencies; - final Long lastPageIndex; + private final List currencies; + private final Long lastPageIndex; } diff --git a/backend/src/test/java/hanglog/admin/presentation/AdminMemberControllerTest.java b/backend/src/test/java/hanglog/admin/presentation/AdminMemberControllerTest.java index 647791be9..e1ce4863c 100644 --- a/backend/src/test/java/hanglog/admin/presentation/AdminMemberControllerTest.java +++ b/backend/src/test/java/hanglog/admin/presentation/AdminMemberControllerTest.java @@ -18,6 +18,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.fasterxml.jackson.databind.ObjectMapper; +import hanglog.admin.domain.AdminMember; +import hanglog.admin.domain.type.AdminType; import hanglog.admin.dto.request.AdminMemberCreateRequest; import hanglog.admin.dto.request.PasswordUpdateRequest; import hanglog.admin.dto.response.AdminMemberResponse; @@ -67,7 +69,8 @@ void setUp() { @Test void getAdminMembers() throws Exception { // given - final AdminMemberResponse response = new AdminMemberResponse(1L, "adminUser", "MASTER"); + final AdminMember adminMember = new AdminMember(1L, "adminUser", "password", AdminType.MASTER); + final AdminMemberResponse response = AdminMemberResponse.from(adminMember); given(adminMemberService.getAdminMembers()).willReturn(List.of(response)); From 114cd242c96be39f8f674c6f026b48bedaa2c28d Mon Sep 17 00:00:00 2001 From: LJW25 Date: Wed, 24 Jan 2024 14:53:35 +0900 Subject: [PATCH 80/88] =?UTF-8?q?refactor:=20category=20=EC=A4=91=EB=B3=B5?= =?UTF-8?q?=20=EC=A0=9C=EA=B1=B0=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hanglog/category/domain/Category.java | 4 --- .../domain/repository/CategoryRepository.java | 4 ++- .../category/service/CategoryService.java | 36 +++++++++++++------ .../global/exception/ExceptionCode.java | 7 ++-- .../category/service/CategoryServiceTest.java | 16 ++++++--- 5 files changed, 43 insertions(+), 24 deletions(-) diff --git a/backend/src/main/java/hanglog/category/domain/Category.java b/backend/src/main/java/hanglog/category/domain/Category.java index 3a4f65623..2578320f2 100644 --- a/backend/src/main/java/hanglog/category/domain/Category.java +++ b/backend/src/main/java/hanglog/category/domain/Category.java @@ -41,10 +41,6 @@ public void update(final CategoryRequest categoryRequest) { this.korName = categoryRequest.getKorName(); } - public boolean isSameNames(final String engName, final String korName) { - return this.engName.equals(engName) && this.korName.equals(korName); - } - @Override public boolean equals(final Object o) { if (this == o) { diff --git a/backend/src/main/java/hanglog/category/domain/repository/CategoryRepository.java b/backend/src/main/java/hanglog/category/domain/repository/CategoryRepository.java index d4685512b..9b3fd20b9 100644 --- a/backend/src/main/java/hanglog/category/domain/repository/CategoryRepository.java +++ b/backend/src/main/java/hanglog/category/domain/repository/CategoryRepository.java @@ -15,5 +15,7 @@ public interface CategoryRepository extends JpaRepository { @Query("SELECT c FROM Category c WHERE c.id = 600") Category findCategoryETC(); - Boolean existsByEngNameAndKorName(String engName, String korName); + Boolean existsByEngName(String engName); + + Boolean existsByKorName(String korName); } diff --git a/backend/src/main/java/hanglog/category/service/CategoryService.java b/backend/src/main/java/hanglog/category/service/CategoryService.java index 743d8088c..30885fda4 100644 --- a/backend/src/main/java/hanglog/category/service/CategoryService.java +++ b/backend/src/main/java/hanglog/category/service/CategoryService.java @@ -1,7 +1,7 @@ package hanglog.category.service; +import static hanglog.global.exception.ExceptionCode.DUPLICATED_CATEGORY_ENG_NAME; import static hanglog.global.exception.ExceptionCode.DUPLICATED_CATEGORY_ID; -import static hanglog.global.exception.ExceptionCode.DUPLICATED_CATEGORY_NAME; import static hanglog.global.exception.ExceptionCode.NOT_FOUND_CATEGORY_ID; import hanglog.category.domain.Category; @@ -39,21 +39,32 @@ public List getAllCategoriesDetail() { } public Long save(final CategoryRequest categoryRequest) { - validateCategoryDuplicateId(categoryRequest); - validateCategoryDuplicateName(categoryRequest); + validateCategoryDuplicate(categoryRequest); return categoryRepository.save(Category.of(categoryRequest)).getId(); } - private void validateCategoryDuplicateId(final CategoryRequest categoryRequest) { - if (categoryRepository.existsById(categoryRequest.getId())) { + private void validateCategoryDuplicate(final CategoryRequest categoryRequest) { + validateCategoryDuplicateId(categoryRequest.getId()); + validateCategoryDuplicateEngName(categoryRequest.getEngName()); + validateCategoryDuplicateKorName(categoryRequest.getKorName()); + } + + private void validateCategoryDuplicateId(final Long id) { + if (categoryRepository.existsById(id)) { throw new BadRequestException(DUPLICATED_CATEGORY_ID); } } - private void validateCategoryDuplicateName(final CategoryRequest categoryRequest) { - if (categoryRepository.existsByEngNameAndKorName(categoryRequest.getEngName(), categoryRequest.getKorName())) { - throw new BadRequestException(DUPLICATED_CATEGORY_NAME); + private void validateCategoryDuplicateEngName(final String engName) { + if (categoryRepository.existsByEngName(engName)) { + throw new BadRequestException(DUPLICATED_CATEGORY_ENG_NAME); + } + } + + private void validateCategoryDuplicateKorName(final String korName) { + if (categoryRepository.existsByKorName(korName)) { + throw new BadRequestException(DUPLICATED_CATEGORY_ENG_NAME); } } @@ -69,10 +80,13 @@ public void update(final Long id, final CategoryRequest categoryRequest) { private void validateCategoryDuplicate(final Category category, final CategoryRequest categoryRequest) { if (!category.getId().equals(categoryRequest.getId())) { - validateCategoryDuplicateId(categoryRequest); + validateCategoryDuplicateId(categoryRequest.getId()); + } + if (!category.getEngName().equals(categoryRequest.getEngName())) { + validateCategoryDuplicateEngName(categoryRequest.getEngName()); } - if (!category.isSameNames(categoryRequest.getEngName(), categoryRequest.getKorName())) { - validateCategoryDuplicateName(categoryRequest); + if (!category.getKorName().equals(categoryRequest.getKorName())) { + validateCategoryDuplicateKorName(categoryRequest.getKorName()); } } } diff --git a/backend/src/main/java/hanglog/global/exception/ExceptionCode.java b/backend/src/main/java/hanglog/global/exception/ExceptionCode.java index c0912e24a..3f6074dbe 100644 --- a/backend/src/main/java/hanglog/global/exception/ExceptionCode.java +++ b/backend/src/main/java/hanglog/global/exception/ExceptionCode.java @@ -65,9 +65,10 @@ public enum ExceptionCode { INVALID_ADMIN_AUTHORITY(8201, "해당 관리자 기능에 대한 접근 권한이 없습니다."), DUPLICATED_CITY_NAME(8301, "중복된 나라, 도시 이름입니다."), NOT_FOUND_CITY(8302, "요청한 ID에 해당하는 도시를 찾을 수 없습니다."), - DUPLICATED_CATEGORY_NAME(8311, "중복된 카테고리 이름입니다."), - DUPLICATED_CATEGORY_ID(8312, "중복된 카테고리 아이디입니다."), - NOT_FOUND_CATEGORY(8313, "요청한 ID에 해당하는 카테고리를 찾을 수 없습니다."), + DUPLICATED_CATEGORY_ID(8311, "중복된 카테고리 아이디입니다."), + DUPLICATED_CATEGORY_ENG_NAME(8312, "중복된 카테고리 영어 이름입니다."), + DUPLICATED_CATEGORY_KOR_NAME(8313, "중복된 카테고리 한글 이름입니다."), + NOT_FOUND_CATEGORY(8314, "요청한 ID에 해당하는 카테고리를 찾을 수 없습니다."), INVALID_AUTHORIZATION_CODE(9001, "유효하지 않은 인증 코드입니다."), NOT_SUPPORTED_OAUTH_SERVICE(9002, "해당 OAuth 서비스는 제공하지 않습니다."), diff --git a/backend/src/test/java/hanglog/category/service/CategoryServiceTest.java b/backend/src/test/java/hanglog/category/service/CategoryServiceTest.java index 88c53daca..b5c145893 100644 --- a/backend/src/test/java/hanglog/category/service/CategoryServiceTest.java +++ b/backend/src/test/java/hanglog/category/service/CategoryServiceTest.java @@ -79,7 +79,9 @@ void save() { // given final CategoryRequest categoryRequest = new CategoryRequest(1L, FOOD.getEngName(), FOOD.getKorName()); - given(categoryRepository.existsByEngNameAndKorName(anyString(), anyString())).willReturn(false); + given(categoryRepository.existsById(anyLong())).willReturn(false); + given(categoryRepository.existsByEngName(anyString())).willReturn(false); + given(categoryRepository.existsByKorName(anyString())).willReturn(false); given(categoryRepository.save(any(Category.class))).willReturn(FOOD); // when @@ -95,7 +97,8 @@ void save_DuplicateFail() { // given final CategoryRequest categoryRequest = new CategoryRequest(1L, FOOD.getEngName(), FOOD.getKorName()); - given(categoryRepository.existsByEngNameAndKorName(anyString(), anyString())).willReturn(true); + given(categoryRepository.existsById(anyLong())).willReturn(false); + given(categoryRepository.existsByEngName(anyString())).willReturn(true); // when & then assertThatThrownBy(() -> categoryService.save(categoryRequest)) @@ -106,10 +109,12 @@ void save_DuplicateFail() { @Test void update() { // given - final CategoryRequest categoryRequest = new CategoryRequest(1L, "newName", FOOD.getKorName()); + final CategoryRequest categoryRequest = new CategoryRequest(1L, "newName", "새이름"); given(categoryRepository.findById(anyLong())).willReturn(Optional.of(FOOD)); - given(categoryRepository.existsByEngNameAndKorName(anyString(), anyString())).willReturn(false); + given(categoryRepository.existsById(anyLong())).willReturn(false); + given(categoryRepository.existsByEngName(anyString())).willReturn(false); + given(categoryRepository.existsByKorName(anyString())).willReturn(false); // when & then assertDoesNotThrow(() -> categoryService.update(FOOD.getId(), categoryRequest)); @@ -122,7 +127,8 @@ void update_DuplicateFail() { final CategoryRequest categoryRequest = new CategoryRequest(1L, "newName", FOOD.getKorName()); given(categoryRepository.findById(anyLong())).willReturn(Optional.of(FOOD)); - given(categoryRepository.existsByEngNameAndKorName(anyString(), anyString())).willReturn(true); + given(categoryRepository.existsById(anyLong())).willReturn(false); + given(categoryRepository.existsByEngName(anyString())).willReturn(true); // when & then assertThatThrownBy(() -> categoryService.update(FOOD.getId(), categoryRequest)) From 8a374bb09b374acfe3b78386b64394e78d8f38f6 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Wed, 24 Jan 2024 15:10:05 +0900 Subject: [PATCH 81/88] =?UTF-8?q?refactor:=20currency=20=EC=A4=91=EB=B3=B5?= =?UTF-8?q?=20=EA=B2=80=EC=A6=9D=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hanglog/currency/service/CurrencyService.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/backend/src/main/java/hanglog/currency/service/CurrencyService.java b/backend/src/main/java/hanglog/currency/service/CurrencyService.java index 4ad9c6d52..28b86a6a4 100644 --- a/backend/src/main/java/hanglog/currency/service/CurrencyService.java +++ b/backend/src/main/java/hanglog/currency/service/CurrencyService.java @@ -71,7 +71,7 @@ public void saveYesterdayCurrency() { } public void saveDailyCurrency(final LocalDate date) { - validateDateDuplicate(date); + validateDuplicateDate(date); validateWeekend(date); final List singleCurrencyResponses = requestFilteredCurrencyResponses(date); @@ -88,7 +88,7 @@ public void saveDailyCurrency(final LocalDate date) { currencyRepository.save(currency); } - private void validateDateDuplicate(final LocalDate date) { + private void validateDuplicateDate(final LocalDate date) { if (currencyRepository.existsByDate(date)) { throw new BadRequestException(INVALID_DATE_ALREADY_EXIST); } @@ -159,7 +159,7 @@ private Long getLastPageIndex(final int pageSize) { } public Long save(final CurrencyRequest currencyRequest) { - validateDateDuplicate(currencyRequest.getDate()); + validateDuplicateDate(currencyRequest.getDate()); return currencyRepository.save(Currency.of(currencyRequest)).getId(); } @@ -168,14 +168,14 @@ public void update(final Long id, final CurrencyRequest currencyRequest) { final Currency currency = currencyRepository.findById(id) .orElseThrow(() -> new BadRequestException(NOT_FOUND_CURRENCY_DATA)); - validateDateDuplicate(currency, currencyRequest.getDate()); + validateDuplicateDate(currency.getDate(), currencyRequest.getDate()); currency.update(currencyRequest); } - private void validateDateDuplicate(final Currency currency, final LocalDate newDate) { - if (!currency.getDate().equals(newDate)) { - validateDateDuplicate(newDate); + private void validateDuplicateDate(final LocalDate date, final LocalDate newDate) { + if (!date.equals(newDate)) { + validateDuplicateDate(newDate); } } } From c899d7ad085d35debaa41001fda8d36517ebd037 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Tue, 30 Jan 2024 13:23:11 +0900 Subject: [PATCH 82/88] =?UTF-8?q?refactor:=20username=20=EB=B3=80=EC=88=98?= =?UTF-8?q?=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/hanglog/admin/dto/request/AdminLoginRequest.java | 2 +- .../hanglog/admin/dto/request/AdminMemberCreateRequest.java | 2 +- .../hanglog/admin/dto/response/AdminMemberResponse.java | 2 +- .../main/java/hanglog/admin/service/AdminLoginService.java | 2 +- .../main/java/hanglog/admin/service/AdminMemberService.java | 4 ++-- .../admin/presentation/AdminMemberControllerTest.java | 6 +++--- .../service/AdminMemberServiceIntegrationTest.java | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/backend/src/main/java/hanglog/admin/dto/request/AdminLoginRequest.java b/backend/src/main/java/hanglog/admin/dto/request/AdminLoginRequest.java index 65672765b..8bc764a78 100644 --- a/backend/src/main/java/hanglog/admin/dto/request/AdminLoginRequest.java +++ b/backend/src/main/java/hanglog/admin/dto/request/AdminLoginRequest.java @@ -11,7 +11,7 @@ public class AdminLoginRequest { @NotNull(message = "사용자 이름을 입력해주세요.") @Size(max = 20, message = "사용자 이름은 20자를 초과할 수 없습니다.") - private String userName; + private String username; @NotNull(message = "비밀번호를 입력해주세요.") @Size(min = 4, max = 20, message = "비밀번호는 4자 이상, 20자 이하여야 합니다.") diff --git a/backend/src/main/java/hanglog/admin/dto/request/AdminMemberCreateRequest.java b/backend/src/main/java/hanglog/admin/dto/request/AdminMemberCreateRequest.java index 2f6274b39..e183fdc87 100644 --- a/backend/src/main/java/hanglog/admin/dto/request/AdminMemberCreateRequest.java +++ b/backend/src/main/java/hanglog/admin/dto/request/AdminMemberCreateRequest.java @@ -12,7 +12,7 @@ public class AdminMemberCreateRequest { @NotNull(message = "사용자 이름을 입력해주세요.") @Size(max = 20, message = "사용자 이름은 20자를 초과할 수 없습니다.") - private String userName; + private String username; @NotNull(message = "비밀번호를 입력해주세요.") @Size(min = 4, max = 20, message = "비밀번호는 4자 이상, 20자 이하여야 합니다.") diff --git a/backend/src/main/java/hanglog/admin/dto/response/AdminMemberResponse.java b/backend/src/main/java/hanglog/admin/dto/response/AdminMemberResponse.java index 2e2408a98..321cbd965 100644 --- a/backend/src/main/java/hanglog/admin/dto/response/AdminMemberResponse.java +++ b/backend/src/main/java/hanglog/admin/dto/response/AdminMemberResponse.java @@ -11,7 +11,7 @@ public class AdminMemberResponse { private final Long id; - private final String userName; + private final String username; private final String adminType; public static AdminMemberResponse from(final AdminMember adminMember) { diff --git a/backend/src/main/java/hanglog/admin/service/AdminLoginService.java b/backend/src/main/java/hanglog/admin/service/AdminLoginService.java index 919e29d4b..c887b04a4 100644 --- a/backend/src/main/java/hanglog/admin/service/AdminLoginService.java +++ b/backend/src/main/java/hanglog/admin/service/AdminLoginService.java @@ -33,7 +33,7 @@ public class AdminLoginService { public MemberTokens login(final AdminLoginRequest adminLoginRequest) { final AdminMember adminMember = adminMemberRepository - .findByUsername(adminLoginRequest.getUserName()) + .findByUsername(adminLoginRequest.getUsername()) .orElseThrow(() -> new AdminException(INVALID_USER_NAME)); if (passwordEncoder.matches(adminLoginRequest.getPassword(), adminMember.getPassword())) { diff --git a/backend/src/main/java/hanglog/admin/service/AdminMemberService.java b/backend/src/main/java/hanglog/admin/service/AdminMemberService.java index d2c581b9e..5dfb91658 100644 --- a/backend/src/main/java/hanglog/admin/service/AdminMemberService.java +++ b/backend/src/main/java/hanglog/admin/service/AdminMemberService.java @@ -34,13 +34,13 @@ public List getAdminMembers() { } public Long createAdminMember(final AdminMemberCreateRequest request) { - if (adminMemberRepository.existsByUsername(request.getUserName())) { + if (adminMemberRepository.existsByUsername(request.getUsername())) { throw new AdminException(DUPLICATED_ADMIN_USERNAME); } return adminMemberRepository.save( new AdminMember( - request.getUserName(), + request.getUsername(), passwordEncoder.encode(request.getPassword()), AdminType.getMappedAdminType(request.getAdminType()) ) diff --git a/backend/src/test/java/hanglog/admin/presentation/AdminMemberControllerTest.java b/backend/src/test/java/hanglog/admin/presentation/AdminMemberControllerTest.java index e1ce4863c..b3d35cc79 100644 --- a/backend/src/test/java/hanglog/admin/presentation/AdminMemberControllerTest.java +++ b/backend/src/test/java/hanglog/admin/presentation/AdminMemberControllerTest.java @@ -80,7 +80,7 @@ void getAdminMembers() throws Exception { .cookie(COOKIE)) .andExpect(status().isOk()) .andExpect(jsonPath("$[0].id").value(response.getId())) - .andExpect(jsonPath("$[0].userName").value(response.getUserName())) + .andExpect(jsonPath("$[0].username").value(response.getUsername())) .andExpect(jsonPath("$[0].adminType").value(response.getAdminType())) .andDo(restDocs.document( responseFields( @@ -88,7 +88,7 @@ void getAdminMembers() throws Exception { .type(JsonFieldType.NUMBER) .description("멤버 ID") .attributes(field("constraint", "양의 정수")), - fieldWithPath("[].userName") + fieldWithPath("[].username") .type(JsonFieldType.STRING) .description("사용자 이름") .attributes(field("constraint", "20자 이내의 문자열")), @@ -121,7 +121,7 @@ void createAdminMember() throws Exception { .andExpect(header().string("Location", "/admin/members/2")) .andDo(restDocs.document( requestFields( - fieldWithPath("userName") + fieldWithPath("username") .type(JsonFieldType.STRING) .description("사용자 이름") .attributes(field("constraint", "20자 이내의 문자열")), diff --git a/backend/src/test/java/hanglog/integration/service/AdminMemberServiceIntegrationTest.java b/backend/src/test/java/hanglog/integration/service/AdminMemberServiceIntegrationTest.java index 0a8ed51bb..097116ade 100644 --- a/backend/src/test/java/hanglog/integration/service/AdminMemberServiceIntegrationTest.java +++ b/backend/src/test/java/hanglog/integration/service/AdminMemberServiceIntegrationTest.java @@ -55,7 +55,7 @@ void getAdminMembers() { // then assertSoftly(softly -> { softly.assertThat(actual).hasSize(1); - softly.assertThat(actual.get(0).getUserName()).isEqualTo(CREATE_REQUEST.getUserName()); + softly.assertThat(actual.get(0).getUsername()).isEqualTo(CREATE_REQUEST.getUsername()); }); } From f5cc04066cbf3aedb6429124dba9b7e5a2c257aa Mon Sep 17 00:00:00 2001 From: LJW25 Date: Tue, 30 Jan 2024 13:47:56 +0900 Subject: [PATCH 83/88] =?UTF-8?q?refactor:=20username=20=EB=B3=80=EC=88=98?= =?UTF-8?q?=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/AdminLoginControllerTest.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/backend/src/test/java/hanglog/admin/presentation/AdminLoginControllerTest.java b/backend/src/test/java/hanglog/admin/presentation/AdminLoginControllerTest.java index 15d9183a0..a52746764 100644 --- a/backend/src/test/java/hanglog/admin/presentation/AdminLoginControllerTest.java +++ b/backend/src/test/java/hanglog/admin/presentation/AdminLoginControllerTest.java @@ -1,5 +1,6 @@ package hanglog.admin.presentation; +import static hanglog.admin.domain.type.AdminType.ADMIN; import static hanglog.global.restdocs.RestDocsConfiguration.field; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -21,6 +22,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.fasterxml.jackson.databind.ObjectMapper; +import hanglog.admin.domain.AdminMember; import hanglog.admin.domain.type.AdminType; import hanglog.admin.dto.request.AdminLoginRequest; import hanglog.admin.service.AdminLoginService; @@ -28,6 +30,7 @@ import hanglog.login.domain.MemberTokens; import hanglog.login.dto.AccessTokenResponse; import jakarta.servlet.http.Cookie; +import java.util.Optional; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -76,7 +79,7 @@ void login() throws Exception { final MvcResult mvcResult = resultActions.andExpect(status().isCreated()) .andDo(restDocs.document( requestFields( - fieldWithPath("userName") + fieldWithPath("username") .type(JsonFieldType.STRING) .description("사용자 이름") .attributes(field("constraint", "문자열")), @@ -164,8 +167,12 @@ void logout() throws Exception { doNothing().when(jwtProvider).validateTokens(any()); given(jwtProvider.getSubject(any())).willReturn("1"); doNothing().when(adminLoginService).removeRefreshToken(anyString()); - given(adminMemberRepository.existsByIdAndAdminType(anyLong(), any(AdminType.class))) - .willReturn(false); + given(adminMemberRepository.findById(1L)).willReturn(Optional.of(new AdminMember( + 1L, + "username", + "password", + ADMIN + ))); final MemberTokens memberTokens = new MemberTokens(REFRESH_TOKEN, RENEW_ACCESS_TOKEN); final Cookie cookie = new Cookie("refresh-token", memberTokens.getRefreshToken()); From 78d696b5a4593537a37f7f65c940da14a146d109 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Tue, 30 Jan 2024 13:48:25 +0900 Subject: [PATCH 84/88] =?UTF-8?q?refactor:=20AdminLoginArgumentResolver=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hanglog/admin/AdminLoginArgumentResolver.java | 13 +++++++++++-- .../presentation/AdminCategoryControllerTest.java | 10 +++++++++- .../admin/presentation/AdminCityControllerTest.java | 10 +++++++++- .../presentation/AdminCurrencyControllerTest.java | 10 +++++++++- .../presentation/AdminMemberControllerTest.java | 9 ++++++++- 5 files changed, 46 insertions(+), 6 deletions(-) diff --git a/backend/src/main/java/hanglog/admin/AdminLoginArgumentResolver.java b/backend/src/main/java/hanglog/admin/AdminLoginArgumentResolver.java index 2ef1e884c..972296592 100644 --- a/backend/src/main/java/hanglog/admin/AdminLoginArgumentResolver.java +++ b/backend/src/main/java/hanglog/admin/AdminLoginArgumentResolver.java @@ -1,10 +1,13 @@ package hanglog.admin; +import static hanglog.admin.domain.type.AdminType.ADMIN; import static hanglog.admin.domain.type.AdminType.MASTER; +import static hanglog.global.exception.ExceptionCode.INVALID_ADMIN_AUTHORITY; import static hanglog.global.exception.ExceptionCode.INVALID_REQUEST; import static hanglog.global.exception.ExceptionCode.NOT_FOUND_REFRESH_TOKEN; import static org.springframework.http.HttpHeaders.AUTHORIZATION; +import hanglog.admin.domain.AdminMember; import hanglog.admin.domain.repository.AdminMemberRepository; import hanglog.auth.AdminAuth; import hanglog.auth.domain.Accessor; @@ -63,10 +66,16 @@ public Accessor resolveArgument( final Long memberId = Long.valueOf(jwtProvider.getSubject(accessToken)); - if (adminMemberRepository.existsByIdAndAdminType(memberId, MASTER)) { + final AdminMember adminMember = adminMemberRepository.findById(memberId) + .orElseThrow(() -> new RefreshTokenException(INVALID_ADMIN_AUTHORITY)); + + if (adminMember.getAdminType().equals(MASTER)) { return Accessor.master(memberId); } - return Accessor.admin(memberId); + if (adminMember.getAdminType().equals(ADMIN)) { + return Accessor.admin(memberId); + } + throw new RefreshTokenException(INVALID_ADMIN_AUTHORITY); } private String extractRefreshToken(final Cookie... cookies) { diff --git a/backend/src/test/java/hanglog/admin/presentation/AdminCategoryControllerTest.java b/backend/src/test/java/hanglog/admin/presentation/AdminCategoryControllerTest.java index bd29393af..38c90f800 100644 --- a/backend/src/test/java/hanglog/admin/presentation/AdminCategoryControllerTest.java +++ b/backend/src/test/java/hanglog/admin/presentation/AdminCategoryControllerTest.java @@ -1,5 +1,6 @@ package hanglog.admin.presentation; +import static hanglog.admin.domain.type.AdminType.ADMIN; import static hanglog.category.fixture.CategoryFixture.FOOD; import static hanglog.global.restdocs.RestDocsConfiguration.field; import static org.mockito.ArgumentMatchers.any; @@ -19,6 +20,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.fasterxml.jackson.databind.ObjectMapper; +import hanglog.admin.domain.AdminMember; import hanglog.category.dto.request.CategoryRequest; import hanglog.category.dto.response.CategoryDetailResponse; import hanglog.category.service.CategoryService; @@ -26,6 +28,7 @@ import hanglog.login.domain.MemberTokens; import jakarta.servlet.http.Cookie; import java.util.List; +import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -61,7 +64,12 @@ void setUp() { given(refreshTokenRepository.existsById(any())).willReturn(true); doNothing().when(jwtProvider).validateTokens(any()); given(jwtProvider.getSubject(any())).willReturn("1"); - given(adminMemberRepository.existsByIdAndAdminType(any(), any())).willReturn(false); + given(adminMemberRepository.findById(1L)).willReturn(Optional.of(new AdminMember( + 1L, + "username", + "password", + ADMIN + ))); } @DisplayName("카테고리 상세 목록을 조회한다.") diff --git a/backend/src/test/java/hanglog/admin/presentation/AdminCityControllerTest.java b/backend/src/test/java/hanglog/admin/presentation/AdminCityControllerTest.java index 1a43cf63d..513bd32ba 100644 --- a/backend/src/test/java/hanglog/admin/presentation/AdminCityControllerTest.java +++ b/backend/src/test/java/hanglog/admin/presentation/AdminCityControllerTest.java @@ -1,5 +1,6 @@ package hanglog.admin.presentation; +import static hanglog.admin.domain.type.AdminType.ADMIN; import static hanglog.global.restdocs.RestDocsConfiguration.field; import static hanglog.trip.fixture.CityFixture.PARIS; import static org.mockito.ArgumentMatchers.any; @@ -19,6 +20,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.fasterxml.jackson.databind.ObjectMapper; +import hanglog.admin.domain.AdminMember; import hanglog.city.dto.request.CityRequest; import hanglog.city.dto.response.CityDetailResponse; import hanglog.city.service.CityService; @@ -27,6 +29,7 @@ import jakarta.servlet.http.Cookie; import java.math.BigDecimal; import java.util.List; +import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -62,7 +65,12 @@ void setUp() { given(refreshTokenRepository.existsById(any())).willReturn(true); doNothing().when(jwtProvider).validateTokens(any()); given(jwtProvider.getSubject(any())).willReturn("1"); - given(adminMemberRepository.existsByIdAndAdminType(any(), any())).willReturn(false); + given(adminMemberRepository.findById(1L)).willReturn(Optional.of(new AdminMember( + 1L, + "username", + "password", + ADMIN + ))); } @DisplayName("도시 상세 목록을 조회한다.") diff --git a/backend/src/test/java/hanglog/admin/presentation/AdminCurrencyControllerTest.java b/backend/src/test/java/hanglog/admin/presentation/AdminCurrencyControllerTest.java index bf4028964..a0006a495 100644 --- a/backend/src/test/java/hanglog/admin/presentation/AdminCurrencyControllerTest.java +++ b/backend/src/test/java/hanglog/admin/presentation/AdminCurrencyControllerTest.java @@ -1,5 +1,6 @@ package hanglog.admin.presentation; +import static hanglog.admin.domain.type.AdminType.ADMIN; import static hanglog.currency.fixture.CurrencyFixture.CURRENCY_1; import static hanglog.currency.fixture.CurrencyFixture.CURRENCY_2; import static hanglog.global.restdocs.RestDocsConfiguration.field; @@ -20,6 +21,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.fasterxml.jackson.databind.ObjectMapper; +import hanglog.admin.domain.AdminMember; import hanglog.currency.dto.request.CurrencyRequest; import hanglog.currency.dto.response.CurrencyListResponse; import hanglog.currency.dto.response.CurrencyResponse; @@ -28,6 +30,7 @@ import hanglog.login.domain.MemberTokens; import jakarta.servlet.http.Cookie; import java.util.List; +import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -63,7 +66,12 @@ void setUp() { given(refreshTokenRepository.existsById(any())).willReturn(true); doNothing().when(jwtProvider).validateTokens(any()); given(jwtProvider.getSubject(any())).willReturn("1"); - given(adminMemberRepository.existsByIdAndAdminType(any(), any())).willReturn(false); + given(adminMemberRepository.findById(1L)).willReturn(Optional.of(new AdminMember( + 1L, + "username", + "password", + ADMIN + ))); } @DisplayName("도시 상세 목록을 조회한다.") diff --git a/backend/src/test/java/hanglog/admin/presentation/AdminMemberControllerTest.java b/backend/src/test/java/hanglog/admin/presentation/AdminMemberControllerTest.java index b3d35cc79..80b31b0ba 100644 --- a/backend/src/test/java/hanglog/admin/presentation/AdminMemberControllerTest.java +++ b/backend/src/test/java/hanglog/admin/presentation/AdminMemberControllerTest.java @@ -1,5 +1,6 @@ package hanglog.admin.presentation; +import static hanglog.admin.domain.type.AdminType.MASTER; import static hanglog.global.restdocs.RestDocsConfiguration.field; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; @@ -28,6 +29,7 @@ import hanglog.login.domain.MemberTokens; import jakarta.servlet.http.Cookie; import java.util.List; +import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -62,7 +64,12 @@ void setUp() { given(refreshTokenRepository.existsById(any())).willReturn(true); doNothing().when(jwtProvider).validateTokens(any()); given(jwtProvider.getSubject(any())).willReturn("1"); - given(adminMemberRepository.existsByIdAndAdminType(any(), any())).willReturn(true); + given(adminMemberRepository.findById(1L)).willReturn(Optional.of(new AdminMember( + 1L, + "username", + "password", + MASTER + ))); } @DisplayName("관리자 멤버 목록을 조회한다.") From bf38a584e655224c7ded767ce1f9c396b205b0d3 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Tue, 30 Jan 2024 13:53:56 +0900 Subject: [PATCH 85/88] =?UTF-8?q?refactor:=20Enum=20=EA=B0=9C=ED=96=89=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/hanglog/admin/domain/type/AdminType.java | 3 ++- backend/src/main/java/hanglog/auth/domain/Authority.java | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/hanglog/admin/domain/type/AdminType.java b/backend/src/main/java/hanglog/admin/domain/type/AdminType.java index 2c715fbd0..88f15371c 100644 --- a/backend/src/main/java/hanglog/admin/domain/type/AdminType.java +++ b/backend/src/main/java/hanglog/admin/domain/type/AdminType.java @@ -6,7 +6,8 @@ import java.util.Arrays; public enum AdminType { - ADMIN, MASTER; + ADMIN, + MASTER; public static AdminType getMappedAdminType(final String adminType) { return Arrays.stream(values()) diff --git a/backend/src/main/java/hanglog/auth/domain/Authority.java b/backend/src/main/java/hanglog/auth/domain/Authority.java index 305a859a9..946b29614 100644 --- a/backend/src/main/java/hanglog/auth/domain/Authority.java +++ b/backend/src/main/java/hanglog/auth/domain/Authority.java @@ -1,5 +1,8 @@ package hanglog.auth.domain; public enum Authority { - GUEST, MEMBER, ADMIN, MASTER + GUEST, + MEMBER, + ADMIN, + MASTER } From 6e61d2c561da4169d2f3ad9d3dce6fdb379b6dc2 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Wed, 31 Jan 2024 01:15:39 +0900 Subject: [PATCH 86/88] =?UTF-8?q?docs:=20Restdocs=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/docs/asciidoc/admindocs.adoc | 196 ++++++++++++++++++ backend/src/docs/asciidoc/docs.adoc | 2 +- .../AdminCurrencyControllerTest.java | 4 +- 3 files changed, 199 insertions(+), 3 deletions(-) create mode 100644 backend/src/docs/asciidoc/admindocs.adoc diff --git a/backend/src/docs/asciidoc/admindocs.adoc b/backend/src/docs/asciidoc/admindocs.adoc new file mode 100644 index 000000000..21cdbc7e5 --- /dev/null +++ b/backend/src/docs/asciidoc/admindocs.adoc @@ -0,0 +1,196 @@ += HangLog +:toc: left +:source-highlighter: highlightjs +:sectlinks: + +[[overview-http-status-codes]] +=== HTTP status codes + +|=== +| 상태 코드 | 설명 + +| `200 OK` +| 성공 + +| `201 Created` +| 리소스 생성 + +| `204 NO_CONTENT` +| 성공 후 반환 값 없음 + +| `400 Bad Request` +| 잘못된 요청 + +| `401 Unauthorized` +| 비인증 상태 + +| `403 Forbidden` +| 권한 거부 + +| `404 Not Found` +| 존재하지 않는 요청 리소스 + +| `500 Internal Server Error` +| 서버 에러 +|=== + +=== Exception codes +[[exception-codes]] +include::{snippets}/exception-code-controller-test/get-exception-codes/exception-response-fields.adoc[] + +== 관리자 로그인 API + +=== 로그인 (POST /admin/login) + +==== 요청 +include::{snippets}/admin-login-controller-test/login/http-request.adoc[] +include::{snippets}/admin-login-controller-test/login/request-fields.adoc[] + +==== 응답 +include::{snippets}/admin-login-controller-test/login/http-response.adoc[] +include::{snippets}/admin-login-controller-test/login/response-fields.adoc[] + +=== 토큰 재발급 (POST /admin/token) + +==== 요청 +include::{snippets}/admin-login-controller-test/extend-login/http-request.adoc[] +요청 쿠키 +include::{snippets}/admin-login-controller-test/extend-login/request-cookies.adoc[] + +==== 응답 +include::{snippets}/admin-login-controller-test/extend-login/http-response.adoc[] +include::{snippets}/admin-login-controller-test/extend-login/response-fields.adoc[] + +=== 로그아웃 (DELETE /admin/logout) + +==== 요청 +include::{snippets}/admin-login-controller-test/logout/http-request.adoc[] +요청 헤더 +include::{snippets}/admin-login-controller-test/logout/request-headers.adoc[] +요청 쿠키 +include::{snippets}/admin-login-controller-test/logout/request-cookies.adoc[] + +==== 응답 +include::{snippets}/admin-login-controller-test/logout/http-response.adoc[] + + +== 관리자 멤버 관리 API + +=== 관리자 멤버 목록 조회 (GET /admin/members) + +==== 요청 +include::{snippets}/admin-member-controller-test/get-admin-members/http-request.adoc[] + +==== 응답 +include::{snippets}/admin-member-controller-test/get-admin-members/http-response.adoc[] +include::{snippets}/admin-member-controller-test/get-admin-members/response-fields.adoc[] + +=== 관리자 멤버 생성 (POST /admin/members) + +==== 요청 +include::{snippets}/admin-member-controller-test/create-admin-member/http-request.adoc[] +include::{snippets}/admin-member-controller-test/create-admin-member/request-fields.adoc[] + +==== 응답 +include::{snippets}/admin-member-controller-test/create-admin-member/http-response.adoc[] + +=== 관리자 멤버 비밀번호 수정 (PATCH /admin/members/:memberId/password) + +==== 요청 +include::{snippets}/admin-member-controller-test/update-password/http-request.adoc[] +include::{snippets}/admin-member-controller-test/update-password/path-parameters.adoc[] +include::{snippets}/admin-member-controller-test/update-password/request-fields.adoc[] + +==== 응답 +include::{snippets}/admin-member-controller-test/update-password/http-response.adoc[] + +== 도시 관리 API + +=== 도시 목록 조회 (GET /admin/cities) + +==== 요청 +include::{snippets}/admin-city-controller-test/get-cities-detail/http-request.adoc[] + +==== 응답 +include::{snippets}/admin-city-controller-test/get-cities-detail/http-response.adoc[] +include::{snippets}/admin-city-controller-test/get-cities-detail/response-fields.adoc[] + +=== 도시 생성 (POST /admin/cities) + +==== 요청 +include::{snippets}/admin-city-controller-test/create-city/http-request.adoc[] +include::{snippets}/admin-city-controller-test/create-city/request-fields.adoc[] + +==== 응답 +include::{snippets}/admin-city-controller-test/create-city/http-response.adoc[] + +=== 도시 수정 (PUT /admin/cities/:citiId) + +==== 요청 +include::{snippets}/admin-city-controller-test/update-city/http-request.adoc[] +include::{snippets}/admin-city-controller-test/update-city/path-parameters.adoc[] +include::{snippets}/admin-city-controller-test/update-city/request-fields.adoc[] + +==== 응답 +include::{snippets}/admin-city-controller-test/update-city/http-response.adoc[] + + +== 카테고리 관리 API + +=== 카테고리 목록 조회 (GET /admin/categories) + +==== 요청 +include::{snippets}/admin-category-controller-test/get-categories-detail/http-request.adoc[] + +==== 응답 +include::{snippets}/admin-category-controller-test/get-categories-detail/http-response.adoc[] +include::{snippets}/admin-category-controller-test/get-categories-detail/response-fields.adoc[] + +=== 카테고리 생성 (POST /admin/categories) + +==== 요청 +include::{snippets}/admin-category-controller-test/create-category/http-request.adoc[] +include::{snippets}/admin-category-controller-test/create-category/request-fields.adoc[] + +==== 응답 +include::{snippets}/admin-category-controller-test/create-category/http-response.adoc[] + +=== 카테고리 수정 (PUT /admin/categories/:categoryId) + +==== 요청 +include::{snippets}/admin-category-controller-test/update-category/http-request.adoc[] +include::{snippets}/admin-category-controller-test/update-category/path-parameters.adoc[] +include::{snippets}/admin-category-controller-test/update-category/request-fields.adoc[] + +==== 응답 +include::{snippets}/admin-category-controller-test/update-category/http-response.adoc[] + +== 환율 관리 API + +=== 환율 목록 페이지 조회 (GET /admin/currencies) + +==== 요청 +include::{snippets}/admin-currency-controller-test/get-currencies-detail/http-request.adoc[] + +==== 응답 +include::{snippets}/admin-currency-controller-test/get-currencies-detail/http-response.adoc[] +include::{snippets}/admin-currency-controller-test/get-currencies-detail/response-fields.adoc[] + +=== 환율 생성 (POST /admin/currencies) + +==== 요청 +include::{snippets}/admin-currency-controller-test/create-currency/http-request.adoc[] +include::{snippets}/admin-currency-controller-test/create-currency/request-fields.adoc[] + +==== 응답 +include::{snippets}/admin-currency-controller-test/create-currency/http-response.adoc[] + +=== 환율 수정 (PUT /admin/currencies/:currencyId) + +==== 요청 +include::{snippets}/admin-currency-controller-test/update-currency/http-request.adoc[] +include::{snippets}/admin-currency-controller-test/update-currency/path-parameters.adoc[] +include::{snippets}/admin-currency-controller-test/update-currency/request-fields.adoc[] + +==== 응답 +include::{snippets}/admin-currency-controller-test/update-currency/http-response.adoc[] diff --git a/backend/src/docs/asciidoc/docs.adoc b/backend/src/docs/asciidoc/docs.adoc index 5d52e796b..911860750 100644 --- a/backend/src/docs/asciidoc/docs.adoc +++ b/backend/src/docs/asciidoc/docs.adoc @@ -261,7 +261,7 @@ include::{snippets}/login-controller-test/extend-login/request-fields.adoc[] include::{snippets}/login-controller-test/extend-login/http-response.adoc[] include::{snippets}/login-controller-test/extend-login/response-fields.adoc[] -=== 로그아웃 (POST /logout) +=== 로그아웃 (DELETE /logout) ==== 요청 include::{snippets}/login-controller-test/logout/http-request.adoc[] diff --git a/backend/src/test/java/hanglog/admin/presentation/AdminCurrencyControllerTest.java b/backend/src/test/java/hanglog/admin/presentation/AdminCurrencyControllerTest.java index a0006a495..383639f12 100644 --- a/backend/src/test/java/hanglog/admin/presentation/AdminCurrencyControllerTest.java +++ b/backend/src/test/java/hanglog/admin/presentation/AdminCurrencyControllerTest.java @@ -74,9 +74,9 @@ void setUp() { ))); } - @DisplayName("도시 상세 목록을 조회한다.") + @DisplayName("환율 상세 목록을 조회한다.") @Test - void getCitiesDetail() throws Exception { + void getCurrenciesDetail() throws Exception { // given final CurrencyResponse response1 = CurrencyResponse.of(CURRENCY_1); final CurrencyResponse response2 = CurrencyResponse.of(CURRENCY_2); From 3438afd56d5b43147d472d46013ea960d2428f77 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sun, 4 Feb 2024 01:20:25 +0900 Subject: [PATCH 87/88] =?UTF-8?q?refactor:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=ED=95=9C=EA=B8=80=EB=AA=85=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/repository/CategoryRepository.java | 2 -- .../hanglog/category/service/CategoryService.java | 11 ----------- .../java/hanglog/global/exception/ExceptionCode.java | 1 - .../hanglog/category/service/CategoryServiceTest.java | 2 -- 4 files changed, 16 deletions(-) diff --git a/backend/src/main/java/hanglog/category/domain/repository/CategoryRepository.java b/backend/src/main/java/hanglog/category/domain/repository/CategoryRepository.java index 9b3fd20b9..846b3a0c9 100644 --- a/backend/src/main/java/hanglog/category/domain/repository/CategoryRepository.java +++ b/backend/src/main/java/hanglog/category/domain/repository/CategoryRepository.java @@ -16,6 +16,4 @@ public interface CategoryRepository extends JpaRepository { Category findCategoryETC(); Boolean existsByEngName(String engName); - - Boolean existsByKorName(String korName); } diff --git a/backend/src/main/java/hanglog/category/service/CategoryService.java b/backend/src/main/java/hanglog/category/service/CategoryService.java index 30885fda4..996888d85 100644 --- a/backend/src/main/java/hanglog/category/service/CategoryService.java +++ b/backend/src/main/java/hanglog/category/service/CategoryService.java @@ -47,7 +47,6 @@ public Long save(final CategoryRequest categoryRequest) { private void validateCategoryDuplicate(final CategoryRequest categoryRequest) { validateCategoryDuplicateId(categoryRequest.getId()); validateCategoryDuplicateEngName(categoryRequest.getEngName()); - validateCategoryDuplicateKorName(categoryRequest.getKorName()); } private void validateCategoryDuplicateId(final Long id) { @@ -62,13 +61,6 @@ private void validateCategoryDuplicateEngName(final String engName) { } } - private void validateCategoryDuplicateKorName(final String korName) { - if (categoryRepository.existsByKorName(korName)) { - throw new BadRequestException(DUPLICATED_CATEGORY_ENG_NAME); - } - } - - public void update(final Long id, final CategoryRequest categoryRequest) { final Category category = categoryRepository.findById(id) .orElseThrow(() -> new BadRequestException(NOT_FOUND_CATEGORY_ID)); @@ -85,8 +77,5 @@ private void validateCategoryDuplicate(final Category category, final CategoryRe if (!category.getEngName().equals(categoryRequest.getEngName())) { validateCategoryDuplicateEngName(categoryRequest.getEngName()); } - if (!category.getKorName().equals(categoryRequest.getKorName())) { - validateCategoryDuplicateKorName(categoryRequest.getKorName()); - } } } diff --git a/backend/src/main/java/hanglog/global/exception/ExceptionCode.java b/backend/src/main/java/hanglog/global/exception/ExceptionCode.java index 3f6074dbe..f8def49ed 100644 --- a/backend/src/main/java/hanglog/global/exception/ExceptionCode.java +++ b/backend/src/main/java/hanglog/global/exception/ExceptionCode.java @@ -67,7 +67,6 @@ public enum ExceptionCode { NOT_FOUND_CITY(8302, "요청한 ID에 해당하는 도시를 찾을 수 없습니다."), DUPLICATED_CATEGORY_ID(8311, "중복된 카테고리 아이디입니다."), DUPLICATED_CATEGORY_ENG_NAME(8312, "중복된 카테고리 영어 이름입니다."), - DUPLICATED_CATEGORY_KOR_NAME(8313, "중복된 카테고리 한글 이름입니다."), NOT_FOUND_CATEGORY(8314, "요청한 ID에 해당하는 카테고리를 찾을 수 없습니다."), INVALID_AUTHORIZATION_CODE(9001, "유효하지 않은 인증 코드입니다."), diff --git a/backend/src/test/java/hanglog/category/service/CategoryServiceTest.java b/backend/src/test/java/hanglog/category/service/CategoryServiceTest.java index b5c145893..3c96145d7 100644 --- a/backend/src/test/java/hanglog/category/service/CategoryServiceTest.java +++ b/backend/src/test/java/hanglog/category/service/CategoryServiceTest.java @@ -81,7 +81,6 @@ void save() { given(categoryRepository.existsById(anyLong())).willReturn(false); given(categoryRepository.existsByEngName(anyString())).willReturn(false); - given(categoryRepository.existsByKorName(anyString())).willReturn(false); given(categoryRepository.save(any(Category.class))).willReturn(FOOD); // when @@ -114,7 +113,6 @@ void update() { given(categoryRepository.findById(anyLong())).willReturn(Optional.of(FOOD)); given(categoryRepository.existsById(anyLong())).willReturn(false); given(categoryRepository.existsByEngName(anyString())).willReturn(false); - given(categoryRepository.existsByKorName(anyString())).willReturn(false); // when & then assertDoesNotThrow(() -> categoryService.update(FOOD.getId(), categoryRequest)); From 5df36147c66427e6ed032f881504c0863c50e8e6 Mon Sep 17 00:00:00 2001 From: LJW25 Date: Sun, 4 Feb 2024 01:27:44 +0900 Subject: [PATCH 88/88] =?UTF-8?q?refactor:=20=ED=99=98=EC=9C=A8=20request?= =?UTF-8?q?=20dto=20krw=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/hanglog/currency/domain/Currency.java | 3 +-- .../currency/dto/request/CurrencyRequest.java | 5 ----- .../presentation/AdminCurrencyControllerTest.java | 14 ++------------ .../currency/service/CurrencyServiceTest.java | 15 +++++---------- .../controller/AdminCurrencyIntegrationTest.java | 5 ++--- 5 files changed, 10 insertions(+), 32 deletions(-) diff --git a/backend/src/main/java/hanglog/currency/domain/Currency.java b/backend/src/main/java/hanglog/currency/domain/Currency.java index 68f50bcae..9eecb2ddc 100644 --- a/backend/src/main/java/hanglog/currency/domain/Currency.java +++ b/backend/src/main/java/hanglog/currency/domain/Currency.java @@ -85,7 +85,7 @@ public static Currency of(final CurrencyRequest currencyRequest) { currencyRequest.getSgd(), currencyRequest.getThb(), currencyRequest.getHkd(), - currencyRequest.getKrw() + 1d ); } @@ -100,7 +100,6 @@ public void update(final CurrencyRequest currencyRequest) { this.sgd = currencyRequest.getSgd(); this.thb = currencyRequest.getThb(); this.hkd = currencyRequest.getHkd(); - this.krw = currencyRequest.getKrw(); } public double getUnitRateOfJpy() { diff --git a/backend/src/main/java/hanglog/currency/dto/request/CurrencyRequest.java b/backend/src/main/java/hanglog/currency/dto/request/CurrencyRequest.java index 0aef30cce..dc2ecbf28 100644 --- a/backend/src/main/java/hanglog/currency/dto/request/CurrencyRequest.java +++ b/backend/src/main/java/hanglog/currency/dto/request/CurrencyRequest.java @@ -61,9 +61,4 @@ public class CurrencyRequest { @DecimalMin(value = "0", message = "HKD 환율이 0원보다 작을 수 없습니다.") @DecimalMax(value = "100000", message = "HKD 환율이 10만원보다 클 수 없습니다.") private final double hkd; - - @NotNull(message = "KRW 환율을 입력해주세요.") - @DecimalMin(value = "0", message = "KRW 환율이 0원보다 작을 수 없습니다.") - @DecimalMax(value = "100000", message = "KRW 환율이 10만원보다 클 수 없습니다.") - private final double krw; } diff --git a/backend/src/test/java/hanglog/admin/presentation/AdminCurrencyControllerTest.java b/backend/src/test/java/hanglog/admin/presentation/AdminCurrencyControllerTest.java index 383639f12..562fe66a1 100644 --- a/backend/src/test/java/hanglog/admin/presentation/AdminCurrencyControllerTest.java +++ b/backend/src/test/java/hanglog/admin/presentation/AdminCurrencyControllerTest.java @@ -175,8 +175,7 @@ void createCurrency() throws Exception { CURRENCY_1.getChf(), CURRENCY_1.getSgd(), CURRENCY_1.getThb(), - CURRENCY_1.getHkd(), - CURRENCY_1.getKrw() + CURRENCY_1.getHkd() ); given(currencyService.save(any(CurrencyRequest.class))).willReturn(1L); @@ -230,10 +229,6 @@ void createCurrency() throws Exception { fieldWithPath("hkd") .type(JsonFieldType.NUMBER) .description("HKD") - .attributes(field("constraint", "double")), - fieldWithPath("krw") - .type(JsonFieldType.NUMBER) - .description("KRW") .attributes(field("constraint", "double")) ) )); @@ -253,8 +248,7 @@ void updateCurrency() throws Exception { CURRENCY_1.getChf(), CURRENCY_1.getSgd(), CURRENCY_1.getThb(), - CURRENCY_1.getHkd(), - CURRENCY_1.getKrw() + CURRENCY_1.getHkd() ); doNothing().when(currencyService).update(1L, request); @@ -311,10 +305,6 @@ void updateCurrency() throws Exception { fieldWithPath("hkd") .type(JsonFieldType.NUMBER) .description("HKD") - .attributes(field("constraint", "double")), - fieldWithPath("krw") - .type(JsonFieldType.NUMBER) - .description("KRW") .attributes(field("constraint", "double")) ) )); diff --git a/backend/src/test/java/hanglog/currency/service/CurrencyServiceTest.java b/backend/src/test/java/hanglog/currency/service/CurrencyServiceTest.java index b7d1cee33..8fe2d7848 100644 --- a/backend/src/test/java/hanglog/currency/service/CurrencyServiceTest.java +++ b/backend/src/test/java/hanglog/currency/service/CurrencyServiceTest.java @@ -74,8 +74,7 @@ void save() { CURRENCY_1.getChf(), CURRENCY_1.getSgd(), CURRENCY_1.getThb(), - CURRENCY_1.getHkd(), - CURRENCY_1.getKrw() + CURRENCY_1.getHkd() ); given(currencyRepository.existsByDate(any())).willReturn(false); @@ -102,8 +101,7 @@ void save_DuplicateDateFail() { CURRENCY_1.getChf(), CURRENCY_1.getSgd(), CURRENCY_1.getThb(), - CURRENCY_1.getHkd(), - CURRENCY_1.getKrw() + CURRENCY_1.getHkd() ); given(currencyRepository.existsByDate(any())).willReturn(true); @@ -127,8 +125,7 @@ void update() { CURRENCY_1.getChf(), CURRENCY_1.getSgd(), CURRENCY_1.getThb(), - CURRENCY_1.getHkd(), - CURRENCY_1.getKrw() + CURRENCY_1.getHkd() ); given(currencyRepository.findById(anyLong())).willReturn(Optional.of(CURRENCY_1)); @@ -151,8 +148,7 @@ void update_DuplicateFail() { CURRENCY_1.getChf(), CURRENCY_1.getSgd(), CURRENCY_1.getThb(), - CURRENCY_1.getHkd(), - CURRENCY_1.getKrw() + CURRENCY_1.getHkd() ); given(currencyRepository.findById(anyLong())).willReturn(Optional.of(CURRENCY_1)); @@ -177,8 +173,7 @@ void update_NotFoundFail() { CURRENCY_1.getChf(), CURRENCY_1.getSgd(), CURRENCY_1.getThb(), - CURRENCY_1.getHkd(), - CURRENCY_1.getKrw() + CURRENCY_1.getHkd() ); given(currencyRepository.findById(anyLong())).willReturn(Optional.empty()); diff --git a/backend/src/test/java/hanglog/integration/controller/AdminCurrencyIntegrationTest.java b/backend/src/test/java/hanglog/integration/controller/AdminCurrencyIntegrationTest.java index fae60d675..ba2a0b4ba 100644 --- a/backend/src/test/java/hanglog/integration/controller/AdminCurrencyIntegrationTest.java +++ b/backend/src/test/java/hanglog/integration/controller/AdminCurrencyIntegrationTest.java @@ -28,8 +28,7 @@ public class AdminCurrencyIntegrationTest extends AdminIntegrationTest { 1139.06, 820.17, 34.89, - 142.78, - 1.0 + 142.78 ); @DisplayName("새로운 환율 정보를 추가할 수 있다.") @@ -70,7 +69,7 @@ void getCurrencies() { softly.assertThat(currencyListResponse.getCurrencies()).isNotEmpty(); softly.assertThat(currencyListResponse.getCurrencies().get(0)) .usingRecursiveComparison() - .ignoringFields("id") + .ignoringFields("id", "krw") .isEqualTo(CURRENCY_REQUEST); } );