diff --git a/owner-apigateway-service/src/main/resources/application.yml b/owner-apigateway-service/src/main/resources/application.yml index 98f3cdf..2c62442 100644 --- a/owner-apigateway-service/src/main/resources/application.yml +++ b/owner-apigateway-service/src/main/resources/application.yml @@ -24,14 +24,14 @@ spring: cors-configurations: '[/**]': allowedOrigins: "*" - allowedHeaders: "*" allowedMethods: - - POST - GET + - POST + - DELETE - PUT - OPTIONS - DELETE -# add-to-simple-url-handler-mapping: true + allowedHeaders: '*' routes: - id: owner-frontend-service uri: lb://OWNER-FRONTEND-SERVICE @@ -72,12 +72,18 @@ spring: - Method=POST filters: - RewritePath=/user-service/(?.*),/$\{segment} + - id: user-service + uri: lb://USER-SERVICE + predicates: + - Path=/user-service/store-owner + - Method=POST + filters: + - RewritePath=/user-service/(?.*),/$\{segment} - id: user-service uri: lb://USER-SERVICE predicates: - Path=/user-service/** filters: - - AuthorizationHeaderFilter - RewritePath=/user-service/(?.*),/$\{segment} token: diff --git a/owner-vue/src/App.vue b/owner-vue/src/App.vue index 1121e76..650d4c7 100644 --- a/owner-vue/src/App.vue +++ b/owner-vue/src/App.vue @@ -1,28 +1,13 @@ + + \ No newline at end of file diff --git a/owner-vue/src/api/user.js b/owner-vue/src/api/user.js new file mode 100644 index 0000000..50ad23c --- /dev/null +++ b/owner-vue/src/api/user.js @@ -0,0 +1,8 @@ +import axios from "axios"; + +export default { + + requestRegisterUser(user) { + return axios.post("http://localhost:8001/user-service/store-owner", user); + } +} \ No newline at end of file diff --git a/owner-vue/src/main.js b/owner-vue/src/main.js index effb410..d9e8961 100644 --- a/owner-vue/src/main.js +++ b/owner-vue/src/main.js @@ -5,6 +5,7 @@ import router from './router' import axios from "axios"; import customUtil from './util/customUtil' + Vue.config.productionTip = false Vue.prototype.$axios = axios; Vue.prototype.$customUtil = customUtil; @@ -16,4 +17,3 @@ new Vue({ router, render: h => h(App) }).$mount('#app') - diff --git a/owner-vue/src/router/index.js b/owner-vue/src/router/index.js index 12f7d17..1fc109b 100644 --- a/owner-vue/src/router/index.js +++ b/owner-vue/src/router/index.js @@ -1,33 +1,60 @@ import Vue from 'vue' import VueRouter from 'vue-router' +import DashboardLayout from "@/views/Layout/DashboardLayout"; +import AuthLayout from "@/views/Layout/AuthLayout"; + Vue.use(VueRouter) const routes = [ + { + path: '/dashboard', + redirect: 'dashboard', + component: DashboardLayout, + children: [ + { + path: "/dashboard", + name: 'dashboard', + component: () => import('./../views/Dashboard') + }, + { + path: '/category', + name: 'category', + component: () => import('./../views/Category') + }, + { + path: '/menu', + name: 'menu', + component: () => import('./../views/Menu') + }, + { + path: '/prev-order', + name: 'prev-order', + component: () => import('./../views/PrevOrder') + }, + { + path: '/order', + name: 'order', + component: () => import('./../views/Order.vue') + } + ] + }, { path: '/', - name: 'dashboard', - component: () => import('./../views/Dashboard') - }, - { - path: '/category', - name: 'category', - component: () => import('./../views/Category') - }, - { - path: '/menu', - name: 'menu', - component: () => import('./../views/Menu') - }, - { - path: '/prev-order', - name: 'prev-order', - component: () => import('./../views/PrevOrder') - }, - { - path: '/order', - name: 'order', - component: () => import('./../views/Order.vue') + redirect: 'login', + component: AuthLayout, + children: [ + { + path: '/login', + name: 'login', + component: () => import('./../views/LoginUser.vue') + }, + { + path: '/register', + name: 'register', + component: () => import('./../views/RegisterUser.vue') + } + ] } ] diff --git a/owner-vue/src/views/About.vue b/owner-vue/src/views/About.vue deleted file mode 100644 index 3fa2807..0000000 --- a/owner-vue/src/views/About.vue +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/owner-vue/src/views/Home.vue b/owner-vue/src/views/Home.vue deleted file mode 100644 index 44553c4..0000000 --- a/owner-vue/src/views/Home.vue +++ /dev/null @@ -1,15 +0,0 @@ - - - diff --git a/owner-vue/src/views/Layout/AuthLayout.vue b/owner-vue/src/views/Layout/AuthLayout.vue new file mode 100644 index 0000000..f5d3b62 --- /dev/null +++ b/owner-vue/src/views/Layout/AuthLayout.vue @@ -0,0 +1,21 @@ + + + + + \ No newline at end of file diff --git a/owner-vue/src/views/Layout/DashboardLayout.vue b/owner-vue/src/views/Layout/DashboardLayout.vue new file mode 100644 index 0000000..ea79710 --- /dev/null +++ b/owner-vue/src/views/Layout/DashboardLayout.vue @@ -0,0 +1,33 @@ + + + + + \ No newline at end of file diff --git a/owner-vue/src/components/Sidebar.vue b/owner-vue/src/views/Layout/Sidebar.vue similarity index 100% rename from owner-vue/src/components/Sidebar.vue rename to owner-vue/src/views/Layout/Sidebar.vue diff --git a/owner-vue/src/components/Topbar.vue b/owner-vue/src/views/Layout/Topbar.vue similarity index 100% rename from owner-vue/src/components/Topbar.vue rename to owner-vue/src/views/Layout/Topbar.vue diff --git a/owner-vue/src/views/RegisterUser.vue b/owner-vue/src/views/RegisterUser.vue new file mode 100644 index 0000000..9d71845 --- /dev/null +++ b/owner-vue/src/views/RegisterUser.vue @@ -0,0 +1,96 @@ + + + + + \ No newline at end of file diff --git a/user-service/build.gradle b/user-service/build.gradle index fea05ef..a5e8c0c 100644 --- a/user-service/build.gradle +++ b/user-service/build.gradle @@ -35,7 +35,7 @@ dependencies { implementation 'org.springframework.cloud:spring-cloud-starter-openfeign' /*implementation 'org.springframework.boot:spring-boot-starter-amqp'*/ implementation 'org.springframework.boot:spring-boot-starter-security' - compileOnly 'org.springframework.boot:spring-boot-starter-oauth2-client' + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' implementation 'org.springframework.cloud:spring-cloud-starter-config' /*implementation 'org.springframework.kafka:spring-kafka'*/ implementation 'org.springframework.boot:spring-boot-starter-data-redis' @@ -55,6 +55,8 @@ dependencies { testImplementation 'org.springframework.security:spring-security-test' testImplementation 'com.h2database:h2' + testImplementation 'org.springframework.security:spring-security-test' + testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' // operation block 을 위한 의존성 asciidoctorExtensions 'org.springframework.restdocs:spring-restdocs-asciidoctor' diff --git a/user-service/src/docs/asciidoc/api-docs.adoc b/user-service/src/docs/asciidoc/api-docs.adoc index dfde993..8bb36ad 100644 --- a/user-service/src/docs/asciidoc/api-docs.adoc +++ b/user-service/src/docs/asciidoc/api-docs.adoc @@ -68,4 +68,10 @@ domain-httpRequestCode-etc === 회원 조회 operation::customer-get[snippets='curl-request,http-request,http-response,path-parameters,response-fields'] === 회원 조회 (존재하지 않는 회원) -operation::customer-get-notExistUserException[snippets='curl-request,http-request,http-response,path-parameters,response-fields'] \ No newline at end of file +operation::customer-get-notExistUserException[snippets='curl-request,http-request,http-response,path-parameters,response-fields'] + +== 점주 +=== 회원가입 - 점주 +operation::storeOwner-post[snippets='curl-request,http-request,http-response,request-fields,response-fields'] +=== 회원가입 - 점주 : 중복 이메일 +operation::storeOwner-post-duplicateUserEmailException[snippets='curl-request,http-request,http-response,request-fields,response-fields'] \ No newline at end of file diff --git a/user-service/src/main/java/com/justpickup/userservice/domain/user/exception/DuplicateUserEmail.java b/user-service/src/main/java/com/justpickup/userservice/domain/user/exception/DuplicateUserEmail.java new file mode 100644 index 0000000..6d4f0a3 --- /dev/null +++ b/user-service/src/main/java/com/justpickup/userservice/domain/user/exception/DuplicateUserEmail.java @@ -0,0 +1,11 @@ +package com.justpickup.userservice.domain.user.exception; + +import com.justpickup.userservice.global.exception.CustomException; +import org.springframework.http.HttpStatus; + +public class DuplicateUserEmail extends CustomException { + + public DuplicateUserEmail(String message) { + super(HttpStatus.CONFLICT, message); + } +} diff --git a/user-service/src/main/java/com/justpickup/userservice/domain/user/repository/UserRepository.java b/user-service/src/main/java/com/justpickup/userservice/domain/user/repository/UserRepository.java index 4b8113c..c5efd3c 100644 --- a/user-service/src/main/java/com/justpickup/userservice/domain/user/repository/UserRepository.java +++ b/user-service/src/main/java/com/justpickup/userservice/domain/user/repository/UserRepository.java @@ -7,4 +7,5 @@ import java.util.Optional; public interface UserRepository extends JpaRepository { Optional findByEmail(String username); + boolean existsByEmail(String email); } diff --git a/user-service/src/main/java/com/justpickup/userservice/domain/user/service/UserService.java b/user-service/src/main/java/com/justpickup/userservice/domain/user/service/UserService.java index a6e5a93..3d09f91 100644 --- a/user-service/src/main/java/com/justpickup/userservice/domain/user/service/UserService.java +++ b/user-service/src/main/java/com/justpickup/userservice/domain/user/service/UserService.java @@ -2,12 +2,8 @@ package com.justpickup.userservice.domain.user.service; import com.justpickup.userservice.domain.user.dto.CustomerDto; import com.justpickup.userservice.domain.user.dto.StoreOwnerDto; -import com.justpickup.userservice.domain.user.entity.StoreOwner; public interface UserService { - - CustomerDto findCustomerByUserId(Long userId); - StoreOwner saveStoreOwner(StoreOwnerDto storeOwnerDto); - + void saveStoreOwner(StoreOwnerDto storeOwnerDto); } diff --git a/user-service/src/main/java/com/justpickup/userservice/domain/user/service/UserServiceImpl.java b/user-service/src/main/java/com/justpickup/userservice/domain/user/service/UserServiceImpl.java index f3b01a4..7ac0eda 100644 --- a/user-service/src/main/java/com/justpickup/userservice/domain/user/service/UserServiceImpl.java +++ b/user-service/src/main/java/com/justpickup/userservice/domain/user/service/UserServiceImpl.java @@ -5,6 +5,7 @@ import com.justpickup.userservice.domain.user.dto.StoreOwnerDto; import com.justpickup.userservice.domain.user.entity.Customer; import com.justpickup.userservice.domain.user.entity.StoreOwner; import com.justpickup.userservice.domain.user.entity.User; +import com.justpickup.userservice.domain.user.exception.DuplicateUserEmail; import com.justpickup.userservice.domain.user.exception.NotExistUserException; import com.justpickup.userservice.domain.user.repository.CustomerRepository; import com.justpickup.userservice.domain.user.repository.UserRepository; @@ -32,8 +33,6 @@ public class UserServiceImpl implements UserService, UserDetailsService { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; - - @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByEmail(username) @@ -54,12 +53,18 @@ public class UserServiceImpl implements UserService, UserDetailsService { @Override @Transactional - public StoreOwner saveStoreOwner(StoreOwnerDto storeOwnerDto) { + public void saveStoreOwner(StoreOwnerDto storeOwnerDto) { + String email = storeOwnerDto.getEmail(); + boolean exists = userRepository.existsByEmail(email); + + if (exists) throw new DuplicateUserEmail(email + "은 중복된 이메일입니다."); + String encode = passwordEncoder.encode(storeOwnerDto.getPassword()); - StoreOwner storeOwner = new StoreOwner(storeOwnerDto.getEmail(), encode, storeOwnerDto.getName(), + StoreOwner storeOwner = new StoreOwner(email, encode, storeOwnerDto.getName(), storeOwnerDto.getPhoneNumber(), storeOwnerDto.getBusinessNumber()); - return userRepository.save(storeOwner); + StoreOwner save = userRepository.save(storeOwner); } + } diff --git a/user-service/src/main/java/com/justpickup/userservice/domain/user/web/UserController.java b/user-service/src/main/java/com/justpickup/userservice/domain/user/web/UserController.java index bd7b6a9..377a1bb 100644 --- a/user-service/src/main/java/com/justpickup/userservice/domain/user/web/UserController.java +++ b/user-service/src/main/java/com/justpickup/userservice/domain/user/web/UserController.java @@ -1,24 +1,21 @@ package com.justpickup.userservice.domain.user.web; import com.justpickup.userservice.domain.user.dto.CustomerDto; -import com.justpickup.userservice.domain.user.entity.Customer; +import com.justpickup.userservice.domain.user.dto.StoreOwnerDto; import com.justpickup.userservice.domain.user.service.UserService; import com.justpickup.userservice.global.dto.Result; -import lombok.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpRequest; import org.springframework.http.HttpStatus; 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.RequestParam; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.client.RestTemplate; +import org.springframework.web.bind.annotation.*; -import javax.servlet.http.HttpServletRequest; import javax.validation.Valid; -import java.util.Objects; -import java.util.Optional; +import javax.validation.constraints.Email; +import javax.validation.constraints.NotEmpty; @RestController @RequiredArgsConstructor @@ -51,6 +48,36 @@ public class UserController { } } + @PostMapping("/store-owner") + public ResponseEntity joinStoreOwner(@Valid @RequestBody JoinStoreOwnerRequest joinRequest) { + // 회원 가입 + userService.saveStoreOwner(joinRequest.toStoreOwnerDto()); + + return ResponseEntity.status(HttpStatus.CREATED) + .body(Result.createSuccessResult(null)); + } + + @Data @NoArgsConstructor @AllArgsConstructor + static class JoinStoreOwnerRequest { + @Email(message = "email 형식이 아닙니다.") + @NotEmpty + private String email; + @NotEmpty + private String password; + @NotEmpty + private String name; + @NotEmpty + private String phoneNumber; + @NotEmpty + private String businessNumber; + + public StoreOwnerDto toStoreOwnerDto() { + return StoreOwnerDto.builder() + .email(email).password(password).name(name) + .password(password).businessNumber(businessNumber) + .build(); + } + } } diff --git a/user-service/src/main/java/com/justpickup/userservice/global/security/SecurityConfig.java b/user-service/src/main/java/com/justpickup/userservice/global/security/SecurityConfig.java index d9863c3..e0da657 100644 --- a/user-service/src/main/java/com/justpickup/userservice/global/security/SecurityConfig.java +++ b/user-service/src/main/java/com/justpickup/userservice/global/security/SecurityConfig.java @@ -57,7 +57,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { .userService(oAuthService); http.addFilter(loginAuthenticationFilter); - http.addFilterBefore(new HeaderAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class); +// http.addFilterBefore(new HeaderAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class); } @Override diff --git a/user-service/src/test/java/com/justpickup/userservice/domain/user/web/UserControllerTest.java b/user-service/src/test/java/com/justpickup/userservice/domain/user/web/UserControllerTest.java index d27be15..93c2c3d 100644 --- a/user-service/src/test/java/com/justpickup/userservice/domain/user/web/UserControllerTest.java +++ b/user-service/src/test/java/com/justpickup/userservice/domain/user/web/UserControllerTest.java @@ -3,25 +3,34 @@ package com.justpickup.userservice.domain.user.web; import com.fasterxml.jackson.databind.ObjectMapper; import com.justpickup.userservice.config.TestConfig; import com.justpickup.userservice.domain.user.dto.CustomerDto; +import com.justpickup.userservice.domain.user.exception.DuplicateUserEmail; import com.justpickup.userservice.domain.user.exception.NotExistUserException; import com.justpickup.userservice.domain.user.service.UserService; import com.justpickup.userservice.global.dto.Code; +import com.justpickup.userservice.global.security.SecurityConfig; +import com.justpickup.userservice.global.utils.CookieProvider; 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.AutoConfigureMockMvc; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.FilterType; import org.springframework.context.annotation.Import; -import org.springframework.restdocs.payload.FieldDescriptor; +import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willThrow; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; -import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; -import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; -import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; @@ -29,7 +38,10 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@WebMvcTest(UserController.class) +@WebMvcTest(controllers = UserController.class, + excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = SecurityConfig.class)} +) +@AutoConfigureMockMvc(addFilters = false) @Import(TestConfig.class) @AutoConfigureRestDocs(uriHost = "127.0.0.1", uriPort = 8001) class UserControllerTest { @@ -43,6 +55,9 @@ class UserControllerTest { @MockBean UserService userService; + @SpyBean + CookieProvider cookieProvider; + @Test @DisplayName("회원 조회") void getCustomer() throws Exception { @@ -115,4 +130,72 @@ class UserControllerTest { )) ; } + + @Test + @DisplayName("회원가입 - 점주") + void registerStoreOwner() throws Exception { + UserController.JoinStoreOwnerRequest requestBody = + new UserController.JoinStoreOwnerRequest("test@naver.com", "1234", "Park", + "010-1234-5678", "1234"); + + ResultActions actions = mockMvc.perform(post("/store-owner") + .content(objectMapper.writeValueAsString(requestBody)) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ); + + actions.andExpect(status().isCreated()) + .andDo(print()) + .andDo(document("storeOwner-post", + requestFields( + fieldWithPath("email").description("이메일"), + fieldWithPath("password").description("비밀번호"), + fieldWithPath("name").description("이름"), + fieldWithPath("phoneNumber").description("휴대폰번호"), + fieldWithPath("businessNumber").description("사업자등록번호") + ), + responseFields( + fieldWithPath("code").description("결과코드 SUCCESS/ERROR"), + fieldWithPath("message").description("메시지"), + fieldWithPath("data").description("데이터") + ) + )) + ; + } + + @Test + @DisplayName("회원가입 - 점주 : 존재하는 회원 이메일") + void registerStoreOwnerDuplicateUserEmailException() throws Exception { + String email = "test@naver.com"; + UserController.JoinStoreOwnerRequest requestBody = + new UserController.JoinStoreOwnerRequest(email, "1234", "Park", + "010-1234-5678", "1234"); + + willThrow(new DuplicateUserEmail(email + "은 중복된 이메일입니다.")) + .given(userService).saveStoreOwner(any()); + + ResultActions actions = mockMvc.perform(post("/store-owner") + .content(objectMapper.writeValueAsString(requestBody)) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + ); + + actions.andExpect(status().isConflict()) + .andDo(print()) + .andDo(document("storeOwner-post-duplicateUserEmailException", + requestFields( + fieldWithPath("email").description("이메일"), + fieldWithPath("password").description("비밀번호"), + fieldWithPath("name").description("이름"), + fieldWithPath("phoneNumber").description("휴대폰번호"), + fieldWithPath("businessNumber").description("사업자등록번호") + ), + responseFields( + fieldWithPath("code").description("결과코드 SUCCESS/ERROR"), + fieldWithPath("message").description("메시지"), + fieldWithPath("data").description("데이터") + ) + )) + ; + } } \ No newline at end of file