Category: Spring

0

Spring Boot 게시판 만들기 2 - 게시글 작성페이지 만들기

2. 게시글 작성페이지 설계도메인 설계하기사용자로부터 게시글 제목, 작성자 이름, 게시글 내용과 게시글들을 구별해주기 위해 Id값과 게시글이 언제 작성되었는지 알기 위한 작성시간을 추가해 도메인을 만든다. Post.java @Entity@NoArgsConstructor@AllArgsConstructor@Builder@Getterpublic class Post { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @NotEmpty private String title; @NotEmpty private String name; @NotEmpty private String content; @NotNull private LocalDateTime writeTime;} DTO 작성하기사용자로부터 Form을 통해 title, name, content를 입력 받는다. Post 객체를 사용해 데이터를 전달받을 수 있지만 View로직이 추가되게 된다. Entity는 DB와 데이터를 주고 받는 객체이기에 DTO를 만들어 Entity를 View로직으로부터 분리해 추후 생길 수 있는 문제를 없애도록 한다.

0

Spring Boot 게시판 만들기 1 - 게시글 작성 페이지

1. 게시글 작성 페이지 만들기간단하게 게시글 제목, 사용자 이름, 게시글 내용 세가지만 입력 받는 폼을 만들 것이다. 게시글 작성페이지는 Bootstrap을 이용해 만들건데, BootsWatch의 Cosmo navbar가 마음에 들어 이것도 사용할 것이다. 먼저, Bootstrap 사이트에서 css, js, fonts를 다운 받고 BootsWatch사이트 Cosmo 페이지에서 custom.min.css을 다운 받은 후 springboot 프로젝트내의 resources/static위치에 폴더들은 넣는다. Bootstrap 사이트 : https://getbootstrap.com/docs/4.6/getting-started/introduction/Bootswatch 사이트 : https://bootswatch.com/

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에 넣어서 보낸다.