Category: 강의

0

레스토랑 예약 사이트 만들기 14 - 테이블 예약

패스트 캠퍼스에서 @RestController@RequiredArgsConstructorpublic class ReservationController { private final ReservationService reservationService; @GetMapping("/reservations") public List<Reservation> list(Authentication authentication){ Claims claims = (Claims) authentication.getPrincipal(); Long restaurantId = claims.get("restaurantId", Long.class);// Long restaurantId = 1004L; List<Reservation> reservations = reservationService.getReservations(restaurantId); return reservations; }} @WebMvcTest(ReservationController.class)class ReservationControllerTest { @Autowired private MockMvc mockMvc; @MockBean private ReservationService reservationService; @Test @DisplayName("예약목록을 가져온다.") public void list() throws Exception { String token = "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjEwMDQsIm5hbWUiOiJPd25lciIsInJlc3RhdXJhbnRJZCI6MTAwNH0.cQTXhzTW48F5Nj3eXa80Y9J4OJryzFvoHtT8ELl4kTw"; ResultActions resultActions = mockMvc.perform(get("/reservations") .header("Authorization", "Bearer " + token)); resultActions .andExpect(status().isOk()); verify(reservationService).getReservations(1004L); }} @Service@RequiredArgsConstructorpublic class ReservationService { private final ReservationRepository reservationRepository; public List<Reservation> getReservations(Long restaurantId) { return reservationRepository.findAllByRestaurantId(restaurantId); }} class ReservationServiceTest { @Mock private ReservationRepository reservationRepository; private ReservationService reservationService; @BeforeEach public void setUp(){ MockitoAnnotations.openMocks(this); this.reservationService = new ReservationService(reservationRepository); } @Test @DisplayName("예약목록들을 가져온다.") public void getReservation(){ Long restaurantId = 1004L; List<Reservation> reservations = reservationService.getReservations(restaurantId); verify(reservationRepository).findAllByRestaurantId(restaurantId); }} public class JwtAuthenticationFilter extends BasicAuthenticationFilter { private final JwtUtil jwtUtil; public JwtAuthenticationFilter(AuthenticationManager authenticationManager, JwtUtil jwtUtil) { super(authenticationManager); this.jwtUtil = jwtUtil; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { Authentication authentication = getAuthentication(request); if(authentication != null){ SecurityContext context = SecurityContextHolder.getContext(); context.setAuthentication(authentication); } chain.doFilter(request, response); } private Authentication getAuthentication(HttpServletRequest request){ // Header에서 Data를 얻어야 한다. String token = request.getHeader("Authorization"); if(token == null){ return null; } Claims claims = jwtUtil.getClaims(token.substring("Bearer ".length())); Authentication authentication = new UsernamePasswordAuthenticationToken(claims, null); return authentication; }} @Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Value("${jwt.secret}") private String secret; @Override protected void configure(HttpSecurity http) throws Exception { Filter filter = new JwtAuthenticationFilter(authenticationManager(), jwtUtil()); http .cors().disable() .csrf().disable() .formLogin().disable() .headers().frameOptions().disable(); http .addFilter(filter) .sessionManagement() // Session을 사용하지 않음 .sessionCreationPolicy(SessionCreationPolicy.STATELESS); } @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } @Bean public JwtUtil jwtUtil(){ return new JwtUtil(secret); }}

0

레스토랑 예약 사이트 만들기 13 - 인가

JWT로부터 데이터 가져오기public class JwtUtil { private Key key; public JwtUtil(String secret) { this.key = Keys.hmacShaKeyFor(secret.getBytes()); } public String createToken(Long userId, String name) { String token = Jwts.builder() .claim("userId", 1004L) .claim("name", name) .signWith(key, SignatureAlgorithm.HS256) .compact(); return token; } public Claims getClaims(String token){ Claims claims = Jwts.parserBuilder() .setSigningKey(key) .build() .parseClaimsJws(token) .getBody(); return claims; }} @Test@DisplayName("Claims를 가져온다.")public void getClaims(){ String token = "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjEwMDQsIm5hbWUiOiJUZXN0ZXIifQ.I4DNdunio2m54tfUEaXHC_E-gvCQo6ZhHO15Ewkat6U"; Claims claims = jwtUtil.getClaims(token); assertThat(claims.get("userId", Long.class)).isEqualTo(1004L); assertThat(claims.get("name")).isEqualTo("Tester");} JWTFilter추가@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Value("${jwt.secret}") private String secret; @Override protected void configure(HttpSecurity http) throws Exception { Filter filter = new JwtAuthenticationFilter(authenticationManager(), jwtUtil()); http .cors().disable() .csrf().disable() .formLogin().disable() .headers().frameOptions().disable(); http .addFilter(filter) .sessionManagement() // Session을 사용하지 않음 .sessionCreationPolicy(SessionCreationPolicy.STATELESS); ; } @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } @Bean public JwtUtil jwtUtil(){ return new JwtUtil(secret); }} JWT 인증 Filterpublic class JwtAuthenticationFilter extends BasicAuthenticationFilter { private JwtUtil jwtUtil; public JwtAuthenticationFilter(AuthenticationManager authenticationManager, JwtUtil jwtUtil) { super(authenticationManager); this.jwtUtil = jwtUtil; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { Authentication authentication = getAuthentication(request); if(authentication != null){ SecurityContext context = SecurityContextHolder.getContext(); context.setAuthentication(authentication); } chain.doFilter(request, response); } private Authentication getAuthentication(HttpServletRequest request){ // Header에서 Data를 얻어야 한다. String token = request.getHeader("Authorization"); if(token == null){ return null; } Claims claims = jwtUtil.getClaims(token.substring("Bearer ".length())); Authentication authentication = new UsernamePasswordAuthenticationToken(claims, null); return authentication; }} 요청시 Token값을 가지고 있는지 확인@RestController@RequiredArgsConstructorpublic class ReviewController { private final ReviewService reviewService; @PostMapping("/restaurants/{restaurantId}/reviews") public ResponseEntity<?> create( Authentication authentication, @PathVariable Long restaurantId, @Valid @RequestBody Review resource ) throws URISyntaxException { Claims claims = (Claims) authentication.getPrincipal(); String name = claims.get("name", String.class); Review review = reviewService.addReview(restaurantId, resource); String url = "/restaurants/" + restaurantId + "/reviews/" + review.getId(); return ResponseEntity.created(new URI (url)).body("{}"); }}

0

레스토랑 예약 사이트 만들기 12 - JWT

JWT를 이용해 AccesToken관리하기JwtUtil 클래스에서는 JWT를 생성하기 위한 createToken메서드가 존재한다. JWT에는 userId와 name을 넣을 것이고 HMAC-SHA256 해싱 알고리즘을 사용해 토큰의 유효성을 검사할 것이다. public class JwtUtil { private String secret; public JwtUtil(String secret) { this.secret = secret; } public String createToken(Long userId, String name) { Key key = Keys.hmacShaKeyFor(secret.getBytes()); String token = Jwts.builder() .claim("userId", 1004L) .claim("name", name) .signWith(key, SignatureAlgorithm.HS256) .compact(); return token; }} JWT가 올바르게 생성됐는지 확인하는 테스트 코드class JwtUtilTest { @Test public void createToken(){ String secret = "12345678901234567890123456789012"; JwtUtil jwtUtil = new JwtUtil(secret); String token = jwtUtil.createToken(1004L, ""); assertThat(token).contains("."); }} JwtUtil을 Bean으로 등록@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { // application.yml에 정의된 JWT Secret값을 가져온다. @Value("${jwt.secret}") private String secret; @Override protected void configure(HttpSecurity http) throws Exception { http .cors().disable() .csrf().disable() .formLogin().disable() .headers().frameOptions().disable(); } @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } @Bean public JwtUtil jwtUtil(){ return new JwtUtil(secret); }} 사용자 요청 결과로 JWT반환하는 로직을 구현/session 경로로 사용자 정보를 받으면 우선 사용자 유효성을 확인한 뒤, 등록된 사용자인 경우 사용자 정보를 JWT에 일부 담아 Response Body에 넣어 사용자에게 반환한다.

0

레스토랑 예약 사이트 만들기 11 - 인증

AccessToken을 이용한 인증@RestController@RequiredArgsConstructorpublic class SessionController { private final UserService userService; @PostMapping("/session") public ResponseEntity<SessionResponseDto> create(@RequestBody SessionRequestDto resource){ String email = resource.getEmail(); String password = resource.getPassword(); User user = userService.authenticate(email, password); String url = "/session"; String accessToken = user.getAccessToken(); SessionResponseDto sessionResponseDto = SessionResponseDto.builder() .accessToken(accessToken) .build(); return ResponseEntity.created(URI.create(url)).body(sessionResponseDto); }} @WebMvcTest(SessionController.class)class SessionControllerTest { @Autowired private MockMvc mockMvc; @MockBean private UserService userService; @Test @DisplayName("AccessToken을 반환하는지 확인한다.") public void create() throws Exception { String email = "tester@example.com"; String password = "test"; User mockUser = User.builder().password("ACCESSTOKEN").build(); given(userService.authenticate(email, password)).willReturn(mockUser); ResultActions resultActions = mockMvc.perform(post("/session") .contentType(MediaType.APPLICATION_JSON) .content("{\"email\":\"tester@example.com\",\"password\":\"test\"}")); resultActions .andExpect(status().isCreated()) .andExpect(header().string("location", "/session")) // AccessToken을 사용하는지 확인한다. .andExpect(content().string("{\"accessToken\":\"ACCESSTOKE\"}")); verify(userService).authenticate(eq(email), eq(password)); } @Test @DisplayName("올바르지 않은 이메일을 이용한 요청을 시도") public void createWithNotExistedEmail() throws Exception { String email = "x@example.com"; String password = "test"; given(userService.authenticate(email, password)).willThrow(EmailNotExistedException.class); ResultActions resultActions = mockMvc.perform(post("/session") .contentType(MediaType.APPLICATION_JSON) .content("{\"email\":\"x@example.com\",\"password\":\"test\"}")); resultActions .andExpect(status().isBadRequest()); verify(userService).authenticate(eq(email), eq(password)); } @Test @DisplayName("올바르지 않은 패스워드를 이용한 요청을 시도") public void createWithInvalidAttributes() throws Exception { String email = "tester@example.com"; String password = "x"; given(userService.authenticate(email, password)).willThrow(PasswordWrongException.class); ResultActions resultActions = mockMvc.perform(post("/session") .contentType(MediaType.APPLICATION_JSON) .content("{\"email\":\"tester@example.com\",\"password\":\"x\"}")); resultActions .andExpect(status().isBadRequest()); verify(userService).authenticate(eq(email), eq(password)); }} public class EmailNotExistedException extends RuntimeException{ public EmailNotExistedException(){ super("Email is Not registered"); }} public class PasswordWrongException extends RuntimeException{ public PasswordWrongException(){ super("Password is Wrong"); }} @ControllerAdvicepublic class SessionErrorAdvice { @ResponseBody @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(PasswordWrongException.class) public String handlePasswordWrong(){ return "{}"; } @ResponseBody @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(EmailNotExistedException.class) public String handleEmailNotExisted(){ return "{}"; }} @Service@RequiredArgsConstructorpublic class UserService { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; public User registerUser(String email, String name, String password) { // 회원이 이미 등록되어 있는지 Email을 통해 유효성 검사 Optional<User> optional = userRepository.findByEmail(email); // 회원이 이미 존재하는 경우 예외처리를 한다. if(optional.isPresent()){ throw new EmailExistedException(email); }// // 패스워드를 암호화해서 저장한다.// // 암호화 방식은 BCrypt방식을 이용해 암호화를 진행// BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); String encodedPassword = passwordEncoder.encode(password); User user = User.builder() .email(email) .name(name) .password(encodedPassword) .level(1L) .build(); userRepository.save(user); return user; } public User authenticate(String email, String password) { User user = userRepository.findByEmail(email) .orElseThrow(() -> new EmailNotExistedException()); String encodedPassword = passwordEncoder.encode(password); if(!passwordEncoder.matches(password, user.getPassword())){ throw new PasswordWrongException(); } return user; }} @Test@DisplayName("올바른 파라미터를 이용해 사용자 유효성 검사를 한다.")public void authenticateWithValidAttributes(){ String email = "test@example.com"; String name = "Tester"; String password = "test"; User mockUser = User.builder() .Id(1004L) .email(email) .name(name) .password(password) .build(); given(userRepository.findByEmail(email)).willReturn(Optional.of(mockUser)); given(passwordEncoder.matches(any(), any())).willReturn(true); User user = userService.authenticate(email, password); assertThat(user.getEmail()).isEqualTo(email);}@Test@DisplayName("올바르지 않은 이메일을 이용해 사용자 유효성 검사를 한다.")public void authenticateNotValidEmail(){ String email = "x@example.com"; String password = "test"; given(userRepository.findByEmail(email)).willThrow(EmailNotExistedException.class); assertThatThrownBy(()->{ User user = userService.authenticate(email, password); }).isInstanceOf(EmailNotExistedException.class);}@Test@DisplayName("올바르지 않은 이메일을 이용해 사용자 유효성 검사를 한다.")public void authenticateNotValidPassword(){ String email = "test@example.com"; String name = "Tester"; String password = "x"; User mockUser = User.builder() .Id(1004L) .email(email) .name(name) .password(password) .build(); given(userRepository.findByEmail(email)).willReturn(Optional.of(mockUser)); given(passwordEncoder.matches(password, mockUser.getPassword())).willReturn(false); assertThatThrownBy(()->{ User user = userService.authenticate(email, password); }).isInstanceOf(PasswordWrongException.class);} @Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .cors().disable() .csrf().disable() .formLogin().disable() .headers().frameOptions().disable(); } @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); }}

0

레스토랑 예약 사이트 만들기 10 - 회원 가입

Security 설정하기@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .cors().disable() .csrf().disable() .formLogin().disable() .headers().frameOptions().disable(); }} @Service@RequiredArgsConstructorpublic class UserService { private final UserRepository userRepository; public User registerUser(String email, String name, String password) { User user = User.builder() .email(email) .name(name) .password(encodedPassword) .level(1L) .build(); userRepository.save(user); return user; }} 패스워드에 암호화 하기@Service@RequiredArgsConstructorpublic class UserService { private final UserRepository userRepository; public User registerUser(String email, String name, String password) { // 패스워드를 암호화해서 저장한다. BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); String encodedPassword = passwordEncoder.encode(password); User user = User.builder() .email(email) .name(name) .password(encodedPassword) .level(1L) .build(); userRepository.save(user); return user; }} class UserServiceTest { @Mock private UserRepository userRepository; private UserService userService; @BeforeEach public void setUp(){ MockitoAnnotations.openMocks(this); userService = new UserService(userRepository); } @Test @DisplayName("사용자를 등록한다.") public void registerUser(){ String email = "test@example.com"; String name = "Tester"; String password = "test"; User user = userService.registerUser(email, name, password); assertThat(user.getEmail()).isEqualTo(email); assertThat(user.getName()).isEqualTo(name); assertThat(user.getPassword()).isEqualTo(password); verify(userRepository).save(any()); }} @RestController@RequiredArgsConstructorpublic class UserController { private final UserService userService; @PostMapping("/users") public ResponseEntity<?> create(@RequestBody User resource){ String email = resource.getEmail(); String name = resource.getName(); String password = resource.getPassword(); User user = userService.registerUser(email, name, password); String url = "/users/" + user.getId(); return ResponseEntity.created(URI.create(url)).body("{}"); }} @WebMvcTest(UserController.class)class UserControllerTest { @Autowired private MockMvc mockMvc; @MockBean private UserService userService; @Test @DisplayName("사용자를 생성한다.") public void create() throws Exception { String email = "tester@example.com"; String name = "Tester"; String password = "test"; User mockUser = User.builder() .Id(1004L) .email(email) .password(password) .name(name) .build(); given(userService.registerUser(email, name, password)) .willReturn(mockUser); ResultActions resultActions = mockMvc.perform(post("/users") .contentType(MediaType.APPLICATION_JSON) .content("{\"email\" : \"tester@example.com\", \"name\" : \"Tester\", \"password\" : \"test\"}")); resultActions .andExpect(status().isCreated()) .andExpect(header().string("location", "/users/1004")); verify(userService).registerUser(any(), any(), any()); }} 존재하는 회원에 대한 예외처리

0

레스토랑 예약 사이트 만들기 9 - 사용자 관리

@Entity@Getter@Setter@NoArgsConstructor@AllArgsConstructor@Builderpublic class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long Id; @NotEmpty private String email; @NotEmpty private String name; @NotNull private Long level; public boolean isAdmin() { return level > 2L; } public void deactivate(){ level = 0L; } public boolean isActive() { return level > 0L; }} @Repositorypublic interface UserRepository extends JpaRepository<User, Long> {} @Service@RequiredArgsConstructorpublic class UserService { private final UserRepository userRepository; public List<User> getUsers() { return userRepository.findAll(); } public User addUser(String email, String name) { User user = User.builder() .email(email) .name(name) .level(1L) .build(); userRepository.save(user); return user; } public User updateUser(Long id, String email, String name, Long level ) { User user = userRepository.findById(id).orElse(null); user.setEmail(email); user.setName(name); user.setLevel(level); return user; } public User deactivateUser(Long userId) { User user = userRepository.findById(userId).orElse(null); user.deactivate(); return user; }} class UserServiceTest { @Mock private UserRepository userRepository; private UserService userService; @BeforeEach private void setUp() { MockitoAnnotations.openMocks(this); userService = new UserService(userRepository); } @Test @DisplayName("유저목록을 가져온다.") public void getUsers() { List<User> mockUsers = new ArrayList<>(); mockUsers.add(User.builder() .name("tester") .email("test@example.com") .level(3L) .build()); given(userRepository.findAll()).willReturn(mockUsers); List<User> users = userService.getUsers(); User user = users.get(0); assertThat(user.getName()).isEqualTo("tester"); assertThat(user.getEmail()).isEqualTo("test@example.com"); } @Test @DisplayName("유저를 추가한다.") public void addUser(){ String email = "test@example.com"; String name = "tester"; User mockUser = User.builder() .name(name) .email(email) .build(); given(userRepository.save(any())).willReturn(mockUser); User user = userService.addUser(email, name); assertThat(user.getName()).isEqualTo(name); assertThat(user.getEmail()).isEqualTo(email); } @Test @DisplayName("유저를 업데이트 한다.") public void updateUser(){ Long id = 1004L; Long level = 3L; String email = "test@example.com"; String name = "Superman"; User mockUser = User.builder() .Id(id) .name("Administrator") .level(1L) .email(email) .build(); given(userRepository.findById(id)).willReturn(Optional.of(mockUser)); User user = userService.updateUser(id, email, name, level); verify(userRepository).findById(id); assertThat(user.getName()).isEqualTo("Superman"); assertThat(user.isAdmin()).isEqualTo(true); } @Test @DisplayName("유저를 삭제한다.") public void deactiveUser(){ Long id = 1004L; Long level = 2L; String email = "test@example.com"; String name = "Superman"; User mockUser = User.builder() .Id(id) .level(level) .email(email) .name(name) .build(); given(userRepository.findById(id)).willReturn(Optional.of(mockUser)); User user = userService.deactivateUser(1004L); verify(userRepository).findById(1004L); assertThat(user.isAdmin()).isEqualTo(false); assertThat(user.isActive()).isEqualTo(false); }} @RestController@RequiredArgsConstructorpublic class UserController { // 1. User list // 2. User create -> 회원가입 // 3. User update // 4. User delete -> level: 0 => 아무것도 못 함. // (1: customer 2: restaurant owner 3: admin) private final UserService userService; @GetMapping("/users") public List<User> list(){ return userService.getUsers(); } @PostMapping("/users") public ResponseEntity<?> create(@RequestBody User resource) throws URISyntaxException { User user = userService.addUser(resource.getEmail(), resource.getName()); String url = "/users" + user.getId(); return ResponseEntity.created(URI.create(url)).body("{}"); } @PatchMapping("/users/{userId}") public void udpate(@PathVariable("userId") Long id, @RequestBody User user){ userService.updateUser(id, user.getEmail(), user.getName(), user.getLevel()); } @DeleteMapping("/users/{userId}") public String deactivate(@PathVariable("userId")Long userId){ userService.deactivateUser(userId); return "{}"; }} @WebMvcTest(UserController.class)class UserControllerTest { @Autowired private MockMvc mockMvc; @MockBean private UserService userService; @Test @DisplayName("유저목록을_가져온다") public void list() throws Exception { List<User> users = new ArrayList<>(); users.add(User.builder() .email("tester@example.com") .name("tester") .level(1L) .build()); given(userService.getUsers()).willReturn(users); mockMvc.perform(get("/users")) .andExpect(status().isOk()) .andExpect(content().string(containsString("tester"))); } @Test @DisplayName("유저를 추가한다.") public void create() throws Exception { String email = "admin@example.com"; String name = "Administrator"; User user = User.builder() .email(email) .name(name) .build(); given(userService.addUser(email, name)).willReturn(user); ResultActions resultActions = mockMvc.perform(post("/users") .contentType(MediaType.APPLICATION_JSON) .content("{\"email\" : \"admin@example.com\", \"name\" : \"Administrator\"}")); verify(userService).addUser(email, name) ; resultActions .andExpect(status().isCreated()); } @Test @DisplayName("유저상태를 업데이트 한다") public void update() throws Exception { Long id = 1004L; String email = "admin@example.com"; String name = "Administrator"; Long level = 3L; User user = User.builder() .email(email) .name(name) .level(level) .build(); given(userService.updateUser(id, email, name, level)).willReturn(user); ResultActions resultActions = mockMvc.perform(patch("/users/1004") .contentType(MediaType.APPLICATION_JSON) .content("{\"level\": 3" + ",\"email\" : \"admin@example.com\"" + ", \"name\" : \"Administrator\"}")); verify(userService).updateUser(eq(id), eq(email), eq(name), eq(level)); resultActions .andExpect(status().isOk()); } @Test @DisplayName("유저를 삭제한다.") public void deactivate() throws Exception { mockMvc.perform(delete("/users/1004")) .andExpect(status().isOk()); verify(userService).deactivateUser(1004L); }}

0

레스토랑 예약 사이트 만들기 8 - 가게목록 필터링

@Entity@Getter@Setter@NoArgsConstructor@AllArgsConstructor@Builderpublic class Region { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name;} @Repositorypublic interface RegionRepository extends JpaRepository<Region,Long> {} @Service@RequiredArgsConstructorpublic class RegionService { private final RegionRepository regionRepository; public List<Region> getRegions() { return regionRepository.findAll(); }} @Service@RequiredArgsConstructorpublic class RegionService { private final RegionRepository regionRepository; public List<Region> getRegions() { return regionRepository.findAll(); }} class RegionServiceTest { private RegionService regionService; @Mock private RegionRepository regionRepository; @BeforeEach public void setUp(){ MockitoAnnotations.openMocks(this ); regionService = new RegionService(regionRepository); } @Test public void 지역정보들을_가져온다() { List<Region> mockRegions = new ArrayList<>(); mockRegions.add(Region.builder() .name("Seoul") .build()); given(regionRepository.findAll()).willReturn(mockRegions); List<Region> regions = regionService.getRegions(); Region region = regions.get(0); assertThat(region.getName()).isEqualTo("Seoul"); }} @RestController@RequiredArgsConstructorpublic class RegionController { private final RegionService regionService; @GetMapping("/regions") public List<Region> list(){ List<Region> regions = regionService.getRegions(); return regions; }} @WebMvcTest(RegionController.class)class RegionControllerTest { @Autowired private MockMvc mockMvc; @MockBean private RegionService regionService; @Test public void 지역목록들을_가져온다() throws Exception { List<Region> regions = new ArrayList<>(); regions.add(Region.builder().name("Seoul").build()); given(regionService.getRegions()).willReturn(regions); mockMvc.perform(get("/regions")) .andExpect(status().isOk()) .andExpect(content().string(containsString("Seoul"))); }} 카테고리 만들기@Entity@Getter@Setter@NoArgsConstructor@AllArgsConstructor@Builderpublic class Category { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) public Long id; public String name;}

0

레스토랑 예약 사이트 만들기 7 - 멀티모듈

dependencies { implementation project(':eatgo-common') implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-web' // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation implementation 'org.springframework.boot:spring-boot-starter-validation' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'com.h2database:h2' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test'} jar { enabled = true}bootJar { enabled = false} @Testpublic void 레스토랑에_해당되는_메뉴들을_반환한다() { List<MenuItem> mockMenuItems = new ArrayList<>(); mockMenuItems.add(MenuItem.builder() .name("Kimchi") .build()); given(menuItemRepository.findAllByRestaurantId(1004L)).willReturn(mockMenuItems); List<MenuItem> menuItems = menuItemService.getMenuItems(1004L); MenuItem menuItem = menuItems.get(0); assertThat(menuItem.getName()).isEqualTo("Kimchi");} public List<MenuItem> getMenuItems(Long restaurantId) { return menuItemRepository.findAllByRestaurantId(restaurantId);} 리뷰 정보 가져오기@GetMapping("/reviews")public List<Review> list(){ List<Review> reviews = reivewService.getReviews(); return reviews;} @Testpublic void 레스토랑에_해당되는_리뷰들을_가져온다() throws Exception { List<Review> mockReviews = new ArrayList<>(); mockReviews.add(Review.builder() .description("Cool!") .build()); given(reviewService.getReviews()).willReturn(mockReviews); mockMvc.perform(get("/reviews")) .andExpect(status().isOk()) .andExpect(content().string(containsString("Cool!")));} public List<Review> getReviews() { return reviewRepository.findAll();} @Testpublic void 리뷰들을_가져온다(){ List<Review> mockReviews = new ArrayList<>(); mockReviews.add(Review.builder() .description("Cool!") .build()); given(reviewRepository.findAll()).willReturn(mockReviews); List<Review> reviews = reviewService.getReviews(); Review review = reviews.get(0); assertThat(review.getDescription()).isEqualTo("Cool!");}

0

레스토랑 예약 사이트 만들기 6 - 리뷰 기능

새로운 리뷰 생성 오청을 처리하기 위한 control@RestController@RequiredArgsConstructorpublic class ReviewController { private final ReviewService reivewService; @PostMapping("/restaurants/{restaurantId}/reviews") public ResponseEntity<?> create( @PathVariable Long restaurantId, @Valid @RequestBody Review resource ) throws URISyntaxException { Review review = reivewService.addReview(restaurantId, resource); String url = "/restaurants/" + restaurantId + "/reviews/" + review.getId(); return ResponseEntity.created(new URI (url)).body("{}"); }} @WebMvcTest(ReviewController.class)class ReviewControllerTest { @Autowired private MockMvc mockMvc; @MockBean private ReviewService reviewService; @Test public void 리뷰를_생성한다() throws Exception { given(reviewService.addReview(eq(1L), any())).willReturn( Review.builder() .id(1004L) .name("JOKER") .score(3) .description("Mat-it-da") .build()); mockMvc.perform(post("/restaurants/1/reviews") .contentType(MediaType.APPLICATION_JSON) .content("{\"name\":\"JOKER\", \"score\":3,\"description\" : \"Mat-it-da\"}")) .andExpect(status().isCreated()) .andExpect(header().string("location", "/restaurants/1/reviews/1004")); verify(reviewService).addReview(eq(1L), any()); }} Review 도메인 객체를 생성@Entity@Builder@NoArgsConstructor@AllArgsConstructor@Getter@Setterpublic class Review { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @NotNull private Long restaurantId; @NotEmpty private String name; @NotNull private Integer score; @NotEmpty private String description;} Review를 저장하기 위한 Repository 생성@Repositorypublic interface ReviewRepository extends JpaRepository<Review, Long> { List<Review> findAllByRestaurantId(Long restaurantId);} Review Service 클래스를 생성한다.@Service@RequiredArgsConstructorpublic class ReviewService { private final ReviewRepository reviewRepository; public Review addReview(Long restaurantId, Review review) { review.setRestaurantId(restaurantId); reviewRepository.save(review); return review; }}

0

레스토랑 예약 사이트 만들기 5 - 메뉴 관리

메뉴를 추가하는 요청을 처리하기 위한 control@RestController@RequiredArgsConstructorpublic class MenuItemController { private final MenuItemService menuItemService; @PatchMapping("/restaurants/{restaurantId}/menuitems") public String buikUpdate(){ List<MenuItem> menuItems = new ArrayList<>(); menuItemService.bulkUpdate(menuItems); return ""; }} @WebMvcTest(MenuItemController.class)class MenuItemControllerTest { @Autowired private MockMvc mockMvc; @MockBean private MenuItemService menuItemService; @Test public void bulkUpdate() throws Exception { ResultActions resultActions = mockMvc.perform(patch("/restaurants/1/menuitems") .contentType(MediaType.APPLICATION_JSON) .content("[]")); resultActions .andExpect(status().isOk()); verify(menuItemService).bulkUpdate(any()); }} @Service@RequiredArgsConstructorpublic class MenuItemService { private final MenuItemRepository menuItemRepository; public void bulkUpdate(Long restaurantId, List<MenuItem> menuItems) { for(MenuItem menuItem : menuItems){ menuItem.setRestaurantId(restaurantId); menuItemRepository.save(menuItem); } }} class MenuItemServiceTest { private MenuItemService menuItemService; @Mock private MenuItemRepository menuItemRepository; @BeforeEach public void setUp(){ MockitoAnnotations.initMocks(this); menuItemService = new MenuItemService(menuItemRepository); } @Test public void bulkUpdate() { List<MenuItem> menuItems = new ArrayList<>(); menuItems.add(MenuItem.builder() .name("Kimchi") .build()); menuItems.add(MenuItem.builder() .name("Gukbob") .build()); menuItemService.bulkUpdate(1L, menuItems); verify(menuItemRepository, times(2)).save(any());; }} 메뉴 삭제 요청@Builder@Entity@Setter@Getter@NoArgsConstructor@AllArgsConstructorpublic class MenuItem { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private Long restaurantId; private String name; @Transient private boolean destroy; public MenuItem(String name){ this.name = name; }} @Service@RequiredArgsConstructorpublic class MenuItemService { private final MenuItemRepository menuItemRepository; public void bulkUpdate(Long restaurantId, List<MenuItem> menuItems) { for(MenuItem menuItem : menuItems){ updateOrDeleteMenuItems(restaurantId, menuItem); } } private void updateOrDeleteMenuItems(Long restaurantId, MenuItem menuItem) { if(menuItem.isDestroy()){ menuItemRepository.deleteById(menuItem.getId()); }else { menuItem.setRestaurantId(restaurantId); menuItemRepository.save(menuItem); } }} @Testpublic void 메뉴를_삭제한다() { List<MenuItem> menuItems = new ArrayList<>(); menuItems.add(MenuItem.builder() .name("Kimchi") .build()); menuItems.add(MenuItem.builder() .name("Gukbob") .build()); menuItems.add(MenuItem.builder() .id(1004L) .destroy(true) .build()); menuItemService.bulkUpdate(1L, menuItems); verify(menuItemRepository, times(2)).save(any()); verify(menuItemRepository, times(1)).deleteById(eq(1004L));}

0

레스토랑 예약 사이트 만들기 4 - 예외 처리

없는 페이지 요청에 대한 예외처리@Testpublic void 없는_페이지에대한_예외처리를_한다() throws Exception{ given(restaurantService.getRestaurant(404L)).willThrow(new RestaurantNotFoundException(404L)); ResultActions resultActions = mockMvc.perform(get("/restaurants/404")); resultActions .andExpect(status().isNotFound()) .andExpect(content().string("{}"));} public class RestaurantNotFoundException extends RuntimeException{ public RestaurantNotFoundException(Long id) { super("Could not find Restaurant" + id ); }} @ControllerAdvicepublic class RestaurantErrorAdvice { @ResponseBody @ResponseStatus(HttpStatus.NOT_FOUND) @ExceptionHandler(RestaurantNotFoundException.class) public String handleNotFound(){ return "{}"; }} Service 계층에서의 예외처리@Testpublic void 없는_레스토랑을_가져온다() { assertThatThrownBy(() -> { Restaurant restaurant = restaurantService.getRestaurant(404L); }).isInstanceOf(RestaurantNotFoundException.class);// Restaurant restaurant = restaurantService.getRestaurant(404L);} public Restaurant getRestaurant(Long id) { Optional<Restaurant> optional = restaurantRepository.findById(id); if (optional.isPresent()) { List<MenuItem> menuItems = menuItemRepository.findAllByRestaurantId(id); Restaurant restaurant = new Restaurant(); restaurant.setMenuItems(menuItems); return restaurant; }else{ throw new RestaurantNotFoundException(id); }}

0

레스토랑 예약 사이트 만들기 3

<!DOCTYPE html><html lang="ko"><head> <meta charset="UTF-8"> <title>Title</title></head><body><div id="app">Hello, world!</div><script src="./src/index.js"></script></body></html> (async () => { const url = "http://localhost:8080/restaurants"; const response = await fetch(url); const restaurants = await response.json(); console.log(restaurants); const element = document.getElementById('app'); element.innerHTML = JSON.stringify(restaurants);})(); (async () => { const url = "http://localhost:8080/restaurants"; const response = await fetch(url); const restaurants = await response.json(); console.log(restaurants); const element = document.getElementById('app'); element.innerHTML = ` ${restaurants[0].id} ${restaurants[0].name} `})(); (async () => { const url = "http://localhost:8080/restaurants"; const response = await fetch(url); const restaurants = await response.json(); console.log(restaurants); const element = document.getElementById('app'); element.innerHTML = ` ${restaurants.map(restaurant => ` <p> ${restaurant.id} ${restaurant.name} ${restaurant.address} </p> `).join('')} `;})();

0

레스토랑 예약 사이트 만들기 2 - 메뉴 정보 추가(관리자)

레스토랑에 메뉴정보를 담기@Entity@Getter@Setter@NoArgsConstructor@AllArgsConstructor@Builderpublic class Restaurant { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @NotNull private Long categoryId; @NotEmpty private String name; @NotEmpty private String address; @Transient @JsonInclude(JsonInclude.Include.NON_NULL) private List<MenuItem> menuItems; public void addMenuItem(MenuItem menuItem){ menuItems.add(menuItem); } public void setMenuItems(List<MenuItem> menuItems){ this.menuItems = new ArrayList<>(menuItems); } public String getInformation(){ return name + " in " + address; }} 메뉴에 대한 정보를 담고있는 MenuItem 클래스@Builder@Entity@Setter@Getter@NoArgsConstructor@AllArgsConstructorpublic class MenuItem { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private Long restaurantId; private String name; @Transient @JsonInclude(JsonInclude.Include.NON_DEFAULT) private boolean destroy; public MenuItem(String name){ this.name = name; }} 레스토랑에서 메뉴정보 가져오기public interface MenuItemRepository extends JpaRepository<MenuItem, Long> { List<MenuItem> findAllByRestaurantId(Long restaurantId);} 레스토랑에 메뉴등록 요청 보내기추가하고자 하는 데이터는 Http Body에 넣어서 보낸다.

0

레스토랑 예약 사이트 1 - 레스토랑 관리(사용자) 레스토랑 정보 가져오기

사용자 페이지사용자의 경우 레스토랑을 관리하지 않으므로 레스토랑 정보를 추가하거나 수정할필요는 없고 조회하는 기능만 있으면 된다. 모든 Restaurant정보 가져오기 특정 Restaurant정보 가져오기 Request Parameter를 통한 레스토랑 조회 control요청시 RequestParameter를 통해 원하는 지역, 카테고리를 통해 해당 조건을 만족하는 레스토랑 정보를 모두 가져올 수 있도록 한다. 특정 Restaurant 정보를 가져오는 함수들은 관리자에서 만들었던 내용과 같으므로 생각한다. @CrossOrigin@RestController@RequiredArgsConstructorpublic class RestaurantController { private final RestaurantService restaurantService; @GetMapping("/restaurants") public List<Restaurant> list(@RequestParam("region") String region, @RequestParam("category") Long category) { List<Restaurant> restaurants = restaurantService.getRestaurants(region, category); return restaurants; } @GetMapping("/restaurants/{id}") public Restaurant detail(@PathVariable Long id) throws Exception { Restaurant restaurant = restaurantService.getRestaurant(id); return restaurant; }} Request Parameter를 통한 레스토랑 조회 테스트 코드 작성@Testpublic void RequestParameter를_이용한_list를_확인한다() throws Exception { List<Restaurant> restaurants = new ArrayList<>(); restaurants.add(Restaurant.builder() .id(1004L) .categoryId(1L) .name("JOKER House") .address("Seoul") .build()); given(restaurantService.getRestaurants("Seoul", 1L)).willReturn(restaurants); ResultActions resultActions = mockMvc.perform(get("/restaurants?region=Seoul&category=1")); resultActions .andExpect(status().isOk()) .andExpect(content().string(containsString("\"id\":1004"))) .andExpect(content().string(containsString("\"name\":\"JOKER House\""))) .andDo(print());}

0

레스토랑 예약 사이트 1 - 레스토랑 관리(관리자) 레스토랑 정보 업데이트

4. 레스토랑 정보 업데이트레스토랑 업데이트 하기 위한 control 추가레스토랑 정보를 업데이트 하기 위해서는 업데이트 하고자 하는 레스토랑을 가지고 와야 한다. /restaurants경로뒤에 레스토랑 id를 붙여 요청이 들어오면 id를 이용해 레스토랑을 찾고 request body에 들어있는 정보로 레스토랑을 업데이트 한다. 업데이트 요청을 처리하기 위해 Http 메소드 중에서 patch메소드를 이용할 것이다. @PatchMapping("/restaurants/{id}")public String update(@PathVariable("id") Long id, @RequestBody Restaurant restaurant) throws Exception { restaurantService.updateRestaurant(id, restaurant.getName(), restaurant.getAddress()); return "{}";} 업데이트 요청에 대한 테스트 코드 작성@Testpublic void 레스토랑정보_업데이트() throws Exception { String JOKER = "JOKER Bar"; String Busan = "Busan"; Restaurant restaurant = Restaurant.builder() .categoryId(1L) .name(JOKER) .address(Busan) .build(); String content = objectMapper.writeValueAsString(restaurant); ResultActions resultActions = mockMvc.perform(patch("/restaurants/1004") .contentType(MediaType.APPLICATION_JSON) .content(content)); resultActions .andExpect(status().isOk()) .andDo(print()); verify(restaurantService).updateRestaurant(1004L, JOKER, Busan);} 레스토랑 업데이트를 하기 위한 Service 메소드먼저 id를 갖고 저장돼 있는 해당 레스토랑을 가져온 후 전달 받은 name값과 address값으로 업데이트 한다. 만약 해당 레스토랑이 없을 경우 RestaurantNotFoundException예외를 일으키도록 한다.