From 9d50750669b93604bed32c9375e94e07cda6eae8 Mon Sep 17 00:00:00 2001 From: letung999 Date: Thu, 2 Nov 2023 16:18:42 +0700 Subject: [PATCH 01/28] implement authentication and fix error security --- pom.xml | 39 ++++++++++++- .../config/security/CustomUserDetails.java | 12 ++-- .../security/JwtAuthenticationFilter.java | 23 +++++--- .../ppn/config/security/JwtTokenProvider.java | 18 +++--- .../ppn/config/security/SecurityConfig.java | 2 +- .../ppn/ppn/controller/AuthController.java | 57 +++++++++++++++++++ src/main/java/com/ppn/ppn/entities/Car.java | 2 + .../java/com/ppn/ppn/entities/Payment.java | 2 + src/main/java/com/ppn/ppn/entities/Users.java | 4 ++ .../com/ppn/ppn/payload/LoginRequest.java | 15 +++++ .../com/ppn/ppn/payload/LoginResponse.java | 24 ++++++++ .../com/ppn/ppn/service/UsersServiceImpl.java | 16 ++++++ .../resources/application-prod.properties | 6 +- src/main/resources/application.properties | 4 +- src/main/resources/bootstrap.properties | 2 +- 15 files changed, 193 insertions(+), 33 deletions(-) create mode 100644 src/main/java/com/ppn/ppn/controller/AuthController.java create mode 100644 src/main/java/com/ppn/ppn/payload/LoginRequest.java create mode 100644 src/main/java/com/ppn/ppn/payload/LoginResponse.java diff --git a/pom.xml b/pom.xml index 02a0387..7637209 100644 --- a/pom.xml +++ b/pom.xml @@ -21,6 +21,9 @@ 2.2.6.RELEASE 6.0.2 0.11.2 + 0.11.5 + 0.11.5 + 0.9.1 @@ -102,10 +105,42 @@ ${spring-security-config.version} + + + + + + + + + + + + + + + + + + + + + io.jsonwebtoken - jjwt-api - ${jjwt-api.version} + jjwt + ${jjwt.version} + + + + com.sun.xml.bind + jaxb-core + 2.3.0.1 + + + javax.xml.bind + jaxb-api + 2.3.1 diff --git a/src/main/java/com/ppn/ppn/config/security/CustomUserDetails.java b/src/main/java/com/ppn/ppn/config/security/CustomUserDetails.java index ea9ca49..21f4501 100644 --- a/src/main/java/com/ppn/ppn/config/security/CustomUserDetails.java +++ b/src/main/java/com/ppn/ppn/config/security/CustomUserDetails.java @@ -32,31 +32,31 @@ public Collection getAuthorities() { @Override public String getPassword() { - return null; + return users.getPassword(); } @Override public String getUsername() { - return null; + return users.getEmail(); } @Override public boolean isAccountNonExpired() { - return false; + return true; } @Override public boolean isAccountNonLocked() { - return false; + return true; } @Override public boolean isCredentialsNonExpired() { - return false; + return true; } @Override public boolean isEnabled() { - return false; + return true; } } diff --git a/src/main/java/com/ppn/ppn/config/security/JwtAuthenticationFilter.java b/src/main/java/com/ppn/ppn/config/security/JwtAuthenticationFilter.java index c960f55..c29b2b2 100644 --- a/src/main/java/com/ppn/ppn/config/security/JwtAuthenticationFilter.java +++ b/src/main/java/com/ppn/ppn/config/security/JwtAuthenticationFilter.java @@ -28,18 +28,23 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - if (!request.getRequestURI().contains("/login") && !request.getRequestURI().contains("/getByEmail")) { - String jwt = getJwtFromRequest(request); - if (StringUtils.hasText(jwt) && jwtTokenProvider.validateToken(jwt)) { - String userName = jwtTokenProvider.getUserNameFromJWT(jwt); - UserDetails userDetails = customUserDetailsService.loadUserByUsername(userName); - if (userDetails != null) { - UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); - authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - SecurityContextHolder.getContext().setAuthentication(authentication); + try { + if (!request.getRequestURI().contains("/login") && !request.getRequestURI().contains("/getByEmail")) { + String jwt = getJwtFromRequest(request); + if (StringUtils.hasText(jwt) && jwtTokenProvider.validateToken(jwt)) { + String userName = jwtTokenProvider.getUserNameFromJWT(jwt); + UserDetails userDetails = customUserDetailsService.loadUserByUsername(userName); + if (userDetails != null) { + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authentication); + } } } + } catch (Exception ex){ + log.error("failed on set user authentication", ex); } + filterChain.doFilter(request, response); } private String getJwtFromRequest(HttpServletRequest request) { diff --git a/src/main/java/com/ppn/ppn/config/security/JwtTokenProvider.java b/src/main/java/com/ppn/ppn/config/security/JwtTokenProvider.java index 6735071..098a922 100644 --- a/src/main/java/com/ppn/ppn/config/security/JwtTokenProvider.java +++ b/src/main/java/com/ppn/ppn/config/security/JwtTokenProvider.java @@ -17,15 +17,15 @@ public class JwtTokenProvider { private String JWT_SECRET; @Value("${ppn.app.jwt.expiration}") - private String JWT_EXPIRATION; + private Long JWT_EXPIRATION; - public String generateToken(CustomUserDetails userDetails) { - Date currentDate = new Date(); - Date expiryDate = new Date(currentDate.getTime() + JWT_EXPIRATION); + public String generateToken(CustomUserDetails customUserDetails) { + Date now = new Date(); + Date expiryDate = new Date(now.getTime() + JWT_EXPIRATION); return Jwts.builder() - .setSubject(userDetails.getUsers().getEmail()) - .setIssuedAt(currentDate) + .setSubject(customUserDetails.getUsers().getEmail()) + .setIssuedAt(now) .setExpiration(expiryDate) .signWith(SignatureAlgorithm.HS512, JWT_SECRET) .compact(); @@ -33,9 +33,8 @@ public String generateToken(CustomUserDetails userDetails) { public String getUserNameFromJWT(String token) { - Claims claims = Jwts.parserBuilder() + Claims claims = Jwts.parser() .setSigningKey(JWT_SECRET) - .build() .parseClaimsJws(token) .getBody(); return claims.getSubject(); @@ -43,9 +42,8 @@ public String getUserNameFromJWT(String token) { public boolean validateToken(String authToken) { try { - Jwts.parserBuilder() + Jwts.parser() .setSigningKey(JWT_SECRET) - .build() .parseClaimsJws(authToken); return true; } catch (MalformedJwtException ex) { diff --git a/src/main/java/com/ppn/ppn/config/security/SecurityConfig.java b/src/main/java/com/ppn/ppn/config/security/SecurityConfig.java index ea42ac8..7940803 100644 --- a/src/main/java/com/ppn/ppn/config/security/SecurityConfig.java +++ b/src/main/java/com/ppn/ppn/config/security/SecurityConfig.java @@ -43,7 +43,7 @@ SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.csrf(csrf -> csrf.disable()) .authorizeHttpRequests((authorize) -> - authorize.requestMatchers(HttpMethod.GET, "/api/**").permitAll() + authorize.requestMatchers("/api/**").permitAll() .requestMatchers("/api/auth/**").permitAll() .requestMatchers("/swagger-ui/**").permitAll() .requestMatchers("/v3/api-docs/**").permitAll() diff --git a/src/main/java/com/ppn/ppn/controller/AuthController.java b/src/main/java/com/ppn/ppn/controller/AuthController.java new file mode 100644 index 0000000..38ce61c --- /dev/null +++ b/src/main/java/com/ppn/ppn/controller/AuthController.java @@ -0,0 +1,57 @@ +package com.ppn.ppn.controller; + +import com.ppn.ppn.config.security.CustomUserDetails; +import com.ppn.ppn.config.security.JwtTokenProvider; +import com.ppn.ppn.entities.Users; +import com.ppn.ppn.payload.LoginRequest; +import com.ppn.ppn.payload.LoginResponse; +import com.ppn.ppn.service.UsersServiceImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/api/v1/auth") +public class AuthController { + @Autowired + private UsersServiceImpl usersService; + @Autowired + private JwtTokenProvider jwtTokenProvider; + @Autowired + private AuthenticationManager authenticationManager; + + @PostMapping({"/login"}) + public ResponseEntity login(@RequestBody LoginRequest request) { + Users user = new Users(); + user.setEmail(request.getEmail()); + user.setPassword(request.getPassword()); + Users userCheckLogin = usersService.checkLogin(user); + try { + if (userCheckLogin != null) { + Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(request.getEmail(), request.getPassword())); + String jwt = jwtTokenProvider.generateToken((CustomUserDetails) authentication.getPrincipal()); + Map response = new HashMap<>(); + response.put("accessToken", jwt); + response.put("user", userCheckLogin); + + LoginResponse> resultData = new LoginResponse<>(HttpStatus.OK, response); + return ResponseEntity.ok(resultData); + + } else { + return ResponseEntity.ok(new LoginResponse(HttpStatus.BAD_REQUEST, "ERR_MSG_UNAUTHENTICATED_ACCESS")); + } + } catch (Exception ex) { + return ResponseEntity.ok(new LoginResponse<>(HttpStatus.INTERNAL_SERVER_ERROR, "ERR_MSG_SOME_THING_WENT_WRONG")); + } + } +} diff --git a/src/main/java/com/ppn/ppn/entities/Car.java b/src/main/java/com/ppn/ppn/entities/Car.java index 40bd47d..47cb99d 100644 --- a/src/main/java/com/ppn/ppn/entities/Car.java +++ b/src/main/java/com/ppn/ppn/entities/Car.java @@ -1,5 +1,6 @@ package com.ppn.ppn.entities; +import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; @@ -22,6 +23,7 @@ public class Car extends BaseEntity { @Column(name = "model") private String model; + @JsonIgnore @ManyToOne() @JoinColumn(name = "userId") private Users users; diff --git a/src/main/java/com/ppn/ppn/entities/Payment.java b/src/main/java/com/ppn/ppn/entities/Payment.java index 3981d79..81d3565 100644 --- a/src/main/java/com/ppn/ppn/entities/Payment.java +++ b/src/main/java/com/ppn/ppn/entities/Payment.java @@ -1,5 +1,6 @@ package com.ppn.ppn.entities; +import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; @@ -29,6 +30,7 @@ public class Payment extends BaseEntity { @Column(name = "paymentMethod") private String paymentMethod; + @JsonIgnore @ManyToOne @JoinColumn(name = "userId") private Users users; diff --git a/src/main/java/com/ppn/ppn/entities/Users.java b/src/main/java/com/ppn/ppn/entities/Users.java index 2247cdd..588e310 100644 --- a/src/main/java/com/ppn/ppn/entities/Users.java +++ b/src/main/java/com/ppn/ppn/entities/Users.java @@ -1,5 +1,6 @@ package com.ppn.ppn.entities; +import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; @@ -35,12 +36,15 @@ public class Users extends BaseEntity { @Column(name = "status") private String status; + @JsonIgnore @OneToMany(mappedBy = "users") private List payments; + @JsonIgnore @OneToMany(mappedBy = "users") private List cars; + @JsonIgnore @ManyToMany @JoinTable( joinColumns = @JoinColumn(name = "userId", referencedColumnName = "userId"), diff --git a/src/main/java/com/ppn/ppn/payload/LoginRequest.java b/src/main/java/com/ppn/ppn/payload/LoginRequest.java new file mode 100644 index 0000000..971c80c --- /dev/null +++ b/src/main/java/com/ppn/ppn/payload/LoginRequest.java @@ -0,0 +1,15 @@ +package com.ppn.ppn.payload; + +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class LoginRequest { + @NotBlank(message = "email is required") + private String email; + + @NotBlank(message = "password is required") + private String password; +} diff --git a/src/main/java/com/ppn/ppn/payload/LoginResponse.java b/src/main/java/com/ppn/ppn/payload/LoginResponse.java new file mode 100644 index 0000000..1228fb9 --- /dev/null +++ b/src/main/java/com/ppn/ppn/payload/LoginResponse.java @@ -0,0 +1,24 @@ +package com.ppn.ppn.payload; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.http.HttpStatus; + +@Getter +@Setter +public class LoginResponse { + private HttpStatus status; + private T data; + + private String message; + + public LoginResponse(HttpStatus status, T data) { + this.status = status; + this.data = data; + } + + public LoginResponse(HttpStatus status, String message) { + this.status = status; + this.message = message; + } +} diff --git a/src/main/java/com/ppn/ppn/service/UsersServiceImpl.java b/src/main/java/com/ppn/ppn/service/UsersServiceImpl.java index 927f7ce..3f63ec8 100644 --- a/src/main/java/com/ppn/ppn/service/UsersServiceImpl.java +++ b/src/main/java/com/ppn/ppn/service/UsersServiceImpl.java @@ -5,12 +5,16 @@ import com.ppn.ppn.mapper.UsersMapper; import com.ppn.ppn.repository.UsersRepository; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @Service public class UsersServiceImpl implements IUsersService { @Autowired private UsersRepository userRepository; + + @Autowired + private PasswordEncoder passwordEncoder; private UsersMapper usersMapper = UsersMapper.INSTANCE; @Override @@ -19,4 +23,16 @@ public UsersDto createUsers(UsersDto usersDto) { Users dataSaved = userRepository.save(users); return usersMapper.usersToUsersDto(dataSaved); } + + public Users checkLogin(Users user) { + Users userCheck = null; + if (user.getEmail() != null) { + userCheck = userRepository.findByEmail(user.getEmail()).get(); + } + if (userCheck != null && passwordEncoder.matches(user.getPassword(), userCheck.getPassword())) { + return userCheck; + } + return null; + } + } diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties index 89ffb2b..0398818 100644 --- a/src/main/resources/application-prod.properties +++ b/src/main/resources/application-prod.properties @@ -7,7 +7,7 @@ spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=false spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL55Dialect spring.jpa.properties.hibernate.globally_quoted_identifiers=true -server.port=5000 +server.port=8080 #security -ppn.app.jwt.secret=${jwt-secret} -ppn.app.jwt.expiration=${jwt-expiration} \ No newline at end of file +#ppn.app.jwt.secret=${jwt-secret} +#ppn.app.jwt.expiration=${jwt-expiration} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 96720ae..7295881 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -12,4 +12,6 @@ spring.jpa.properties.hibernate.globally_quoted_identifiers=true aws.ppn.accessKey=${accessKey} aws.ppn.secretKey=${secretKey} #active environment -spring.profiles.active=prod \ No newline at end of file +#spring.profiles.active=prod +ppn.app.jwt.secret=${jwt-secret} +ppn.app.jwt.expiration=${jwt-expiration} \ No newline at end of file diff --git a/src/main/resources/bootstrap.properties b/src/main/resources/bootstrap.properties index a5c58e5..5763f0a 100644 --- a/src/main/resources/bootstrap.properties +++ b/src/main/resources/bootstrap.properties @@ -3,4 +3,4 @@ aws.paramstore.default-context=dev aws.paramstore.profile-separator= aws.paramstore.enabled=true #active environment -spring.profiles.active=prod \ No newline at end of file +#spring.profiles.active=prod \ No newline at end of file From b9249d9a7747b5c0ceb04abe6e1a0744818fc806 Mon Sep 17 00:00:00 2001 From: letung999 Date: Thu, 2 Nov 2023 16:30:30 +0700 Subject: [PATCH 02/28] refactor name version of dependencies --- pom.xml | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/pom.xml b/pom.xml index 7637209..ef2f857 100644 --- a/pom.xml +++ b/pom.xml @@ -20,10 +20,9 @@ 1.5.3.Final 2.2.6.RELEASE 6.0.2 - 0.11.2 - 0.11.5 - 0.11.5 0.9.1 + 2.3.0.1 + 2.3.1 @@ -105,27 +104,6 @@ ${spring-security-config.version} - - - - - - - - - - - - - - - - - - - - - io.jsonwebtoken jjwt @@ -135,12 +113,12 @@ com.sun.xml.bind jaxb-core - 2.3.0.1 + ${jaxb-core.version} javax.xml.bind jaxb-api - 2.3.1 + ${jaxb-api.version} From ab1a5e131ada41dc5f78177ef6a32175ae2f927d Mon Sep 17 00:00:00 2001 From: letung999 Date: Thu, 2 Nov 2023 20:54:13 +0700 Subject: [PATCH 03/28] active prod environment --- src/main/resources/application-prod.properties | 4 ++-- src/main/resources/application.properties | 4 +--- src/main/resources/bootstrap.properties | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties index 0398818..46af98a 100644 --- a/src/main/resources/application-prod.properties +++ b/src/main/resources/application-prod.properties @@ -9,5 +9,5 @@ spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL55Dialect spring.jpa.properties.hibernate.globally_quoted_identifiers=true server.port=8080 #security -#ppn.app.jwt.secret=${jwt-secret} -#ppn.app.jwt.expiration=${jwt-expiration} \ No newline at end of file +ppn.app.jwt.secret=${jwt-secret} +ppn.app.jwt.expiration=${jwt-expiration} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 7295881..96720ae 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -12,6 +12,4 @@ spring.jpa.properties.hibernate.globally_quoted_identifiers=true aws.ppn.accessKey=${accessKey} aws.ppn.secretKey=${secretKey} #active environment -#spring.profiles.active=prod -ppn.app.jwt.secret=${jwt-secret} -ppn.app.jwt.expiration=${jwt-expiration} \ No newline at end of file +spring.profiles.active=prod \ No newline at end of file diff --git a/src/main/resources/bootstrap.properties b/src/main/resources/bootstrap.properties index 5763f0a..a5c58e5 100644 --- a/src/main/resources/bootstrap.properties +++ b/src/main/resources/bootstrap.properties @@ -3,4 +3,4 @@ aws.paramstore.default-context=dev aws.paramstore.profile-separator= aws.paramstore.enabled=true #active environment -#spring.profiles.active=prod \ No newline at end of file +spring.profiles.active=prod \ No newline at end of file From 075bc75e51ad9c50005cd92352d678e4e9951538 Mon Sep 17 00:00:00 2001 From: letung999 Date: Thu, 2 Nov 2023 20:59:20 +0700 Subject: [PATCH 04/28] modify relationship entities to get data --- src/main/java/com/ppn/ppn/entities/Role.java | 2 ++ src/main/java/com/ppn/ppn/entities/Users.java | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/ppn/ppn/entities/Role.java b/src/main/java/com/ppn/ppn/entities/Role.java index 95f7006..7acb067 100644 --- a/src/main/java/com/ppn/ppn/entities/Role.java +++ b/src/main/java/com/ppn/ppn/entities/Role.java @@ -1,5 +1,6 @@ package com.ppn.ppn.entities; +import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; @@ -19,6 +20,7 @@ public class Role extends BaseEntity { @Column(name = "roleName") private String roleName; + @JsonIgnore @ManyToMany(mappedBy = "roles") private Set users; } diff --git a/src/main/java/com/ppn/ppn/entities/Users.java b/src/main/java/com/ppn/ppn/entities/Users.java index 588e310..a6ca741 100644 --- a/src/main/java/com/ppn/ppn/entities/Users.java +++ b/src/main/java/com/ppn/ppn/entities/Users.java @@ -44,7 +44,6 @@ public class Users extends BaseEntity { @OneToMany(mappedBy = "users") private List cars; - @JsonIgnore @ManyToMany @JoinTable( joinColumns = @JoinColumn(name = "userId", referencedColumnName = "userId"), From a920b6227cac895ffa6ee0df345261eebfac91a8 Mon Sep 17 00:00:00 2001 From: letung999 Date: Thu, 2 Nov 2023 21:01:52 +0700 Subject: [PATCH 05/28] add message status to response --- .../java/com/ppn/ppn/constant/MessageStatus.java | 12 ++++++++++++ src/main/java/com/ppn/ppn/payload/LoginResponse.java | 6 ++++++ 2 files changed, 18 insertions(+) create mode 100644 src/main/java/com/ppn/ppn/constant/MessageStatus.java diff --git a/src/main/java/com/ppn/ppn/constant/MessageStatus.java b/src/main/java/com/ppn/ppn/constant/MessageStatus.java new file mode 100644 index 0000000..47c0d61 --- /dev/null +++ b/src/main/java/com/ppn/ppn/constant/MessageStatus.java @@ -0,0 +1,12 @@ +package com.ppn.ppn.constant; + +public class MessageStatus { + public static final String ERR_MSG_SOME_THING_WENT_WRONG = "Some thing went wrong"; + public static final String ERR_MSG_INVALID_DATA = "Value {0} is incorrect"; + public static final String ERR_MSG_UNAUTHORIZED_ACCESS = "unauthorized access"; + public static final String ERR_MSG_UNAUTHENTICATED_ACCESS = "wrong username or password"; + public static final String INF_MSG_SUCCESSFULLY = "successfully"; + public static final String ERR_MSG_DATA_DUPLICATED = "data is exist"; + public static final String ERR_MSG_DATA_NOT_FOUND = "data is not found"; + public static final String ERR_MSG_CAR_ASSIGN_USER = "the car {0} is assigning for a user {1} active, cannot be deleted"; +} diff --git a/src/main/java/com/ppn/ppn/payload/LoginResponse.java b/src/main/java/com/ppn/ppn/payload/LoginResponse.java index 1228fb9..5314279 100644 --- a/src/main/java/com/ppn/ppn/payload/LoginResponse.java +++ b/src/main/java/com/ppn/ppn/payload/LoginResponse.java @@ -21,4 +21,10 @@ public LoginResponse(HttpStatus status, String message) { this.status = status; this.message = message; } + + public LoginResponse(HttpStatus status, T data, String message) { + this.status = status; + this.data = data; + this.message = message; + } } From 32a48f778c566861758f12e3de62e5fa185febf0 Mon Sep 17 00:00:00 2001 From: letung999 Date: Thu, 2 Nov 2023 21:02:57 +0700 Subject: [PATCH 06/28] turn on jpa auditing --- src/main/java/com/ppn/ppn/PpnApplication.java | 4 +++- src/main/java/com/ppn/ppn/controller/AuthController.java | 7 +++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/ppn/ppn/PpnApplication.java b/src/main/java/com/ppn/ppn/PpnApplication.java index 1bff3b4..14a7669 100644 --- a/src/main/java/com/ppn/ppn/PpnApplication.java +++ b/src/main/java/com/ppn/ppn/PpnApplication.java @@ -2,10 +2,12 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + +@EnableJpaAuditing @SpringBootApplication public class PpnApplication { - public static void main(String[] args) { SpringApplication.run(PpnApplication.class, args); } diff --git a/src/main/java/com/ppn/ppn/controller/AuthController.java b/src/main/java/com/ppn/ppn/controller/AuthController.java index 38ce61c..d7fb7fd 100644 --- a/src/main/java/com/ppn/ppn/controller/AuthController.java +++ b/src/main/java/com/ppn/ppn/controller/AuthController.java @@ -20,6 +20,9 @@ import java.util.HashMap; import java.util.Map; +import static com.ppn.ppn.constant.MessageStatus.ERR_MSG_SOME_THING_WENT_WRONG; +import static com.ppn.ppn.constant.MessageStatus.ERR_MSG_UNAUTHENTICATED_ACCESS; + @RestController @RequestMapping("/api/v1/auth") public class AuthController { @@ -48,10 +51,10 @@ public ResponseEntity login(@RequestBody LoginRequest request) { return ResponseEntity.ok(resultData); } else { - return ResponseEntity.ok(new LoginResponse(HttpStatus.BAD_REQUEST, "ERR_MSG_UNAUTHENTICATED_ACCESS")); + return ResponseEntity.ok(new LoginResponse(HttpStatus.BAD_REQUEST, ERR_MSG_UNAUTHENTICATED_ACCESS)); } } catch (Exception ex) { - return ResponseEntity.ok(new LoginResponse<>(HttpStatus.INTERNAL_SERVER_ERROR, "ERR_MSG_SOME_THING_WENT_WRONG")); + return ResponseEntity.ok(new LoginResponse<>(HttpStatus.INTERNAL_SERVER_ERROR, ERR_MSG_SOME_THING_WENT_WRONG)); } } } From 7be8a5b24d2f2249b166c25eb60d5a835e647b15 Mon Sep 17 00:00:00 2001 From: letung999 Date: Thu, 2 Nov 2023 22:23:05 +0700 Subject: [PATCH 07/28] add correlationId --- .../security/JwtAuthenticationFilter.java | 28 ++++++++++++------- .../resources/application-prod.properties | 3 +- src/main/resources/application.properties | 3 +- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/ppn/ppn/config/security/JwtAuthenticationFilter.java b/src/main/java/com/ppn/ppn/config/security/JwtAuthenticationFilter.java index c29b2b2..886e049 100644 --- a/src/main/java/com/ppn/ppn/config/security/JwtAuthenticationFilter.java +++ b/src/main/java/com/ppn/ppn/config/security/JwtAuthenticationFilter.java @@ -16,6 +16,7 @@ import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; +import java.util.UUID; @Slf4j @Component @@ -29,19 +30,26 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { - if (!request.getRequestURI().contains("/login") && !request.getRequestURI().contains("/getByEmail")) { - String jwt = getJwtFromRequest(request); - if (StringUtils.hasText(jwt) && jwtTokenProvider.validateToken(jwt)) { - String userName = jwtTokenProvider.getUserNameFromJWT(jwt); - UserDetails userDetails = customUserDetailsService.loadUserByUsername(userName); - if (userDetails != null) { - UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); - authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - SecurityContextHolder.getContext().setAuthentication(authentication); + String correlationId = getCorrelationId(request); + log.info("start REST with request {} with correlationId {}", request.getRequestURI(), correlationId); + if (correlationId != null) { + if (!request.getRequestURI().contains("/login")) { + String jwt = getJwtFromRequest(request); + if (StringUtils.hasText(jwt) && jwtTokenProvider.validateToken(jwt)) { + String userName = jwtTokenProvider.getUserNameFromJWT(jwt); + UserDetails userDetails = customUserDetailsService.loadUserByUsername(userName); + if (userDetails != null) { + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authentication); + } } } + } else { + correlationId = UUID.randomUUID().toString(); + log.info("No correlationId found in header. Generated: {}", correlationId); } - } catch (Exception ex){ + } catch (Exception ex) { log.error("failed on set user authentication", ex); } filterChain.doFilter(request, response); diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties index 46af98a..0d79cf1 100644 --- a/src/main/resources/application-prod.properties +++ b/src/main/resources/application-prod.properties @@ -10,4 +10,5 @@ spring.jpa.properties.hibernate.globally_quoted_identifiers=true server.port=8080 #security ppn.app.jwt.secret=${jwt-secret} -ppn.app.jwt.expiration=${jwt-expiration} \ No newline at end of file +ppn.app.jwt.expiration=${jwt-expiration} +#send log to cloud watch \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 96720ae..a730858 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -12,4 +12,5 @@ spring.jpa.properties.hibernate.globally_quoted_identifiers=true aws.ppn.accessKey=${accessKey} aws.ppn.secretKey=${secretKey} #active environment -spring.profiles.active=prod \ No newline at end of file +spring.profiles.active=prod +#send log to cloud watch \ No newline at end of file From 7c3f3ff4c4e9e47374305b47487a9b2b9a86ca42 Mon Sep 17 00:00:00 2001 From: letung999 Date: Sat, 4 Nov 2023 23:02:32 +0700 Subject: [PATCH 08/28] handle verify email --- pom.xml | 14 ++ .../template/ThymeleafTemplateConfig.java | 29 +++ .../com/ppn/ppn/constant/ApprovalStatus.java | 7 + .../com/ppn/ppn/constant/HostConstant.java | 5 + .../ppn/ppn/controller/UserController.java | 65 ++++- src/main/java/com/ppn/ppn/dto/UsersDto.java | 6 +- src/main/java/com/ppn/ppn/entities/Users.java | 2 + .../com/ppn/ppn/payload/HtmlTemplate.java | 20 ++ .../ppn/ppn/payload/VerifyMailRequest.java | 19 ++ .../ppn/ppn/repository/UsersRepository.java | 2 + .../ppn/service/EmailSenderServiceImpl.java | 47 ++++ .../com/ppn/ppn/service/UsersServiceImpl.java | 35 +++ .../constract/IEmailSenderService.java | 8 + .../{ => constract}/IUsersService.java | 3 +- .../resources/application-prod.properties | 12 +- src/main/resources/application.properties | 2 +- src/main/resources/templates/VerifyEmail.html | 231 ++++++++++++++++++ 17 files changed, 498 insertions(+), 9 deletions(-) create mode 100644 src/main/java/com/ppn/ppn/config/template/ThymeleafTemplateConfig.java create mode 100644 src/main/java/com/ppn/ppn/constant/ApprovalStatus.java create mode 100644 src/main/java/com/ppn/ppn/constant/HostConstant.java create mode 100644 src/main/java/com/ppn/ppn/payload/HtmlTemplate.java create mode 100644 src/main/java/com/ppn/ppn/payload/VerifyMailRequest.java create mode 100644 src/main/java/com/ppn/ppn/service/EmailSenderServiceImpl.java create mode 100644 src/main/java/com/ppn/ppn/service/constract/IEmailSenderService.java rename src/main/java/com/ppn/ppn/service/{ => constract}/IUsersService.java (64%) create mode 100644 src/main/resources/templates/VerifyEmail.html diff --git a/pom.xml b/pom.xml index ef2f857..0292633 100644 --- a/pom.xml +++ b/pom.xml @@ -23,6 +23,7 @@ 0.9.1 2.3.0.1 2.3.1 + 3.0.11.RELEASE @@ -120,6 +121,19 @@ jaxb-api ${jaxb-api.version} + + + + org.springframework.boot + spring-boot-starter-mail + + + + + org.thymeleaf + thymeleaf-spring5 + ${thymeleaf-spring5.version} + diff --git a/src/main/java/com/ppn/ppn/config/template/ThymeleafTemplateConfig.java b/src/main/java/com/ppn/ppn/config/template/ThymeleafTemplateConfig.java new file mode 100644 index 0000000..e02c7e5 --- /dev/null +++ b/src/main/java/com/ppn/ppn/config/template/ThymeleafTemplateConfig.java @@ -0,0 +1,29 @@ +package com.ppn.ppn.config.template; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.thymeleaf.spring5.SpringTemplateEngine; +import org.thymeleaf.templatemode.TemplateMode; +import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver; + +import java.nio.charset.StandardCharsets; + +@Configuration +public class ThymeleafTemplateConfig { + @Bean + public SpringTemplateEngine springTemplateEngine() { + SpringTemplateEngine templateEngine = new SpringTemplateEngine(); + templateEngine.addTemplateResolver(htmlTemplateResolver()); + return templateEngine; + } + + @Bean + public ClassLoaderTemplateResolver htmlTemplateResolver(){ + ClassLoaderTemplateResolver emailTemplateResolver = new ClassLoaderTemplateResolver(); + emailTemplateResolver.setPrefix("/templates/"); + emailTemplateResolver.setSuffix(".html"); + emailTemplateResolver.setTemplateMode(TemplateMode.HTML); + emailTemplateResolver.setCharacterEncoding(StandardCharsets.UTF_8.name()); + return emailTemplateResolver; + } +} diff --git a/src/main/java/com/ppn/ppn/constant/ApprovalStatus.java b/src/main/java/com/ppn/ppn/constant/ApprovalStatus.java new file mode 100644 index 0000000..d314a07 --- /dev/null +++ b/src/main/java/com/ppn/ppn/constant/ApprovalStatus.java @@ -0,0 +1,7 @@ +package com.ppn.ppn.constant; + +public enum ApprovalStatus { + ACTIVE, + INACTIVE, + PENDING, +} diff --git a/src/main/java/com/ppn/ppn/constant/HostConstant.java b/src/main/java/com/ppn/ppn/constant/HostConstant.java new file mode 100644 index 0000000..d1c0fd2 --- /dev/null +++ b/src/main/java/com/ppn/ppn/constant/HostConstant.java @@ -0,0 +1,5 @@ +package com.ppn.ppn.constant; + +public class HostConstant { + public static final String HOST_URL_VERIFY_CODE = "http://localhost:8080/api/v1/users?verifyCode="; +} diff --git a/src/main/java/com/ppn/ppn/controller/UserController.java b/src/main/java/com/ppn/ppn/controller/UserController.java index 0fa8ec9..a550ca7 100644 --- a/src/main/java/com/ppn/ppn/controller/UserController.java +++ b/src/main/java/com/ppn/ppn/controller/UserController.java @@ -1,24 +1,79 @@ package com.ppn.ppn.controller; import com.ppn.ppn.dto.UsersDto; +import com.ppn.ppn.payload.HtmlTemplate; +import com.ppn.ppn.payload.VerifyMailRequest; +import com.ppn.ppn.service.EmailSenderServiceImpl; import com.ppn.ppn.service.UsersServiceImpl; +import jakarta.mail.MessagingException; +import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +import static com.ppn.ppn.constant.HostConstant.HOST_URL_VERIFY_CODE; @RequestMapping("api/v1/users") @RestController +@Slf4j public class UserController { @Autowired private UsersServiceImpl usersService; + @Autowired + private EmailSenderServiceImpl emailSenderService; + + @Autowired + private HttpServletRequest httpServletRequest; + + @Value("${cox.automation.email}") + private String coxAutomationEmail; + + @Value("${cox.name}") + private String nameCompany; + @PostMapping("/create") - public ResponseEntity add(@RequestBody @Valid UsersDto usersDto){ + public ResponseEntity add(@RequestBody @Valid UsersDto usersDto) { UsersDto result = usersService.createUsers(usersDto); + //send mail for user: + Map properties = new HashMap<>(); + properties.put("name", usersDto.getEmail()); + properties.put("location", "Viet Nam"); + properties.put("sign", nameCompany); + properties.put("linkConfirm", HOST_URL_VERIFY_CODE + result.getVerifyCode()); + + VerifyMailRequest mail = VerifyMailRequest.builder() + .from(coxAutomationEmail) + .to("letung012000@gmail.com")// email from request body + .htmlTemplate(new HtmlTemplate("VerifyEmail", properties)) + .subject("This is email confirm from COX-AUTOMATION") + .build(); + try { + log.info("send email to user {}", usersDto.getEmail()); + emailSenderService.sendMail(mail); + } catch (MessagingException e) { + log.error("send email fail! with email {}", usersDto.getEmail()); + throw new RuntimeException(e); + } + return ResponseEntity.ok(result); } + + @GetMapping("/verify-email") + public ResponseEntity verifyEmail(@RequestParam(name = "verifyCode") String verifyCode) throws MessagingException { + log.info("verify code: {}", verifyCode); + try{ + usersService.verifyUser(verifyCode); + }catch (Exception ex){ + log.info("verify fail with code: {}", verifyCode); + ex.printStackTrace(); + } + return ResponseEntity.ok("success"); + } } diff --git a/src/main/java/com/ppn/ppn/dto/UsersDto.java b/src/main/java/com/ppn/ppn/dto/UsersDto.java index 0b03bd8..3c61ea5 100644 --- a/src/main/java/com/ppn/ppn/dto/UsersDto.java +++ b/src/main/java/com/ppn/ppn/dto/UsersDto.java @@ -18,6 +18,9 @@ public class UsersDto extends BaseEntityDto { @NotBlank(message = "email is required") private String email; + @NotBlank(message = "password is required") + private String password; + @NotBlank(message = "firstName is required") private String firstName; @@ -27,9 +30,10 @@ public class UsersDto extends BaseEntityDto { @NotBlank(message = "phoneNumber is required") private String phoneNumber; - @NotBlank(message = "status is required") private String status; + private String verifyCode; + private List payments; private List cars; diff --git a/src/main/java/com/ppn/ppn/entities/Users.java b/src/main/java/com/ppn/ppn/entities/Users.java index a6ca741..927acc9 100644 --- a/src/main/java/com/ppn/ppn/entities/Users.java +++ b/src/main/java/com/ppn/ppn/entities/Users.java @@ -44,6 +44,8 @@ public class Users extends BaseEntity { @OneToMany(mappedBy = "users") private List cars; + @Column(name = "verifyCode") + private String verifyCode; @ManyToMany @JoinTable( joinColumns = @JoinColumn(name = "userId", referencedColumnName = "userId"), diff --git a/src/main/java/com/ppn/ppn/payload/HtmlTemplate.java b/src/main/java/com/ppn/ppn/payload/HtmlTemplate.java new file mode 100644 index 0000000..20341b9 --- /dev/null +++ b/src/main/java/com/ppn/ppn/payload/HtmlTemplate.java @@ -0,0 +1,20 @@ +package com.ppn.ppn.payload; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.Map; + +@Getter +@Setter +public class HtmlTemplate { + private String template; + private Map props; + + public HtmlTemplate(String template, Map props) { + this.template = template; + this.props = props; + } +} diff --git a/src/main/java/com/ppn/ppn/payload/VerifyMailRequest.java b/src/main/java/com/ppn/ppn/payload/VerifyMailRequest.java new file mode 100644 index 0000000..2a40ead --- /dev/null +++ b/src/main/java/com/ppn/ppn/payload/VerifyMailRequest.java @@ -0,0 +1,19 @@ +package com.ppn.ppn.payload; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Builder +public class VerifyMailRequest { + private HtmlTemplate htmlTemplate; + private String email; + private String firstName; + private String gender; + private String phoneNumber; + private String from; + private String to; + private String subject; +} diff --git a/src/main/java/com/ppn/ppn/repository/UsersRepository.java b/src/main/java/com/ppn/ppn/repository/UsersRepository.java index 600d008..4304981 100644 --- a/src/main/java/com/ppn/ppn/repository/UsersRepository.java +++ b/src/main/java/com/ppn/ppn/repository/UsersRepository.java @@ -9,4 +9,6 @@ @Repository public interface UsersRepository extends JpaRepository { Optional findByEmail(String email); + + Optional findByVerifyCode(String verifyCode); } diff --git a/src/main/java/com/ppn/ppn/service/EmailSenderServiceImpl.java b/src/main/java/com/ppn/ppn/service/EmailSenderServiceImpl.java new file mode 100644 index 0000000..1116dd7 --- /dev/null +++ b/src/main/java/com/ppn/ppn/service/EmailSenderServiceImpl.java @@ -0,0 +1,47 @@ +package com.ppn.ppn.service; + +import com.ppn.ppn.payload.VerifyMailRequest; +import com.ppn.ppn.service.constract.IEmailSenderService; +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMessage; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Service; +import org.thymeleaf.context.Context; +import org.thymeleaf.spring5.SpringTemplateEngine; + +import java.nio.charset.StandardCharsets; + + +@Service +public class EmailSenderServiceImpl implements IEmailSenderService { + @Autowired + private JavaMailSender emailSender; + + @Autowired + private SpringTemplateEngine templateEngine; + + @Override + public void sendMail(VerifyMailRequest mail) throws MessagingException { + MimeMessage message = emailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(message, + MimeMessageHelper.MULTIPART_MODE_MIXED_RELATED, + StandardCharsets.UTF_8.name()); + + String html = getHtmlContent(mail); + + helper.setTo(mail.getTo()); + helper.setFrom(mail.getFrom()); + helper.setSubject(mail.getSubject()); + helper.setText(html, true); + + emailSender.send(message); + } + + private String getHtmlContent(VerifyMailRequest mail) { + Context context = new Context(); + context.setVariables(mail.getHtmlTemplate().getProps()); + return templateEngine.process(mail.getHtmlTemplate().getTemplate(), context); + } +} diff --git a/src/main/java/com/ppn/ppn/service/UsersServiceImpl.java b/src/main/java/com/ppn/ppn/service/UsersServiceImpl.java index 3f63ec8..d0eafbe 100644 --- a/src/main/java/com/ppn/ppn/service/UsersServiceImpl.java +++ b/src/main/java/com/ppn/ppn/service/UsersServiceImpl.java @@ -4,10 +4,18 @@ import com.ppn.ppn.entities.Users; import com.ppn.ppn.mapper.UsersMapper; import com.ppn.ppn.repository.UsersRepository; +import com.ppn.ppn.service.constract.IUsersService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import java.util.Objects; +import java.util.Optional; +import java.util.Random; + +import static com.ppn.ppn.constant.ApprovalStatus.ACTIVE; +import static com.ppn.ppn.constant.ApprovalStatus.PENDING; + @Service public class UsersServiceImpl implements IUsersService { @Autowired @@ -20,10 +28,23 @@ public class UsersServiceImpl implements IUsersService { @Override public UsersDto createUsers(UsersDto usersDto) { Users users = usersMapper.usersDtoUsers(usersDto); + users.setStatus(String.valueOf(PENDING)); + users.setVerifyCode(randomString(20)); Users dataSaved = userRepository.save(users); return usersMapper.usersToUsersDto(dataSaved); } + @Override + public boolean verifyUser(String verifyCode) { + Optional users = userRepository.findByVerifyCode(verifyCode); + if (Objects.isNull(users)) { + return false; + } + users.get().setStatus(String.valueOf(ACTIVE)); + userRepository.save(users.get()); + return true; + } + public Users checkLogin(Users user) { Users userCheck = null; if (user.getEmail() != null) { @@ -35,4 +56,18 @@ public Users checkLogin(Users user) { return null; } + //private methods + private String randomString(int length) { + String data = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + + "0123456789" + + "abcdefghijklmnopqrstuvxyz"; + Random random = new Random(); + StringBuilder resultData = new StringBuilder(); + for (int i = 0; i < length; ++i) { + int index = random.nextInt(length) + 0; + resultData.append(data.charAt(index)); + } + return resultData.toString(); + } + } diff --git a/src/main/java/com/ppn/ppn/service/constract/IEmailSenderService.java b/src/main/java/com/ppn/ppn/service/constract/IEmailSenderService.java new file mode 100644 index 0000000..3aeaeef --- /dev/null +++ b/src/main/java/com/ppn/ppn/service/constract/IEmailSenderService.java @@ -0,0 +1,8 @@ +package com.ppn.ppn.service.constract; + +import com.ppn.ppn.payload.VerifyMailRequest; +import jakarta.mail.MessagingException; + +public interface IEmailSenderService { + void sendMail(VerifyMailRequest verifyMailRequest) throws MessagingException; +} diff --git a/src/main/java/com/ppn/ppn/service/IUsersService.java b/src/main/java/com/ppn/ppn/service/constract/IUsersService.java similarity index 64% rename from src/main/java/com/ppn/ppn/service/IUsersService.java rename to src/main/java/com/ppn/ppn/service/constract/IUsersService.java index c580f34..873489d 100644 --- a/src/main/java/com/ppn/ppn/service/IUsersService.java +++ b/src/main/java/com/ppn/ppn/service/constract/IUsersService.java @@ -1,8 +1,9 @@ -package com.ppn.ppn.service; +package com.ppn.ppn.service.constract; import com.ppn.ppn.dto.UsersDto; import com.ppn.ppn.entities.Users; public interface IUsersService { UsersDto createUsers(UsersDto usersDto); + boolean verifyUser(String verifyCode); } diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties index 0d79cf1..9e35682 100644 --- a/src/main/resources/application-prod.properties +++ b/src/main/resources/application-prod.properties @@ -11,4 +11,14 @@ server.port=8080 #security ppn.app.jwt.secret=${jwt-secret} ppn.app.jwt.expiration=${jwt-expiration} -#send log to cloud watch \ No newline at end of file +#send log to cloud watch +#mail +spring.mail.host=smtp.gmail.com +spring.mail.port=587 +spring.mail.username=${user-email} +spring.mail.password=${password-email} +spring.mail.properties.mail.smtp.auth=true +spring.mail.properties.mail.smtp.starttls.enable=true +spring.mail.properties.mail.smtp.starttls.required=true +cox.automation.email=cox-automation@gmail.com +cox.name=MANHEIM-COX-AUTOMATION \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a730858..18a84f4 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -13,4 +13,4 @@ aws.ppn.accessKey=${accessKey} aws.ppn.secretKey=${secretKey} #active environment spring.profiles.active=prod -#send log to cloud watch \ No newline at end of file +#send log to cloud watch diff --git a/src/main/resources/templates/VerifyEmail.html b/src/main/resources/templates/VerifyEmail.html new file mode 100644 index 0000000..d1b5cca --- /dev/null +++ b/src/main/resources/templates/VerifyEmail.html @@ -0,0 +1,231 @@ + + + + + + + + + + + + +
+ We're thrilled to have you here! Get ready to dive into your new account. +
+ + + + + + + + + + + + + + + + + +
+ + + + +
+
+ + + + +
+

Welcome!

+ +
+
+ + + + + + + + + + + + + + + + + + + +
+

We're excited to have you get started. First, you need to confirm your + account. Just press the button below.

+
+ + + + +
+ + + + +
+ Confirm + Account +
+
+
+

If that doesn't work, copy and paste the following link in your + browser:

+
+

https://bit.li.utlddssdstueincx +

+
+

If you have any questions, just reply to this email—we're always + happy to help out.

+
+

+ Regards,
+ +

+

+ +
+
+ + + + +
+

Need more help?

+

We’re here to + help you out

+
+
+ + + + +
+
+

If these emails get annoying, please feel free to unsubscribe. +

+
+
+ + + \ No newline at end of file From 3e2b2c6b39a5457a3cc44f1a5bba2d5ced9b4d85 Mon Sep 17 00:00:00 2001 From: letung999 Date: Mon, 6 Nov 2023 11:12:09 +0700 Subject: [PATCH 09/28] override MethodArgumentNotValidException and handle exception --- .../ppn/exception/ErrorInvalidArgument.java | 19 ++++++++++ .../com/ppn/ppn/exception/ErrorParameter.java | 16 --------- .../ppn/ppn/exception/ExceptionResponse.java | 12 ------- .../ppn/exception/GlobalExceptionHandler.java | 36 ++++++++++++++++++- 4 files changed, 54 insertions(+), 29 deletions(-) create mode 100644 src/main/java/com/ppn/ppn/exception/ErrorInvalidArgument.java delete mode 100644 src/main/java/com/ppn/ppn/exception/ErrorParameter.java delete mode 100644 src/main/java/com/ppn/ppn/exception/ExceptionResponse.java diff --git a/src/main/java/com/ppn/ppn/exception/ErrorInvalidArgument.java b/src/main/java/com/ppn/ppn/exception/ErrorInvalidArgument.java new file mode 100644 index 0000000..512920d --- /dev/null +++ b/src/main/java/com/ppn/ppn/exception/ErrorInvalidArgument.java @@ -0,0 +1,19 @@ +package com.ppn.ppn.exception; + + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; +import java.util.Map; + +@Getter +@Setter +@Builder +public class ErrorInvalidArgument { + private LocalDateTime localDateTime; + private String path; + private String status; + private Map error; +} diff --git a/src/main/java/com/ppn/ppn/exception/ErrorParameter.java b/src/main/java/com/ppn/ppn/exception/ErrorParameter.java deleted file mode 100644 index a37b5ee..0000000 --- a/src/main/java/com/ppn/ppn/exception/ErrorParameter.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.ppn.ppn.exception; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -@Getter -@Setter -@NoArgsConstructor -@AllArgsConstructor -public class ErrorParameter { - private String code; - private String message; - private String properties; -} diff --git a/src/main/java/com/ppn/ppn/exception/ExceptionResponse.java b/src/main/java/com/ppn/ppn/exception/ExceptionResponse.java deleted file mode 100644 index cebd2c6..0000000 --- a/src/main/java/com/ppn/ppn/exception/ExceptionResponse.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.ppn.ppn.exception; - -import lombok.Getter; -import lombok.Setter; - -import java.util.List; - -@Getter -@Setter -public class ExceptionResponse { - List errors; -} diff --git a/src/main/java/com/ppn/ppn/exception/GlobalExceptionHandler.java b/src/main/java/com/ppn/ppn/exception/GlobalExceptionHandler.java index dbdb28a..382a861 100644 --- a/src/main/java/com/ppn/ppn/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/ppn/ppn/exception/GlobalExceptionHandler.java @@ -1,4 +1,38 @@ package com.ppn.ppn.exception; -public class GlobalExceptionHandler { +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; + +@ControllerAdvice +public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { + @Override + protected ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException ex, + HttpHeaders headers, + HttpStatusCode statusCode, + WebRequest webRequest) { + Map errors = new HashMap<>(); + ex.getBindingResult().getAllErrors().forEach(e -> { + String fieldName = ((FieldError) e).getField(); + String message = e.getDefaultMessage(); + errors.put(fieldName, message); + }); + ErrorInvalidArgument errorInvalidArgument = ErrorInvalidArgument.builder() + .path(webRequest.getDescription(false)) + .status(HttpStatusCode.valueOf(400).toString()) + .localDateTime(LocalDateTime.now()) + .error(errors) + .build(); + return new ResponseEntity<>(errorInvalidArgument, HttpStatus.BAD_REQUEST); + } } From 8d60e66d9450641dd31fc83d0e67dc2ae6ee203b Mon Sep 17 00:00:00 2001 From: letung999 Date: Mon, 6 Nov 2023 15:06:37 +0700 Subject: [PATCH 10/28] create role api --- .../com/ppn/ppn/constant/MessageStatus.java | 1 + .../com/ppn/ppn/constant/RoleConstant.java | 10 +++++ .../ppn/ppn/controller/RoleController.java | 25 +++++++++++ ...java => ErrorInvalidArgumentResponse.java} | 2 +- .../com/ppn/ppn/exception/ErrorResponse.java | 17 ++++++++ .../ppn/exception/GlobalExceptionHandler.java | 32 ++++++++++++++- .../exception/ResourceDuplicateException.java | 19 +++++++++ .../ppn/ppn/exception/RoleInputException.java | 22 ++++++++++ .../ppn/ppn/repository/RoleRepository.java | 3 ++ .../com/ppn/ppn/service/RoleServiceImpl.java | 41 +++++++++++++++++++ .../com/ppn/ppn/service/UsersServiceImpl.java | 10 +++++ .../ppn/service/constract/IRoleService.java | 7 ++++ 12 files changed, 186 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/ppn/ppn/constant/RoleConstant.java create mode 100644 src/main/java/com/ppn/ppn/controller/RoleController.java rename src/main/java/com/ppn/ppn/exception/{ErrorInvalidArgument.java => ErrorInvalidArgumentResponse.java} (87%) create mode 100644 src/main/java/com/ppn/ppn/exception/ErrorResponse.java create mode 100644 src/main/java/com/ppn/ppn/exception/ResourceDuplicateException.java create mode 100644 src/main/java/com/ppn/ppn/exception/RoleInputException.java create mode 100644 src/main/java/com/ppn/ppn/service/RoleServiceImpl.java create mode 100644 src/main/java/com/ppn/ppn/service/constract/IRoleService.java diff --git a/src/main/java/com/ppn/ppn/constant/MessageStatus.java b/src/main/java/com/ppn/ppn/constant/MessageStatus.java index 47c0d61..c4351bd 100644 --- a/src/main/java/com/ppn/ppn/constant/MessageStatus.java +++ b/src/main/java/com/ppn/ppn/constant/MessageStatus.java @@ -9,4 +9,5 @@ public class MessageStatus { public static final String ERR_MSG_DATA_DUPLICATED = "data is exist"; public static final String ERR_MSG_DATA_NOT_FOUND = "data is not found"; public static final String ERR_MSG_CAR_ASSIGN_USER = "the car {0} is assigning for a user {1} active, cannot be deleted"; + public static final String ERR_MSG_ROLE_IS_NOT_CONTAIN = "role input value is not contain role have defined in system"; } diff --git a/src/main/java/com/ppn/ppn/constant/RoleConstant.java b/src/main/java/com/ppn/ppn/constant/RoleConstant.java new file mode 100644 index 0000000..a34812f --- /dev/null +++ b/src/main/java/com/ppn/ppn/constant/RoleConstant.java @@ -0,0 +1,10 @@ +package com.ppn.ppn.constant; + +public class RoleConstant { + public static final String SYSTEM_ADMIN = "SYSTEM_ADMIN"; + public static final String VIEWER = "VIEWER"; + public static final String OWNER = "OWNER"; + public static final String ADMIN = "ADMIN"; + public static final String EDITOR = "EDITOR"; + public static final String CONTRIBUTE = "CONTRIBUTE"; +} diff --git a/src/main/java/com/ppn/ppn/controller/RoleController.java b/src/main/java/com/ppn/ppn/controller/RoleController.java new file mode 100644 index 0000000..05eace2 --- /dev/null +++ b/src/main/java/com/ppn/ppn/controller/RoleController.java @@ -0,0 +1,25 @@ +package com.ppn.ppn.controller; + + +import com.ppn.ppn.dto.RoleDto; +import com.ppn.ppn.service.RoleServiceImpl; +import jakarta.validation.Valid; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("api/v1/role") +public class RoleController { + @Autowired + private RoleServiceImpl roleService; + + @PostMapping("/create") + public ResponseEntity create(@RequestBody @Valid RoleDto roleDto) { + RoleDto resultData = roleService.create(roleDto); + return ResponseEntity.ok(resultData); + } +} diff --git a/src/main/java/com/ppn/ppn/exception/ErrorInvalidArgument.java b/src/main/java/com/ppn/ppn/exception/ErrorInvalidArgumentResponse.java similarity index 87% rename from src/main/java/com/ppn/ppn/exception/ErrorInvalidArgument.java rename to src/main/java/com/ppn/ppn/exception/ErrorInvalidArgumentResponse.java index 512920d..77a5f29 100644 --- a/src/main/java/com/ppn/ppn/exception/ErrorInvalidArgument.java +++ b/src/main/java/com/ppn/ppn/exception/ErrorInvalidArgumentResponse.java @@ -11,7 +11,7 @@ @Getter @Setter @Builder -public class ErrorInvalidArgument { +public class ErrorInvalidArgumentResponse { private LocalDateTime localDateTime; private String path; private String status; diff --git a/src/main/java/com/ppn/ppn/exception/ErrorResponse.java b/src/main/java/com/ppn/ppn/exception/ErrorResponse.java new file mode 100644 index 0000000..fbc2d8a --- /dev/null +++ b/src/main/java/com/ppn/ppn/exception/ErrorResponse.java @@ -0,0 +1,17 @@ +package com.ppn.ppn.exception; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; + +@Getter +@Setter +@Builder +public class ErrorResponse { + private LocalDateTime localDateTime; + private String path; + private String message; + private String statusCode; +} diff --git a/src/main/java/com/ppn/ppn/exception/GlobalExceptionHandler.java b/src/main/java/com/ppn/ppn/exception/GlobalExceptionHandler.java index 382a861..6a6eb2f 100644 --- a/src/main/java/com/ppn/ppn/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/ppn/ppn/exception/GlobalExceptionHandler.java @@ -7,6 +7,7 @@ import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.context.request.WebRequest; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; @@ -27,12 +28,39 @@ protected ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotV String message = e.getDefaultMessage(); errors.put(fieldName, message); }); - ErrorInvalidArgument errorInvalidArgument = ErrorInvalidArgument.builder() + ErrorInvalidArgumentResponse errorInvalidArgumentResponse = ErrorInvalidArgumentResponse.builder() .path(webRequest.getDescription(false)) .status(HttpStatusCode.valueOf(400).toString()) .localDateTime(LocalDateTime.now()) .error(errors) .build(); - return new ResponseEntity<>(errorInvalidArgument, HttpStatus.BAD_REQUEST); + return new ResponseEntity<>(errorInvalidArgumentResponse, HttpStatus.BAD_REQUEST); } + + @ExceptionHandler(ResourceDuplicateException.class) + public ResponseEntity handleResourcesDuplicateException(ResourceDuplicateException ex, + WebRequest webRequest) { + ErrorResponse errorResponse = ErrorResponse.builder() + .localDateTime(LocalDateTime.now()) + .path(webRequest.getDescription(false)) + .message(ex.getMessage()) + .statusCode(HttpStatusCode.valueOf(400).toString()) + .build(); + + return new ResponseEntity(errorResponse, HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(RoleInputException.class) + public ResponseEntity handleRoleInputException(RoleInputException ex, + WebRequest request) { + ErrorResponse errorResponse = ErrorResponse.builder() + .localDateTime(LocalDateTime.now()) + .statusCode(HttpStatusCode.valueOf(400).toString()) + .message(ex.getMessage()) + .path(request.getDescription(false)) + .build(); + + return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); + } + } diff --git a/src/main/java/com/ppn/ppn/exception/ResourceDuplicateException.java b/src/main/java/com/ppn/ppn/exception/ResourceDuplicateException.java new file mode 100644 index 0000000..87e97b2 --- /dev/null +++ b/src/main/java/com/ppn/ppn/exception/ResourceDuplicateException.java @@ -0,0 +1,19 @@ +package com.ppn.ppn.exception; + +import lombok.Getter; +import lombok.Setter; + +import static com.ppn.ppn.constant.MessageStatus.ERR_MSG_DATA_DUPLICATED; + +@Getter +@Setter +public class ResourceDuplicateException extends RuntimeException { + private String fieldName; + private String value; + + public ResourceDuplicateException(String fieldName, String value) { + super(String.format("%s " + ERR_MSG_DATA_DUPLICATED + " with value: %s", fieldName, value)); + this.fieldName = fieldName; + this.value = value; + } +} diff --git a/src/main/java/com/ppn/ppn/exception/RoleInputException.java b/src/main/java/com/ppn/ppn/exception/RoleInputException.java new file mode 100644 index 0000000..b532469 --- /dev/null +++ b/src/main/java/com/ppn/ppn/exception/RoleInputException.java @@ -0,0 +1,22 @@ +package com.ppn.ppn.exception; + +import lombok.Getter; +import lombok.Setter; + +import java.util.Arrays; +import java.util.List; + +import static com.ppn.ppn.constant.MessageStatus.ERR_MSG_ROLE_IS_NOT_CONTAIN; +import static com.ppn.ppn.constant.RoleConstant.*; + +@Getter +@Setter +public class RoleInputException extends RuntimeException { + private static List nameRoles = Arrays.asList(SYSTEM_ADMIN, ADMIN, OWNER, VIEWER, CONTRIBUTE, EDITOR); + private String value; + + public RoleInputException(String value) { + super(String.format("%s " + ERR_MSG_ROLE_IS_NOT_CONTAIN + " %s ", value, nameRoles)); + this.value = value; + } +} diff --git a/src/main/java/com/ppn/ppn/repository/RoleRepository.java b/src/main/java/com/ppn/ppn/repository/RoleRepository.java index 3501509..79d02d7 100644 --- a/src/main/java/com/ppn/ppn/repository/RoleRepository.java +++ b/src/main/java/com/ppn/ppn/repository/RoleRepository.java @@ -4,6 +4,9 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.Optional; + @Repository public interface RoleRepository extends JpaRepository { + Optional findByRoleName(String roleName); } diff --git a/src/main/java/com/ppn/ppn/service/RoleServiceImpl.java b/src/main/java/com/ppn/ppn/service/RoleServiceImpl.java new file mode 100644 index 0000000..be29643 --- /dev/null +++ b/src/main/java/com/ppn/ppn/service/RoleServiceImpl.java @@ -0,0 +1,41 @@ +package com.ppn.ppn.service; + +import com.ppn.ppn.dto.RoleDto; +import com.ppn.ppn.entities.Role; +import com.ppn.ppn.exception.ResourceDuplicateException; +import com.ppn.ppn.exception.RoleInputException; +import com.ppn.ppn.mapper.RoleMapper; +import com.ppn.ppn.repository.RoleRepository; +import com.ppn.ppn.service.constract.IRoleService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import static com.ppn.ppn.constant.RoleConstant.*; + +@Slf4j +@Service +public class RoleServiceImpl implements IRoleService { + @Autowired + private RoleRepository roleRepository; + + private RoleMapper roleMapper = RoleMapper.INSTANCE; + + @Override + public RoleDto create(RoleDto roleDto) { + Optional role = roleRepository.findByRoleName(roleDto.getRoleName()); + if (role.isPresent()) { + throw new ResourceDuplicateException("Role", roleDto.getRoleName()); + } + List nameRoles = Arrays.asList(SYSTEM_ADMIN, VIEWER, OWNER, CONTRIBUTE, EDITOR, ADMIN); + if (!nameRoles.contains(roleDto.getRoleName())) { + throw new RoleInputException(roleDto.getRoleName()); + } + Role dataSave = roleRepository.save(roleMapper.roleDtoToRole(roleDto)); + return roleMapper.roleToRoleDto(dataSave); + } +} diff --git a/src/main/java/com/ppn/ppn/service/UsersServiceImpl.java b/src/main/java/com/ppn/ppn/service/UsersServiceImpl.java index d0eafbe..1cec2f9 100644 --- a/src/main/java/com/ppn/ppn/service/UsersServiceImpl.java +++ b/src/main/java/com/ppn/ppn/service/UsersServiceImpl.java @@ -1,10 +1,13 @@ package com.ppn.ppn.service; import com.ppn.ppn.dto.UsersDto; +import com.ppn.ppn.entities.Role; import com.ppn.ppn.entities.Users; +import com.ppn.ppn.exception.ResourceDuplicateException; import com.ppn.ppn.mapper.UsersMapper; import com.ppn.ppn.repository.UsersRepository; import com.ppn.ppn.service.constract.IUsersService; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @@ -17,6 +20,7 @@ import static com.ppn.ppn.constant.ApprovalStatus.PENDING; @Service +@Slf4j public class UsersServiceImpl implements IUsersService { @Autowired private UsersRepository userRepository; @@ -27,9 +31,15 @@ public class UsersServiceImpl implements IUsersService { @Override public UsersDto createUsers(UsersDto usersDto) { + Optional usersEntity = userRepository.findByEmail(usersDto.getEmail()); + if(!Objects.isNull(usersEntity)){ + throw new ResourceDuplicateException("Email", usersDto.getEmail()); + } Users users = usersMapper.usersDtoUsers(usersDto); users.setStatus(String.valueOf(PENDING)); users.setVerifyCode(randomString(20)); + + Users dataSaved = userRepository.save(users); return usersMapper.usersToUsersDto(dataSaved); } diff --git a/src/main/java/com/ppn/ppn/service/constract/IRoleService.java b/src/main/java/com/ppn/ppn/service/constract/IRoleService.java new file mode 100644 index 0000000..0433821 --- /dev/null +++ b/src/main/java/com/ppn/ppn/service/constract/IRoleService.java @@ -0,0 +1,7 @@ +package com.ppn.ppn.service.constract; + +import com.ppn.ppn.dto.RoleDto; + +public interface IRoleService { + RoleDto create(RoleDto roleDto); +} From 360a06bd24ebff25078ede33f99a3c3aa836e991 Mon Sep 17 00:00:00 2001 From: letung999 Date: Mon, 6 Nov 2023 16:58:55 +0700 Subject: [PATCH 11/28] fix error verify email --- .../ppn/ppn/config/security/InitialData.java | 4 ++- .../com/ppn/ppn/constant/HostConstant.java | 2 +- .../com/ppn/ppn/constant/MessageStatus.java | 1 + .../ppn/ppn/controller/UserController.java | 10 ++----- .../ppn/exception/GlobalExceptionHandler.java | 12 ++++++++ .../exception/ResourcesNotFoundException.java | 20 +++++++++++++ .../com/ppn/ppn/service/UsersServiceImpl.java | 30 ++++++++++++++----- 7 files changed, 63 insertions(+), 16 deletions(-) create mode 100644 src/main/java/com/ppn/ppn/exception/ResourcesNotFoundException.java diff --git a/src/main/java/com/ppn/ppn/config/security/InitialData.java b/src/main/java/com/ppn/ppn/config/security/InitialData.java index b604a00..0b7c5e0 100644 --- a/src/main/java/com/ppn/ppn/config/security/InitialData.java +++ b/src/main/java/com/ppn/ppn/config/security/InitialData.java @@ -13,6 +13,8 @@ import java.util.HashSet; import java.util.List; +import static com.ppn.ppn.constant.RoleConstant.SYSTEM_ADMIN; + @Component public class InitialData implements ApplicationRunner { @Autowired @@ -29,7 +31,7 @@ public void run(ApplicationArguments args) throws Exception { List roles = roleRepository.findAll(); if (roles.isEmpty()) { Role role = new Role(); - role.setRoleName("System Admin"); + role.setRoleName(SYSTEM_ADMIN); roleRepository.save(role); roles = roleRepository.findAll(); diff --git a/src/main/java/com/ppn/ppn/constant/HostConstant.java b/src/main/java/com/ppn/ppn/constant/HostConstant.java index d1c0fd2..ff1945a 100644 --- a/src/main/java/com/ppn/ppn/constant/HostConstant.java +++ b/src/main/java/com/ppn/ppn/constant/HostConstant.java @@ -1,5 +1,5 @@ package com.ppn.ppn.constant; public class HostConstant { - public static final String HOST_URL_VERIFY_CODE = "http://localhost:8080/api/v1/users?verifyCode="; + public static final String HOST_URL_VERIFY_CODE = "http://localhost:8080/api/v1/users/verify-email?verifyCode="; } diff --git a/src/main/java/com/ppn/ppn/constant/MessageStatus.java b/src/main/java/com/ppn/ppn/constant/MessageStatus.java index c4351bd..fdcef2e 100644 --- a/src/main/java/com/ppn/ppn/constant/MessageStatus.java +++ b/src/main/java/com/ppn/ppn/constant/MessageStatus.java @@ -10,4 +10,5 @@ public class MessageStatus { public static final String ERR_MSG_DATA_NOT_FOUND = "data is not found"; public static final String ERR_MSG_CAR_ASSIGN_USER = "the car {0} is assigning for a user {1} active, cannot be deleted"; public static final String ERR_MSG_ROLE_IS_NOT_CONTAIN = "role input value is not contain role have defined in system"; + public static final String ERR_MSG_VERIFY_SUCCESS="user have been verified success!"; } diff --git a/src/main/java/com/ppn/ppn/controller/UserController.java b/src/main/java/com/ppn/ppn/controller/UserController.java index a550ca7..30b693d 100644 --- a/src/main/java/com/ppn/ppn/controller/UserController.java +++ b/src/main/java/com/ppn/ppn/controller/UserController.java @@ -18,6 +18,7 @@ import java.util.Map; import static com.ppn.ppn.constant.HostConstant.HOST_URL_VERIFY_CODE; +import static com.ppn.ppn.constant.MessageStatus.ERR_MSG_VERIFY_SUCCESS; @RequestMapping("api/v1/users") @RestController @@ -68,12 +69,7 @@ public ResponseEntity add(@RequestBody @Valid UsersDto usersDto) { @GetMapping("/verify-email") public ResponseEntity verifyEmail(@RequestParam(name = "verifyCode") String verifyCode) throws MessagingException { log.info("verify code: {}", verifyCode); - try{ - usersService.verifyUser(verifyCode); - }catch (Exception ex){ - log.info("verify fail with code: {}", verifyCode); - ex.printStackTrace(); - } - return ResponseEntity.ok("success"); + usersService.verifyUser(verifyCode); + return ResponseEntity.ok(ERR_MSG_VERIFY_SUCCESS); } } diff --git a/src/main/java/com/ppn/ppn/exception/GlobalExceptionHandler.java b/src/main/java/com/ppn/ppn/exception/GlobalExceptionHandler.java index 6a6eb2f..e234713 100644 --- a/src/main/java/com/ppn/ppn/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/ppn/ppn/exception/GlobalExceptionHandler.java @@ -63,4 +63,16 @@ public ResponseEntity handleRoleInputException(RoleInputException return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); } + @ExceptionHandler(ResourcesNotFoundException.class) + public ResponseEntity handleResourcesNotFoundException(ResourcesNotFoundException ex, + WebRequest webRequest) { + ErrorResponse errorResponse = ErrorResponse.builder() + .localDateTime(LocalDateTime.now()) + .message(ex.getMessage()) + .statusCode(HttpStatusCode.valueOf(404).toString()) + .path(webRequest.getDescription(false)) + .build(); + return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND); + } + } diff --git a/src/main/java/com/ppn/ppn/exception/ResourcesNotFoundException.java b/src/main/java/com/ppn/ppn/exception/ResourcesNotFoundException.java new file mode 100644 index 0000000..c19f817 --- /dev/null +++ b/src/main/java/com/ppn/ppn/exception/ResourcesNotFoundException.java @@ -0,0 +1,20 @@ +package com.ppn.ppn.exception; + + +import lombok.Getter; +import lombok.Setter; + +import static com.ppn.ppn.constant.MessageStatus.ERR_MSG_DATA_NOT_FOUND; + +@Getter +@Setter +public class ResourcesNotFoundException extends RuntimeException { + private String fieldName; + private String value; + + public ResourcesNotFoundException(String fieldName, String value) { + super(String.format("%s: " + ERR_MSG_DATA_NOT_FOUND + " %s: ", fieldName, value)); + this.fieldName = fieldName; + this.value = value; + } +} diff --git a/src/main/java/com/ppn/ppn/service/UsersServiceImpl.java b/src/main/java/com/ppn/ppn/service/UsersServiceImpl.java index 1cec2f9..7a33973 100644 --- a/src/main/java/com/ppn/ppn/service/UsersServiceImpl.java +++ b/src/main/java/com/ppn/ppn/service/UsersServiceImpl.java @@ -4,7 +4,9 @@ import com.ppn.ppn.entities.Role; import com.ppn.ppn.entities.Users; import com.ppn.ppn.exception.ResourceDuplicateException; +import com.ppn.ppn.exception.ResourcesNotFoundException; import com.ppn.ppn.mapper.UsersMapper; +import com.ppn.ppn.repository.RoleRepository; import com.ppn.ppn.repository.UsersRepository; import com.ppn.ppn.service.constract.IUsersService; import lombok.extern.slf4j.Slf4j; @@ -12,12 +14,14 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; -import java.util.Objects; +import java.util.HashSet; import java.util.Optional; import java.util.Random; +import java.util.Set; import static com.ppn.ppn.constant.ApprovalStatus.ACTIVE; import static com.ppn.ppn.constant.ApprovalStatus.PENDING; +import static com.ppn.ppn.constant.RoleConstant.VIEWER; @Service @Slf4j @@ -25,6 +29,8 @@ public class UsersServiceImpl implements IUsersService { @Autowired private UsersRepository userRepository; + @Autowired + private RoleRepository roleRepository; @Autowired private PasswordEncoder passwordEncoder; private UsersMapper usersMapper = UsersMapper.INSTANCE; @@ -32,14 +38,24 @@ public class UsersServiceImpl implements IUsersService { @Override public UsersDto createUsers(UsersDto usersDto) { Optional usersEntity = userRepository.findByEmail(usersDto.getEmail()); - if(!Objects.isNull(usersEntity)){ + if (usersEntity.isPresent()) { throw new ResourceDuplicateException("Email", usersDto.getEmail()); } + + Optional role = roleRepository.findByRoleName(VIEWER); + if (role.isEmpty()) { + throw new ResourcesNotFoundException("Role", VIEWER); + } + Set roles = new HashSet<>(); + roles.add(role.get()); + + //set data Users users = usersMapper.usersDtoUsers(usersDto); users.setStatus(String.valueOf(PENDING)); - users.setVerifyCode(randomString(20)); - + users.setVerifyCode(randomString(30)); + users.setPassword(passwordEncoder.encode(usersDto.getPassword())); + users.setRoles(roles); Users dataSaved = userRepository.save(users); return usersMapper.usersToUsersDto(dataSaved); } @@ -47,8 +63,8 @@ public UsersDto createUsers(UsersDto usersDto) { @Override public boolean verifyUser(String verifyCode) { Optional users = userRepository.findByVerifyCode(verifyCode); - if (Objects.isNull(users)) { - return false; + if (users.isEmpty()) { + throw new ResourcesNotFoundException("users", verifyCode); } users.get().setStatus(String.valueOf(ACTIVE)); userRepository.save(users.get()); @@ -74,7 +90,7 @@ private String randomString(int length) { Random random = new Random(); StringBuilder resultData = new StringBuilder(); for (int i = 0; i < length; ++i) { - int index = random.nextInt(length) + 0; + int index = random.nextInt(data.length()) + 0; resultData.append(data.charAt(index)); } return resultData.toString(); From 9edea17da03dbd4f2e3f4fe0f8b67f0b0148e484 Mon Sep 17 00:00:00 2001 From: letung999 Date: Mon, 6 Nov 2023 17:07:29 +0700 Subject: [PATCH 12/28] remove redudant class --- .../ppn/exception/ErrorInvalidArgument.java | 19 -------- src/main/resources/bootstrap.properties | 2 +- .../resources/templates/Registration.html | 45 ------------------- 3 files changed, 1 insertion(+), 65 deletions(-) delete mode 100644 src/main/java/com/ppn/ppn/exception/ErrorInvalidArgument.java delete mode 100644 src/main/resources/templates/Registration.html diff --git a/src/main/java/com/ppn/ppn/exception/ErrorInvalidArgument.java b/src/main/java/com/ppn/ppn/exception/ErrorInvalidArgument.java deleted file mode 100644 index 512920d..0000000 --- a/src/main/java/com/ppn/ppn/exception/ErrorInvalidArgument.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.ppn.ppn.exception; - - -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; - -import java.time.LocalDateTime; -import java.util.Map; - -@Getter -@Setter -@Builder -public class ErrorInvalidArgument { - private LocalDateTime localDateTime; - private String path; - private String status; - private Map error; -} diff --git a/src/main/resources/bootstrap.properties b/src/main/resources/bootstrap.properties index 5763f0a..a5c58e5 100644 --- a/src/main/resources/bootstrap.properties +++ b/src/main/resources/bootstrap.properties @@ -3,4 +3,4 @@ aws.paramstore.default-context=dev aws.paramstore.profile-separator= aws.paramstore.enabled=true #active environment -#spring.profiles.active=prod \ No newline at end of file +spring.profiles.active=prod \ No newline at end of file diff --git a/src/main/resources/templates/Registration.html b/src/main/resources/templates/Registration.html deleted file mode 100644 index 81ebcde..0000000 --- a/src/main/resources/templates/Registration.html +++ /dev/null @@ -1,45 +0,0 @@ - - - - Spring boot email template with Thymeleaf - - - - - - - - - - -

- -

- Wow! You've got a long name (more than 5 chars)! -

-

- You have been successfully subscribed to the Fake newsletter on - -

- -

- You can find your inlined image just below this text. -

-

- - - htts://javabydeveloper.com - -

-

- Regards,
- -

-

- - \ No newline at end of file From efaee967a063d0104fcc6c98892847f8418a4d26 Mon Sep 17 00:00:00 2001 From: letung999 Date: Mon, 6 Nov 2023 22:04:01 +0700 Subject: [PATCH 13/28] create a general for response api and modify response at controller --- .../ppn/ppn/controller/AuthController.java | 39 +++++++++++++------ .../ppn/ppn/controller/RoleController.java | 16 +++++++- .../ppn/ppn/controller/UserController.java | 24 ++++++++++-- .../java/com/ppn/ppn/payload/APIResponse.java | 27 +++++++++++++ .../com/ppn/ppn/payload/LoginResponse.java | 30 -------------- 5 files changed, 89 insertions(+), 47 deletions(-) create mode 100644 src/main/java/com/ppn/ppn/payload/APIResponse.java delete mode 100644 src/main/java/com/ppn/ppn/payload/LoginResponse.java diff --git a/src/main/java/com/ppn/ppn/controller/AuthController.java b/src/main/java/com/ppn/ppn/controller/AuthController.java index d7fb7fd..2b96e5a 100644 --- a/src/main/java/com/ppn/ppn/controller/AuthController.java +++ b/src/main/java/com/ppn/ppn/controller/AuthController.java @@ -3,11 +3,10 @@ import com.ppn.ppn.config.security.CustomUserDetails; import com.ppn.ppn.config.security.JwtTokenProvider; import com.ppn.ppn.entities.Users; +import com.ppn.ppn.payload.APIResponse; import com.ppn.ppn.payload.LoginRequest; -import com.ppn.ppn.payload.LoginResponse; import com.ppn.ppn.service.UsersServiceImpl; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -17,11 +16,11 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.time.LocalDateTime; import java.util.HashMap; import java.util.Map; -import static com.ppn.ppn.constant.MessageStatus.ERR_MSG_SOME_THING_WENT_WRONG; -import static com.ppn.ppn.constant.MessageStatus.ERR_MSG_UNAUTHENTICATED_ACCESS; +import static com.ppn.ppn.constant.MessageStatus.*; @RestController @RequestMapping("/api/v1/auth") @@ -34,7 +33,7 @@ public class AuthController { private AuthenticationManager authenticationManager; @PostMapping({"/login"}) - public ResponseEntity login(@RequestBody LoginRequest request) { + public ResponseEntity login(@RequestBody LoginRequest request) { Users user = new Users(); user.setEmail(request.getEmail()); user.setPassword(request.getPassword()); @@ -46,15 +45,33 @@ public ResponseEntity login(@RequestBody LoginRequest request) { Map response = new HashMap<>(); response.put("accessToken", jwt); response.put("user", userCheckLogin); - - LoginResponse> resultData = new LoginResponse<>(HttpStatus.OK, response); - return ResponseEntity.ok(resultData); - + APIResponse apiResponse = APIResponse.builder() + .message(INF_MSG_SUCCESSFULLY) + .timeStamp(LocalDateTime.now()) + .isSuccess(true) + .statusCode(200) + .data(response) + .build(); + return ResponseEntity.ok(apiResponse); } else { - return ResponseEntity.ok(new LoginResponse(HttpStatus.BAD_REQUEST, ERR_MSG_UNAUTHENTICATED_ACCESS)); + APIResponse apiResponse = APIResponse.builder() + .message(ERR_MSG_UNAUTHENTICATED_ACCESS) + .timeStamp(LocalDateTime.now()) + .isSuccess(false) + .statusCode(400) + .data(null) + .build(); + return ResponseEntity.ok(apiResponse); } } catch (Exception ex) { - return ResponseEntity.ok(new LoginResponse<>(HttpStatus.INTERNAL_SERVER_ERROR, ERR_MSG_SOME_THING_WENT_WRONG)); + APIResponse apiResponse = APIResponse.builder() + .message(ERR_MSG_SOME_THING_WENT_WRONG) + .timeStamp(LocalDateTime.now()) + .isSuccess(false) + .statusCode(500) + .data(null) + .build(); + return ResponseEntity.ok(apiResponse); } } } diff --git a/src/main/java/com/ppn/ppn/controller/RoleController.java b/src/main/java/com/ppn/ppn/controller/RoleController.java index 05eace2..237deb5 100644 --- a/src/main/java/com/ppn/ppn/controller/RoleController.java +++ b/src/main/java/com/ppn/ppn/controller/RoleController.java @@ -2,6 +2,7 @@ import com.ppn.ppn.dto.RoleDto; +import com.ppn.ppn.payload.APIResponse; import com.ppn.ppn.service.RoleServiceImpl; import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; @@ -11,6 +12,10 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.time.LocalDateTime; + +import static com.ppn.ppn.constant.MessageStatus.INF_MSG_SUCCESSFULLY; + @RestController @RequestMapping("api/v1/role") public class RoleController { @@ -18,8 +23,15 @@ public class RoleController { private RoleServiceImpl roleService; @PostMapping("/create") - public ResponseEntity create(@RequestBody @Valid RoleDto roleDto) { + public ResponseEntity create(@RequestBody @Valid RoleDto roleDto) { RoleDto resultData = roleService.create(roleDto); - return ResponseEntity.ok(resultData); + APIResponse apiResponse = APIResponse.builder() + .message(INF_MSG_SUCCESSFULLY) + .timeStamp(LocalDateTime.now()) + .isSuccess(true) + .statusCode(201) + .data(resultData) + .build(); + return ResponseEntity.ok(apiResponse); } } diff --git a/src/main/java/com/ppn/ppn/controller/UserController.java b/src/main/java/com/ppn/ppn/controller/UserController.java index 30b693d..174fe71 100644 --- a/src/main/java/com/ppn/ppn/controller/UserController.java +++ b/src/main/java/com/ppn/ppn/controller/UserController.java @@ -1,6 +1,7 @@ package com.ppn.ppn.controller; import com.ppn.ppn.dto.UsersDto; +import com.ppn.ppn.payload.APIResponse; import com.ppn.ppn.payload.HtmlTemplate; import com.ppn.ppn.payload.VerifyMailRequest; import com.ppn.ppn.service.EmailSenderServiceImpl; @@ -14,11 +15,13 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.time.LocalDateTime; import java.util.HashMap; import java.util.Map; import static com.ppn.ppn.constant.HostConstant.HOST_URL_VERIFY_CODE; import static com.ppn.ppn.constant.MessageStatus.ERR_MSG_VERIFY_SUCCESS; +import static com.ppn.ppn.constant.MessageStatus.INF_MSG_SUCCESSFULLY; @RequestMapping("api/v1/users") @RestController @@ -40,7 +43,7 @@ public class UserController { private String nameCompany; @PostMapping("/create") - public ResponseEntity add(@RequestBody @Valid UsersDto usersDto) { + public ResponseEntity add(@RequestBody @Valid UsersDto usersDto) { UsersDto result = usersService.createUsers(usersDto); //send mail for user: Map properties = new HashMap<>(); @@ -62,14 +65,27 @@ public ResponseEntity add(@RequestBody @Valid UsersDto usersDto) { log.error("send email fail! with email {}", usersDto.getEmail()); throw new RuntimeException(e); } + APIResponse apiResponse = APIResponse.builder() + .message(INF_MSG_SUCCESSFULLY) + .timeStamp(LocalDateTime.now()) + .isSuccess(true) + .statusCode(201) + .data(result) + .build(); - return ResponseEntity.ok(result); + return ResponseEntity.ok(apiResponse); } @GetMapping("/verify-email") - public ResponseEntity verifyEmail(@RequestParam(name = "verifyCode") String verifyCode) throws MessagingException { + public ResponseEntity verifyEmail(@RequestParam(name = "verifyCode") String verifyCode) throws MessagingException { log.info("verify code: {}", verifyCode); usersService.verifyUser(verifyCode); - return ResponseEntity.ok(ERR_MSG_VERIFY_SUCCESS); + APIResponse apiResponse = APIResponse.builder() + .message(ERR_MSG_VERIFY_SUCCESS) + .timeStamp(LocalDateTime.now()) + .statusCode(200) + .isSuccess(true) + .build(); + return ResponseEntity.ok(apiResponse); } } diff --git a/src/main/java/com/ppn/ppn/payload/APIResponse.java b/src/main/java/com/ppn/ppn/payload/APIResponse.java new file mode 100644 index 0000000..323b308 --- /dev/null +++ b/src/main/java/com/ppn/ppn/payload/APIResponse.java @@ -0,0 +1,27 @@ +package com.ppn.ppn.payload; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; + +@Getter +@Setter +@AllArgsConstructor +@Builder +public class APIResponse { + private String message; + private LocalDateTime timeStamp; + private Boolean isSuccess; + private Integer statusCode; + private T data; + + public APIResponse(String message, LocalDateTime timeStamp, Boolean isSuccess, Integer statusCode) { + this.message = message; + this.timeStamp = timeStamp; + this.isSuccess = isSuccess; + this.statusCode = statusCode; + } +} diff --git a/src/main/java/com/ppn/ppn/payload/LoginResponse.java b/src/main/java/com/ppn/ppn/payload/LoginResponse.java deleted file mode 100644 index 5314279..0000000 --- a/src/main/java/com/ppn/ppn/payload/LoginResponse.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.ppn.ppn.payload; - -import lombok.Getter; -import lombok.Setter; -import org.springframework.http.HttpStatus; - -@Getter -@Setter -public class LoginResponse { - private HttpStatus status; - private T data; - - private String message; - - public LoginResponse(HttpStatus status, T data) { - this.status = status; - this.data = data; - } - - public LoginResponse(HttpStatus status, String message) { - this.status = status; - this.message = message; - } - - public LoginResponse(HttpStatus status, T data, String message) { - this.status = status; - this.data = data; - this.message = message; - } -} From d45e87d1a1f24777cfd5164e3655bef527fd9b72 Mon Sep 17 00:00:00 2001 From: letung999 Date: Tue, 7 Nov 2023 23:18:32 +0700 Subject: [PATCH 14/28] handle upload file amazon s3 --- pom.xml | 20 +++++- .../com/ppn/ppn/config/aws/AWSConfig.java | 29 +++++++++ .../com/ppn/ppn/constant/MessageStatus.java | 7 ++- .../ppn/ppn/controller/FileController.java | 63 +++++++++++++++++++ .../ppn/exception/FileDownloadException.java | 17 +++++ .../ppn/ppn/exception/FileEmptyException.java | 17 +++++ .../ppn/exception/FileUploadException.java | 17 +++++ .../ppn/exception/GlobalExceptionHandler.java | 63 +++++++++++++++++++ .../service/FileUploadToS3ServiceImpl.java | 63 +++++++++++++++++++ .../constract/IFileUploadToS3Service.java | 9 +++ .../resources/application-prod.properties | 8 +++ 11 files changed, 310 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/ppn/ppn/config/aws/AWSConfig.java create mode 100644 src/main/java/com/ppn/ppn/controller/FileController.java create mode 100644 src/main/java/com/ppn/ppn/exception/FileDownloadException.java create mode 100644 src/main/java/com/ppn/ppn/exception/FileEmptyException.java create mode 100644 src/main/java/com/ppn/ppn/exception/FileUploadException.java create mode 100644 src/main/java/com/ppn/ppn/service/FileUploadToS3ServiceImpl.java create mode 100644 src/main/java/com/ppn/ppn/service/constract/IFileUploadToS3Service.java diff --git a/pom.xml b/pom.xml index 726c71a..eb9f249 100644 --- a/pom.xml +++ b/pom.xml @@ -18,12 +18,15 @@ 2022.0.1 1.5.3.Final 1.5.3.Final - 2.2.6.RELEASE + 2.2.6.RELEASE + 6.0.2 0.9.1 2.3.0.1 2.3.1 3.0.11.RELEASE + 1.12.319 + 2.11.0 @@ -104,7 +107,7 @@ spring-security-config ${spring-security-config.version} - + io.jsonwebtoken jjwt @@ -134,6 +137,19 @@ thymeleaf-spring5 ${thymeleaf-spring5.version} + + + com.amazonaws + aws-java-sdk-s3 + ${aws-java-sdk-s3.version} + + + + commons-io + commons-io + ${commons-io.version} + + diff --git a/src/main/java/com/ppn/ppn/config/aws/AWSConfig.java b/src/main/java/com/ppn/ppn/config/aws/AWSConfig.java new file mode 100644 index 0000000..9f0b865 --- /dev/null +++ b/src/main/java/com/ppn/ppn/config/aws/AWSConfig.java @@ -0,0 +1,29 @@ +package com.ppn.ppn.config.aws; + +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.regions.Regions; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class AWSConfig { + @Value("${aws.ppn.secretKey}") + private String secretKey; + + @Value("${aws.ppn.accessKey}") + private String accessKey; + + @Bean + public AmazonS3 initS3Client(){ + AWSCredentials credentials = new BasicAWSCredentials(this.accessKey, this.secretKey); + return AmazonS3ClientBuilder.standard() + .withRegion(Regions.US_EAST_1) + .withCredentials(new AWSStaticCredentialsProvider(credentials)) + .build(); + } +} diff --git a/src/main/java/com/ppn/ppn/constant/MessageStatus.java b/src/main/java/com/ppn/ppn/constant/MessageStatus.java index fdcef2e..6e81f65 100644 --- a/src/main/java/com/ppn/ppn/constant/MessageStatus.java +++ b/src/main/java/com/ppn/ppn/constant/MessageStatus.java @@ -10,5 +10,10 @@ public class MessageStatus { public static final String ERR_MSG_DATA_NOT_FOUND = "data is not found"; public static final String ERR_MSG_CAR_ASSIGN_USER = "the car {0} is assigning for a user {1} active, cannot be deleted"; public static final String ERR_MSG_ROLE_IS_NOT_CONTAIN = "role input value is not contain role have defined in system"; - public static final String ERR_MSG_VERIFY_SUCCESS="user have been verified success!"; + public static final String ERR_MSG_VERIFY_SUCCESS = "user have been verified success!"; + public static final String ERR_MSG_EMPTY_FILE = "File is empty. Can't save an empty file"; + public static final String ERR_MSG_DOWN_LOAD_FILE = "Bucket is not exist or empty"; + public static final String ERR_MSG_UP_LOAD_FILE = "Invalid file. File extension or file name is not supported"; + + } diff --git a/src/main/java/com/ppn/ppn/controller/FileController.java b/src/main/java/com/ppn/ppn/controller/FileController.java new file mode 100644 index 0000000..e3ef5b3 --- /dev/null +++ b/src/main/java/com/ppn/ppn/controller/FileController.java @@ -0,0 +1,63 @@ +package com.ppn.ppn.controller; + +import com.ppn.ppn.exception.FileEmptyException; +import com.ppn.ppn.payload.APIResponse; +import com.ppn.ppn.service.FileUploadToS3ServiceImpl; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.FilenameUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import static com.ppn.ppn.constant.MessageStatus.*; + +@RestController +@Slf4j +@RequestMapping("/api/v1/file") +public class FileController { + @Autowired + private FileUploadToS3ServiceImpl fileUploadToS3Service; + + @PostMapping("/upload") + public ResponseEntity uploadFile(@RequestPart("file") List multipartFile) throws FileEmptyException, IOException { + if (multipartFile.size() == 0) { + throw new FileEmptyException(ERR_MSG_EMPTY_FILE); + } + List allowedFileExtensions = new ArrayList<>(Arrays.asList("pdf", "txt", "epub", "csv", "png", "jpg", "jpeg", "srt")); + Set inputFileExtension = multipartFile.stream().map(extension -> { + return FilenameUtils.getExtension(extension.getOriginalFilename()); + }).collect(Collectors.toSet()); + + if (allowedFileExtensions.containsAll(inputFileExtension)) { + List urls = fileUploadToS3Service.uploadFileToS3(multipartFile); + APIResponse apiResponse = APIResponse.builder() + .message(INF_MSG_SUCCESSFULLY) + .timeStamp(LocalDateTime.now()) + .isSuccess(true) + .data(urls) + .statusCode(200) + .build(); + return ResponseEntity.ok(apiResponse); + } + APIResponse apiResponse = APIResponse.builder() + .message(ERR_MSG_UP_LOAD_FILE) + .timeStamp(LocalDateTime.now()) + .isSuccess(true) + .data(null) + .statusCode(400) + .build(); + return ResponseEntity.ok(apiResponse); + } +} diff --git a/src/main/java/com/ppn/ppn/exception/FileDownloadException.java b/src/main/java/com/ppn/ppn/exception/FileDownloadException.java new file mode 100644 index 0000000..1a6f039 --- /dev/null +++ b/src/main/java/com/ppn/ppn/exception/FileDownloadException.java @@ -0,0 +1,17 @@ +package com.ppn.ppn.exception; + +import lombok.Getter; +import lombok.Setter; + +import static com.ppn.ppn.constant.MessageStatus.ERR_MSG_DOWN_LOAD_FILE; + +@Getter +@Setter +public class FileDownloadException extends RuntimeException { + private String fileName; + + public FileDownloadException(String fileName) { + super(String.format("%s in " + ERR_MSG_DOWN_LOAD_FILE, fileName)); + this.fileName = fileName; + } +} diff --git a/src/main/java/com/ppn/ppn/exception/FileEmptyException.java b/src/main/java/com/ppn/ppn/exception/FileEmptyException.java new file mode 100644 index 0000000..b432263 --- /dev/null +++ b/src/main/java/com/ppn/ppn/exception/FileEmptyException.java @@ -0,0 +1,17 @@ +package com.ppn.ppn.exception; + +import lombok.Getter; +import lombok.Setter; + +import static com.ppn.ppn.constant.MessageStatus.ERR_MSG_EMPTY_FILE; + +@Getter +@Setter +public class FileEmptyException extends RuntimeException { + private String fileName; + + public FileEmptyException(String fileName) { + super(String.format("%s with: " + ERR_MSG_EMPTY_FILE, fileName)); + this.fileName = fileName; + } +} diff --git a/src/main/java/com/ppn/ppn/exception/FileUploadException.java b/src/main/java/com/ppn/ppn/exception/FileUploadException.java new file mode 100644 index 0000000..7e88f50 --- /dev/null +++ b/src/main/java/com/ppn/ppn/exception/FileUploadException.java @@ -0,0 +1,17 @@ +package com.ppn.ppn.exception; + +import lombok.Getter; +import lombok.Setter; + +import static com.ppn.ppn.constant.MessageStatus.ERR_MSG_UP_LOAD_FILE; + +@Getter +@Setter +public class FileUploadException extends RuntimeException { + private String fileName; + + public FileUploadException(String fileName) { + super(String.format("%s: ", ERR_MSG_UP_LOAD_FILE, fileName)); + this.fileName = fileName; + } +} diff --git a/src/main/java/com/ppn/ppn/exception/GlobalExceptionHandler.java b/src/main/java/com/ppn/ppn/exception/GlobalExceptionHandler.java index 8b5e5c9..086b0c6 100644 --- a/src/main/java/com/ppn/ppn/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/ppn/ppn/exception/GlobalExceptionHandler.java @@ -1,5 +1,7 @@ package com.ppn.ppn.exception; +import com.amazonaws.AmazonServiceException; +import com.amazonaws.SdkClientException; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatusCode; @@ -74,4 +76,65 @@ public ResponseEntity handleResourcesNotFoundException(ResourcesN .build(); return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND); } + + @ExceptionHandler(FileDownloadException.class) + public ResponseEntity handleFileDownloadException(FileDownloadException ex, + WebRequest webRequest) { + ErrorResponse errorResponse = ErrorResponse.builder() + .localDateTime(LocalDateTime.now()) + .message(ex.getMessage()) + .statusCode(HttpStatusCode.valueOf(204).toString()) + .path(webRequest.getDescription(false)) + .build(); + return new ResponseEntity<>(errorResponse, HttpStatus.NO_CONTENT); + } + + @ExceptionHandler(FileUploadException.class) + public ResponseEntity handleFileUploadException(FileUploadException ex, + WebRequest webRequest) { + ErrorResponse errorResponse = ErrorResponse.builder() + .localDateTime(LocalDateTime.now()) + .message(ex.getMessage()) + .statusCode(HttpStatusCode.valueOf(400).toString()) + .path(webRequest.getDescription(false)) + .build(); + return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(FileEmptyException.class) + public ResponseEntity handleFileEmptyException(FileEmptyException ex, + WebRequest webRequest) { + ErrorResponse errorResponse = ErrorResponse.builder() + .localDateTime(LocalDateTime.now()) + .message(ex.getMessage()) + .statusCode(HttpStatusCode.valueOf(204).toString()) + .path(webRequest.getDescription(false)) + .build(); + return new ResponseEntity<>(errorResponse, HttpStatus.NO_CONTENT); + } + + // handle exception occur when the call was transmitted successfully but Amazon S3 can't process request. + @ExceptionHandler(AmazonServiceException.class) + public ResponseEntity handleAmazonServiceException(AmazonServiceException ex, WebRequest webRequest){ + ErrorResponse errorResponse = ErrorResponse.builder() + .localDateTime(LocalDateTime.now()) + .message(ex.getMessage()) + .statusCode(HttpStatusCode.valueOf(409).toString()) + .path(webRequest.getDescription(false)) + .build(); + return new ResponseEntity<>(errorResponse, HttpStatus.CONFLICT); + } + + //handle exception occur when can't contact with Amazon S3 + @ExceptionHandler(SdkClientException.class) + public ResponseEntity handleSdkClientException(SdkClientException ex, WebRequest webRequest){ + ErrorResponse errorResponse = ErrorResponse.builder() + .localDateTime(LocalDateTime.now()) + .message(ex.getMessage()) + .statusCode(HttpStatusCode.valueOf(503).toString()) + .path(webRequest.getDescription(false)) + .build(); + return new ResponseEntity<>(errorResponse, HttpStatus.SERVICE_UNAVAILABLE); + } + } diff --git a/src/main/java/com/ppn/ppn/service/FileUploadToS3ServiceImpl.java b/src/main/java/com/ppn/ppn/service/FileUploadToS3ServiceImpl.java new file mode 100644 index 0000000..892c014 --- /dev/null +++ b/src/main/java/com/ppn/ppn/service/FileUploadToS3ServiceImpl.java @@ -0,0 +1,63 @@ +package com.ppn.ppn.service; + +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.PutObjectRequest; +import com.ppn.ppn.service.constract.IFileUploadToS3Service; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.FilenameUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Slf4j +public class FileUploadToS3ServiceImpl implements IFileUploadToS3Service { + @Value("${aws.ppn.bucketName}") + private String bucketName; + private final AmazonS3 amazonS3; + @Override + public List uploadFileToS3(List multipartFiles) { + List urls = multipartFiles.stream().map(multipartFile -> { + String fileName = generateFileName(multipartFile); + String url = null; + try { + url = upload(multipartFile, fileName, bucketName); + } catch (IOException e) { + throw new RuntimeException(e); + } + return url; + }).collect(Collectors.toList()); + return urls; + } + + private String upload(MultipartFile multipartFile, String fileName, String bucketName) throws IOException { + File file = new File(multipartFile.getOriginalFilename()); + try (FileOutputStream fileOutputStream = new FileOutputStream(file)) { + fileOutputStream.write(multipartFile.getBytes()); + } + PutObjectRequest request = new PutObjectRequest(bucketName, fileName, file); + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentType("plain/" + FilenameUtils.getExtension(multipartFile.getOriginalFilename())); + metadata.addUserMetadata("Title", "File Upload - " + fileName); + metadata.setContentLength(file.length()); + request.setMetadata(metadata); + amazonS3.putObject(request); + // delete file + file.delete(); + return amazonS3.getUrl(bucketName, fileName).toString(); + } + + private String generateFileName(MultipartFile multiPart) { + return new Date().getTime() + "-" + multiPart.getOriginalFilename().replace(" ", "_"); + } +} diff --git a/src/main/java/com/ppn/ppn/service/constract/IFileUploadToS3Service.java b/src/main/java/com/ppn/ppn/service/constract/IFileUploadToS3Service.java new file mode 100644 index 0000000..b7f0468 --- /dev/null +++ b/src/main/java/com/ppn/ppn/service/constract/IFileUploadToS3Service.java @@ -0,0 +1,9 @@ +package com.ppn.ppn.service.constract; + +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +public interface IFileUploadToS3Service { + List uploadFileToS3(List multipartFiles); +} diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties index 67c2f04..2266648 100644 --- a/src/main/resources/application-prod.properties +++ b/src/main/resources/application-prod.properties @@ -22,4 +22,12 @@ spring.mail.properties.mail.smtp.starttls.enable=true spring.mail.properties.mail.smtp.starttls.required=true cox.automation.email=cox-automation@gmail.com cox.name=MANHEIM-COX-AUTOMATION +#connect aws +aws.ppn.accessKey=${accessKey} +aws.ppn.secretKey=${secretKey} +#bucket name +aws.ppn.bucketName=${bucket-name} +##config max size of file +spring.servlet.multipart.max-file-size=10MB +spring.servlet.multipart.max-request-size=10MB From 8cc3154bfd861237a6b260578e81f883678714f8 Mon Sep 17 00:00:00 2001 From: letung999 Date: Wed, 8 Nov 2023 18:07:01 +0700 Subject: [PATCH 15/28] =?UTF-8?q?d=C3=86handle=20d=C3=86=C3=86ownload=20fi?= =?UTF-8?q?le=20from=20s3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ppn/ppn/controller/FileController.java | 24 ++++++++-- .../service/FileUploadToS3ServiceImpl.java | 45 ++++++++++++++++++- .../constract/IFileUploadToS3Service.java | 3 ++ 3 files changed, 66 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/ppn/ppn/controller/FileController.java b/src/main/java/com/ppn/ppn/controller/FileController.java index e3ef5b3..ca6fbee 100644 --- a/src/main/java/com/ppn/ppn/controller/FileController.java +++ b/src/main/java/com/ppn/ppn/controller/FileController.java @@ -3,14 +3,15 @@ import com.ppn.ppn.exception.FileEmptyException; import com.ppn.ppn.payload.APIResponse; import com.ppn.ppn.service.FileUploadToS3ServiceImpl; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.FilenameUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestPart; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; @@ -60,4 +61,19 @@ public ResponseEntity uploadFile(@RequestPart("file") List mul .build(); return ResponseEntity.ok(apiResponse); } + + @GetMapping("/download") + public ResponseEntity downloadFileFromS3(@RequestParam("fileName") @NotBlank @NotNull String fileName) throws IOException { + Object response = fileUploadToS3Service.downloadFileFromS3(fileName); + if (response != null) { + return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"").body(response); + } else { + APIResponse apiResponse = APIResponse.builder() + .message("File could not be downloaded") + .isSuccess(false) + .statusCode(400) + .build(); + return new ResponseEntity<>(apiResponse, HttpStatus.NOT_FOUND); + } + } } diff --git a/src/main/java/com/ppn/ppn/service/FileUploadToS3ServiceImpl.java b/src/main/java/com/ppn/ppn/service/FileUploadToS3ServiceImpl.java index 892c014..4278c33 100644 --- a/src/main/java/com/ppn/ppn/service/FileUploadToS3ServiceImpl.java +++ b/src/main/java/com/ppn/ppn/service/FileUploadToS3ServiceImpl.java @@ -1,19 +1,23 @@ package com.ppn.ppn.service; import com.amazonaws.services.s3.AmazonS3; -import com.amazonaws.services.s3.model.ObjectMetadata; -import com.amazonaws.services.s3.model.PutObjectRequest; +import com.amazonaws.services.s3.model.*; +import com.ppn.ppn.exception.FileDownloadException; import com.ppn.ppn.service.constract.IFileUploadToS3Service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.FilenameUtils; import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.Resource; +import org.springframework.core.io.UrlResource; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Date; import java.util.List; import java.util.stream.Collectors; @@ -25,6 +29,7 @@ public class FileUploadToS3ServiceImpl implements IFileUploadToS3Service { @Value("${aws.ppn.bucketName}") private String bucketName; private final AmazonS3 amazonS3; + @Override public List uploadFileToS3(List multipartFiles) { List urls = multipartFiles.stream().map(multipartFile -> { @@ -40,6 +45,31 @@ public List uploadFileToS3(List multipartFiles) { return urls; } + @Override + public Object downloadFileFromS3(String fileName) throws IOException { + if (bucketIsEmpty()) { + throw new FileDownloadException(fileName); + } + S3Object object = amazonS3.getObject(bucketName, fileName); + try (S3ObjectInputStream s3ObjectInputStream = object.getObjectContent()) { + try (FileOutputStream fileOutputStream = new FileOutputStream(fileName)) { + byte[] read_buf = new byte[1024]; + int read_len = 0; + while ((read_len = s3ObjectInputStream.read(read_buf)) > 0) { + fileOutputStream.write(read_buf, 0, read_len); + } + } + Path path = Paths.get(fileName); + Resource resource = new UrlResource(path.toUri()); + + if (resource.exists() || resource.isReadable()) { + return resource; + } else { + throw new FileDownloadException(fileName); + } + } + } + private String upload(MultipartFile multipartFile, String fileName, String bucketName) throws IOException { File file = new File(multipartFile.getOriginalFilename()); try (FileOutputStream fileOutputStream = new FileOutputStream(file)) { @@ -57,7 +87,18 @@ private String upload(MultipartFile multipartFile, String fileName, String bucke return amazonS3.getUrl(bucketName, fileName).toString(); } + //private methods + private String generateFileName(MultipartFile multiPart) { return new Date().getTime() + "-" + multiPart.getOriginalFilename().replace(" ", "_"); } + + private boolean bucketIsEmpty() { + ListObjectsV2Result objectsV2Result = amazonS3.listObjectsV2(this.bucketName); + if (objectsV2Result == null) { + return false; + } + List objects = objectsV2Result.getObjectSummaries(); + return objects.isEmpty(); + } } diff --git a/src/main/java/com/ppn/ppn/service/constract/IFileUploadToS3Service.java b/src/main/java/com/ppn/ppn/service/constract/IFileUploadToS3Service.java index b7f0468..b2ffafa 100644 --- a/src/main/java/com/ppn/ppn/service/constract/IFileUploadToS3Service.java +++ b/src/main/java/com/ppn/ppn/service/constract/IFileUploadToS3Service.java @@ -2,8 +2,11 @@ import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; import java.util.List; public interface IFileUploadToS3Service { List uploadFileToS3(List multipartFiles); + + Object downloadFileFromS3(String fileName) throws IOException; } From 4b17d5fc1c0c5ded24fc70f3b39a0b44cfae2acf Mon Sep 17 00:00:00 2001 From: letung999 Date: Thu, 9 Nov 2023 14:09:42 +0700 Subject: [PATCH 16/28] implement redis server --- pom.xml | 5 +++ .../ppn/config/redis/RedisConfiguration.java | 38 +++++++++++++++++++ .../java/com/ppn/ppn/entities/CacheData.java | 25 ++++++++++++ .../ppn/repository/CacheDataRepository.java | 10 +++++ 4 files changed, 78 insertions(+) create mode 100644 src/main/java/com/ppn/ppn/config/redis/RedisConfiguration.java create mode 100644 src/main/java/com/ppn/ppn/entities/CacheData.java create mode 100644 src/main/java/com/ppn/ppn/repository/CacheDataRepository.java diff --git a/pom.xml b/pom.xml index eb9f249..e7ec9a2 100644 --- a/pom.xml +++ b/pom.xml @@ -149,6 +149,11 @@ commons-io ${commons-io.version} + + + org.springframework.boot + spring-boot-starter-data-redis + diff --git a/src/main/java/com/ppn/ppn/config/redis/RedisConfiguration.java b/src/main/java/com/ppn/ppn/config/redis/RedisConfiguration.java new file mode 100644 index 0000000..5db2b64 --- /dev/null +++ b/src/main/java/com/ppn/ppn/config/redis/RedisConfiguration.java @@ -0,0 +1,38 @@ +package com.ppn.ppn.config.redis; + +import org.springframework.boot.autoconfigure.data.redis.RedisProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; + +@Configuration +@EnableRedisRepositories(value = "com.ppn.ppn.repository") +public class RedisConfiguration { + @Bean + public LettuceConnectionFactory redisConnectionFactory() { + RedisProperties properties = redisProperties(); + RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration(); + + configuration.setHostName(properties.getHost()); + configuration.setPort(properties.getPort()); + + return new LettuceConnectionFactory(configuration); + } + + @Bean + public RedisTemplate redisTemplate() { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(redisConnectionFactory()); + + return template; + } + @Bean + @Primary + public RedisProperties redisProperties() { + return new RedisProperties(); + } +} diff --git a/src/main/java/com/ppn/ppn/entities/CacheData.java b/src/main/java/com/ppn/ppn/entities/CacheData.java new file mode 100644 index 0000000..57a8497 --- /dev/null +++ b/src/main/java/com/ppn/ppn/entities/CacheData.java @@ -0,0 +1,25 @@ +package com.ppn.ppn.entities; + + +import jakarta.persistence.Id; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.springframework.data.redis.core.RedisHash; +import org.springframework.data.redis.core.index.Indexed; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Accessors(chain = true) +@RedisHash("cacheData") +public class CacheData { + @Id + private String key; + + @Indexed + private String value; +} diff --git a/src/main/java/com/ppn/ppn/repository/CacheDataRepository.java b/src/main/java/com/ppn/ppn/repository/CacheDataRepository.java new file mode 100644 index 0000000..5128356 --- /dev/null +++ b/src/main/java/com/ppn/ppn/repository/CacheDataRepository.java @@ -0,0 +1,10 @@ +package com.ppn.ppn.repository; + +import com.ppn.ppn.entities.CacheData; +import org.springframework.data.repository.CrudRepository; + +import java.util.List; + +public interface CacheDataRepository extends CrudRepository { + List findByKeyContainsIgnoreCase(String keyword); +} From 222986754e4ff8da22b0f133b4af48a47a9e8feb Mon Sep 17 00:00:00 2001 From: letung999 Date: Thu, 9 Nov 2023 16:59:10 +0700 Subject: [PATCH 17/28] implement search and advance search api --- .../com/ppn/ppn/constant/PagingConstant.java | 6 ++ .../ppn/ppn/controller/UserController.java | 36 ++++++- .../ppn/payload/BaseSearchUserRequest.java | 21 ++++ .../ppn/ppn/payload/SearchUserRequest.java | 7 +- .../ppn/ppn/payload/SearchUserResponse.java | 17 ++++ .../ppn/ppn/repository/UsersRepository.java | 3 +- .../com/ppn/ppn/service/UsersServiceImpl.java | 96 +++++++++++++++++-- .../ppn/service/constract/IUsersService.java | 11 ++- 8 files changed, 184 insertions(+), 13 deletions(-) create mode 100644 src/main/java/com/ppn/ppn/constant/PagingConstant.java create mode 100644 src/main/java/com/ppn/ppn/payload/BaseSearchUserRequest.java create mode 100644 src/main/java/com/ppn/ppn/payload/SearchUserResponse.java diff --git a/src/main/java/com/ppn/ppn/constant/PagingConstant.java b/src/main/java/com/ppn/ppn/constant/PagingConstant.java new file mode 100644 index 0000000..a91e9de --- /dev/null +++ b/src/main/java/com/ppn/ppn/constant/PagingConstant.java @@ -0,0 +1,6 @@ +package com.ppn.ppn.constant; + +public class PagingConstant { + public static final String PAGE_DEFAULT = "1"; + public static final String SIZE_DEFAULT = "20"; +} diff --git a/src/main/java/com/ppn/ppn/controller/UserController.java b/src/main/java/com/ppn/ppn/controller/UserController.java index 174fe71..af986da 100644 --- a/src/main/java/com/ppn/ppn/controller/UserController.java +++ b/src/main/java/com/ppn/ppn/controller/UserController.java @@ -1,9 +1,7 @@ package com.ppn.ppn.controller; import com.ppn.ppn.dto.UsersDto; -import com.ppn.ppn.payload.APIResponse; -import com.ppn.ppn.payload.HtmlTemplate; -import com.ppn.ppn.payload.VerifyMailRequest; +import com.ppn.ppn.payload.*; import com.ppn.ppn.service.EmailSenderServiceImpl; import com.ppn.ppn.service.UsersServiceImpl; import jakarta.mail.MessagingException; @@ -12,16 +10,20 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.domain.PageRequest; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.time.LocalDateTime; import java.util.HashMap; +import java.util.List; import java.util.Map; import static com.ppn.ppn.constant.HostConstant.HOST_URL_VERIFY_CODE; import static com.ppn.ppn.constant.MessageStatus.ERR_MSG_VERIFY_SUCCESS; import static com.ppn.ppn.constant.MessageStatus.INF_MSG_SUCCESSFULLY; +import static com.ppn.ppn.constant.PagingConstant.PAGE_DEFAULT; +import static com.ppn.ppn.constant.PagingConstant.SIZE_DEFAULT; @RequestMapping("api/v1/users") @RestController @@ -88,4 +90,32 @@ public ResponseEntity verifyEmail(@RequestParam(name = "verifyCode") String v .build(); return ResponseEntity.ok(apiResponse); } + + @GetMapping("/all") + public ResponseEntity all(@RequestParam(value = PAGE_DEFAULT) @Valid Integer page, + @RequestParam(value = SIZE_DEFAULT) @Valid Integer size) { + PageRequest pageRequest = PageRequest.of(page, size); + List resultData = usersService.all(pageRequest); + APIResponse apiResponse = APIResponse.builder() + .message(INF_MSG_SUCCESSFULLY) + .timeStamp(LocalDateTime.now()) + .statusCode(200) + .data(resultData) + .isSuccess(true) + .build(); + return ResponseEntity.ok(apiResponse); + } + + @PostMapping("/search") + public ResponseEntity search(@RequestBody SearchUserRequest request) { + PageRequest pageRequest = PageRequest.of(request.getPageIndex(), request.getPageSize()); + SearchUserResponse searchUserResponse = usersService.search(request, pageRequest); + APIResponse apiResponse = APIResponse.builder() + .message(INF_MSG_SUCCESSFULLY) + .isSuccess(true) + .timeStamp(LocalDateTime.now()) + .data(searchUserResponse) + .build(); + return ResponseEntity.ok(apiResponse); + } } diff --git a/src/main/java/com/ppn/ppn/payload/BaseSearchUserRequest.java b/src/main/java/com/ppn/ppn/payload/BaseSearchUserRequest.java new file mode 100644 index 0000000..a6dba9b --- /dev/null +++ b/src/main/java/com/ppn/ppn/payload/BaseSearchUserRequest.java @@ -0,0 +1,21 @@ +package com.ppn.ppn.payload; + +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class BaseSearchUserRequest { + @NotNull + private Integer pageIndex; + + @NotNull + private Integer pageSize; + + private String sortBy; + + @JsonProperty + private boolean isAscending; +} diff --git a/src/main/java/com/ppn/ppn/payload/SearchUserRequest.java b/src/main/java/com/ppn/ppn/payload/SearchUserRequest.java index ed8037a..2a6daa7 100644 --- a/src/main/java/com/ppn/ppn/payload/SearchUserRequest.java +++ b/src/main/java/com/ppn/ppn/payload/SearchUserRequest.java @@ -5,5 +5,10 @@ @Getter @Setter -public class SearchUserRequest { +public class SearchUserRequest extends BaseSearchUserRequest { + private String email; + private String firstName; + private String phoneNumber; + private String status; + private String gender; } diff --git a/src/main/java/com/ppn/ppn/payload/SearchUserResponse.java b/src/main/java/com/ppn/ppn/payload/SearchUserResponse.java new file mode 100644 index 0000000..9501d58 --- /dev/null +++ b/src/main/java/com/ppn/ppn/payload/SearchUserResponse.java @@ -0,0 +1,17 @@ +package com.ppn.ppn.payload; + +import com.ppn.ppn.dto.UsersDto; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +@Builder +public class SearchUserResponse { + private List userDtoList; + private Long numOfItems; + private Integer numOfPage; +} diff --git a/src/main/java/com/ppn/ppn/repository/UsersRepository.java b/src/main/java/com/ppn/ppn/repository/UsersRepository.java index 4304981..7acdd36 100644 --- a/src/main/java/com/ppn/ppn/repository/UsersRepository.java +++ b/src/main/java/com/ppn/ppn/repository/UsersRepository.java @@ -2,12 +2,13 @@ import com.ppn.ppn.entities.Users; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.stereotype.Repository; import java.util.Optional; @Repository -public interface UsersRepository extends JpaRepository { +public interface UsersRepository extends JpaRepository, JpaSpecificationExecutor { Optional findByEmail(String email); Optional findByVerifyCode(String verifyCode); diff --git a/src/main/java/com/ppn/ppn/service/UsersServiceImpl.java b/src/main/java/com/ppn/ppn/service/UsersServiceImpl.java index ca4058a..6da74a2 100644 --- a/src/main/java/com/ppn/ppn/service/UsersServiceImpl.java +++ b/src/main/java/com/ppn/ppn/service/UsersServiceImpl.java @@ -6,26 +6,28 @@ import com.ppn.ppn.exception.ResourceDuplicateException; import com.ppn.ppn.exception.ResourcesNotFoundException; import com.ppn.ppn.mapper.UsersMapper; +import com.ppn.ppn.payload.SearchUserRequest; +import com.ppn.ppn.payload.SearchUserResponse; import com.ppn.ppn.repository.RoleRepository; import com.ppn.ppn.repository.UsersRepository; import com.ppn.ppn.service.constract.IUsersService; +import jakarta.persistence.criteria.Predicate; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; -import java.util.HashSet; -import java.util.Optional; -import java.util.Random; -import java.util.Set; +import java.util.*; +import java.util.stream.Collectors; import static com.ppn.ppn.constant.ApprovalStatus.ACTIVE; import static com.ppn.ppn.constant.ApprovalStatus.PENDING; import static com.ppn.ppn.constant.RoleConstant.VIEWER; - - @Service @Slf4j public class UsersServiceImpl implements IUsersService { @@ -34,7 +36,7 @@ public class UsersServiceImpl implements IUsersService { @Autowired private RoleRepository roleRepository; - + @Autowired private PasswordEncoder passwordEncoder; private UsersMapper usersMapper = UsersMapper.INSTANCE; @@ -75,6 +77,59 @@ public boolean verifyUser(String verifyCode) { return true; } + @Override + public List all(Pageable pageable) { + Page usersPage = userRepository.findAll(pageable); + List usersList = usersPage.getContent(); + if (usersList.size() == 0) { + return new ArrayList(); + } + List usersDtoList = usersList.stream().map(users -> { + UsersDto usersDto = new UsersDto(); + usersDto.setUserId(users.getUserId()); + usersDto.setFirstName(users.getFirstName()); + usersDto.setCars(users.getCars()); + usersDto.setPhoneNumber(users.getPhoneNumber()); + usersDto.setGender(users.getGender()); + usersDto.setEmail(users.getEmail()); + usersDto.setPayments(users.getPayments()); + users.setRoles(users.getRoles()); + return usersDto; + }).collect(Collectors.toList()); + + return usersDtoList; + } + + @Override + public SearchUserResponse search(SearchUserRequest request, Pageable pageable) { + Specification spec = buildSearchUserFilter(request); + Page usersPage = userRepository.findAll(spec, pageable); + List usersData = usersPage.getContent(); + //mapper Data + List usersDtos = usersData.stream().map(users -> { + UsersDto usersDto = new UsersDto(); + usersDto.setEmail(users.getEmail()); + usersDto.setPhoneNumber(users.getPhoneNumber()); + usersDto.setPayments(users.getPayments()); + usersDto.setFirstName(users.getFirstName()); + usersDto.setStatus(users.getStatus()); + usersDto.setCars(users.getCars()); + usersDto.setGender(users.getPhoneNumber()); + usersDto.setCreatedDate(users.getCreatedDate()); + usersDto.setUpdatedDate(users.getUpdatedDate()); + return usersDto; + }).collect(Collectors.toList()); + + SearchUserResponse searchUserResponse = SearchUserResponse.builder() + .userDtoList(usersDtos) + .numOfItems(usersPage.getTotalElements()) + .numOfPage(usersPage.getTotalPages()) + .build(); + + return searchUserResponse; + } + + public Users checkLogin(Users user) { Users userCheck = null; if (user.getEmail() != null) { @@ -100,4 +155,31 @@ private String randomString(int length) { return resultData.toString(); } + private Specification buildSearchUserFilter(SearchUserRequest request) { + return ((root, query, criteriaBuilder) -> { + List finalFilter = new ArrayList<>(); + if (request.getEmail() != null && !request.getEmail().isEmpty()) { + finalFilter.add(criteriaBuilder.equal(root.get("email"), request.getEmail())); + } + if (request.getGender() != null && !request.getGender().isEmpty()) { + finalFilter.add(criteriaBuilder.equal(root.get("gender"), request.getGender())); + } + if (request.getPhoneNumber() != null && !request.getPhoneNumber().isEmpty()) { + finalFilter.add(criteriaBuilder.equal(root.get("phoneNumber"), request.getPhoneNumber())); + } + if (request.getStatus() != null && !request.getStatus().isEmpty()) { + finalFilter.add(criteriaBuilder.equal(root.get("status"), request.getStatus())); + } + if (request.getFirstName() != null && !request.getFirstName().isEmpty()) { + finalFilter.add(criteriaBuilder.like(criteriaBuilder.lower(root.get("firstName")), "%" + request.getFirstName().toLowerCase() + "%")); + } + if (request.isAscending() && request.getSortBy() != null && !request.getSortBy().isEmpty()) { + query.orderBy(criteriaBuilder.asc(root.get(request.getSortBy()))); + } + if (!request.isAscending() && request.getSortBy() != null && !request.getSortBy().isEmpty()) { + query.orderBy(criteriaBuilder.desc(root.get(request.getSortBy()))); + } + return criteriaBuilder.and(finalFilter.toArray(new Predicate[0])); + }); + } } diff --git a/src/main/java/com/ppn/ppn/service/constract/IUsersService.java b/src/main/java/com/ppn/ppn/service/constract/IUsersService.java index 873489d..0d6744d 100644 --- a/src/main/java/com/ppn/ppn/service/constract/IUsersService.java +++ b/src/main/java/com/ppn/ppn/service/constract/IUsersService.java @@ -1,9 +1,18 @@ package com.ppn.ppn.service.constract; import com.ppn.ppn.dto.UsersDto; -import com.ppn.ppn.entities.Users; +import com.ppn.ppn.payload.SearchUserRequest; +import com.ppn.ppn.payload.SearchUserResponse; +import org.springframework.data.domain.Pageable; + +import java.util.List; public interface IUsersService { UsersDto createUsers(UsersDto usersDto); + boolean verifyUser(String verifyCode); + + List all(Pageable pageable); + + SearchUserResponse search(SearchUserRequest request, Pageable pageable); } From 1e01d3a93028f863f346bb6453bc42b91d1946a5 Mon Sep 17 00:00:00 2001 From: letung999 Date: Sat, 11 Nov 2023 03:35:02 +0700 Subject: [PATCH 18/28] =?UTF-8?q?handle=20get=20=C3=83=C3=83information=20?= =?UTF-8?q?company=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ppn/ppn/controller/CompanyController.java | 56 +++++++++++++++ .../com/ppn/ppn/dto/CompanyProfileDto.java | 16 +++++ .../ppn/ppn/dto/CompanyProfileMappingDto.java | 14 ++++ .../java/com/ppn/ppn/entities/CacheData.java | 3 +- .../com/ppn/ppn/entities/CompanyProfile.java | 32 +++++++++ .../ppn/entities/CompanyProfileMapping.java | 26 +++++++ .../exception/ResourcesNotFoundException.java | 3 + .../ppn/ppn/mapper/CompanyProfileMapper.java | 15 ++++ .../mapper/CompanyProfileMappingMapper.java | 16 +++++ .../ppn/repository/CacheDataRepository.java | 2 + .../ppn/ppn/repository/CompanyRepository.java | 29 ++++++++ .../service/CompanyProfileServiceImpl.java | 68 +++++++++++++++++++ .../constract/ICompanyProfileService.java | 13 ++++ 13 files changed, 292 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/ppn/ppn/controller/CompanyController.java create mode 100644 src/main/java/com/ppn/ppn/dto/CompanyProfileDto.java create mode 100644 src/main/java/com/ppn/ppn/dto/CompanyProfileMappingDto.java create mode 100644 src/main/java/com/ppn/ppn/entities/CompanyProfile.java create mode 100644 src/main/java/com/ppn/ppn/entities/CompanyProfileMapping.java create mode 100644 src/main/java/com/ppn/ppn/mapper/CompanyProfileMapper.java create mode 100644 src/main/java/com/ppn/ppn/mapper/CompanyProfileMappingMapper.java create mode 100644 src/main/java/com/ppn/ppn/repository/CompanyRepository.java create mode 100644 src/main/java/com/ppn/ppn/service/CompanyProfileServiceImpl.java create mode 100644 src/main/java/com/ppn/ppn/service/constract/ICompanyProfileService.java diff --git a/src/main/java/com/ppn/ppn/controller/CompanyController.java b/src/main/java/com/ppn/ppn/controller/CompanyController.java new file mode 100644 index 0000000..8d74dfb --- /dev/null +++ b/src/main/java/com/ppn/ppn/controller/CompanyController.java @@ -0,0 +1,56 @@ +package com.ppn.ppn.controller; + +import com.ppn.ppn.dto.CompanyProfileDto; +import com.ppn.ppn.payload.APIResponse; +import com.ppn.ppn.service.CompanyProfileServiceImpl; +import jakarta.validation.Valid; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.PageRequest; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.time.LocalDateTime; +import java.util.Date; +import java.util.List; + +import static com.ppn.ppn.constant.MessageStatus.INF_MSG_SUCCESSFULLY; +import static com.ppn.ppn.constant.PagingConstant.PAGE_DEFAULT; +import static com.ppn.ppn.constant.PagingConstant.SIZE_DEFAULT; + +@RestController +@RequestMapping("/api/v1/company") +public class CompanyController { + @Autowired + private CompanyProfileServiceImpl companyProfileService; + + @GetMapping("/dateOfEstablishment") + public ResponseEntity getInformationCompanyByDateOfEstablish(@RequestParam String date) { + List companyProfiles = companyProfileService.getInformationCompanyProfileByEstablishDate(date); + APIResponse apiResponse = APIResponse.builder() + .message(INF_MSG_SUCCESSFULLY) + .timeStamp(LocalDateTime.now()) + .statusCode(200) + .isSuccess(true) + .data(companyProfiles) + .build(); + return ResponseEntity.ok(apiResponse); + } + + @GetMapping("/all-companyId") + public ResponseEntity getAllCompanyId(@RequestParam @Valid Integer page, + @RequestParam @Valid Integer size) { + PageRequest pageRequest = PageRequest.of(page, size); + List companyIds = companyProfileService.getAllListCompanyId(pageRequest); + APIResponse apiResponse = APIResponse.builder() + .message(INF_MSG_SUCCESSFULLY) + .timeStamp(LocalDateTime.now()) + .statusCode(200) + .isSuccess(true) + .data(companyIds) + .build(); + return ResponseEntity.ok(apiResponse); + } +} diff --git a/src/main/java/com/ppn/ppn/dto/CompanyProfileDto.java b/src/main/java/com/ppn/ppn/dto/CompanyProfileDto.java new file mode 100644 index 0000000..bb4c4cd --- /dev/null +++ b/src/main/java/com/ppn/ppn/dto/CompanyProfileDto.java @@ -0,0 +1,16 @@ +package com.ppn.ppn.dto; + +import com.ppn.ppn.entities.CompanyProfileMapping; +import lombok.Getter; +import lombok.Setter; + +import java.util.Date; + +@Getter +@Setter +public class CompanyProfileDto extends BaseEntityDto { + private Integer companyId; + private String companyIdName; + private Date dateOfEstablishment; + private CompanyProfileMapping companyProfileMapping; +} diff --git a/src/main/java/com/ppn/ppn/dto/CompanyProfileMappingDto.java b/src/main/java/com/ppn/ppn/dto/CompanyProfileMappingDto.java new file mode 100644 index 0000000..b48873c --- /dev/null +++ b/src/main/java/com/ppn/ppn/dto/CompanyProfileMappingDto.java @@ -0,0 +1,14 @@ +package com.ppn.ppn.dto; + +import com.ppn.ppn.entities.CompanyProfile; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class CompanyProfileMappingDto extends BaseEntityDto { + private Integer companyProfileMappingId; + private Integer companyId; + private String ParentCompanyId; + private CompanyProfile companyProfile; +} diff --git a/src/main/java/com/ppn/ppn/entities/CacheData.java b/src/main/java/com/ppn/ppn/entities/CacheData.java index 57a8497..740882a 100644 --- a/src/main/java/com/ppn/ppn/entities/CacheData.java +++ b/src/main/java/com/ppn/ppn/entities/CacheData.java @@ -1,12 +1,12 @@ package com.ppn.ppn.entities; -import jakarta.persistence.Id; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.experimental.Accessors; +import org.springframework.data.annotation.Id; import org.springframework.data.redis.core.RedisHash; import org.springframework.data.redis.core.index.Indexed; @@ -23,3 +23,4 @@ public class CacheData { @Indexed private String value; } + diff --git a/src/main/java/com/ppn/ppn/entities/CompanyProfile.java b/src/main/java/com/ppn/ppn/entities/CompanyProfile.java new file mode 100644 index 0000000..ce0760d --- /dev/null +++ b/src/main/java/com/ppn/ppn/entities/CompanyProfile.java @@ -0,0 +1,32 @@ +package com.ppn.ppn.entities; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.Date; + +@Entity +@Table(name = "CompanyProfile") +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class CompanyProfile extends BaseEntity{ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer companyId; + + @Column(name = "company_id_name") + private String companyIdName; + + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "dataOfEstablishment") + private Date dateOfEstablishment; + + @OneToOne(cascade = CascadeType.ALL) + @JoinColumn(name = "company_profile_mapping_Id") + private CompanyProfileMapping companyProfileMapping; +} diff --git a/src/main/java/com/ppn/ppn/entities/CompanyProfileMapping.java b/src/main/java/com/ppn/ppn/entities/CompanyProfileMapping.java new file mode 100644 index 0000000..41b33cc --- /dev/null +++ b/src/main/java/com/ppn/ppn/entities/CompanyProfileMapping.java @@ -0,0 +1,26 @@ +package com.ppn.ppn.entities; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; + +@Entity +@Table(name = "company_profile_mapping") +@Getter +@Setter +public class CompanyProfileMapping extends BaseEntity{ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer companyProfileMappingId; + + @Column(name = "company_id") + private Integer companyId; + + @Column(name = "parent_company_id") + private String ParentCompanyId; + + @JsonIgnore + @OneToOne(mappedBy = "companyProfileMapping") + private CompanyProfile companyProfile; +} diff --git a/src/main/java/com/ppn/ppn/exception/ResourcesNotFoundException.java b/src/main/java/com/ppn/ppn/exception/ResourcesNotFoundException.java index c19f817..c8abb37 100644 --- a/src/main/java/com/ppn/ppn/exception/ResourcesNotFoundException.java +++ b/src/main/java/com/ppn/ppn/exception/ResourcesNotFoundException.java @@ -12,6 +12,9 @@ public class ResourcesNotFoundException extends RuntimeException { private String fieldName; private String value; + public ResourcesNotFoundException() { + super(String.format(ERR_MSG_DATA_NOT_FOUND)); + } public ResourcesNotFoundException(String fieldName, String value) { super(String.format("%s: " + ERR_MSG_DATA_NOT_FOUND + " %s: ", fieldName, value)); this.fieldName = fieldName; diff --git a/src/main/java/com/ppn/ppn/mapper/CompanyProfileMapper.java b/src/main/java/com/ppn/ppn/mapper/CompanyProfileMapper.java new file mode 100644 index 0000000..923627a --- /dev/null +++ b/src/main/java/com/ppn/ppn/mapper/CompanyProfileMapper.java @@ -0,0 +1,15 @@ +package com.ppn.ppn.mapper; + +import com.ppn.ppn.dto.CompanyProfileDto; +import com.ppn.ppn.entities.CompanyProfile; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper(componentModel = "spring") +public interface CompanyProfileMapper { + CompanyProfileMapper INSTANCE = Mappers.getMapper(CompanyProfileMapper.class); + + CompanyProfileDto companyProfileToCompanyProfileDto(CompanyProfile companyProfile); + + CompanyProfile companyProfileDtoToCompanyProfile(CompanyProfileDto companyProfileDto); +} diff --git a/src/main/java/com/ppn/ppn/mapper/CompanyProfileMappingMapper.java b/src/main/java/com/ppn/ppn/mapper/CompanyProfileMappingMapper.java new file mode 100644 index 0000000..01c0698 --- /dev/null +++ b/src/main/java/com/ppn/ppn/mapper/CompanyProfileMappingMapper.java @@ -0,0 +1,16 @@ +package com.ppn.ppn.mapper; + + +import com.ppn.ppn.dto.CompanyProfileMappingDto; +import com.ppn.ppn.entities.CompanyProfileMapping; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper(componentModel = "spring") +public interface CompanyProfileMappingMapper { + CompanyProfileMappingMapper INSTANCE = Mappers.getMapper(CompanyProfileMappingMapper.class); + + CompanyProfileMappingDto companyProfileMappingToCompanyProfileMappingDto(CompanyProfileMapping companyProfileMapping); + + CompanyProfileMapping companyProfileMappingDtoToCompanyProfileMapping(CompanyProfileMappingDto companyProfileMappingDto); +} diff --git a/src/main/java/com/ppn/ppn/repository/CacheDataRepository.java b/src/main/java/com/ppn/ppn/repository/CacheDataRepository.java index 5128356..f72c5de 100644 --- a/src/main/java/com/ppn/ppn/repository/CacheDataRepository.java +++ b/src/main/java/com/ppn/ppn/repository/CacheDataRepository.java @@ -2,9 +2,11 @@ import com.ppn.ppn.entities.CacheData; import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; import java.util.List; +@Repository public interface CacheDataRepository extends CrudRepository { List findByKeyContainsIgnoreCase(String keyword); } diff --git a/src/main/java/com/ppn/ppn/repository/CompanyRepository.java b/src/main/java/com/ppn/ppn/repository/CompanyRepository.java new file mode 100644 index 0000000..2fe3cc0 --- /dev/null +++ b/src/main/java/com/ppn/ppn/repository/CompanyRepository.java @@ -0,0 +1,29 @@ +package com.ppn.ppn.repository; + +import com.ppn.ppn.entities.CompanyProfile; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.Date; +import java.util.List; + +@Repository +public interface CompanyRepository extends JpaRepository { + @Query(value = "select new CompanyProfile (c.companyId, c.companyIdName," + + " c.dateOfEstablishment,c.companyProfileMapping)" + + " from CompanyProfile c " + + "join CompanyProfileMapping cp" + + " on c.companyId = cp.companyId and c.dateOfEstablishment >= ?1 " + + " order by c.dateOfEstablishment desc ") + List getListCompanyProfile(Date date); + + @Query(value = "select new CompanyProfile (c.companyId, c.companyIdName, c.dateOfEstablishment, c.companyProfileMapping)" + + " from CompanyProfile c" + + " inner join CompanyProfileMapping cp" + + " on c.companyId = cp.companyId" + + " order by cp.ParentCompanyId, c.companyId") + Page getListCompanyProfileId(Pageable pageable); +} diff --git a/src/main/java/com/ppn/ppn/service/CompanyProfileServiceImpl.java b/src/main/java/com/ppn/ppn/service/CompanyProfileServiceImpl.java new file mode 100644 index 0000000..b4a65c2 --- /dev/null +++ b/src/main/java/com/ppn/ppn/service/CompanyProfileServiceImpl.java @@ -0,0 +1,68 @@ +package com.ppn.ppn.service; + +import com.ppn.ppn.dto.CompanyProfileDto; +import com.ppn.ppn.entities.CompanyProfile; +import com.ppn.ppn.exception.ResourcesNotFoundException; +import com.ppn.ppn.repository.CompanyRepository; +import com.ppn.ppn.service.constract.ICompanyProfileService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +@Service +@Slf4j +public class CompanyProfileServiceImpl implements ICompanyProfileService { + @Autowired + private CompanyRepository companyRepository; + + @Override + public List getInformationCompanyProfileByEstablishDate(String date) { + SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); + List companyProfiles = new ArrayList<>(); + try { + Date dateOfEstablish = simpleDateFormat.parse(date); + companyProfiles = companyRepository.getListCompanyProfile(dateOfEstablish); + } catch (ParseException e) { + log.error("cannot parse string input value date to date {}, with error: {}", date, e.getMessage()); + } + + if (companyProfiles.size() == 0) { + throw new ResourcesNotFoundException("companyProfiles", date); + } + List companyProfileDtos = companyProfiles.stream().map(companyProfile -> { + CompanyProfileDto companyProfileDto = new CompanyProfileDto(); + companyProfileDto.setCompanyId(companyProfile.getCompanyId()); + companyProfileDto.setCompanyIdName(companyProfile.getCompanyIdName()); + companyProfileDto.setCompanyProfileMapping(companyProfile.getCompanyProfileMapping()); + return companyProfileDto; + }).collect(Collectors.toList()); + + return companyProfileDtos; + } + + @Override + public List getAllListCompanyId(Pageable pageable) { + List companyIdList = new ArrayList<>(); + + Page profilePage = companyRepository.getListCompanyProfileId(pageable); + if (profilePage.getContent().size() == 0) { + throw new ResourcesNotFoundException(); + } + companyIdList = profilePage.getContent() + .stream() + .map(companyProfile -> { + String companyId = companyProfile.getCompanyIdName(); + return companyId; + }).collect(Collectors.toList()); + return companyIdList; + } +} diff --git a/src/main/java/com/ppn/ppn/service/constract/ICompanyProfileService.java b/src/main/java/com/ppn/ppn/service/constract/ICompanyProfileService.java new file mode 100644 index 0000000..09d6229 --- /dev/null +++ b/src/main/java/com/ppn/ppn/service/constract/ICompanyProfileService.java @@ -0,0 +1,13 @@ +package com.ppn.ppn.service.constract; + +import com.ppn.ppn.dto.CompanyProfileDto; +import org.springframework.data.domain.Pageable; + +import java.util.Date; +import java.util.List; + +public interface ICompanyProfileService { + List getInformationCompanyProfileByEstablishDate(String date); + + List getAllListCompanyId(Pageable pageable); +} From a694744e38ed81fbc21bef5803749bb674c4e098 Mon Sep 17 00:00:00 2001 From: letung999 Date: Sun, 12 Nov 2023 15:46:32 +0700 Subject: [PATCH 19/28] handle cache data for get all users --- pom.xml | 8 ++++ .../ppn/ppn/controller/CompanyController.java | 3 -- .../ppn/ppn/controller/UserController.java | 46 +++++++++++++++++-- .../com/ppn/ppn/service/UsersServiceImpl.java | 3 +- .../java/com/ppn/ppn/utils/BuildCacheKey.java | 12 +++++ 5 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/ppn/ppn/utils/BuildCacheKey.java diff --git a/pom.xml b/pom.xml index e7ec9a2..3b2fe6e 100644 --- a/pom.xml +++ b/pom.xml @@ -27,6 +27,7 @@ 3.0.11.RELEASE 1.12.319 2.11.0 + 2.13.4 @@ -155,6 +156,13 @@ spring-boot-starter-data-redis + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${jackson-datatype-jsr310.version} + + diff --git a/src/main/java/com/ppn/ppn/controller/CompanyController.java b/src/main/java/com/ppn/ppn/controller/CompanyController.java index 8d74dfb..5d315af 100644 --- a/src/main/java/com/ppn/ppn/controller/CompanyController.java +++ b/src/main/java/com/ppn/ppn/controller/CompanyController.java @@ -13,12 +13,9 @@ import org.springframework.web.bind.annotation.RestController; import java.time.LocalDateTime; -import java.util.Date; import java.util.List; import static com.ppn.ppn.constant.MessageStatus.INF_MSG_SUCCESSFULLY; -import static com.ppn.ppn.constant.PagingConstant.PAGE_DEFAULT; -import static com.ppn.ppn.constant.PagingConstant.SIZE_DEFAULT; @RestController @RequestMapping("/api/v1/company") diff --git a/src/main/java/com/ppn/ppn/controller/UserController.java b/src/main/java/com/ppn/ppn/controller/UserController.java index af986da..108ac52 100644 --- a/src/main/java/com/ppn/ppn/controller/UserController.java +++ b/src/main/java/com/ppn/ppn/controller/UserController.java @@ -1,9 +1,16 @@ package com.ppn.ppn.controller; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.ppn.ppn.dto.UsersDto; +import com.ppn.ppn.entities.CacheData; import com.ppn.ppn.payload.*; +import com.ppn.ppn.repository.CacheDataRepository; import com.ppn.ppn.service.EmailSenderServiceImpl; import com.ppn.ppn.service.UsersServiceImpl; +import com.ppn.ppn.utils.BuildCacheKey; import jakarta.mail.MessagingException; import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; @@ -18,6 +25,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import static com.ppn.ppn.constant.HostConstant.HOST_URL_VERIFY_CODE; import static com.ppn.ppn.constant.MessageStatus.ERR_MSG_VERIFY_SUCCESS; @@ -38,6 +46,9 @@ public class UserController { @Autowired private HttpServletRequest httpServletRequest; + @Autowired + private CacheDataRepository cacheDataRepository; + @Value("${cox.automation.email}") private String coxAutomationEmail; @@ -92,10 +103,39 @@ public ResponseEntity verifyEmail(@RequestParam(name = "verifyCode") String v } @GetMapping("/all") - public ResponseEntity all(@RequestParam(value = PAGE_DEFAULT) @Valid Integer page, - @RequestParam(value = SIZE_DEFAULT) @Valid Integer size) { - PageRequest pageRequest = PageRequest.of(page, size); + public ResponseEntity all(@RequestParam(defaultValue = PAGE_DEFAULT) @Valid Integer page, + @RequestParam(defaultValue = SIZE_DEFAULT) @Valid Integer size) throws JsonProcessingException { + PageRequest pageRequest = PageRequest.of(page - 1, size); + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerModule(new JavaTimeModule()); + + String cacheKeyUser = BuildCacheKey.cacheKeyAllData("allUsers", page, size); + Optional cacheData = cacheDataRepository.findById(cacheKeyUser); + + //cache hit + if (cacheData.isPresent()) { + String userAsString = cacheData.get().getValue(); + TypeReference> listType = new TypeReference<>() { + }; + List resultData = objectMapper.readValue(userAsString, listType); + APIResponse apiResponse = APIResponse.builder() + .message(INF_MSG_SUCCESSFULLY) + .timeStamp(LocalDateTime.now()) + .statusCode(200) + .data(resultData) + .isSuccess(true) + .build(); + return ResponseEntity.ok(apiResponse); + } + + //cache miss List resultData = usersService.all(pageRequest); + String usersAsString = objectMapper.writeValueAsString(resultData); + CacheData cache = new CacheData(cacheKeyUser, usersAsString); + + //save cache + cacheDataRepository.save(cache); + APIResponse apiResponse = APIResponse.builder() .message(INF_MSG_SUCCESSFULLY) .timeStamp(LocalDateTime.now()) diff --git a/src/main/java/com/ppn/ppn/service/UsersServiceImpl.java b/src/main/java/com/ppn/ppn/service/UsersServiceImpl.java index 6da74a2..ee8dfc7 100644 --- a/src/main/java/com/ppn/ppn/service/UsersServiceImpl.java +++ b/src/main/java/com/ppn/ppn/service/UsersServiceImpl.java @@ -92,8 +92,9 @@ public List all(Pageable pageable) { usersDto.setPhoneNumber(users.getPhoneNumber()); usersDto.setGender(users.getGender()); usersDto.setEmail(users.getEmail()); + usersDto.setStatus(users.getStatus()); usersDto.setPayments(users.getPayments()); - users.setRoles(users.getRoles()); + usersDto.setRoles(users.getRoles()); return usersDto; }).collect(Collectors.toList()); diff --git a/src/main/java/com/ppn/ppn/utils/BuildCacheKey.java b/src/main/java/com/ppn/ppn/utils/BuildCacheKey.java new file mode 100644 index 0000000..c85cba6 --- /dev/null +++ b/src/main/java/com/ppn/ppn/utils/BuildCacheKey.java @@ -0,0 +1,12 @@ +package com.ppn.ppn.utils; + +public class BuildCacheKey { + public static String cacheKeyAllData(String prefix, int page, int size) { + StringBuilder cacheKey = new StringBuilder(prefix); + cacheKey.append("-") + .append(page) + .append("-") + .append(size); + return cacheKey.toString(); + } +} From 63f95759e3606b8e1a098e37ada4bd910c6c62f2 Mon Sep 17 00:00:00 2001 From: letung999 Date: Sun, 12 Nov 2023 17:07:25 +0700 Subject: [PATCH 20/28] handle cache data search users api --- .../ppn/ppn/controller/UserController.java | 29 ++++++++++++++-- .../ppn/ppn/payload/SearchUserResponse.java | 6 ++-- .../com/ppn/ppn/service/UsersServiceImpl.java | 2 +- .../java/com/ppn/ppn/utils/BuildCacheKey.java | 33 +++++++++++++++++++ 4 files changed, 64 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/ppn/ppn/controller/UserController.java b/src/main/java/com/ppn/ppn/controller/UserController.java index 108ac52..766eb21 100644 --- a/src/main/java/com/ppn/ppn/controller/UserController.java +++ b/src/main/java/com/ppn/ppn/controller/UserController.java @@ -147,9 +147,34 @@ public ResponseEntity all(@RequestParam(defaultValue = PAGE_DEFAULT) @Valid I } @PostMapping("/search") - public ResponseEntity search(@RequestBody SearchUserRequest request) { - PageRequest pageRequest = PageRequest.of(request.getPageIndex(), request.getPageSize()); + public ResponseEntity search(@RequestBody SearchUserRequest request) throws JsonProcessingException { + PageRequest pageRequest = PageRequest.of(request.getPageIndex() - 1, request.getPageSize()); + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerModule(new JavaTimeModule()); + String cacheKey = BuildCacheKey.buildCacheKeySearchUsers("searchUsers", request); + Optional cacheData = cacheDataRepository.findById(cacheKey); + + //cache hit + if (cacheData.isPresent()) { + String usersAsString = cacheData.get().getValue(); + TypeReference responseDataType = new TypeReference() { + }; + SearchUserResponse searchUserResponse = objectMapper.readValue(usersAsString, responseDataType); + APIResponse apiResponse = APIResponse.builder() + .message(INF_MSG_SUCCESSFULLY) + .isSuccess(true) + .timeStamp(LocalDateTime.now()) + .data(searchUserResponse) + .build(); + return ResponseEntity.ok(apiResponse); + } + + //cache miss SearchUserResponse searchUserResponse = usersService.search(request, pageRequest); + String usersAsString = objectMapper.writeValueAsString(searchUserResponse); + CacheData saveCache = new CacheData(cacheKey, usersAsString); + cacheDataRepository.save(saveCache); + APIResponse apiResponse = APIResponse.builder() .message(INF_MSG_SUCCESSFULLY) .isSuccess(true) diff --git a/src/main/java/com/ppn/ppn/payload/SearchUserResponse.java b/src/main/java/com/ppn/ppn/payload/SearchUserResponse.java index 9501d58..f07e797 100644 --- a/src/main/java/com/ppn/ppn/payload/SearchUserResponse.java +++ b/src/main/java/com/ppn/ppn/payload/SearchUserResponse.java @@ -1,15 +1,15 @@ package com.ppn.ppn.payload; import com.ppn.ppn.dto.UsersDto; -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; +import lombok.*; import java.util.List; @Getter @Setter @Builder +@AllArgsConstructor +@NoArgsConstructor public class SearchUserResponse { private List userDtoList; private Long numOfItems; diff --git a/src/main/java/com/ppn/ppn/service/UsersServiceImpl.java b/src/main/java/com/ppn/ppn/service/UsersServiceImpl.java index ee8dfc7..6df76b1 100644 --- a/src/main/java/com/ppn/ppn/service/UsersServiceImpl.java +++ b/src/main/java/com/ppn/ppn/service/UsersServiceImpl.java @@ -115,7 +115,7 @@ public SearchUserResponse search(SearchUserRequest request, Pageable pageable) { usersDto.setFirstName(users.getFirstName()); usersDto.setStatus(users.getStatus()); usersDto.setCars(users.getCars()); - usersDto.setGender(users.getPhoneNumber()); + usersDto.setGender(users.getGender()); usersDto.setCreatedDate(users.getCreatedDate()); usersDto.setUpdatedDate(users.getUpdatedDate()); return usersDto; diff --git a/src/main/java/com/ppn/ppn/utils/BuildCacheKey.java b/src/main/java/com/ppn/ppn/utils/BuildCacheKey.java index c85cba6..f623804 100644 --- a/src/main/java/com/ppn/ppn/utils/BuildCacheKey.java +++ b/src/main/java/com/ppn/ppn/utils/BuildCacheKey.java @@ -1,5 +1,7 @@ package com.ppn.ppn.utils; +import com.ppn.ppn.payload.SearchUserRequest; + public class BuildCacheKey { public static String cacheKeyAllData(String prefix, int page, int size) { StringBuilder cacheKey = new StringBuilder(prefix); @@ -9,4 +11,35 @@ public static String cacheKeyAllData(String prefix, int page, int size) { .append(size); return cacheKey.toString(); } + + public static String buildCacheKeySearchUsers(String prefix, SearchUserRequest request) { + StringBuilder stringBuilder = new StringBuilder(prefix); + stringBuilder.append("-"); + if (request.getFirstName() != null && !request.getFirstName().isEmpty()) { + stringBuilder.append("-").append(request.getFirstName()); + } + + if (request.getEmail() != null && !request.getEmail().isEmpty()) { + stringBuilder.append("-").append(request.getEmail()); + } + + if (request.getGender() != null && !request.getGender().isEmpty()) { + stringBuilder.append("-").append(request.getGender()); + } + if (request.getPhoneNumber() != null && !request.getPhoneNumber().isEmpty()) { + stringBuilder.append("-").append(request.getPhoneNumber()); + } + if (request.getStatus() != null && !request.getStatus().isEmpty()) { + stringBuilder.append("-").append(request.getStatus()); + } + + stringBuilder.append("-").append(request.getPageIndex()); + stringBuilder.append("-").append(request.getPageSize()); + stringBuilder.append("-").append(request.isAscending()); + + if (request.getSortBy() != null && !request.getSortBy().isEmpty()) { + stringBuilder.append("-").append(request.getSortBy()); + } + return stringBuilder.toString(); + } } From d3e5f31e5e5c21de417182e2b738ef6ec5b7d684 Mon Sep 17 00:00:00 2001 From: letung999 Date: Mon, 13 Nov 2023 13:05:46 +0700 Subject: [PATCH 21/28] add a field response data of CompanyProfile --- src/main/java/com/ppn/ppn/service/CompanyProfileServiceImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/ppn/ppn/service/CompanyProfileServiceImpl.java b/src/main/java/com/ppn/ppn/service/CompanyProfileServiceImpl.java index b4a65c2..27c2739 100644 --- a/src/main/java/com/ppn/ppn/service/CompanyProfileServiceImpl.java +++ b/src/main/java/com/ppn/ppn/service/CompanyProfileServiceImpl.java @@ -42,6 +42,7 @@ public List getInformationCompanyProfileByEstablishDate(Strin CompanyProfileDto companyProfileDto = new CompanyProfileDto(); companyProfileDto.setCompanyId(companyProfile.getCompanyId()); companyProfileDto.setCompanyIdName(companyProfile.getCompanyIdName()); + companyProfileDto.setDateOfEstablishment(companyProfile.getDateOfEstablishment()); companyProfileDto.setCompanyProfileMapping(companyProfile.getCompanyProfileMapping()); return companyProfileDto; }).collect(Collectors.toList()); From 741861b4a7457531ace53e8d872ec578cbf4f26a Mon Sep 17 00:00:00 2001 From: letung999 Date: Tue, 14 Nov 2023 09:25:20 +0700 Subject: [PATCH 22/28] add READ.ME for config send log from application to cloudWatch --- README.md | 42 +++++++++++++++++++++++++++++- images/Cloud_WatchLog.png | Bin 0 -> 76613 bytes images/CreateLogGroup_Streams.png | Bin 0 -> 79787 bytes images/LogInsight.png | Bin 0 -> 89837 bytes 4 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 images/Cloud_WatchLog.png create mode 100644 images/CreateLogGroup_Streams.png create mode 100644 images/LogInsight.png diff --git a/README.md b/README.md index f400b65..98e6365 100644 --- a/README.md +++ b/README.md @@ -251,4 +251,44 @@ jobs: The follow of system - \ No newline at end of file + + +## Set up logback.xml to send log from application to CloudWatch + +* Step 1: You will go ahead cloud watch of AWS and create a log group, each log group will have a lot of log streams corresponding to environments. + + + + The follow of system + + +* Step 2: Create logback.xml file in resources folder simple like that: + +```xml + + + + INFO + + + + + + + +``` + +* Step 3: Create CloudWatchAppender class to handle logs, you can see this class in my sources code. + + + + + The follow of system + + +* Step 4: When you can set up successfully, you can monitoring logs of application in cloud watch, if you want to look for a log, only click Logs insights tab and use the command line with filter by correlationId attached each request. + + + + The follow of system + diff --git a/images/Cloud_WatchLog.png b/images/Cloud_WatchLog.png new file mode 100644 index 0000000000000000000000000000000000000000..d3f92d025e4ed257ca58c673cbef7d1278a8d7db GIT binary patch literal 76613 zcma%?cT`hN+wN6F5TuDTX(}oR3L?^*f`EWDX;PyU=|n&v5FjD~(v>P*q=OKV5=sgk zrAw~~y+h~$(oWRpdEf6_=lpTj3L)9E_sr~>*?ZsD@46GAudBg8cbV?gsZ$J^j~_if zb&6W+)TuKrv}Y(=>LQiUlz-=6JvQ??b&9d|_uuIx5k~G)r>>mReDu%=G_iq*0J9HI zlgR`&f~bGsoxa?%>7ucE7m@0!iEASzLV0i0-(S?fd(DRVqvE?88xIZ61~Xr~JbsN< zNIN9*!KvWH8$W~%-afea_`LWhOe(RvJGE}P7YRp?;^*kR`h0pbQe~lDO3U5kp)2p0 zRcNnL-ikukAwSOi=Udx6vHb0sXz_MV+uv_6M{*xS=2qL^Z?@4MQBt6|e+xr!|85pP zhy8b>?FICoBY(fmDAx6d1(F360on25;$EVZN-bTYOd~)!+a|xOYywZ&v9o>0pgkQL z4tejkbIzMvz_nWGLt(;{T`!>LZ|=2r*9~b=>K>h*3vISwqtr@i>Q5Ko9h}nfSny1? z-5E+lOt-fA{ua8Avkkoj+4M+2A6&S{3@==H^Shf(;An9W6WWXZL+~9MEX2oaIR&6S zSra&lTNX1iHr|}5#QsA6wDF5;U9Q1S?HQ4;wmU16r?X9<#Dx%JmraI*EQs3IZJ0*( zK!H#C-OW-enzkiOrXq|q2*KcC9k=9W6e)eCbYkoreEDlF^{e?=Vo02_m7$^6!b*j2 zO3;;4B(41Z;a&)2t{kNxoV*OF7<^VXIUnfqdU~{nVAq?{aA*Q(7+Kvy#bw{qxQ(fH zz;I*j?pPsrD>oagpYTq8Qd_1p8{@?ac?3A~XOw3Y-KmksStv2im#eOc6aV&AM_jPZg(^wNp?ukOHU=AT_snbHnoV!1KWxS+0yc zptXIj`Q`}a&xYH4@VFLf5(z0!B77ratTn~nb8Eh-Kf*Uc7y$_L8z#(^d9WyyK-G$GBKfN zGVyq?g-IE{d(wJy_JN0q#V(9;E=w-+1)YWW1G*P(Nh}A}A5@y(?|D!-6KKH?7IWwJ z;3zZ0MI3>bxanIlamup#1cXn^QWC%0k>_aEBL7kZSt*GSf24UAbtF&T<28kvFz`v8 zw9Bz7fiRyz7!uh^+0EzW3xkgvFpL$UG#2F&X57u)pt$!O)==bZ8*Pz-^?yEAniN!N<@Ji^~+M`cSi^bMfb}0il3Hozya#Dd}?A87i0WU`I)um&ja?l_z zJb>&~`?&X7)A8U^cS_2Jh{As9WIg4S6+!$hRtdGFvbiOeyJE@8 zL%G>U;I$1{D~3b6%bHu-cxtahFN>WV=+*ReXVA)uPrMo5C1%A=M!c$cz4P7D_JuY> zFIGxQ!gs!dyQbWH??wAeP_^#kwN9!~?tAjm5^#vGQ_ak=UJXg!enlisX$N{b#M-jX zC-d2N=jo17A~t4c=n}AYC%YvKRR>xc*yyIFZ_i_>pHDdeP;JRd$lD&ZG@fd&^*-*4 z0KRlUO14DdP*0wuqORB)0sR(f2EI#Rczg`u;k6Rtekbr~>L>@%XLp@WYO}25%|XiE z&0jXotCA?IXT%{l7FU@r9}Yd-!w@yU9?r)pODRqA9QD13(PiQZ*xqSf4nvZNp;2?F z29#X?_5Ny`9)FL0FAM;*JI>Ld_{-FYq}|mSg;Hz?Xw!iBe_JM!H@vKs6(1hO{0x?@ zbBV6JsPO#;1TPU{7_<2EmOza={^@N3i9lVUa)>XzjQx7zAZnJ7BL1vowh!~^xZZP7 zGu1^wnW5{)aB}NuY9a?v^!BrRGLE5%-FGyU`uIlTw@CXy!>w=g6&l;1gAyC>$bN zlO?V$Bs`9;Is3R>>_PqY;1+Je4+PawAnnY-abe)>*ZpbMwBkk9br-VL{t2g3aDsA4_I0d4pFi%x^nz= z5lh}D2VE7*K4@2{2zX-dzh6IpfEbWS7Ja-*-qwLldCvpDhdib$ zZ%#*hh_vFX9`)^JAEu|(-Iw=T9SVnVy~9PQru1rWY>ZEem7^AGvaJR09NS|PsmHkn zH58KjE4`W}n3SZi$?o*+=1`swkvr&5;{F;G8$vSr@67)CVLHsG z)s#mWYxAEZwHZ#K4E2dtX77;9pB`-`V(Bkw83& zVNPcP3%vp=`ZEnD?`u0gfcIIT9zG{b<9v2*laIxK6Og0rUIbuR4|Q@NjTtJ#QB9Lp zO5D+)Nu6nb^qz(6>KeW9B!rGFoS5qU08^Kdy+i2QtrJ&ua`0ezwScmT6 zum<;^WEJaLFPy2(?0&cJWv+U5u`afbKr!E#BYZx~e04;XGUi2hRW(46kQGS7MSF#m zg%T?KwJR)Z|4wGE&_bz_)2?kXl5X}Z&V)m6z452d0?(2Qs9%p8A4%21+B=>{Bvi^`SX9f+6WOWLun{^q;( zY+c1J$lBWQuIexIMdvVk0^%*VyW{D>lP>y27YXPS*hJq5$bo}<=HYtooK z-+7ebOT-Pv%5ncwio=liN7xG1LMiuN>14`{|9aLZn^^$=mfI50l*S)RTF*U$MNL%~ zs-0VQ;aGY)8(*=nD)ZoLY5YbN31M5#jlJI<&VN5jcnnkMzE6oKq2*!(SOJ0+%-NR(xRTt%v zEn)b9B}RvRpU&z>rdIm`Ud%5HuS#Gpr?h_>*1Lk}Rz7pk)@<7GM2ZC79ZFSi#k2B5 zJzj~P#R6B7eT^PWE6k&S*zzJ-gh`hsH(hIueTQzM(dbI6{F73$smmLEv!LpMJj)6i zUY+WBYhhV0O}z2^dx5>O+>}k``{IFnvK6!WgEpbgr@yR`2c3BIIxEB!B3&8`_88cm#xkGT*ze!hrVt0Z0732LzZ_ezZf{6k^~<$ zA9UAU_nE7|W;g``%JqffUeJefM7j9B&iDCz9PaGQxLBLKT*85@T@2`{6SGcKk&Qn0 zw%#&vVm@%K=yh4K>$-yk=GiwleEA^e5(IChNx5t+NZ`fDXD{aN?**dn zt_R+UpB>W#GqJIerwz7;1=C@7@Xx)E^SegNt+;pBj*?}ot>dchw3%y`$^ed5=QuV^ zQr85u_fhgullbltw7S70q83)60%M{t?Mbi0owKFJ>*|_$mlPDNw1h9PpT(m0<^9ZC z&Mo`al2XphjJgvZ9k0K=q1^`cotl=-njvg-9~&lIBoSD_exoc2_x(rf$Dvr|map*z z-Gd)U(34kh_*PT33zdqzXM6)>REP9A@~-hfygR)a@^e`jx$s)s+Rwx` z+c3of$!nH^Yfw*bc1oPbhIZL^*C%3HLyYDF)UXJaLA#FxJ?uL1`J4pP&DhJ8ubcXt z`WzwY3ER_zH)u~eudSgx5)%fbNLiIUXqOx;CC0i^vpw_iW(VHvq}DVi-2ZyrHES?V zT4`R|`*1QQ0;k?aN(ML7IUGRrzrA_btFDK9*`Haw#2~oFNam%HZXA77jo=eNuPvzu?1(_Fb!JJ*QmOwcYB8Xcn6f z>?w{ZKCcUfj*rXtY_%rukO&>^w6Dsr{R_tQ*!&NtgZVh<)v3Gh?+gcr92Q<{uqGX5297pch8~L z@Ajffp#v=8L~D5N%aD^ZVmTa1+7Yy%&hE>?ZRz-u%QWV^TGZt*Eg+}Xqg3E+I1U)c zO(x=P^&-eO70HnZ70j|VdpZzPr!nP9iP>I}mBFx$)IHXl_Cfx6kMv1uWirmp);V#l z%%rUnYjwm8O?uQX_oa&TKEKOraF>$90vy^DFp&Gm|GwobQu`eX>K05uS0;M%j160C|oG>joHf=wd_;`qv zb^oi&6F5rz#XBWdQnaQvhu-5+hc^BZyt+uTRwKk_Lc#5D@Wx&|KZ*<9;l_-Ok@4CA z4$cuJLc+Nkw?W>C87bW2>|D+v`X_4?7PD*^e}QiDN2huYMD zVni-_$2y9>zupR5B!uAGRw|F$RW6%fo!XaJp5%j9KN91)`~iYfo1hI0h>~4XHI8rX zQBavWe%Tkmi3Z~e;<8f@XO~nAM#>_1E}z1N$AGM60+%fI6uUZfGHxg9Wy&^49Sq6S z#8{S?vR6OMiPDJWk_)-ty(4@WzP6_9z4=Q<(!tjK zEoA?7=*0{?>Ft$cz|^h-IHGA7AFwIFz?MPQsqk>J4!YCB2@=C~bG2X%HT3S0e(Um> z7~B_ka$A0y7r>RV1FUG&@G1grh-kGeY6Zv8I>+FqJ?TVTbg@TwZCfX`Rd%dx-#K~3 zJgKCrNN2?M0kdpaK8Vjh*xa_BOyWJV%m!in(oV^RZ-z>s25#zqQ+HdwWa5%yT-QHr zor^n4cIC3@h#IimCe_GeSH}+y1xY^m7!=-?K0{VHA10~VqKf5X`9|EK%}u-naSgnZ z;|1f3j307}9|1y^0v5Mm)4rZe0*r;(rQLpqBe`C%H)qVDATpV_05J`yGWkIGKE=d( zq7aBfSn&nU0f4B6XSZ2`wn9^!t@MTq#s}9j>BAVej`6y@RLjeQ-fLx19*6fFK_B?rIxE`FAhwqXVy$%0nDmKS z!*w&lxQ?nXxcnd})vrr}4O29DvMFX=@>S-;uUi+eK+cNlyrs2Yk_mik$?=Cy9y*pF z=-@jg4p1D2a!fGpH!bqn9EaV2fXiBT*CTOD7wk{E?X%5UGrZS-jW>TWzDSd_pf?~; z>_sQ?b_Y2)7KbV=R;Uq|%+bI)S?HOLGNIF5v&Z|S#nNTNzF^X9ye^(yRI!yq_$hW< zh5C%E!-cAwC;)&I2fa?vC0K|tjch?00+(yEje8qBw!>uUi8 z3%+qTdU}$V)~K)^>5X_|XgE?SXy7?`mX{j9g>s)nmVVqpe&2Q0v9qeT{3X+WkOJ91 z+3DafEiL6=`_-2aEaj>&B4|c{olI0KD{X^vjh8g?bwoX=|+7_bC#a`M#E1p$`A&HVw}AuXkF1d0hoTXNR}2Rem&GvAi-? zdI+3(+}GO@rgvTq_U;k3e8Z=iYx`+8pyCCms)WAZQi>MbCXEe|(?5(A&zE_J5_%zU zJPbx^ine>aB}7Zs!=Z=IgzUw{jUh%F(&xYzs9fZwkX{2W*iyP>0L2 zqwmojuf`uYY%(d&Ux)nw&#|)!V?fYdMR~6PphGrF=SeDX^A%|*3xW^5uZ77df(-QI z|HSRB;4;ZU`JA`WF%V3;I0%5$?bPHI2k#Lu?wNR?oku{VD4bhYF-7V6A? zx_SCUai}cIM-uzMz7^Y2ryOm%F6Jz4b*}vZ-=^xDk4kbuP=~pbb2<$q79}xmVUEl1 zO#!3%hfT*;4O3?Kk5A&wc+q|&Y4V!?WFN46F=z{k&tgHilR!seeBhbb7m5(ymnY_j3m9pIBSvT{3}#%edDJD zn-b_krv9uisAIJj9u>%!`a_^XHox{6tcOVT=^(L!ScLee?KZs@b=^y)Y!GnG&UgXF zt0h`?*1ZAC5Az9HiR8Prma2{V<=o4QttG$uC62)FtjTMm@hw!-ZOs;2n>QOtEyMhP zhJqfBZ@y&6Le5dl2U8T1C`OhwvNIhmg<5gTEL3w?a~YOp$K?cuh6j0Dq$Ep|OlLEC z)TsHPT!oIBZ&LcSr|JAEvCWIScqS zzLz{IIBK9U<8KyM)U=O1pIDChrlX)+47 z07Oxsd-WMP$&(7hWgr2*-TLc`K=!gOq$o)jf_@OR;!SKhttLq4yAY%0SRGgG|H&Mx z2b&}jPx>~S_L>*HAoVU&eI{^1SNB4!fdBMu6VO(L#lS^*g#3y$w>7a1G5{6pArx0E zElLLov!es>{Lxp5Hl-5`t3Y*$WtbwI1Brz~3U_0~+ug<~P(K2>h;~2D#=dVpOy5H> z+f80~O(4v@_ge0y2O)hnCy>pmD8zd3&G2%HonS*jyFQl!yjOP@qd2;h$lsq65@mgf zi@%l{gB_5>-dejt-E1fO*Ao|Y&ec4IOA}UzzIKT1^)Ek1yE6Faat%qqDZ|VQ-=qnR zfxe_gJzs;@=GT7P3+J$Yr8ZMfg^PN&Thkn@gE{SW2M5@QNP-214lCc>)?doW)xwk zQct0OJ0}!}CQkfZ{auQk^T$v6>n~9xR4m0m+L^T2$}@?i#0#SA(2F}Nvt^XeoFTZ{ zp8*tuNiEdXX33IbIn|E*uhRdQLG`Dlzy8)A&*rb$_1hr&yODF08ry)=YX0|FkElb6 zUp6Fc#E$CCR0fz>TL-N^%{|7`bNFnGceVXvqe*HS?W!50#D6w_qj}W`9`e}BM2mjB;!*JS0XRs`P+oQC5 z=R7vmoaz731f{E2x0N`t+~k^$zoQ_X(e%4R{|wRZZl3*PKv7DYDBb+sr+)?yu2D2T zOB``L^RFZL=N$hv4bNd&{#y_z(ug1U(zgo>Oh&=-zBe%%m<2qLZ>^zn5J^AnZ<4$* zsJm1tu-x>D;z<3|4>-^R1VIWx+P>7K&KnvQcJ~ChtWHolK>FFS86!DXc*e~vE5Dki z*Z?0j$R_h9ewlrA=u;8dEXW}B-IRU4eVPblyIS5=M8?bn>R<@pb=tI(dxD`33&9

`af;^DCA`hBp&hCs%|r{j^S?u<-(iY0?N3qoxW*`~oa7fPWS-{#<`BtbXPGq=$a*$F4|B$To5};FqpI zBH#^~QqBl|JW&>_=N);>^Nm>Z|XqDLm+?*&Y zjmFt9wLcCvun^&m$0i+#MI85)^KCe@{2+ul*gfpn0&bSR#t|m7OrC!re$03Q>+GT_ zlNXsK4hpoB*F~Me+o5Hq>(cJ*3&&F*W;sNYPatgcZF)ALG3g`*^e789#+@MMyXmeA z)k9=xONN!LzWk}z2OFJAc1JT3zeg%)%T;M(*!?fr>VQ>CV=sbRv=`N61FPHufdFDv z3@)-xq+_Y`*Hf0Gf!*?#S(@S7B}wacUx&sWE?UQYsPT@q$S(c{_1<__n!yGG08 z^RT3iWULorLS@Us?-Jv_vjEhU&g91uZDJk}1R5;G z=KS5vn8Azz@=^FMS(7mdjgNG)ICz&WqHfKHrdYUWCh$qpg3`J@_8ACJ2TX1{gn-PW z7Wm0kO^uM$?|Yv2;mTQiVhN4m`SwUj-`&SiU+%zRXRr<3^3M|jQvA}R4JAK8mhA!) z_+$lRRJ+%9p+mR4FlKcglLb>wM4-3ap|(`47HD57_x$__xL#0ULSXinZ};@&7+PMe z_LI!7neBtcqHtK4@Dt6|ml@X01M_8ESZ(dDEU*C;Xa`!zq`V-wjbsOjjaeT>iTO&V zx0}vKU=<3gX{wIHn6XrQYC`On`&^7igKOMoN>y@gJZK$E+e_7WwB_`-6Rdc;rb3q0 zhQ$=8t(NbaBm*SSMvs@dPLSfddUAe1lXn?4Uv6H{eD+LHQ@7wCz8q`x=qpW8O_I+X zVM16wD&sbr*)&uSuv|YW{eJFZSB%zauJsl?CueR^u%yJmDeO9R@?10iCEekjw5t}E z(9%I8k{E4PfN_UE+8O&@PWmc!O61r$+sUq*Kio7=r4wPUP~aR$qbW8rHmtU7&DfKT zPm$R{x*C==p;KuTW{J04y1vXH*ucuSv2f1~gimx#v953Z!`-)QF$~^I(jCj(1Z?)> zz6|fajeHyypmkI!qIWJDp@$!E?i-UsT1|4sKwFRe9q>{SIR2{lGXd*PTYmH>4|I3( zd~(3srELwmL(A21x|<`x&*7#8DnW=kYtzf|vXfe+t9LpkqO|;7)_5HcgR1xh_13Ge zbr9_M`u*$)&O2TK#jj8k#>M(0-j|B?T}AS{h6|_Hc9cYv3*&}|F22KUO+=TnvpwCE z*iou&DNF`oUo+gYd61}h=Vg3;W&>;bJOj@+0kh_YtUO)e!iU0&9c#3^QrvB|53A-j z(IeAk3$8-EqmM%q69$G2@>JIH?z!L4Yt+{FSFCp4LvJQsOw zHX~Im6%~ z=z(xXx*FrbZuw0PDnR#VF9DHlYl~0$Yo2j2xQ}Sda(74{k#BuuZ}{n2!^kg+^LcG{ z1%%DTrKP=H_3Q^@jqKPkRp0%Iu~H|poZ_=JYdM@=T1-H z$_=99@@$apA^*thFt%nOZHsvN)scY7PimqAC(5`0gyTzrl5LVaud*#=>tWkEV8i1QQ>~GD$rb+wOP-Xawjdv73xt& zpME)PS?G1r;gS8>jk=AA-Ui)e&6wAbK8lNFg|%l5fQkohb6xz)Pi$JFj_J$rPcp9D zD`pX_3Y|!&ij+4RKXrL*bHjR^mu^{@O5B|vyJs?eiLb%MYDZ{q$nFf5c62lMvi6Ej z=rx#e=!n&gSJLVtB|hlzx-T)Bp*UVR1J5sv<%=6qMD4PJ9Sx~6?B@gzlQvHuifA>0$#?M& z#Sp^JUO(3=`Sx4P%3k4z$F1NzO(R@+;^j(Y`-%kBF0C_-FuyZ_D=qrRq4ostp|$Vy z2OB^0{Sa^gjw@{DQp{rIbS*$d)@T{i&|_h@00}H}onPJkidC59v}1_vF==$ghFvA% zMNe6r?y5atTp??n9S)$hFg9(q>d9dyS6{0ML7p&NrxB;%hPJ_V_-HswK5o-NK~&|7 ztV*u3jeyqFng z&A(Y7;jUn^l=KUyn4%+z zVOfF=mKw0#s}q~7T-YRsizh<#RJMy=>eFDXcIccFRJdvv4fjwDrq=sqVo^FnFrP*2 z?gxbT0~$;Ya8*#y0R(~3*|><`C}0k-v?5`O}DSGrPz#6EzmxRdbfCV_)O6?OSLwhYhdyg z*^cj8uMued1ZnhW)5F-c)D)j5`c^6w=l^VGM2=>eo5OMaURmX@K@NjB5H=*hyXUJe zh~Mc#Ug5A9C)RCBi%BOf&0O_F+jcfkUpU|ZPb+}#NtP))|HN|cHPh^US+w@Ss?cHw z^>Nnh1@AWeo1+3+kQh^HvOm@s1l{66r@33@Hg2b$f=f4l6W{t&wwK@q?O-w* zPJeKCv*AYXM#U8wfElE6rU(?ECX2V{hAF=QH>*JgjO|RQ!DB-pa05va@P%Z+C z^*xQEZYj=ABV=bUMUr8cFY%=!yDVqzV)?Oc9>D5StPd?hHaDwKv032@@v0(r3_rji z5L3579CYvZzc`~^?GxM@^qNt#@1s)bNO~n<-}G(pOZ|aSUtbZ-`U<)l6&yQg=L_b{?wQ(Lj2> zz4?=EDPs@jqYbL+5Ll`~+$N>G%o;5yM)eiEb^)OVKW~SZ+l0CwN^e?%fO?LFF%=jW z@A`C1i&vj5khj96^9#J1GUvn)K^aUIK`&haWswql;@L#tozyP;h0xXzP|4|;4|H-$ zWyugD1qQu8)*$-Kgy4Eybf34i8j$K<88sO_tk`ezk{l1z$f9lRH^a$S(H{}4WzgY$ zE4iHbwM}ta((MeO4p95ycKA7~$rI=BonGxr>uu~G=)6zmLGFh7S}gBR^oQ?U8hmoj z*wMxz7A)lX12Mzr_QJG*3LE;ysHmXqsm{q_iTL`uU8wKXlcjR{Z1cX<@@uEC0zLL+ z>RPuDXs6-4V%3bJwT#!sLLVR|p_~b})Ey5rsgl^@loor8!EjTh`^{i1Tii|KkFR=8 zeW6RD;}>F)Gt2diM+r3@TWoTXl0)~=B7NgQt$txgB}+JAx@ITb*DsNWW)MaZpkfQLyI~b~4-=|y>Ppb-dQ*YR8H5OLX+UPj*QO*(_DgUDjy(~)vi!szHkprEsEK0Q zUZ$0e(^!Fv(@oxkG_O~MQg(H%o=UGw&!^BA_)0SqnY?tmN06M5IEfMB2>9IX3E%P4 zdzqCNzgkV7tn{nND@(3Sq#~f-g#_E0NwL!%%P+ZzWQsH>T=DIpe;)f2ramuXE>x9Z zCRbNVk985oAqFw-&9%d6d*p?}%IsvMD`Z_rkuSLTfCBAe4k*;ZV~|GY{)2Rb(xcC& z8I`A6d(rQkI1Jjwhs#;+lz*e0s#T+&e>pT`y^Q6k@qkMosMkHTVSu_;u^*!)8j$Umk<5!Z!HW#PO6E^QWUaNWBMWhLCHJ)C%4-DVhFD2V0}I& zT@@KD$bNZ#TRNbg`dLMHHm;9~MJPp-TT? zt^dVg{x5<2GxuDn}{!x82`_>c8}clH0)iZ-4(_o&OND-v#_!*t%zb z!B+K63WfR~APTNQOL_glPQSU)87u`Y{fn`f|3kq30I&a_qx4G~@$q}h;_lS$E+ELF z*5U}Eue21pb64#jt~R=jq{}hMsR_gJm!A}Yp?NQ$G4kFg7y1nUMQy(U)}4!3OqbP- zZR?{5r9LFehGF{X-#$`0e}@yxz{IN*<{t1)pUQc3!)Mb0d!1kZcBa)Y)O4jtUp&ii zzW4jgpuA}A=_5Qnla%1amCt4+3!51uP0wTkQxMiouWhRjiC$5P6nKzs!t8(H4?HmE z0=J~4%_Zr%OrAVbQ{F5zO_XWybS>w(!>09HgadRwM!uhvbKuOfCRcw;c}$|eILRF^W-de!ivw`5ZrPD7n`&xu2&;@#4Au$!-E!_?Z zROIxGht0}W)4?@NWm;-OZfzz@72Ropj_P%(J?G*+5F+NE7(HeMFxEoc0zK`Eh3*ow zgGTD8H@I8V1I`!PyVBRSR&y+Xdu*DCm0vGKO&7Dg(TPa>Id;ivcjR>#oxX^ zZqV3&I_qyH({lqkBED$#-2ID??IDRmMSss8kPn1X@b!+O?S+*9o_cFxZFfdqvnBP8 z^_N--N}*{Ue4LhJj;2JkKaAZ>%M41L<$p_Lgx^*#L{Ip>jp<&P@8q*;uT=4kCyb%5 zfamH>7?oM|@@0L5^QL?boM8U&zUk^+Cq+}$ZFHa+@exNf&PVzszEOtWrEkLBtBmus zTaLd)L*Jn6hY=}Qv-!BOiVdz?TeH%lW8c+mc6FsDZnDWK^=aFXPz%o``ph0HS@zS| zOo<9yjaM4^-82_x7_OI*3aUl`iSet+d##_5eIbxln%k>awtCmsyxZD+(b?qmtLZM? zVp`3V!5=OhPOG!IxUosMx99@nMTvg@#N$eO%1HdVWS^9oa*q#-mA=jQeY|w?xIHwn z%`>U0VjF$toBnT1AV=focM5Ic=w|f7tFSEu_hPc$ndW0JMwZ|Bwr>pU?pirr>1Em> zhdDRN_nCC1Ps?epe5H*FbaVp&T>q%XYS(?{$DAKNar9bks5V5o&3r8XIMmAit&Bcl zpS8xoOr(M2H*zS#f% zjD_s9B4(Z@~Q4LjQa{~+SGx$Y1 zSi>4+wx(IYC&xPznD4`{!i_Q0!J!ubaaKcpS1ULpx`qaKXXevC3O^ z;q<`G!w$KGZ=xE*H76*O-Kfb+XcziLM;2qOJ4L~Wy&f9_oA$wDa(_|b5g;smek>I> zkX7-Bb;W#cGX>4|s>bBj)7Q)A+HxMz_u~3EX4GE;fnGvrusO3UJWi(ip%EOHWz7Q; zQ|8W0zJgO7yM--IdpG_tPD`ZuRjqveH#M~u!;X5{bn z`4WWbUd$HmQ>2Mkc&k{H##Sm_%eSL+*7faMkg>zo=^$UKaHG|Ib&*ycFcfW~IGyI{ z9P(NHto_tGn#yT5+#4eyF_U*jGMvov7jlMY5kk6^o5eiChU-76M6M+|A0zjsX?rC- zJBCZKXNG5O<^qkJBg1gJ42x;X1C!qiI)&O?Gq;>>s)pfo>2_`@?BKon#h|?#l7ugk*Qxw8q00? zezCPW@tXtEV~K#%M!s;3$7=h1_-dnD%F%_NUOpXvv1OUKEyi1A8YpRz48?6u^Zlw29+Ggy~mpmaCqy&1J}Z949myEnKyu3y(356F?d zyHpGxO$dW3QoH^MWQxjuO7hySU+8eW34)&dpa$`u->~gt2&O!L0y)=x^en#Hb~q$6 zCEb4%cf1p0Hg3^tv_>A6D8#Rq_Ej0cM`KANS;-IQSqpnFE57I~9>l!)j~#}*F+(Kq z2Jt~_w~0F;4RM!JjE&mEaFO%?4n9Kte!TD;aR8UU)s}BDKmY*@PBuS=g!ld^H8Dxd zngW0Z8tSSanmO&{D0YTKGDI=4@exP7mzL0vJezl{^V)PhAJn(zhy`HHpBeZptpwRy zbFB({j7+1xp}4Uv3FCS9kmKmR;sqwtz}E+B#+3E&kIe_W4aXq90iQNMxMQNMN!V$A ztC&(!LdT&n?$RCRH6lz?Mo#84|0wktjCn|9&6C(K;?&s8o3GA9B;>u5R{FuJd@x|< z-=CE`LSNmsxAhbBJ-E5K-0skorkwCUyHw``y`{};i^N;6Rr<3S2Q!a8&AKq=K7g^ z4>x&^Z#j5QL?m|o1-Om9=#%MNu=ys zuhSKl20j(bv=#9c+sMA}Jcw(bYgP-ncu$$th3zH7nv&~JmXI$fGere)rxOmga~L0N zcFo)jEI*~Q3Xuwe?rl$uE}-%8I<^^#JbivSDe83-a=E7REoX%hRvU-e8fb2tO&!(O zWr0o4%v|F7A6h9}EHfwJE3J*@i3tfxLbpXrYxBIDUCw7zw~ZOEI!Ii+xEzrnHXfh+ zLFQ%KPrBu=y=A<^>q%b)PU1UNyTWfU0LW#j1dioR@ukcCmA!Ibe6BR$l+^E~v!dw* zuiu2!`ny@x@BBCJ#e4ukeO=bGzBg?>D8DU&(7!pqvSSQ=t!H6jvr`hgV_tOJufUxo zqLiLAM2zWsf=m+FrrIIldlKT~zx%k%Zl%GVj53+-gkD)QMRzJ2U3>y(%Qt_}P$j)- z#Op3*tvJ<+SM35M>OJMkz@km`Mvr0&yDR15C5$GMd`i^UEU6f>0N*+kjqc45%eSFW_4zcIcp&)C5%`9U1dhQJ35MWDA=GZ7U zT1ZM}7s$OGH`;O}Fc%nYCq?;@*_Xb`Im~E>9z|XJ<5>r4H{zj7H}=!M*Hv|f=bGcH z8;2+>!TQ-jHL`V3+&T&kc1sYfJf!jY1*!D#C+F>0AXAi=Ql#aa8o}Wua4`Kkf`7Xy z2SiDwYz@>G@vf{fs%7t4K|_MchHpRaC|yrIyZo=wesvou3WDa;M&Q^mU-=f#--HBq zZ$I*S(!froU)f9Gzkt0FgJ7ZvkOSL#TsEw5fXBI<$x&}o-_^jY4VO>3ctw-U zY|k><`)OLpJFsF!P?(BWk8i_A1!!DU4fcI9Vtk}x>`rh#Q6_Eg}q?_Cc zDWqz33*UFNp)&lDsVY$E_^s0Epouk!t)hc?v6t7Kn)X62o*&04co0z-8VT1X+Y6SWF%kN*8sM~Y^2ZRt+uYUF;)qR}Ey5tSKJ{&6dy>qH%`v4Or zSa}@430zF`rs(8aHiwLKm~x!s4_LcfJ3fqNckrAWD9}a+eHf`xB0%;-ho&k zRlFIK*pRJjtqJN`)M?sH5*mg%O$-dt#7P^a4Pf%oK{NbWwcKyP+5-?<=XmiSzk67TScShWzqQd>QRi7@BowGn3WZ3j34d|p zR?N+);?$%ctEr)Q-PPDgjn32Hy?Ud$bjf3x)ZArM)_D`r2<==U-~d$IOS0&*l)yVy zn8mZ=_Xe-aR3@*iOUH)vYBl(XMV?pzF$2yg+4t=xi`IXel zvZ^$6@X{6mm#4D6o1`mXH~EPxzsN+cT3nFzjN$Cf!)*x%Kr|u0WRThe9hsEXn zUtW@DvD4Mt&ps}w8+-KXaCITBs^#uQ9}_PbslZh4&mUe#Bxv?k#JT`z0V8c6>Dt&* z&WKNqPT)X8qtU@px(OeQr4nK3kZRDgA+N6V!n7pO{ASkaTrs2Mq7N=Cxx?*)C5|Lq zEzZ9f8k#(ITVmN3?s4hYeHJaFna%kaj|9F?C&dNJG*e2Q_}-sBZJ9QFD&BW4rQAl> zgvl{}Zsh!nthy|^hx~m;KNtgsCP|fv2j+lpj_(*tPg=lg3}bpaofPx`^Btx}aH)yi zXKxdK1rUFgTVk}Q@pQn|o$|b5!`YJh(EMh=&!|kgsOQy^)p8z_P55gP&1$?Ez~%@` z;Q$`CNZi&b2E_#X=Z`Trs~bY!1^u9jc^42oJrry@f-dv3A-ONim@t+0YvDXOX*yx> z*C?2mdF@-Tv2MlV^Yg1XpD)k9O@!5-YqVsor6s%Z$V8ST( zegG_XqtoQ`KutCkQ zTs}9&+i8UibI2kO&AFSwZaF-6=5k}}XAmy-UVSeQ>scOWF7F1M-kC+1MJhyJIu*R- zuIGJb`0kRDqF?4+xM^EXt$da04tmmPxs!R_`u!)m#WZz5=`N3r74b?948L5aOrR_k z$pcjZIW&GL>3u9NcvPG)`B=FTBfhYwRmHae(Ub8|`D|MvsvcOo91@;kDZ?qo}O zUBtuEfFA;5Atgf<{vqq&4MhFOV!FBB$H+Ha-=-LNp5}LQmoa;71pcT=xg=o<)_{YL z%~EOj%twkrFAy|D66Dm zrl|iJi}amzp|w^I8J%(@;dKr*$!&Tb{p3M}G)OdvsvWK1clnwAfnkL|Zr~ zQ^7?w^C!m!h}6d8be)}qpL5#t$!-I3#JFRw!~DPAh>^YRUEz0cu}Hr9Qvys)$cr{> z@><&}ffD|()emw*vnc19VS%0#lr6GneM*RFn%-_`DpX!`Oco#1uyj@ z^wxel-`ygUP9Bywp)FyWD@>CBXZ}_cW)dzg5_2_pN&1w@W%s8swk&e{Q}|F5I_ChBz))c zxB%Vg^1{u2XqVz3$-()**){*KC4zsOJRxCxGJ0R(M0ed|cERK!Qw;;-{;!IKl15)- zCI7&rI=t8$+=6VW#D;oy1 zD_0K^Q4-rcUR}_R^rU&x)5LU9?P`Mi_?L9#&Z=) zCM%SWJS9jku259Sl}HF$Cuuf zXydPkpVJw))CSJ6YEBTfBlTGXv%gfOy0)=*GK~=cuyV@cz^kOHbp|5Mopklz@0@fA zOp)ps7sKF`8e>ge?T@&eGozLT@9r2p;mRlfnB-rW$M7G&TD8)e3<* zV~VujdVNlY!u#1bz*VrebESjFNE=xdj%lMl5&0ajIY~H-7>9L?W2L^7N-w@+L*kC~ znP6b}U7zD80y#jB*;LfRYny)$yj&=8JXjxgt79nJwF5kMe}F1K_Vc*TQr)U%6A$4F zv~ah_R(?9StV%63lpO**{bGfOoftdaet>Q5YcYFv65#oV zR;b03Od+VJ+&8l_Q8k1-8D5j6y=#6YRLhZvG|&Trd)DGtxCg%PT+#dk53VC&r23Cf zQu-mYgL^=S0WRAGK;Q&>+%wie!ve`vc5KI0Z}@0d6I#kAb#tYAEGm6Hh>Rlh9fIOL z#^9;M$q4){rk#B9Md|PWbRV9^13qQZggj*i|?}UpdV_?2o;D z1D$_5v;9-Poyv^=l>EP@yni?$lK%LgGo9GuH)KssDzz#_iQCJ|w#J_SCFlXLK;vCa zoT<`xQ)YKkccTK?-z)-YY9wV$f^gWmg1@0cM~@Je6X2o~Y47wbC(e63$*BJ}kd|Fu zoODd7E=h(Pomv9i+AXUwDIP6Fpo%eW`obu9;8JzPQyPJYu3cz92$W)XAX%LXymt0i zJ*ebH>Y-Hc*D>d5rTd*W9%14!04j3^Q9e)6Y3W_|I;=mgY)rnzI-<(|&JY|Wb`KnS zSu8J`*Yu#IEWeN`9Z|OFBLPL7GD}XU%7mu1?H)U-s!Yc2!2ET)#QtL z=8X|+9udC3H>@gOBxj)`p*&C0>)mhTHJ<_RO-Ck+Y(lIi9wl!V%J2sH^VC)kPom(B z`F#rBicqk8&-b5|hQ`zhs$`GWcuZ76mK$HpzmAD?3JW^&aI;8p5wcQ{>`W-}m_lur zWQV7&z&Ir75JM3DG~<>mGcc`l%8KUv>QCp(r&$Nmo1aFJ!h0w@?Y;0^4vcyeb-Ra$ z3V?d^qch)dlkHP{zftaac#AdG~~f9D6f&#tTf#piAnDs9TY!_;BZU~iZrQ`6H}RMQ0cbu1%q$_n<k|Kkn^oF7wXhkkM4d$whr`lH!4Gg}>xyKlj%aSCroyjIUHyvOovnl?n;GT{CC8Qg zFLTeH8=EtWbz=3|2^5NHzT(-7nKA=2egUS@2hluY^=Z&fxBJd3-YS*K^>wWs$8VobSgNPu53mlF%w>v@9r)Y4ILfx_kiZfaND z*Iy;sJS~f0{$xyJdEtT6E?G4U3qRnsjM~n+bCuB9_|d4`M!^zCmzhB1 z`LXh{xY2>QMR7T$f?BNgW0>}NjQfW-gLxbLpT6K1ulX3xOdy)3O z1pK2@VOY7P&}~4`EK-=Qj}AJIS`dI_vw@jwci?Fn?%LhB)f{T4Czb?wD2YCkPCeYUs;yek+z(z zwFcXM_TsveCLt}6=I=J=b>{MhX`H5gMJO|t^VJHS-UTnkhV3zGtNruLXP-}yCl{@t ztrg`s$?NM3G@*F|f#)~LyGfk(#DR9BIX9h6UZ2UPVD}Al*4^ACPG*I_f$%l53mOXMW@WmwLlzP&>;( zKQFK_W0Mv+oFNn#MXSfJZXm;-myLTi;3pZ)f9krK@M159ZLyjA`!);#f8Vny5;DJ~ z9=#CuFq(4btqzwtEikvM2UdZNKS{rlrx&MF(x*De;?sZb+qCqAbE@Q!a z=W(-ke>+xMmg(OJU3nVC$S=n?<|jAW+2^Upxc&;przi?|VyC33TCaQQ?`jdug|SYCvhw)>C@&2ldGYnpCDx2ig>zzHQ(}UpGoqxJQ#-1g<>3J;tO;$L!MSB;aG;*ANKm@azq#vXpt#NaPa zCKSVOB%1EOvw)cVL|h=cC-gztXMU^5fgI;{dYWrtjTLWSSH{yTQFnN_UwNI{#KH^M z^s@PKOZmV|y769Q>pBy6l;q3yuhVZ1okru@Y%KC3P`;y8nQeE8Mwf6yu*Nu^)*MS? z&q>B}(HqZL>yrhZ2H_r(`6MRd*y^dKs7PlRq>+IZ?#sr>P9cD4XQzp2vWLj*5A*HL zs;{S$*wqeomNxo9R>g#BRe>@{xTa3YR0toQsofvaR=ddmO?H=Tab2cjGOp7okG2 z?3IS9aEmyJ6L?I5kR)s=8a|LXGSK$D1{J7r?}nWP`FvhEYfitj(8TX&dV*(3tAZ=9 zZ-;gP&--_**!&C$vj2l&jf5KY(rpI|EZURBx!_g%X&igR-OWf+=di1yX3NgAOIPwby2XF;`K56NHKo8l`*^)ch~Q$b zg=;7(4Ex(3c&A4C5;m7lS)I{Nh-b(>uh0J6mmEGJl$Qb#3m}9q=d_sz8EBZeZZ@Fb z)Tc4#hT5sK2@ZMhOOd*=2A8FOUYwn?>L$7k5=2eJmIF2Ce8ai2RIkQ}xes?zb5P## z>~q%7-zBN>)KCRcU{Mw_p{dc=aak(PER32@r%W2co=RkZKU?gA8RPFj2I2b!t#% zgWz{K{pMgqQe{xUllS3K(bw7`zN!8QAzZN@+dVP6)z{~2<)0HR#lut`_Qc$M^3s*0 zA!_-I)nvq9WfY;|s*>odW6Z)Pm)mdtKzLuCjixq9hKrS4XHzXG>VEPXqBD&f$^t!`n-vY@2r9lf5}0vIV6;5LcvQdVXA_M*J$*it`KT{U^v!?3m!!Mv3-)J50d zA1i3$4X@Xk`nKhiu%@^~1mp$*8hjdC3qrf>;-7tYtNS<`)vZEHo>?#ZGaPtqm0o}R zT}}U|Z>L>ibmCpReD6e7Y&sVmaq^Gd)PLkGM%}|XdEb30I68$HQ0Q#F6+UlsPj;_Y ziyUK{Q}A;GJw3au=@wh_sFxWYvI)mjuilv+#$sq|=pKp-TIO_6l>34j5{PrlL;blCTrJbY^t6P}UAnH9Xg>_#LIXDD;qW1IBUlK3T zdB`JILQOqqvwG1mnv3V9X%oIU;c-^F+7L8_tKL^GUHStxc2HeKEv|Hr` z_#n*h@G>p^%{gDR=^YLUi>WeY8O~yszCR>~#!3Ue1=5Lk!eXr0n?zDK!L2fO0$&7w zK5oP*o4_+O=aIC_LC=fJ*`$roysFHaKzEqZ(r_xky2Z5tpN2r!pg&JeE`3ZxbO!n2 zk_%uG?@(MNWHqZ3&ZXCO-As9;io9Kvmu*2G`?h=v*Apv_?aSznOvg*pR34{usT&eq z4+8A4@7378IVf7F?vNb-px5DV>DIV_f=s`fBg zqo~`e^)+86ok$-F9VXCq5-e3c7fRLC>UUQR&U|=FA`MI^b&QOj6krHO^nn|5vk$O7 zdZBDNthYRV_`C0!P<3rdvMjb9)tF7DSl}7JeB-MZfjg z!SuSa=Tbem6X4zN)7Ey3myha=+nqm~CUD)of88W-%1?qfG5%Fcx0?@(TNJE{;{J&dhb?!UoD3mpDrf)T$NsJOPte__?3j8C2P8RYB@F}LF*ZxN?Uj5939u6 zW8w?8wo?N;v`uo^YK6L+y8(aV=2z&_X+(Wcd0>AUp=idro$j(-UHne3n9bCD0ZRhE zqKd*Zz(jfc4S;vGT9cZI245d#beF{S$PU)30??|?&|nqAXlp_<(nf$GYpuOIB<56s z=g&P+;4eFo8jM0egN$7oJ2@x6xK4THik`u!R5H%!*%<7_V9E783cyH4Lq=x9#>kw# zVg6g`>w`bgl6TxKCTJt}veCR_aoUAY_FF1x+#F3PW4bPRTZox@c$Lh#-}(mY$PqJxVc_;8`i zklfPSffm}CBkIN>NiN^EEelUFszpDfk2SE@3D(N7b+k_j5W8R@O3=-99buk+n%72; z|0rsN4Bz9i0*QqM<2vqBwZ68yUGfVItKP}n z1ROv2&`KH7Q;yA5ukN3L>v^lO|MG?_m_H0DWaWd$0Yc0=)gRXU;{`C01#{={fBU`^ zsPEb~`j9^vtxh(M>WsF93TCuUX++P^H{8ULvb#O2~f5BT1I+5Q=HA-p6c>#s|vq7=z) z<%Ys_z-(ly(dN4McW7H~v$s^(a4VqstT`u93g<~_u9Z%(430Qxuqrn)so%oFwn(=g4>4!L!^aN*v$3ndY$ zkVIBbt*KYJ=S8U2%y-KIBqXp9O3izYtsU%`z)xQF=rT6lbt{(O_m># zhjOD5pIg;&9B&M}$A=idDj5@&2i_*PZ#FLT-_^5O*&L_d;ys`)1I|C`QlF_BC4UfS zuvFoe8F#brt#g*n6QTIRq`?m^=es`eyK;u;$cBKh#f+z!9CXGc0alrNZj?@mwLj6a zFS1^Hw^i}H9RKpV(~HlAS^Sb1l_WVdnzOQMl_rL^0p`P(*gYfRdognmQ3hHl6D1Yl z$-I2Y!k`5w11U%OXOvwPWDsDAfe49IxuK#vx=aMM2-#>u3k9Om6uh2k>m&8w*L)$v z3s0<8eB9f6N8y&d);Sxd+^We0SH z2XUR;(_!qqbc+T0dz%ysSt#L>N%E&dnnp-EYy5_*1*|zWw>%o*T?Z|Em#sPGx!gd} zJoCgt-lHYz`RCcpxJl+QQFTQrlJ;{#`J)de|9`~$O@|XQ}(CXdhAhP*DUx=xReJgYm{ z4Z?x$L<(vjK??(_83V#!=(z%$fOVsT!DZZ)<&|YncE?T;3kUD>6|EnHz~{GZJmc?^ z$YDZm<8_;br*cm))j?(7H=c#QR+405!2&)AM_&T)j zZuPRFkhLQOm*e;5_w=F~hU*wR_6Se334VWJYoavH@{4RUhSKdDZ?=-_JlmwZxWf}& zc}R)e>f}-F``s)#iBexFrh1vuC_ zX6kcS@59XVGY3NGu+K5)*U>8#o0v(IisKAi;O+6qwYgU-k_RKx@WsRkzV)q`g|AB@ zt6`py-V|q}7LJ`@?zen@@!g}~;L{wXW9fGmGZ{n9k0q+KmdLX@ubke>Y#uJcP0nQS;QD`o`;TTt6c@(-EL{FWF=NESFg1 zclYS*jcJsVv7b}Q4eJyMto>OHm*ahAUQ+s2Kc5DERsBxKi%Z;!KUf+AH>03fHoK|h zoAs!CqU?xGmrU7h`D+uf{Y+n!6%4TO{hK~$ zDgKv_l{zl@UYTDiF0C{_GeX{;Fr;?JFf-v9yq6_McKDK5pmDW9$1S!dJw|C=Q-$Ln zR?j+D{NOsV?YOXw@S`mXnk z%-tbz4rJn^z;aUbZk?L?fyPO+@1MA$YB-J>W5ni!&&}aSCJF8fovNNQs=(2ZfCWM0cZ^sec2C?~!_77B zaUn}=^{XQdl6>B1{OQA1&x4}QFUaDNsqM!&=K(|wd%D6Opjg;Hgg;`HI)WlJj5V5FXD!q=ByX{c{Wc+Z9pVQ#j8?}qI4 z_t6(J-x8K}-E}{tyX8>`g8E1b=^8DRR<7*XKE$6d)j%$opK8e*eer#7EIVW6cb@yt z1x}(YQ?|imSj7VV&1tymZsnW22=!Y`5AoXDHEHxGNt07i^BiVy>O1s6%8dzHW4F*b zWXr#aUL4PQWlyp*RC}izERg&Rq9;1M=xan#accZ-zJWeeafJmCa9U^2g-pZ!lbqj4-i#|epJ$i*BKz&YP!3$o3A@`7U(R+sYt>D`1E!w3! zM*|nVMEe0FB?n$Rq>SgUUvX?46D_f8=-e-RUfA)%tS*ZQ;kWw6dus=DohD#u%f#NU zYw9Cx5)xuO)_~4Sc0(ohAW#O3+LHe|dXoATM{B|}x3j6Q9ywSxH}O0g54vZ5{cOaC zOY$)s9A=k)h~(Pj=NxP$=$6X8;h)`Rok&U1Lv z{8yJbGW=*8Ed5$cAM4Gn07=tTILE}2~At* z%qk5xH!J~5R|a3Ia2dJWALSNZQR0Nwfkw0#sy+!ATP!cW<^EV{P;L~NNR=TS$G-3Y zqtk$A4ExAQIZfKxX(X?4$>VX;+c=HRTdo!XTbYD-PW^FtowNPRRn$^lzPLH+g}l8h zlc*TjwQ?;5SevwrF7vF4oU(V0*Au?frAEATR;(0Nwrg97u`(p+BcB6D?4q#TsZ;zc z7R!Lte^q#-+dSCsD5_&5X7iORbma4Jyn}T%RRP1}{5#p2%&%7La>;M!&_=BC*<|HB zx2D_@ztLZ$4EqWSq_Z<1e(0`vuQ$Lv;l@|89mSl~v}DaKlvUr%e9Q&0A>Eg`*8NoU z6+iKB+Gcb4z-@xWvtv(O3y%U{E_)TRZkFL4NCfNmlMJEx#Ul@r&}DNhU{ypI!a$H z0tQ=}VCtjy)>$`ZP@gWKTnq9qaTtEfF9Y3wVnKUslzo0A`_MXF%wzk74ujJ2U)FUW zAFlFde~rYZ%MIyCU3vZ#U(k$iy3&CQtc-Wke7fHH=0O&_jl8x5OPbd~;=`r;pXyl- zVktCdJ_GpY8zt?`#*9KP1jVX+pIKvOdu4A739U`AT&ym%K&qGZ_uw_2M^UQYdJ))taE@C~kWXnVlGO&cdN{u& zF$#B9E01FJck@fBEMVkEG z$F_xo*0z{4NTDloe3+2zpPd{A>vec1q5^%^&kD3TlF>Yz{|65Nbd|iP>>+wsO0{Z)k$l4r&}mDN)u$=2k}77)as|Ei^L(v zKV12Jr@6G)gbArHL*9+R!h-p0yCv_YzedNr`6)L#eu0Y-EYRtT8>xs<6>WMKGwwuE zuM>i2*EU}Y`30a(58+3GXWO<(R(;gePZY+#s-? zk)W(6#zam?Wo^hBd{GR!f8D(riUO_Q*jFpmN?F_B!M_ zY?Ac*NbIbix98qdmhupsWKY%a zwFi1@?RLW3>2j~KM}gN~%P1gYX(QrZY=efzrXTSpWLc7wW333naq(BX^JJja?}_#J z576)~1BYXmX}CkNRcy8s5{NeCnTmq~1L8(oac@4EI58@@*l$W-kF+jtc@Y-&-ntQX zW~+3!E&9{2hd3(~JHVaYYXeO~XgL+qcbMVuLr@_$`qSX_NlOFlU1XtCrS{ydP%&1( zPE9}=^kuJy)?TfLDN zHqD2#FO{jrFO_fKDl;URf#s0&>T^8m`B}TSbnlg#T0jvVoHth%H`gjRW#4Q6Fn@jq zoApDWi$(@ll5e-kZu1FQ_B)T4`_DfSP40BuL`{hA%vyvw_U}c&sF*`W( z#iGB|9%oF$wOXEBn_FU`noxIaeS0NQ$mCmGTt6wGb7F>qZD15T*Ynh-pXxV#^7uN& zs3PRSqjvsy-q{EzE6LV1H;}H- z3DfVTGh^7g8>XC_5sJy8SJ_N>)8rT_c(X`EGn{|(Wt2C)^>pn8>*B zp6g>^W&Eg+~{c2n)cV0K+ zB2yQ0v@_0@0+=31a)F(cB_}ZUz#+D1Nw!iP72YJ>gDubr(yYjP{^sc z_XjhbP>X+#@@`#b_g;fgAUvF)uS5Rrf%6`iCWPA(w*oDEh@2B85C5eR`sh!?9XEFR zg+K+#{PYl}HVA#+R+l36a^dFYRQ)b&4|s(+xB5#{kG;dqqqe<*(vYMs6YA#2^`!()_B-Md%?u7t3oQKrRMuu zOR=`>GFqm`UWj6xrt7^1>G-!8wr)NH2y=E}B-Q!+ zgva6BYXos^&6XF61vi@lLB;LMgR)2XNd%1VDoV(E@5M$hvw$Oy_oi0c&zk7xH2?K- zZy||=el^}6R*EeFh14_o{Pl(_$?V6e7SLuC=sdjS7<|0Z9?UN^&fM{Q;c5PYNpllE z!G|aB!UW0(`#l$dW<|q@gWb$$y5s$7FtFVaLI91A-3*}%i`p_nwt!6}?#N2~)Y4Ev z%gXE#76M91;}__Ptl2LAO~ym#r3Xok=qCe7iP{-X>@iC0GGRUf%5`X)<>NJjr9>Pg z)V(DHsNTT91|1dg5YxkrR&5_$$yU;nRbHuF3?)nt&RyH>ug969&DHbWvCph?omh@* z1ND!2)#`mJx4J76u8Wf1>pyL5x~?JBb$9AKu-x+(zBBJ!+s{OHzHq#`-wN-(xs@B<8@ZM8>zI=DfwD`786|#Ra!u9qI!Z z5g)r1mwd6c(W+y6JlQsOba$M!i-&Z*q%U=naJvC-b(L6r1+1ej0{5PzbVCRTy)u*n zg1(5x`-!s6|ijX@G% zKBTIbr6>oWAT*LUL(u*4Ts1=v$5zG-OpSAtvBV`vvPb{RjM6q8K9Ct#l(UjWwFagH z>^z&3Toh-k=dk^9+RAH7A_;XIwP`xuEm!Y+y-K`Dy9rjBlHku*56hcP-@VY+EY@XJ zk@`?$kr$>r;@2+-E^*6Oy58L?`Erzm7PVhqoY^UfjMdhZV4Q2=3A~wM8$k*5*D>%| z>GJZS{ZxaA_Uloq(-^evhSLoV3(nTObvX0ULqzNaercVhMVl>?9hyUInHujHR##*2kT`uR;a1xea(-< z^f(EabpLHf9Fb@{b5_Yfu}5ODl0m!e3$Oc|bCpGICSS{Y4sOCr-CaM^oOEp`Ra&~j zp5Srql}55Qi~CfRH_N4NJ{R_=yp{Ak%KC5veeqfL$t_C-KC7g}w$ZHcsF~qMf;YXB znTZJQw32NhM7Wp0to#qwbLM(@6Y zh;W+AF{cYm_?cN7j)It$g;>i=G%6h1nd?^L9Wl2*RNZXN00pzRNbIoAEA6P`*{lr{ zNIm2XJ0h*8gIY%2q7N;OvIN{4J_Y{dwBLEJTt$`1pO&a&iMt-P7eoKaAu!ulg7k%+ zCbLGjq(brZqe8IhY@1|F^$;Ff<}}{(NtJE~1K1I^cwx3B7cUqlVCjoANkv;G}_(wVK6iTGS-!W2|{AoQb#X*l6Ls^d08{%um z$CT;?hG2;IsW(jDH?)M{Ji{qIkit%hcl6`3EXy2wH%${IZj*M-GMF#R{tcFwc)t!P!Iqw($^Y(T-j(C?mwz#f zJaClivTTvA8*Lie8p8{lVzOx>Lq4u>MjXyLV=PHAN@va=xCm((Q=5nZN&8fvvKQ@_ zc*nX0OXIQcKWUp-WpNkinO0blzJ#pkPX^XT+dHcg+ip^jdhXnogC$Ij$EGag@xjD; zOGO8KicMl(vEkQzbi!TEAac0F9+0@^UlR4n>hy~%JsINvS^wxT>bmj^BO~g23@K)$ zsq8-*e6-c%kC3)lWUrZk3<~bp!6{Wm+ApS!r3UKq#2CKuvzK}WA*H9`<7S^<=&}3& zc<&@a|4+v`^xwN|UODKOw$(Dhm`cmGl&}ci_2MC)Z}})0hne`pcu}d;8r1i#=a!&} z;qC!(D$=okh+KpkvMVE~J0c`Y4SbuP8<~5q!hX#w?{nF*w79U=P9EKV|C#}b1(xvD zulA0{4&+JDQ}!b;hKCD3#HI9Z=YThcwr+(B6quA8{+Kg{vNs$Fftb!M3mF<0J9vC= zIlnnt-q#OVI!8hTe-`tuix_q9V!Maq%bWpL73j37sYAvvMin2HkeIMClrebpXN2tz z`YPRUc$xh>pA$6kuVZ;I>d6yCD4|z?qlLc@Tv25A%ITZR9b!BuJRoAKb{wC)ai@}$ zsl4QK|J}l%_y2PAZHzi?XM(w1`*ake|&>CU5Rnoo$|GD=-Z z!}K4J^u7)+6@ZKUu87Dta{XFJx>)W#@Wt4jRYR@vOxOD`EEmT86_xC3@#UkFBIeIi zu7M+cN<}Xe)5(Amg*Uyw4ei+wTKjWtXKr1|Vq(koaj|TEV%>Q(tzmYL5<__vT#q-$ z)`_wq!NdRkcM4gsD=vkS(BXQ#)f?0n^M1r(m@Li!ekrx)MbRNP4c|FS)rrO69@&`? z-rNeYWrv}scDap_Ldj!gFF45d{|&IPpM%rtUi-AVW647Sj~|Cj3jXUpPcl7m^)Nw( zUuB1T-4lG_tEo1d- zROlQ0;@+JuFxyp)^%}2XrxH@%-7TWZhf@m2zLta^Yqn>zZEOTO=<;NC!wV7Y0%Hz0 zObg-iB_lF)qI)2f`7Wc0WY70j9nkMiQk<~wEiIUNyq{gto-uSpvpBCLw;>~XJEMMd z-dXR87DqW^_7#fAy6qq{k!rH9qr3t+lAnTR$u6Ld#)INMzp02v8^&)F!VQq z#HFfV6W)52i*WD~dXavkYCWU6OTbt6rfb52L~dfPMW4cF%_i1aa_oKL%w4g@wmUOW zYNX;>P(NmBHgB0amR-ovl?<@pSk-bg7Ia{D? z@fRGuY)_6iCTe+#+!2-UYgwpd$j<&T&b7Vp`e0?8JB^;)!@DMCZIi~F8!R98Sv$MF zX`Pj{>x$%vM{D+n5N)?21T_>$!+Oq{0h@GK7A#U6zbQ*RNOvuLTFL$h{i62`4m0v< zx57#{u_(Cw~heR z9n5jtzKQ4_N+MJMMi@Lm#CpkLlGW+b(1x`*Ig7BfR*E=%3E;*}m9l<4>OlJTIv;*O zokOrU(_Q;U0=;#|w?4P`L;l~~tnwm1UXVIao@|8%w)fK`#SL$* zNM_Q6Q#&$%el)3s9{$LJjFvnu?lu!og0e@UURst2Pyv;;6jeYjXF==MhOs1jqfvIE zQ8y3z6)r#Kc2^zUAu5w#H^1fuJJ+Kckf^DpUw_E+AX_5n@UlvyUq|Rl$Jx(-_p?vm zKXnK;EFoSu5wbhqnXl9Hu~S0?@R07gwuc#1U|NIsPHl652Q0`zpnWLgSzwPtfn<5& zfk*v8mWcW7tQ*L&xu|?8X|la{80x> zlPFVS`YydxP&9;ngH4wclT{2qw- zG~-KjDw4=w@}=9gt<5~^_-p}m-$0ksPu-wgi2Wj8Be~V6ToZOI#bZBiGx%ddwzC!{ z;Hu@|O~swg;6{sdm!y4aA6_l8x^adI+d-Ro)jng{JU9*KnLoWYxM9r6OBDI>NL@jh zt}We=Gg{A$N|6Vk<;oOWK6`=5SHjawWAyFr!P_flcMK*l2%Q8szWVwrh}WjwK{vT> ziwg$22z;C!8n2$mC@;L?QmyjJU@LmI&}6r0Om4kn%4tbl<9ZLVU1_d~`6j--&S1|D zp{q7RK??$k^1bU1S^4I(q9wP@;+f9JxUe<)KAV(@*M(xg!S7j3WIZ7CWaM&kMzb9* z_vbPSnIc_y-;I%&KPZr-kRG7?g4gtqwwHecc|<1fc>!tL-9g3-j=V%@VJHQZd7Afo zxFDuF`w4~(vUk^DX#c*cZ28PJD1uhH&N^hxy6bT6xgnBcIuLNlK2$w3G?X=a2F?Qd{pa@p~wo3H_A zObBi->egk9?T(RheAESz7;ZA&#*Z(JX_r7cuW=GpRjG)>1k)SOsK5MR3&o*z4eBA>mXv2s<`1?rhtwn{fGUOyXV4D7Wz$4Bm!(lKlgh z`pq9_n30OK%r6uZtOb3BUC2J%z(`CfZJQT^u*36aXz+ zCDrO&+&=m~%B>=P@;<#Pn>t}jko<*2P}f-#=g3*6IL)8Q*g@MdlYb*D+*&8EoFn5;@p8JNrVV3;<&cut>)tg#;_H@`@<$$-iy*Y;O zc4MBp4Wx9}S{)v_v`fCwJXk+rD{>1No(ZrDzCq7c1th~JwI!{XkTw{+1#$2`vQX7WrFSK;-z=(qQXZ}2u%8)}gb)I(%_^Cs6n=N)iq_6*M=_JupOvi~md zN9A8-n0Wp}P2o6O(BCo@Uj{NoGTO6m`tlgttMh4~vloV&XHEvkQi*qNup1+3cikmDYlrmZ@ zvOX_@mEfJFb#GTaQvyZdZW((+?h9$t1kX9ycop>`LVvElwwsjGjGi+tv=savc)YaF zpEQUs96xN?^!{CV-6_6=**f}YA*H5Y)!*G;N{xW9su^WD+rm4vDHkzhCrFlrXCvP3 zcud1{a-qUbA(yJQNlW~=qS*l|R*<(|eb}<=M+eb$e}s5I7z%2;QX>JIVV6bkxd#@o z-gkfAT;8aT=kn%5VrBMq;2i^TYd6+_B{`Y)+Li%E09e?%S5~Qyxeb&(-ByMB*0obP z4?)qwtaYnrU3_i#xGSo7JxK_K^Gf>nldkqi4hRYrd@BiF2 zBH`-B>5YS4lLf`5;JvLj%PJv;U5d(dBdXO1s9LK{n-@WT{>t zh<+0@JevJBP!);b2{V>oB}CTQ>j>flv%Be!?>gjW zJA$tIrVWm?`t9k5lqci24o@D!{-fwg!eSi3mSONVsOrUV&q&qbEt<8Au#g~(pUpvi zqe_ebcw=KZ3y}l%;DKuA5QnyRT|-u41?UFA@Mk~5aTk8r4%*p6oi^Vz#|U{;pkGd1 z!Na)bWL)fn%bAwOkT1;OYai!SVs4v$d%63!9YnNkK-zLmVCbu?o5S1Ka9P}wii2-+ zyCHh)Eb0EI&D&0k2WLiX{*NQ8`lJ1P*lu}6u6q{)aiOl;=jg@i0#P>e_+VskkhuP{ zG;^Ifc*h6nZQB)R-qrssp1>^5O`Mvs?HCu&Ig3@t8iTg?!`8nPr(kPa19pX2=pLhB zw9=Z}8<~6E=Xd`<%HBL4sy_bzZWlt57Hj2_QbP7!grdm4law{a5{5BUNcQa6lPxAB z#yU&(?AiAjON?!deK2O`KDfHB>-)WbzsLQ!{}i1$bI#{{&U<;io^LI@*#+Q@6&oK+ zqt2cei3wIjRf+gy`F+lsw4!o7LvTAntgOhbOzI*<;brm}aEY8P0gskBsLL^*y!sQo z9Ec1q1Ul$2c`A^fX@CgdrINxfHvZguQ({V;4(N{LcJ>1%wohk7!g$f!ugnAx-i|uV z6dp7;ssG|2l|;%a_Jcwz!H#YI=QBuaK4KHlv%{#V0B}P8v(dIqvEquyniov`bBa0c zdcW>%EH_+qG<0^BGdvq*H4R-W_gw%^k}y*K$+^F_trIYe08Jb&tWnKMh(L4anWI}1 zWc;KYEAL^3SgMf$U421^sfpf^!}<&QF%j^+jd}7+U*wxeeDa*2#T270g0&bLxkX4k zHnK!n&E1^`)(@Ur;W7jzy(0f~`Yzw%d8fj!lN${?0|KxMx4#xc!TR-v$K$7wWx?FH||5IKVDzkIXw>rbECoj zl)9754j5T|-x8iHTLCzb>{iQA;aP$jD9+-<948XKgl*<>;?&hs5EmYn@L43tBg7>= z7bB<1utJ{tpzi@8iveCs+{1lb+PjUp@g6x|gfKpd!9fA&vLd8wWv5d)#o+espn;3i z!<#i;x08U$BajC|DB{GzjaUAksAh|umUlYd5IIdR3@C6S+ZBWX9hb?nykx2SWniN< zd`*-dReP!+E-o%D?IYGFba=PF?4B1J)UPpzW2ofyX=tM0t?)rw%Tz-_VA~BQ;@*KM z_)JFy3v|vLw4S>N*l~wq5=tCjAkdO-j==84QgNN2;DcXZIZmMZJTuEMCumMW)0TzK z_J`@C-H4gOSf^FbT1fzZ@a9T_)RQh{&v z7RW9^W0lNrkOe$?aF?0Z_%|yZsq%-FiYJ>7;r;qy=I~MdMdZv--bDz6wcheFTj*(Q zBLr%JbjNjt)$XWwl}BNT-Ect~_2OYcCr(o0{)%!oX9Mn7tZ)#~VMiIwE^`4DOxy7$T|} z&pK*Z;2~1a9~qWh{t3 zQP9%hQgPH|-ULz-ZXv2q3AMJSu20N~G&c)Io?zMFu6CIwFq-SqXLUn32^o7Y5vwmt z99A&>GrDOP>3JGoCqN>dzFJmFC#^c4(w~#^&h?i({Y~(KrdUx?F=*~cZ!IaQi6nU& zZFD;_BI0Iw#@=w3(4m2-w2@CMUDkcJofIs;x7D76!C>0D!G7zA7}=2EV>! z$5O{5YFAAcQPmU$4EOj-@M6@S-y>$!)Lu~?FzAb$GCq_CWufUD-yT&SR1K{txJ-yb zt!z)-m_vFtCO3gp36IX#&gb@ThSf=&yl}^&DkpxU*@nc4*{59Y}K;Nl6R8@g$GBHbZ~$PU`M#N@X$_LfR5Q zQ#Kbth)jfcTY?1E*WFaP-V}t~EbrOEAJ&DZWfm&128FKu*9`^S`{f5m@sA%rHpTjo zhJd&2)?Sc{k-Xn=Y$J(6p)XZPzfT2VwG!|ubAH(O#|#WIKddUGEazZ}_EfE7Dq5Z;UBP6bNC zB_h9y9@Kap_HDy$$1?R~%-~J>fSRRUpJWEAfO%L@H&X-(3k&3mO*RXhK$FPtbr3I4 zxsy9LU%PKLr;yM!tM=H2I-Wr;NgsT`x&$L}Y&*}|dJ~w&eb)~_)=ohCC!u{2gOGV0 zFE1}OH8r!x2uc&XAo}y1UoSfdQBs(BPyYr43$4h{&-ccn$bY{iWN!5{8vgg*-0`}}_0 zu~-$MFgi}|=carzWGP1N7Ca+WV0;`27$gDUOn{g;cjw+0h{{vUHRunYl5I^Bc)h-( z8N5Zm%?#effbb{b4J{z%McZsgN`;u{(r_t`%q0H=-cn-SEU(~)AcqN}w%0UX!0#S@BsiB- z46Xk^ul_BB{|y+aPkh* znH#Xezjrbb{Fj}=k;wwFqLJSPFN#KRJ~dE4%1uYmuaP@RC_L&bcVA&a@<|}o$#5|K zmLdPlr`p=u-aM^a<%DG**=Gh@MgYQsQ4q}GGqCIcPj%(~Eg^4>rW4SH`if6<19d!V zfn756I{wGu1KSm+tfKhm(+p~$z3dij$0@<)PG%I{Mp9}myX@@ zr#g18@ugFuKq&a`$-~X7sduKQ0c=?G<)3@Tj^7%uSqCPleg-l7ozKCN$Gsewu$~_I zREz3Y50CB*4H@m3pmvqXx&nF%DzKYrau9`Y9iS>sPYrF_pG}TK$KwHde zl58qNyn!&Jj8e%=qh3f@0j-0@-+MG0q0tip5K2!ggzgQ}Qul)BnSZPC?9feU`I8FQ zB!FwG=1-&NKO1gxuQ2!3@>tb-e81~K&4dwAQ4mt$g)hv9MUkVTsBQVZ=3aLNE?LX2 z|0Bxact{vF51W^?54kDdwsGd(425EY zupgAE#xTG!zp%J?9JrDG5);TZBvEdAI7F}@hwG)e#Gzo@p>1TRyAEEsnw;@9!l1_X zA4SJQIrTCdgjJPmDsr-avS^%ma9fJFxvbN>}1b!yUX$A z-o8)pCJlzRCOVn;j_Ro@w<{Rg^@0xd#l)L`YdXx0GZFS^8=S+(H#3W07;$k6{4H-@ zIdd;hZKP4#(Fa;!zu5NwAJ3<|cNud>h6@VP!KeSaa=dA6;k%*=VZKqcrg zWHw>_KH`=>>cxza4g2*@t?5rq@Uz7x2%$o&0NV=$mAWX4C7_JO^V9HWkt9_~E9%+y z`z@YyrVpsRJiceU7|ec!_V`x!>O?toSBj!ZDKd)xs1M&vg1443kya$az;$^DvpE6QImErkgHaCN-|n-E&owo zX*vAw8NF>bInC)$Q{`Z{W1iv|3w}3=k9$%RQplPn)thurmryh@rdLq+mVVPb_AJED zv<*GQg)M7@zJO0cJu{Pq5gSdPpTz}3B|&G{FEq$Ws3472n4d=q5HatbZO9YxAK>ZPWb{+4J%-|k;kPv7@lymbc*e=fuaTTN@Y z7{`QjW=~%)@@T4?sH|yoTnJKQJNez&h0m|y(#xpkGp2(NAG&B7;mssj<~!A!zRucu z9ZSAd8GU}_WN0Zl7S6PUK@D*EwM%#k1WKJq^cbxEx;R%4A(}iWk!`B9lp?0q%5}}4 zA~Y}H&fWtNiq`5R*T+R@-64=2x44*u()%IzF7IflRN9=1Y56rbrn|d{G%iB9hGA`2&;7E z2T?ZM7=p#(`Nqm0dAtLUgTMmW=jP_`ed`p0dB3wg0<6-+tLXL}=7r|SgpFWD?n0I~ zWG00#g)|2{$#OSozvI`utg0`jhli9-_U4Sa9EfbR?q^-@o{?X%;UcshNeCrA<1ceR zodCq>V_!q2&X*S#ULxSemrc(!Im~b_Kw@>R%IJG_Cd;pM=sg%sVCS}iIS)PxPc*oN z@ru7@_IqOQ3OiXqh?&o1a6av^$+V;xo$9Xsi1^DvlcUE>q(XG9&J}ysS)BP5t-4$% z1bRpgYzzMNsV(yu4E^xdZ&Zych}^7~Z(dvZE_#*1C1ddr7VQ>}so7&8_lnR-+-{t) zF$;rj@%B#eYX+%qcGEsctodGNdF=*_yY&kM$ICZi-toog?v>#?+6Gv)s-TlNvh^Z? zMCnP>`fcIg&8ZomP_uU4CpLtKZkboPo&3AQ>%~UAS#G)2pxX}&kGi1EA+MlwR(d*Z z^u&uaR|GHtf^Bij<#RXPMwyv8!b>+AA%_|r^<`o|D{$BDo~;mb_qz=bulF$3x9A4-4# zXkBLoqc|730@UvHj=ivPS-wpU6uH-FRy*XhMD&OHJP;Ysx&YbOT4`LZ<)UJ1h_e^ zqkGy?&yxXK_m$0fNia2}-%>E|+Thg&y@syu8(bVaCChP6_V3u{-A31!z&cksm6)Vs zYt(T`+9v6pPcr$}`(VPD(ZRajXGwx8<2A0Hl-bMa4-rIW6yuICuDOic)pyA*?b1^G zyLa#QyXrtsn@V{`0Ah`TfqM`M(ecwr@ppH>mQ1xaP3GBeU>xq7l>VA|sN(qpLHPt9 zIm(K}U#~dtE*I6|ePZv?vgpO|312P#z%*OjYm9VzNpKB4p@+Z! zB;vTSoYw^T%p_9M!|ohy#yg_(?4!%jh)BJN(KKWWSdgRdu}SB-c>VCa9ViIeAnB1D z6L%@_tBfe@UDwwWU!AXH8Fn&uO4K!`eU7=1In7n)E)ZV1t3E{y`d&!1)8JKyNv+E( z<|sG7%VhmS-QR;*oJI+INU3za%0Z=NtZ*GIzJA7OXyS4Mg!{K?YH_dMbEx+>lLwp8 zbU_Cprf~>S--6ck?k4LWEkHHkHHeacHW*Q5x@x9Gc9xNQfu6YoEIN}({4L2(_`NGf z4(tZ`ERKAHTe)bNL|`7czgTI@;=`cglpmdEA*Sv^w#&f8H!gz7MZjqw!2rK*IMU~{ zy4^nFq3MD$md#G6P>@uhi1A@aDeo9DeC`hF_e86Jt^h=c55t#ZPOxt!o`Sy;m}+n+ zMpuI~zS_L9>5#-gj@99}Jw^0}BF=Tykz_;8ZU40ieY4T;3(m;9PpcISEUfei-#4dn zS|7t8Q7;hA}UT>2Jd}bhu$gF zJwJ;(_at9VSN zn~)oj_0`mR%(Ff28T?AqlG$8fmz5=b8+@6ZDGAV{RV35IP9gSF;siH5C8Ii9VXx5 zFe5l5y!=OF7AoqCDkbYtPk`wGVbh=UkgxC*l_1zTpmc8Ned~~rNw{r_dOke6B@CV4 z2|(-7Cn^lybzy&GXh+tE0v^j&W#o#$zgGe1s+p{}UZ1f5OF93> z^sI#}s-*l5O_HVjH=V+A_&AaD1XrHtUzK0TggE)nR}cnUUd5<6gDZpwh5RlP9Jw%_ z)#p{Pkd=I&H#_vU(xaoAmK=}E$^2tm{R7dHbq`$K+&&xaDi_MKQG^YiXoUK>P>eG3 z7WKMZy!lufGu=7yc+T-eHLIsUuDBu(yst8y$0~*8<%^bgQ79CUp^6t>-&!6Y9UJrA zUYYDtpqdF7q6<9wk{!r)%0-fgIjWZ;kYacp$0WX6MYppxF|GR5@@P0NE@U{NNspd&D39w=}De^>W5!^+}-N%5H~Q9Cy>V6 z!c>MtA;d8cm79OtkrJBQ<^G3h;sQp^j~Eb;1V*m~WHChb4a$DUZ`_~!2}u8f*hfdi z8-I?wUr{96Mls&lB1nDXzqa+vvN>9y?f6~G(kvvhu(a4Hw7=NYjM^4q$|f#R&NK(+ z_Mqu28uQIw-22(6TZr9Y;=u{rg94e`t>(K^BuMghJXs4kzav-P;}~ohD>R|KcXd z)ROG@G}xpf;^Np9xN_jePaywSziV{xd}WB|5}bJgn?=(o5Dtax&T9p7Z*V+ zF;SHeB`E{*-9YVep^8GD`IB56(8oebXkA9)$sP#>~#L4dQ*r2t?*GCf*inPanRe%eA6lVL7;+x?mL|x0tMY731X=Hb$pNt*Gok^_EQTWpBv5ZVq=jVM4D0Qh{BaJ0;oE5 ziQm*M>senTKn_oX;qw!t=@twJTT5MtT@tdOeft)ttFZc3c5_|>aIAt zpGJ?^EX(p(*FU;}i>a1Y^6vHPFqUmtO!wggd6|*}ry*B)Si3z2)sx+C>4){<6L4SM ziW0Oh>ifCkV){6L=S$bbCa(vT|8TLM2mqiKH@gAAd0)3Z5g)a zTE)V=FJ`s`z@0-DJFv)N*tEkJKD&*jGh8{e$hhsta_Snd@-Cm&IeNJ8|@vzK9GenrxNBN%VRi*FGO`YH_Pa9q~8AC7EK7BhH z3G|yBvu?D^o(tqVVh=A0j~CI+v}C-+T32r3%>KW$X`{ROfXV#R+2H0NNqXe1yb{@J zuGuN&t}iNi7|Cv zf%!9Hy389};_(;X4PIl6KX=?WmHjU@RoeX1gy!pB-=v4F%>0}3a6yk2?-}gkX5#L? zyg1UXyd9eKWF@txMz1UxP#xYJ=|rZ%+oE4#Yx3tLqCUzdrhVdCbOR{Nxa9MR4I|E6SNX3Dt@moEuAS&my;Id} z11S|G9LHvLjhgllB-7QM? zNSQA4h4k$c8-wr$MtW;+O!zq+ZE0b5maCq(15IUSA4tMh6m#BAF0yZ~JVz-%K)0UM zs;q%Ox|5L&X}P<2c0Tiir=yYn{VH4C>aoto7+~U2`Vi;ve)I`MF19Qf3AERf5^|`& zg?Wd_f<1HNWUuJS%%I#y=Y|BM+J84doGP0}&uU%vd$o#rtg^NRE={bh%dKBstEyW0 zz_&ZXf-17@%cdIUw_W)e_}AomD?{&ZCP&}90<8Ihx?r~}d&Q^h-A@w&f_%!}_qugN z5p!<6c!BzLUR-sSbE;!h?5XFer257Km6^$qw<=+j)21|lej^|k$b25&kK@yOSB)H;5vTgucr#hJfBlP+&TB&pPQV?!r;X*g|3`3D5j8grVU!80_{7^5@!ro>l8 z)D~E1AC_Td5;Vb9z3Ub-j_9-i~ER(#}V*lND}z>w?tx9wrX zXA%jC$h3=7V>P2h-&JDdelG&>tQGqp{$EP6j}DRGyf@Wj3CWS&5Sh+_aFcx<*(vDI z*V}0y@7GB+?fQc9^2B^L#_4#J>J(F4=Wj8(RFi%mj^M%u725kQw*bxP667{{)hd4lgk) zhV7SUfEVT)BJg17`$>)Euf@<5V$^?8FN{1)5?Gq|CHnOYe!Gyu){|tq?d0iV*TH$y zq!v&s(RE6ElWMu)=n$}BH~IU~*Zj@)ri6ryf6OgCpVg!8Ro+^)V9iSe%q_iGlP)QE zdjENGiHAkemoMDHo=gfBQTtfRQ=g^>2$^$gu5{igTxo{!9__lUCi?vv?tavkgUuD! z0mHxAV})l^9wnvSS8v2mLQV32tzb}N*BC=wr|)1O1k z#^S`0!>t1_k{AP$u`N;AnLL!7aFkf7A^&KKaCM7mkQA!?MOymsqesmUSCiuH%H=s& zmD7mfk+ej{S;(yDIbedkh4e>4$d|qm|K%l1RvZF-Zx){oSm!lu7y$$nFbVm@^zZ7t z7m z5>`$ZRWK{c-}JR^v*F)hS9rlyJNkq4Rfm}%rD?xL7i0WJV_nd$*wYj&Xh|PUOyyTP zvWB?Ko2SGdj51{ZJ|r{G`_SiXU|zh(i2eFKpQsq)m9l z_kTFAvtYr3q3)IKHQqJ@G0*r5;T+hq=fi8fi;SD}j!YchuHGw-6a9jsOOiuUirz|I zmobUXjVsQE70Buxeb!I>$MiWH`Wr?^D+nV<2$e!cJK9&R_n$cI97_7vKn;NMG&hGC zkgIjeB&R@48Q?|vg|K}m(vS56|4MPb>BukMqEX1o%}q!0+va0+mfwW7Yfzb4VAHWd z6z_j$TnU4zvoa80FxsQbg6fH+F{JrKQ8LY>2I%KEa7~g60ut-}8MweE(ca@tU9ZY3;9hBhnt>Wkbb#n38f@usaL~Q;e=_0y4kzl(8!BnR@y;kL z=(u4~w)1eAj_?*wTjFg!>iC${EaCJoAczGQn?hZlWKuHo>TCC6F3Mp|KHlcSUTQdr z7-OsTqGo;~OmrW|#gaXo)y1_jBNklkSl4e>-cFUz=jIknoYF=Z)Pg@dsdX(`mP%Tn z)?8MS6{%KEGy5U^$wd#xL)MO?Fp#FD+wwG$ILDJZtb z>^Pk#FkO>Qi z*KjTm-U*MRo4FIPdorqcxbK^HZ?_1Kc_AKm>a7)ZQdfpska+#k1(}LF496J6m$cER ztVwb~EnyHY{XO*Z6Kccn-;JN@KYKcoeowslgnQQUCVdi!e;{F>#Q%HZQWz}4+kL&$ zTzV9})5!%Fa({>He3#R`y;WKmbO}|%>XtZe{s4p2YU;@tbM?_`hZe?yO=nR(c9(iY2 zeC7ezdi0Ct2rte1q6iF;8(XG0NiLpxvJ;u6b!VVOp^nt^es^Fix;(-Ld(Q&Fy<^iH zs^XKO6i8GOR6M^2+BQ^E>*K0sLVUX>UD0@J%QO7-$iD?0Ko&0_KDfKHdYYghmCl)< zAmy67y4v${hxw`?7%A|4!X=^Rd;P=t4KG60Q&NtpbePpksvCULof+wMJ&Wi7=W$#B z${GXT#FivounS#3NFot!eF_;-s`E1VZU|2wTGR)t^I@80cclF5du!6aH6~#7$nD>R z^}?nmFO19wp4)$eBwxST9CwXfz?^AjcI zTh=s;>9-$Uh?IwY^zSZsmvBR#`owy1r=_->9BeYGl|GLi!mOMJRtfeh!qA`kw^=_Z z?6Bgr4?igWD~_so=>uAt>lC*=z5QOaQy$-4+fKm_P{mpRrll=7{Z7y?L5tO?WL`Fm zmMRE6DB!fMDhtf1SjQD^psC8kk}dpW2K@bbMT)@+IAUn2q#FZYF47d`GA?s*gBE*q8~Dl8MMK@-%pwzu1R zz4@cL&}fvT^j%s0VxdWjo=@J7?Pc;8t=4lb@4ywjfcCsp05O`ny6O*@#H@V>cWUrV?P$=Tm-}BxTQ7lR;*aH0Gp}&r zQv@78MvVp7vAtLA6nJnGWR9jkGyx5{p=xK0-1>3H$NdR#02tdk8i^yTJ**U~&2&gGc%-YHBKWVLuAY#5f<-u1FqtvN!f zSzgXEW*V@m^GX#Zgi4zBXf-_y@_nBw^~vcFEwE-*?R$V0FjxEk(}uQC0m7#LTN~QV z^Fb2u#HL^5Qr2O(jJv-b25`!z-2h1+%Z55PcQWa5IHdTg%u$_W8Ev3`;7 z8+Lm1S+*g4Ul__aj<$l{DYq(3@a`&Q%gZzfuv}p>uKzX3_XASM z#S)pm$1nHJ7w-7Y7hjDysq;W(p}x=m`Y=4e01UXvfld!lWKV>xjUl_*H8P{#McW`0HvT3Ifie)zqmi?H|amGwwN2%N(S zUy!gn03`VTW@j=l)rJ+?oT&)yD1I9E!|6Hr$hPMQM@zW_&W+S%ciSdcNg?o{t6UKw zjIZG$R8%ZSzLq^n=(sh7Zk;^oV9Y*)z);$xwo}hMtjbax={GKe4oX{Fxr~j^h;5qw zV7c_!(#-bTPoSwx^ULJlrgFaD`)S>~!MvQRZag>3=+E1d_#C;1kkCRoR3d1}+2y3|GrOO0D$#i@f+s)=5X-U9=V#EtehV)v#~k2}LP6|2YMSt;lAjL1(^ z$7KYijuISH8?O=RAsTW0MC~G9x42L`fQsNz&4)~v5QLf4I0TvbCue(ZRRu|rd}e-- zHjco)x)NmbUD(dd@^?J?4USN?KgNI7V#tS+Quf^*2H! zSX)M?Yk=C|O47)+*Kbk`z29!|hq|r^S$sG?8%Hw>q1jqVREL=G_Xz8BzF9%*v=Nq@ zNjkT4{5Mca2tgz>i&&Vunj^jU9p_ukZ>4z$KH5FCpu@1AHfP4LFUF~V8h#2u-~8&5 zuGkhmT`KpLWWp@gj~hsI=&i^%f2Et^1zOvFx-zwDKFty9*NAb)2jzmWa)B)vO#kn% zKlMLn8-BXQ&+1*DqG4MjSDNm%f+@T@9(jh!iH8NkgYAoGK5q-fYkboE9H{*JQP9q^ z>ABvkN2=oJaE~_L3-Q>>W)U@`vWXtArVH!ixQ>$CMmuP6jI7*mSrc#AU(ZTqVYrk6 zG=MS}evm>%VXG8U7?Qxf`z0A2Z2K;kJ#+9>ZZZ#sHL_l-cMjSAz7NuXFVE*=9nOSG zD`-S~d z>9L}W&o{?_cU&NGsxOmPEN2pVX`e>+KURo`cn3el>3gxkAC4P7X-$PnaxkytkO{a#nlHgnz&8v#X%(q5xt`YO>DR z$3+u++g$A-#1?^KcRf_1ake8fpE!p&;K%x&+?^yg4Hh(~zlxG;(Gp>;y5rS(-#*81 zadZtW_yLv7L|BizF**N4J0I|~b%D{e>ndz;)(^;f*&?IF@!Vf(dL!=KdY_ZK}&f5Sd)uEVJp811adWkWnK z>00il_AZR51FM7TN)T;5vWt(jW~n*{Xbl0nO82>XaGhxE)F*Yf9G$yr(|2NjtvxQ} z8*^PQ_g=+30Il_aTT{JjJoHNcXH#~CHH9v3K*Q4xA^S2%=?BCKuGOPT$O*6JL|iOV zaf@5U)#8HTOf-Pa8bpUz*? z#BnQAKwp!|@*{@n>^!ke~PDF}rr%-$Uet5vx>B z%I?f(D}99^rp0t2*IzZdywbmVqta{TU-PlcW&#R_aaj!T&&Zo6G2f34Bp*5JF-hIuC;Xk>S z`w>ELl)R}SkXCpT6Qe<%Uybz86D2eI=8YP=&FHq;I*KyPA${Z^MK%X4aa)H}5z+I_ zzX`X=B4&6+|FVour>Hvx#l~f?fVka(1)J{S0YFg9$$RbqX~eP|Vc|sj*_1YFC;*l; zvrNNJVxH#IDr*Uz@%sL9=WWm?+>tRT*b)2LV7q_e>C6j_{#FgP5!!1<2)F7;bAh0s zYS-ze?d{6yMbhr>zC0IDRo8fd+}+nF>w^`jd+u;Ea>0PMW5H$?L)Fw_GjjasjD1B{ z+QeQ9r5?t5YOu(t*X0XRfeW8LX9upN1X*`OwzHfB2M5Jp#jT*0Lj>@E2~Rc5y0HaR zF7U+^n*GVi4I!C74V|1TrC&7kd$KJ5ab+1GdL+M)sm{7bN|)1ESjcd^`ImwUB>l#9 z9_qMELSLaYxGIpSfstYr^e@}D9L0J6TSP*jM_1*aOg>t~h}B*q)y;Il-&s$ahmH<% z%-;&Uw;8ky)w=(LbBcUjAc49^XFg0QCoLexi@s?{_Q)i_yJ2TJ4A^zkR#^dXcEk71uDp= ze>R=ifVmunSp+Lc^?jF~!UMPT&Jf1xUS$mm)WteYzE7yN+(*O!4UPIufN-J!m?1Y* zj~rCH4y$aWsy9S+pssUGlTm3(xl@97!e?cRQV;;;WMEP#p?+qVY%Gru^T@eMj**2F zohJk{7Hb~cMrILh*%JGUWn~pJ|DAU9gH*#tX!e0j%`ftuGO;u_Szjsdtz}>P2fA{z z2qmd^DSp-B>K<(KmSJ&G|Fc=V7a>Jcg+yyHNL;GYK`>En0A|TKSjTP+k={a{P4n85 zCD3@~r!tO%%HCl^lsqo$?zc#P?>VFe9wq1ht>(-qR4oJYRsM9g<#!`8u^VbD3+6_7 zSQ-MZi#pPp>`o4ux-j>_wksaM8)o8z)ZWtWEM63_DDfI~-|h2()q9^3|4wL~ zL?k*>zu5&Hu&|jASlDQAWe)Gi`uR9ppd}o1_hv4@I&l@QQR5HnzxpriB;InY>Msu( z?|d-ZntyfJ{gYsV*{)1dYO2=ClJxyMKY%!=qP*QwaDFsb;e9OIO^j(#u>A;2$Jf^E zEv?Sx>LJN!6HX=mHLh!NM4P>PU+)S`ADcn*&b-uev-l$CjB7c)pT%Oe{|nRJ_aZh2o;Z?7h1-O(hmL4$q3QS19!$(lG_|Mkfh$L1a`zaC@#qeDtyJttmvM(?LYkn4Jjx%`uyrq1^ z!HT)~gU|bvIJhoO-EM_2dX5@#ous?4TmIP5fjy#gzJc^|7_K3-w+U6$tM+-CY#MN&oQ)7%s~sjR;g&ACtB5CgrZ zscg_$ed<2#Q+mYiWc?R!o&N(01q1?6C{;W?KcAN-xI2H~xXhb<4Z)oLyw#4CFLDw1M%gR}nbs9;)0$J>&NA z^L+jK$(x^6AtpPnj`4lJD}&F93;>Q+Q$_y=@t`+!G93pPr`&FQPukQ-|bk082!euWSOos(A=iA|N=X?F?e zS??Kaif6y2$=?tROQN$Mb#tqG(3x8!p>90)7r#p7AtxNldy|K2O>0NBy3Fyf=@*gF zatG*?{CwP!5I07KljeE_B)IJ_goMn;XO;2GX*VM=i||nYvpOhM9g4=qy4cV-u7roB zs{DmZ=iWZy&t4F{18QLvJ?6eq_o-Y!|89;$fkM7GUN10+PyQ&`L!e|Hbh|4>+szB- z>-0+Ws3Gh~pv$vn4q97Qt}l8*zR)bLtv0XI^M+Luv|benEiBYB|B%d*&q*fG4oM$MFaqVoI$O^_c+@3Hb;qcl?<;aM# z?E*)SPV{DW93Y8?-5dGCMJzJ|JiPPj#z}&rK@rj31fzlHjRZ`dZi0nb;Y1{mk>W9` zzE$=xZczq8&xq=;ItPY;)n5`@m%saH_Vyu4#S`mvVFi=^ z{V$+^e!ASVBwO!eiR@*tpxk}8=W!qC<~w5+u1Rs-N9EG3agUt6uFGh-HVutb20wcq z_%`v@UE|8<>G5fIegeQoPiyZx6)Q@q5HDN#B^@l-&C-R1E7zIfLyY!-6dxgw4?3X9 zuIl#{z-~Ku{LKI9aaxLXM*QP2ZYtr&#-+;guk&OH(&O$%f1#eq&{GHdsU z<|&LVS0{&6XXF_#9bqv9vHeRnLt}H{`Zkl)DarUt?x%7`U`cNpj4h*uw3oT1XM7?n?pm~7I2X;`U$S~&XeqAk?$rCgkRvW#_L_mWL zU|M!8*Ep2Y11)cWcV=1F(WfUSkXx-#^&^NOwI#h5QmYn-no^UKH=|6}Ur0;r$ z$xqg}7lZ;x6TlnI{SpB`T(x8m4%{)!PC~BvHLHZ-+c)>2Ec-uUo!bAc|v=icG zZ+Y~Uh4jw`64|87}tPwyY76<)=XtG{C z0%&+*bg#xMV_^)&OHPWKZ-v+Ll&5<&Q_g=b31w{gd5(mG5M;yYi*w1Bk1J!c*lxAy;L$HRYu!6ov`jbH0qT4AZj+1HLfq< zN{-UY(CraEXWLl)H`#_i@lCQPb!7u>-zpN~A0yxY)0FSeAKjVj=IrC#-tB8|J8b5& z_lBKS>b-fWiUuN2z+)CKJYb^F{c`_@1FiJ`ojd=hfq&4i{|CDAe^mi=IJ~}hx~lpz zAkjV~-k-jDqKij#aTP#4?7QLLSH7C>neWZRUB~Qq-gjbNL|V#9-%ktugDY2H9#U(# zYqfAn=_gPBd2UWlPBu234N8Xuc5HXJ%eg}iA-I+C(hk>4=;oY{eo~91Q;G z33hM(kiWL`tORV{E2;FL2QXqbY#A0?aB^_$QAh->G->RgX#d&?Wt3z&X;Ul z4V0~ZH6OEE{B|o00FqU@WWr#JuwoP&DNCW60$hvj;Vz6fj&ntR4_EKF<;4&l9&sTo zHn#D30}4V|OU`55fT8KsGR%zt`Sf}Y_w_g#Hm)N7Nu#TKL_2_z0%za^q^LCeh#K$} zV}?ID--nhh78aIJe6e{=?g;BXqv1_=Z}0lJ7k1wZ%iLCR`s%|xt&B(t?A1bFerIRr zbaNCFb(Pzap4M`b4Dwq~F|_c9c1}YZR+2-r?tStD#NXXoJcW)93*zv_N(>x8qV8i% zh0M^ukc1GIj%TB={i;qy`mHBke@HeT8n>7+YP$X~Rh6;dKQ0+Q@0SEQIyyQzac;*d zXa_26sri>dh|8x5O#T~yCD{P`#+JLN*!)~O4E@wZH8(>RLS0MM+D(u9<{5RB0E1X74-1)%8(_}y&zNxX59ztP`!dx5`ALw&> z$=HgJbA#XOZa-|K6)kl>+3&tGWT#i4+8Br(>n9!4ZvB@!_!@1n9lbGEZT&n z;Vj?TE-HecMJ-!t_TQTwC>-@#dcQ&m6GQ_#>#Rha23SGc4kUy{f~3XHHQ_EchsOSk zVZHeb4O9deEXJbvMhTdb{f64#mWjd^VZ9q?EUJ3^ch&%_vnF&s0`q%lZP5}9AO~7g zDqec8cSrzke8lE}Dd?6HHga`r6L5gD-y3IK$Q=Uo5Wa0MhrhoVEq7XAl-kLl=yqjL z=HnG8)0XI+TodZdpv7@Q?8IK?H2dP>f@9+CLh~{Q+!k&+ z#|2|j+DN=@K!v`-CbhJPL^!n6HRc!-nTU1Of3yd{(61a6iqM%x+Wp-UsP9K!e4HYV z{BGY1JQ@X;YydNVsDtOynX+3w%j1{21cG%cURN{s;XgTtN{Ax-XX1g?In5BXVM%VZ z^d;K*i?8tce|G*{^`1D)AIZjDA>ns3Je(EJTR-bXXinw*1LQx034R<2+@HrVtK#lhG^&vo5zrfjVeFC4HPk6|DNqGibm{o!G zsW3rRVp|A$JFL`y%a;6aU3NO-1fk2*%YQRiVGc|MKok51-iw75fFFZ|fe^@BzcQmD zId-(0}>+%9nZ-M-unp6eEkp{*(-=`(Osun|oh^yI+u9rD8EDYWl7- z_`3O5XyR7QvVvD3NI?44fO$4`k>taBJl8sA`3zg^N55W9nQ~ff({Q^u&Atck)kWFD zl~VIQsp@^uK6~%~q3o>#qI&+mZ;Q}XPy`W_mhNtlk_PEs1O%iz)+LngZt3ojhFws4 z=|+h~VCh&{Vria5zu)WkyRZBH=Q)4v&dixJan8(qUgv#2<3}K^CW0L+fA2n*0HvhB z&GwOfkq6SZKK0x+q}W9N=nYo zsL%d5q4}_p5q1_PCPr3T$-o!^?C(@EC9CmluZ8Yx;^k|+Cl`4AVjY0;*`C(8KF#7? z-u;;WbT=w85Q1~758MZSd{5%>JzsfHJI3jO(b!mMXz2SXiT6q-CMF=zDZZB06fbYR zn_EDaC@vnO0&AA*?eQma9+Vl>=nb`k0ox}vZgYHn;9H*{NPdv2n6Jl4@yaZ3x&G^$ zY+-)TLLu03V-Kkn{TdD<*dmB~MIA_SqF@obdqwFoo7@a9E`4KbQQQH#>;p zOA7VnJ467CdpTa+DFWY*64&n?1;X3b*4EV2^z7 zZW0z06euKzhJ_Jyi6TUAy@oG}gV@PdvY6P861s!!B9j}VzoFxYTglgCPUP@uY{Leg zK8fQ-S~=CX(2D_|&?}k68Zwb%WvZrId|x?=n$3s#E%JiO-z(bC2{I&pHM42{#nx9h zoIs!E$E6QS zgTrK_c3Q9Z7R0{WKh86p$4!+eAx=+uLVdQ`C**Alg8i-bWsK8qlWSEdeV~~bKWhZnA?^g|MFN#-MNA0*O1KGCzPj;)}vW*WO9!TNW!mmMHE!i!7IX?L0q%kHf`wC6V#Jz4$~v~{sNyb{ zw)&<$#9D_g@t`t}wng_SHT56@3StY;+y`VWB|qqb+{5PQt&l5@g(1s~UT;!HFctDA zV!NMrhD~ldfT}2XbcaHPS(=b1Qm^6o#jm|b5trZgA`UyXqgoFazd(?_=BA7Tmq5Tp zV8EuWc{8)sGpd%bk4Q*DAK^&)Q75O_awFFw$Goo5>Wj&eufpGw(SwGg+?8(>0dY!4ih@r^ zUeyEe76_yTvhw4 zjO_hu{32VifU@LBR3n5!)GZyM8v$=V>0v0U@&+F@pCACefx$6d|{RUSYvcLf3x{1k4E-GrSq@ zhtIS4?RQP#blVe|Je`y3x-SSkrOI3RAnfOE6XeU4mzWS%_|-Y7KoFXI*MxL^0RLzR zvju4puYcjirYA|SISXi{kEJN>?BhpO_9#|c4{KpcML0KKs6g{5G2wb-4ll^M)%0O^ zsck&U+!~cPoxK$?_8T#Z_^$1aadjwkHEQRtLEKXz&y3a0RLLR`vb$x(C)V9`!(b2`5$LX2*IR zf26T?dv*|)9f-7Xxa4$J6V8ohbKBDIv&~gxV|w&@;Rlz^DIIH=Aln#-C)Ip#Rn%qJ z9MR!Sm~QwSp5fZAUE|Cp@u)AzD$wYIca2@Z&2)HfEC)x*FR0dCLa-D?PTz?3=jy&p zP=3W@Hea;Js@XXB2tewA%A76XwOyKUIM^b7ZwPW23_LsbB=Bf>tV0hHxo@{{rmv$7 zP31CDtL7@>{h{$p{%j6s(0R#T()^U@T7A2g>J>6D_>eQiMA^a!{(`uRw6`>lc%0I= zz`IY^%VxZ@8Tgr&h^$9e)bYqu?SsnVY!wDmE%Wc#_AQ!YzRpj zuZkjh;Ocw2C}4jTysIY$0PoP(kQj6?c)_oGPWeXo4n-xzl2=5KCvw?*O|q5u9UJn9 zb;wkr#$LvRM?dMk`J6uoww+UYI-j7Q0A_cOVYC8VLoH?CTy7|Csg7{u}jU=Je2i%Fd2OhiWrCUZ)Sy|GNYlmLmyYx37W(i&uESZN3; zyJR-l!rmshA`PNKy1B3k6Hn*v7>&G- zx>7Kk_`K7Zc>(s+pKy)bthaD60xvHNtTg!uL1$R0^>7yKotNN#`sr$jfSQErV_M&` zD!OB9SwsDyYA%D~cG=P3TI}WZ4(~G(pYD=mAw?2(6wt7I{m9?RM;r$3C1{G=-oAUjNdU zoTNy)rgH3W-7;T||J4G-qI(Q=lhKfi7G?$@+nFjn+&Cj7obqlr>1rf-bTT^KC&vyx z8RhPvip0@|*J9sZW3V$V9*ISJwLW+6I3b&O|HzDEn18t0-r3|C7I<)m(Ai<6xb*OPqcf8M7Ur8S-+z~}rF^^;R`oZA$D@q%=GD5O zqGM1eoV<;?wbesZGBeVxV&+}?NP{51f`UC;6KlL%$Do$NOQ$JW(8ytdaNxwOpMGu0 z-Y>}M_<)4GWwK2JfjQ|twJIK$NB(-vP5dDCMuz%x7X-AeMem$ABB(bGy~k%zj{+JK z?^Q~#J|ko%h5%@kD!*`Qh)I61S?+6W;EiA82YCm%^nNi1jqneT0MObI2Niu_pLUKu zaQ{VQ61u#hmwQ;}EKmki1wwYlb-(0YZ|PA#zGa$z&y9UgkA-2+M&McSlYo^0XyfljWsMwSM4m_P2U~ZHhKa5La6hx|xz zBnf_q?n&4+$+Jp2M_8@jbpee)0J5Po0!7aDcTF!IWRLafwQFS0#jUSOvgdlL=_aA) z<7Td!$jrQL=kwp4XxZ4|UUFbSnLNje70IH2ImKjG|3=^yxsKF1FncUH_N0VQVZf`tIS!i+zXei8NyenG_p&R zQ(nnDXP2H#4 z)LEV|s49)$ri$R*(Va`(*%zSev@2_tlclQt7sQtm~wJtabf%D zT=J=u?63=3m_34G_WhA;r<;BGqOY3`t^H(YkC}^&M!pL83sfz4p$Yx<<9e*qLGlc{ z*P*z``%7!l7A+3!_iJ?>q=OaGw?X)~V$d;YN?b8YuTMzfCRK61wGszOy(ZcI&Jy3e z_n(f>gOrHB#ofJ!tQmL>zk3UTS>WRO-JFxXs?(zDfaia6HV7Yt{~9g@_s1;GDRE6< zw=pR&3pz~b&2pU*A)54(vgP5QrSZRsFi|(TU{XalKf5s)Brv8<03;ZKLy4q9-u!=j zuLf}o6N<52Frj}!P~=#+x&KuTOy>ToaDG1C`nraO`g*}X>vYW8Kj*Jik@Muw{QXaM zFL_X6oKC)PddAFpL%1+syZA$JsE}5e1*nXb72|&wZsR9Pj4!G0{P6NTnp{+0U0)yL zT+VSd3|b(g>n8WX0q1*;%#;6F{CCsRDkT4G^FD8|o1;YDzJ(%3k^iaCNZgIXv?x_M zIYmWbFNBO7i<@Wz;u7;~o| z4qY_2Twh;LPEMYgoW}U+-*lFeGShfUb?YIo+;Onxtzu;4!iUo7~rc-*`pgz^E!e>deJ zB}g>^cDy^6XJ#4~ACLc1M2^r0bC{^9$y+f#VG8Kt{9o4S!MEE5176CHNt)fZwg2=f zF#?+KOPJbzp~dZBO?iC#%a<=U*47E1X=rI_MMXvF>5HnXV?^i|3HZBco!CLrk#}!l zj(1r7aVzUtzHL62LVB8$)6-0fc#rRayY1#0WH5)UEb-O4?=%7EdfT|Sr)O`WmH3}> z-@ZBf`CE`f!Zi{CorgL3ryq>L0t;9SK(!>Ko1wF0iLQitWEX6@P(Y zi4DONba?-E-=7lQjzc#(EV>J2EG#VG1^bsXZ*6TUC@Mx%h^r8a={Yle3@T3WK0hu9 zmBaZ&^{|9EX79C-%-0Z6@NtZoB)P@=_dAR2?J5wRk^SGD_wV1wxX(0pn8W^O2)Mbz zt$B|?RRq*D>&E9Q#@7%ge~kY_MPmPWTG<9bFdkX|?OU(!-9QVk^*g8N-%FjcQ7@^C zc`w%ZbucZl-F~KJYPyVh5?+DCf46(ORiVVhL`B6|p+id>fmts55(j?u5mh~}0XJjC zpYG(KL}iM7qYn?8p4QI&y}h-?#LPU<-yaqp4vSlvEYS$O>9e!rUVw6SV8tT2<1=R zZZ&l1;QhvU2$q;8!q9bEz(Z;jX0r>=tBcdw*;&$Cw{(juE0Zv)X)P@+O-(`~q9=B~ zD?06Gr#ZpLJ3nb)=a|Yq)CRG0S~!#zn0d*=kxXMEuI_H`0z$1BsCH46rNt<=nRcT` z^;ootZo4Y8-pRGSM_P9AzxAYHd?o|$yxXMQUym9Y8%fSjdI)i*NWsNZP;IT}7MZN= zMeBT}lTjq)d|h1qBIzz`B@!E#!z8Q zQ?>q34aN# zK0zwYfKZ&GQ4xr9xwu{q+DNeKE0&i7N@Z&hN} z*7?zpS)7{^ehY)>4FAqhDSCx!ZE2|t-@rU8cjbjKU*{P^KTg><&dki{Xln~md@Nqi z3APtMe2bxA{xnG1J<*g^AYAp!?#F$fqu4S;$%>pn>>_|m+D^SD+$)pJWp8g`&+kb3 z+emcfMiaO(kJ8!V*9on3&j8A&zaJ6yCK$>mrWvzmqNkH_+`rQY@{2Q0(|)V2ZjWS( zriQQEv0FL0x(kq6Z_jcjVcIqX<)gagV})FHT=%CBeZU-$qe(C4ra``<6Kk za;f(hgCCaKp#8g7Pw(DsG*3!++{`BLv2+h82BnX}N|0_I-a@wE{We0vXDkQluZJIm z_LqMMvJyjF`m-ZxeZKw%yj;1t`CnJ5L0|)54YhWxnK+?NOKbgscTY~^hKroa_&>}G zFW7vvUSYlUoFInK2MiWgLt>nRw$fOG%@@QFkq4M7Z0W4IPrp%3eJ(&>3v^S`NVrT| zy>30(6}{B_E&^!>$cvQKE>uzi)ptJObC{W z#wk(00Z}%JN-HrpAnM12SoN>ihc7lYu-b!;(o<}$^yqL3)ZJ}aiRXE> z^B%MEj51sP$7FC;chdF1Cs!BdlCK+sL%Nga{bUN1q9M$XM3l~~E=Ac&+W=UKO{5Tx zoM!|wA9Zb74S5Vo7}~S&h}gmTJN=B4*xep;+%u>fvr(?Op-`1PHhGj(218Zyeu1WLV$q1o$Sw^6@O+B(`Y2{2h;~+!bs1964b;XOP`W`0Ii90;?g@m|E z5zt6T(+}2$k(lF-e2Pp;>g8Sv-}1^z%nJvKdmvv`o$dRM&upo_zL!#xtX3==C_-y2 z_a8Ce-$nub@|et4ZI?i_7P5WkIL7d&cB;DCKGWRR{w((S7{zq|lO*G>J~RL_y@lA| zt!1S@ENjNvPm)rtSVCS=lz^D%i7&0}a-7s?1&z3Fqc*ectKPc&v2{O%qRlX61@c5s zSk-CNr%+=Xtb&22oCjysnQKJ>Q5|Wv?9`6WXlgXI!1p=dc-&RmDm9v~|>ILoV$?+h<_%mbSUDjy1*Vi3UEo}Jm?-}P=^ z71d&)Q+E1>@p#eV?Kp6b8I?1bn70@3R{{fMyKOxtGmxKS%rp8T zi-!+Zyk?xU#%q>;W&rPUObcmiUq9sETZLNqzh)OZI_shweAt{lV@KM|CdV~o)jW{u z31nrH0-=BcsDilfhjRw0`90=r@aMGINkt$9*Pl*V04BDf)Z@GCNR9A`qOd=>OuyT9 zB#t2Xi%GzM%1mM;r81{ zQp%x)j~@;xJuvQ@DRh&ZMI;s42U)vN?Hm_2lZUzGq$qGc@`^A8UI(ZVllo(J*?RV- zIkrxZWyG|zy;3ew-E>{D++K(33qMi)qo!O97m3I$iu}UOT)snlAevLLdSw8;k4TR(iw?ORhJ?O?RJejg9aNq2h6nela~ z1Sf6A1 zBM;1{BpU6WO5q>t6oyL{B$Wu64k$JXd!)_RL>#QXvBw=vgYetF{NUF5H6w?g;vEdU ziycpGuh%ISby)cP>uKHHeoU@Q+KMRL*=USOQFQEaQFg1|k{pWi0qeX-OSgcYacO5kXz1=$ zZUzPEzsrDY^rtQ^Jnzv<0IwP}doCQ?zd<{hDZgG1*B~ZsclFyO?`Kgp+N+B%DS`0s zh<^nrh)<|Nhm(_X%Sj z-{oo@Ws&(l*D~&KSM4q?6m>W@q5&P=8I3{T#kNd#Jo0AuX@biO?(P*B-$MCR!_E&K zYC{_EPgw@z9VYWw!n&Onr6sxF7@Qr+Og~AI0jb(yyAUta^+vz>hqejn_=f_rsdZ*k z)>YjiXxaqVBujG9z}bF+uW^mmQ^K4UcNkK|_xE7?TZ+=wz(kL;dtcCLV{w4ZJN96wuc^^c9lvh}kkS5m0f< zk%lpqRat|qyk#R0(pWu3>us(&$D{S5R0Akq%Yv-fDdc=3vnJ+OGS9UumL3u@Wv2$W zneXH67%E@7rB}j*xgB*yssUk~6D5u73@`d^S0pl@#G4p&O$BK@;+J^dSjN%^UI^$L z4y*_WO#d2!3{Ah*MASH85xG(OW5qtBoekXR-E% zzsqv84qW)-VJR`eQQ}2eZF!Cbg)z`Fk~MTXm)JRE-GS3 zBH%L^YTCCsYPmg4tt&iLLzwK7I4B=_UaJsf?Bre)WfPPDAy&=~KkF9AqDASQW096E zfRoW%Es;XvoW4k@xui2XWGY|kGy)34OIsozJ!eH8nVaXLKg5m<2J*6YVL*|7AoF|c z>sT=@GsC<-(0)H9^3nahNQh-}g#l=U@6}!zBsrHspLyr>L8%g0F}$cBtVm2!T{=gz zDhgFJZ;`jOC>}{d@Axf`G+dDB#$yG27g!XaYJmmn5$=qsYxF^DuGb&kX}KLhgRB5@ zcZGHAJi=XjCTNZ$0ZpA6NGAJZ6!~PYLW6aJ_e(|GvZ8LcTOwZN@2TgV(qwT%~nzIX^&8@0oK>Wf^v_ zs{BzIgdVF+zPW$wqQWg)XM1y6OP{?Zo`N3*w{x9+YN0qI^uc8e1A}I@Cv=Ur9%XhL z;pf5ljp-fvkv{~-6qzG)vpzbPi& zySO&58+646gNp2ax1e;kquj_MX0Zmj%79ZWFNjcQG=Inkz6q$RJwws^MVN=%qXP@} z2`eQVx5Bm-tEHI+SJ;G#;2J6uv=Bv5MvsjmolB z1&*pHt#BNtF9AI|6C_9;&X!MCpY-Pd&(*ch>0(=ccD0|pWmNCc9B1CGs5MzqmO3-k zZQt_aoP0r#SBA$@KhgNLdWy0ne`E*Z{*1 z;(KYAEM@YvTwH+v7}lAgU{1)(*Sfq~M;|sS7IBzlwTBuO2wSy;*)ev&f9Sy{h;Md| zAo_TcB7KG*KZ^A}CiWjL^~YNN^Y{N9@NfJd==MM6HRc3Nz#qhS^YiAofpY(^fWNUA ztK`Ns|4Zin2K`I@{z>pR?;8yGzXJZv;Lo}L6_2^tKUe*m;!ixsy;bNuQ8!$rMRfDx zJucD7e|Gw}-?ece-D3sD1sk!hzr9 zmX^VJ@qL$>l|i$9VTu*QbRIl54&Gxz2VLU5jJ4gjJqemV?MR|z9=i%q9CB#`LWojpw2crRU{+`0Kvx{(l7g>Fuz+ej=nQgQDJQN(|Yn4VPa$OrKDv9$doz9 zA7VMoY|93ljUy4_9QDP_2d}g9wJxMq%37x;HnU^iU5BD{s2H_W{2|HdffoGoOZCLk zOcIg!KUC&5dxuDo4BiavrHqm-9S|v=CNSX0N0K385m?dSuxY3ZG_4F)^cgGST8X=GW`!#&wFXDByDMfq3uV z8efe=o}=4F5MAMyM88hEtbj=R3<~N3OM{hFC>?$UZUBUW1El(nnT4kxY+w@GTy+fL z(3|3ah4X}$Aag+lcD~Qe|AUk)gH@}<#P4ltyj~T<2;&!*7eX(OXAy5DeA)Uw@Fb$u zKimf7qDo1Hw#p95Le%Yw`zq-^d&K;9nZc1uTePYgj7q2Ps^?J_m}>N}>9FcEqfEJ6U|}!dTvA`#{Y-G8 zpr;iO(4~|P6>D#+9c#FvaVpC0DDljy9ia1Uv`bl@Ey_lS(%dvu8{O~&!!{}=3$V3( znHt7rTB_lg+yg3}{keiC||^QvT7T{SJo`<{Au90ZqQ z(_h zl|Z?G(Ji<^W+_jqQZOLwPuCEc(?)6Sa2N`<4g)-+QZASD|g}1@LAeD9H z!u&T%YxO{-!XT_qLZPigRj|qIuCl1pJ^HRT)$MZghysQ|#Wsq$3vcqpgbt+92mG&) zb_@?b4v^NLL(a!5k*9r(Q~5&s<)TGXA~}f6=`G49bi1pGMg1|8N$PHp83^`Fl&5G) z8s4~$`vq5np!A_fclg?d(GiIvVKnM?6KYS0NW~_pdVoW?Q2VSZs@8DWLZ^F%49C0jjQmH zd4+%m4}f!b@2h4q9jDX+tVT6;1UtHDMugtK)3W5UayQ9P!S!i&iRQ?QVVKO$Ckg*A zwNHpRE45_nub7t;6Gea3KI=DXpH?H6l=Izg5qdhX^1XT~6BY(-!e<(}tOhF`~0vYsW$YSZ@?q@im^73PL`iVahnp+NeUwZ@@lGk1rH2;N>V%QZXbj68^xx! za(`|5>ImUvKRH$;Omm}ALH?vW+Z#gNCyig1!|7WdI{lmrYg~gEJ;@?ozH4-idFK9+J?ho&F}yLO8f43#IMee} zxp};?d=pD-qP;ViP#hf75=6hsEXh@?5cbxLb8T6=>s;RHiT1W174K0kK^YqlHl69G zhbzQV3Z8lQ7Cx=|K61aeV~DHul3u6s3uxlQ)2_SP#=`$<0jdcIOlKk=rfYxyI?4Ue zI7s{ZaJLLx_kDp3C(iBC)FXZP_KJDA`XP7QzTBc~`dFi=G7ZlHGxEI7-g_d9D3FtQ zYr57Vt(M1;6Z>`p>nD@fYy2XOYV!kyWcifEygFR9kdhv zSMlias`+LvwimE)cN~n7aKVFCgX?q;YQlp)LC_l$;E^_cRlRVHFB8j5()>@D} zToxk+X#%MUMA7Bt*Q?$Ywo4s+!^B}~jXI|8XYW52BCKzjHilJ)DzhyIysWy28FQ*5 zlW0|Xk84zzugQ~`=$5#42*eApN@o|JWbdtjfhqdW-e_F8LL#0DCP6IUCut~qi$Z5I zBAVy+Cf#3Sg8H7TupMG`tA*O2Cf&QAhb8*uN|VhXWGf^JBjd`JU^y`Vik(l10Jn>r zIqV`fU815AJPV`Z#N2KFpuJAH`+3JDQC0W9Q^ItM&|+t%uHw%)Kh`l#LEN6b!RS_Y ziPP75cLC`wqkMweC{NaYfsp2_dfW6WPs?Y7W&{3T7Y_LFD0>WF@H6NMe@PqPo3YA> z1(d1%#()yWZP5E?$!hnWaYZLLRl$N?U-Szz@ymWrSSAH)FFQ4U_X1cdF3;eqq}^BuFOg6wKJB< zJgy(GN@+X-Po~BbcH-jvXwEScud@zrRh#UyI)R;fD`pF(WkWbSu3@t6K%vQ&w7<*OBx7gVJx&OhXxfUcZZ1^{b*y0KFt zp!BeF(UNodf>9wSX)n{4cj zZ?eI4;_J`Trf-3b44%zK8rPyl>K&do>EUeTj<%1pu&y>UI`QYZ}s&;2Bo0#jR>@cL%V^(qfn<-e*)Miu@Q zOa&3uhC(dcg_w^>jbt~x0kr_@;FWO*!o=a@d)Ul~a)nWN@%_d3JHhl+)K0{cKr?y09%ssb`h(JRV)(3D2v?r`NTRki)6Ow^8!F|=d+%P*upYF-S z0qlZ^%Cs^=6a}Pzy6b2-27PwuGBK|MQoRF-fl|E(I*y|HXc)^di)LlGv6Zb*jQ5Xt zUwl=Yv`S9sba_A>Jsc_$wOup;UZ7UD%=ToZ`wfTvXabBet5tMt>)0( zcgh`ov3`)z{AB%M`=NTLBrbjuh9S8hoP8H)&q6?#((X5M&GiGmL1WQjxp`>;ddAbP z*KPga!l-%%Mp8;V5B+LeT!1hV!HN`RU>-wsR)l&3j7-%6lC zMmAToAq7v}*pZ+$9n6qdxoc+9%e@@%&KD?*ix90cS>IIprb{Q1Nsmm8oF0sc)DpAD zejkqfyjW$mpvhGxKbR_(g!@_U zmpS#6vjCdk&fLEAnmK$IgU)|2&7dRrwQ4%=IBxI}uQIz=ZnB{v2)TC>Yv*F0kT7rP zXh63ekb1r+YPmW6N7Pj_jvimQEx+1v5yX@cu{n{Q&wa@|L9gUbImMUh5I@r24TXW5 zWl9#ZXpl!!L$mGgctnyQ9*sXs6VYcaze=8DI<=`2dy!1ggV+;Hq@~p}kB^lzA^ed0 z=JIfE`x^5o?i=VQTK*B_@PX*U=R5DPPMoDOysSd9HhRY%KMlN4bu3C_)bHkgWKl(- zuUDA@bOftTv{u&8&_x-K1yNbWJ@$zAStN8D~kt8G~KAFZX?-g`wtgu-_3 zJ8$a)lck^$x9?(u?N7#s=AM4FlRd7&yPvIkbYohB(NB4Mn3E%BMv6y34To#uOnH!Z zYj#T>YpyVErExk8gGx;zs8TZp)i_v~UNmKl^`)8}mp9KvAB=x~0UBXs2p328I`@cq zHpYa*FY!j-y*dcqe?Ftc75zjrTw76`0M-^4*`gm!i+n`(c|qjI%u4E2b-~^+cfaQM zA)!ROFBOmoyY8N;QV4&0oje4hj1EsZoTBO(9EauUWIYp3URq}x#04Q2WNNnviV(*MZD{wT-(0I9#avKt}UKlJN=1^oN>e+khrc{DbWN3HUE&_kTe?OuLD@BD{4LVvDP@n`;a2Vd1bY#^^6Kp()E&EF!NN zt|Y52(!=r78qfNs&Qr#YAyX0u9bBY`r|cmQ@McryqGB;xdW;n7&+!S;*mFiUlK%5z zr~ae}GeOD1`OO2d&MwKuf=sQDS{-< zD7M-dWdyW#z^7zOQZbp6WVmt~P>)gg4CMvQmImPknMuG$(#f9RSjC{V>WYWy+`hMI zkr~5yvQl<}G(vFzs-cLO+y4w0}X~Eid@myUH|fxa(wJr|oivTipLV`0yn~^=q!?#`P+^bOxbJ z{yA}$+64w=az0T$>u+PmFU!Kr)J204l`rx+!g~a` zWI(;><{>wUDNR?(W(wxoXYN|&IC^Fbg#IzpG*l5k%a>h|jXaRc*hos~?s z>odJLO@fT&CT18?{~{y{Eiy$Yn)~6B+s)weBQw%>p4SieT+OV9APyg~C~>g|6{U~( z;C3DJJ7tHU)%hV@gPfb&(Yx)C+(+j-W*4UvXFV!a!^c|ci$51uciiEk#G>Y=Kc3*M z7W9FVlh9G~M7KZZ;26!}h|b6SM8RXBuFHM_0MoA@x762hg4XI3K7ubGyF_Hxker{& z9pTuD?{STI66V>5TyWI}Q1DGnh3tWbe)Ad1H^jaY52}mUOniPN zG&w(H`TNY?+~MD{lh8);s=h~K8`<(zynAO)Px&2=4t&7}k5Cm|WCzA^JIA3t@RR+4 z{)0T`Yo!4&bwm8K_Sa(Z?RCGCsMQ&|H$sR_HOb>f@OJ0U$3M3(fAXSJZ9zx;sQE>^ zx`cUdDt9j85n_CYfoQnT<;o`=Ajv_`2B+EeDVh_OC2D2_=a)T)qVXl>RLQTm@*4y3 z5lGR@rR#D2b>S^r#-$Dt@ax}92Jyf1lQxcj(+uH_6~moO+HKp|`p@P1liJ11+KOZD zhfL!CcsuHiX~2vI$K|#F?b-Ag3}F6!rRFLVyz`@h_N~x0@nM6t3VZ2C35+hJ0T+$? zgRRB^M^l~F`vb%5uWL0|w-F-+xZ<5QMX%`WS7AvQ&z8XHfhpR}zp_20&zop8vt?b-9(p5$xrq3}u%K{Fd!pK-*;pqReRq=nL* zcIXVdNliAQF#w{}RcTQt`dFtTMCUNN7IDIE8AiidYy=y2AAIJ#*{c>N< zFAT^~e3&wtEJui7KUKQZu59-}AYL}X*7GjALFn&GjWh-wRh@TU*+!E%33$DVMRPBL#&RLoPBR+g&6i!ikTZ=4iX~~at;M{= zHp>eRV07O5nVtQoopxk#Ka$Gljg;Q1VARZiv}$o}Q%<#s+;)*M(=F?Pg9&Iq^d~)u zgcYJ@WiOEG*S8Mg`XaQHqrPj)DPOfSA<1|bmv`^X5WB+E26EiAy0Fd_IkAUGN8-^v zO1J;qcpN9AakX976o9WA=!u=+wu|04L)%|?9AVREoQ&Ws!(=z&Qbno8;BjMOr;rb4DT3D@r^pZJS1_NaK5OG~y7)07{#J^kr$Elh=xt={ z6(P?iFDgKUpZphd)x(Q)5!01k_I7T2+oOh91Mz-<389R>SDkAc+iCCn(UN~Q?dn|8;;p6f`i^VZ+{o0;jUngzuCxZYk(j&=P zy=s#S=~FplZ2I?w5MszkL?vI#Du&8tby#8ev}s6S+mmc4i=cJ%5ZpzF2Fcubu4-r( zzgV`O3Fptpispy>_#=jUBc}JfyfRHzFj8=&*CU$2=V|djrK*cs0i0)37C!d z5tl>Z&BE?p#@D}yf32Q`Iz;$%2iu2PL@n+|xBpl?2Y`$}vYz{llDx%*`K-R!1H@PG zeh&|+7Dq+%(qGkR5}bn6>iI{Zmlf05?QO8Woq+|wS>IEtjcG0Ko8bM`jITz}+J&+1By_I4*9YB{ z_C2pJK4`HDg$J^pMp;k4G9Zcbfib(ku?Tu|(L~Mt+lRx)c=X0_;f#LsE8=B;rB$ep zrG&S{>m>B>+F(H)B&+%RGz&I7uGOc|ogee;I@r_Q$0%emO6W$J`P;YNJ^t3_(K6D6 zE_`@k#u0-TsgBcdPPJ3b5JK8mt0vXc8+Yd-83wu;-!Rd{Pe zbCSEAOUSOWq}blwmAkD*M`yCG%vjS)bk@=?wkH(x^^(1N-|i4^;OzVKpF-0%%ADL( zEmaR@PUO1n<%0O)*3!6n-D}eykd!wg`m;2-TH<&Z*{F`wJ_;TKr%M9wAaX^)W7V5h z!3|Ml4LKdqZ9wP>MCTdT-0Q|KvpO!jugvufb@S>1_Et>9Zo`gPRHOFbb-U#;EtZ5+CvVZXVBUOMVtev!B6mOH!$_9`vvsnw75*t@zr`%4o)cBEr^;fSB0yVat&1R7}1d1^CjwZo9iUjg*-8K5c6sh*+V4q zS*jW+yUj0c#T6#?$88NhU1v71hi^9jbiG<$TG2B7pg{Ua?bd5w2?)a#P`3v3YOGV5 zNbx!;^!kktDvriSx+^$uO@(CQlaS-+Hkv)tO~kuV*J=hpBk7<5RubVvU|X z8<^PyYC9nZG~8ZmbLEzATE6<&5yEZ}rR_)U3$w&L$3@;@j$?_p~J5i0d=mbZ7 z+(7+4DWN($vf2HD4<3_zqrt{#nlGq}6S>Mv?=NZ?B-SN7-{=WUkktxtynkIrr|HGf|8K zgB3VAukHK3#>lu8j}^AQS%-?MXllifmIG4g?!$@X^!yFz3pm-@Zl2D-40CRwxcE+i zN3XIcT5xfF{bluH^U8^P)JXAAn1bueH1=ySBlxGl;(D*j=0>8nS3Oll z(FD}qu#o|epqP%_NZ>0r;=9K`kv!z7*E%%}bP!>8gzO@pjry1u<@*#Q11d4b)`5|v z+XkZt6{ah%x(55tc&1x6+)P&vW8kO?7tA zC>u$nzP)lZ6RB>2G^`5!asZi;EBSiSW0%czDJpavJ^iM{O~_pet5KzbI6F0$6)601 zXNJfl4eg6KT9VL_xX=0?^mzbQV{Qs~Vy*EAB)Lp0kuZ%+t;V6AW6ElBch99X((XY_rt%ff%cu0}*7DCS-zD9cvM%M)V9rk{l zVQdeZKlQ@SN5AP15MR>zi~$br0IB*Twy{c1G^F4DMf8L>}f2pdA9^%%xIu*K4k1kseRitI0o(Z3ces%L4A{wT3 zEOV>JUaJ$Ke*$PxaS`>HeW#i&5K*evT*pT` z#Ml?zhH#|b#mlkdcsRLPv`-lQn1Sg<{+Hup-~9_p?qLu2(4ti%6}S%iN)ah3urS;f z#B%Y>+cLPGA|2V*doSF{N>Oq_d1Kla>!CR)DOAL=+z6=N%3|&bS-xjUv2NBcig|eCGSsFTH4c zyFQN;vswzYb-ecxBP)5}^GNj<&ZR5lCtjy;G+Q1RQQAcH2}GuREwRHO&<5f?ogPO+F#a1k_y#E9Ub?DYe_*6@ zJM426+xxRo?{4Lul1I+5arnM)$F$^IHPm85>S0j<4co3oXNd{169#b9M+Y6PMonzY z65_IL&_$~SZMg_*i)N<_b2?8|p}Y~cFv$u_ltXQ7_pivr?QFsxRffU2 zrnkda)Z-pdd*v5TC3vMV)_(@bPfV5Sbo$N=oL8u#Gp$@*;aksUGux5DFk+dJV6k{+ z#gM(uO9ia(Kn`0}kQs5+12;wb!h@6KHZ%ULu^x%p+QyL|>@K9OtCEimbk;YI96*xjFV)e^%{oP&~D+#SJaxSjBM94yi{OUJJsD} zJ{v1YxM~#}Q(2gDUF79SYw)ulTx`5#vrclJZB-4^@W-uNt-+G*BO>0j*DFYJJp=81 z4RnRQY^VA8QPOO+%%W&rU3Ky9x_{=N`}0|HpBCBcZ!n z<+2DPw?w&bQ3$beX>M8NRv03=TcPCEVn#yyx)WM1E3{dJ+{&d;VhdwJhRJNqey6_m z$M5`o&igz*m-9HE&-?X$JwMb0lm!u5zpz28+x_yaTyi1=qT`1MZEsw0mMhH8^7ar* zT!4D?3UGFJ!N!mF`5&N>TEA7`rCooHy*F-!HzJH#!?_+^Z=Esjm)R3NAGrup0~bbP z|KukFJ1vF6C(&lowCc!*Mq{K4FO$JISanoZ^y}-KSx}Kh|IP7pVySdR^x3TW|9)|8 zk2~W+`*xfUw;8>EJw?hSBYUkKS5m+KIcI4acWwH?wKh++rw|T<$K{m6J7SI4)5M+7 z(DCLg;fH(lpc<L*13OG_}|mMbmw_VSvh(*g61dvi3q z3>Pc^!NAb)wb>XyutNG*PNm<+DtJ5FA$Q;GPjk=Z_qo}a?WBL~H82(>=|#ltm^rrq zyo@XDM_q5}?r(P8Vu3hE_Hz_-Bq9R8;l3vIF!#q;+w z`$lxL4vc`|>|~H$bAp0p%iP7LoAt1BzVS3ltkP{^%Oyp*ik@P=KuSGh@7fBwS`oA- zA(C{69g`xx-r@yuSA`uAq~cf8nU(7MIYe^{XwU%6(%a z745(ROvSK`h0q$Z(%8yZy5}XrTDc5g5I&`T&NAY1FEpmKtSJL5W*y{$DG;z5GOgA3 zJyS7H&8kO7Lq@6c<}v<5Qn7XyM)sgg%)tB`5;6<#2OXP(-4i5AW=6yXmmvnVPVk+k zqi)Ve1qiJ^pGWO5RYJ(#4TKcaHSWXw)8(?=BkH`LH9mbojVVP~DIOD^D?i{- z-`S&ukl7q^!}6k*2Y#|`-k(%td@x*3Bq$1;JZU+Q2+E0CLL;(A9B@xLz}dHYlj~mZ zcDKALDxIxv6y&|A*gSB9sry`_O_nn>#%NadCq+yzjIjk#1X^1dyi@XrR~L=l(%1-> zZbPpQ)!LUGJ9yEp>AsxgEL6cb6!B$0eWWSsEL?u7@&o@Od`7|j0phZny4KN7geYRX z&6$YP*wzMrJv{PUKr>aAX+F@Dj;x8zezh?ah^h_70OahhLSo->?)XM5m`1WFii8FA zcess}2u9#{i2^xG6&017oSfq9EG#x3ifRVjh6Dnky4uDn8nBVY^GQxp0qjnY$>!$U zhhfo5XSqvfT6OR&(LRjeJ9tzCECqj2`l~!q{GG-p1>3c?B)vrre5uv|wzV|oHwE6j zJkenv7hg9ZN~HVfobu);zg%>9*Oi91dB`o8`c#^2fi&o1g+5iV6uz`q&N9|Rg*DY4 zWME)By#E}Vk54$o@~nx&^BAO4Lv>M+)-)BuvqfeCu*IBUeK|eHPHXoDbRHt+?#{cF zz_?~M8Z4PX51jkv@>Tq@Y3yefLy}vZX*6HCLz!PgA%5F9e9TkXY;@qJBGh0#pMGw? zjZc+=n{f+i5sO!iI(OJDKhe6!<1C)Vj_vT8NZ=oI`AhWNv>Jl5;vO$d- z32B$P~9*u?+|T3x50GxrTn(N7Vdq)7V&iLbXlV z(?mT7bUT$LL;*Bk!o%T@LO9;|$U6oX2}WhwZ!t8BvUk8D*V-*bM7#MltMx@H7G8PB z1axZN<7nezpDZuMjjeBXuLtgd7#Vt)54S&j_;R%BExPwZ_~&+?VH&PHa6ag>YHcuX zKaN;m@}j(=dd+DOn%;r%zLWff?8-}b>A*Fsn5rGlbLgVwJ$KY|tHwA$deZYxKB&ik~``3m8dKhnzzJ!Nou&+tKD@2%gJdi@D= zn0T&GFIVql*bQ^i^yyfcJ36MUS^G}zvxsQ#?eeDW4&F}KnsklYvOex0K z_wqL3yP7>$B}j{^0Vgu$9fLFixniKkHa+oz-8NkhF1RtwDo#GB>8FQT2k%l;Yk<4l z`)cq5x$SVq53-t^_LOUqRSzq%EPgxtK_}dIS2SCzXdrm z^s`US&nLqeJ#0XSX=)%!A{TDwipRH{Yrn`G5QkYR7X7QRg0w@N>}r9%x2)D$Jvw<= zAR=Yw!bdpT=D7NST zNC}i(R_wRW7{D$Ram@JC_qUlmg3ie8?-|H2PGFp!yBoGnkxRSV8j}4)3}iEOkD3_! z)U-K2E4D)dtS8cK1m<_#is>h2Qq?;3-NG7!sIMp{DpQyb?(zsXW$rbT?m!58@$_z? zX{(5iyU?8otn`HWHa>1bJ}geBhz`~D8SHuK2t!abu5Z*y;gp?zHZ%q7J=7dl5ZK}K zTMGebA2l^KfL-6#%Bs7wv-ACXMPYJPRaI^6%2;E_ki0KoMCR8M*I%v)dzlx^jXH&# zn6;3NSx8TE6-hv$@f+_viX=<2ld03lyw%R7{^Gh(zGf=tLlIitHjJCwyqa#2T`Gjz zqanro+%PR@eVLjm(FI|ivR%h(c_4P$N^C)MjaUOhQ(Ib*UrfGRzr@F>W87NiZeC%3 zw%Dqfyfax){t0_JV;u~($zMgJxFxA3iyYqa?AP`6wl;fpBPDZlPMG#>asq8*KZd&) z9J^jgHkJ^xf2P`@dr3wDatT{{k5s}Rex(b8KL6GE&H2W)-!scNY8V?=rv-wnwXVb~ z0kcazrenyQCF*_}%zf(p=@I#7g;!?Ar6eNX#&k+-}v560Y> z8yrB}CL29hzb?lu@SRdg6Nz-V5$GebM^*zncdOTf;SX69cI<8UMxL%HKyM-k4K>d@ zg?)0$F-^KxoD33~H0?0bztMBEB>CQw^+!n_3xhV|SZ1gOnGZkv@U(hxh|%JuX>fM$otE_*xj?T;7uN{f}Fi<#HBv{+*l$(CdaQ|!8?ADZ-xDTw%KbZ&8zUEU*W1tm4 z*C^w+6}%8LnT=XBCaBpc6A@wHMf&y)v%So+TpV0W;U1Y`;e2Eioejgh$+Ij>pz}pB z$E$AyIHKvWJ35tQiSPiXC^k1C1gMA%Av;XREP&tIOYEZZQo5rAekqJxO*IB;i%kc7 z;UZvR0-^i(wm+5K|I)Q1pB`aoLqw@09%s2!25oo^*O2}z{cy&K^7Y3WliueM{ToRE zaQRG>7?Q%ebezjcqSDGTD9n_y{mljJ|FI4_EPmrRO!i5iP2KPe=lC=`TW>kldatw9 z(YlPtNte7M3>0PiulRP9u+UK8$9_^r$KleY89Lp^%d4cgxUIe2EagF3S{fP+1FZVC zwzj|c3wHzs4JFZ0Geau47@5A1M-A6TAHR}}j?eh#&bD?_#G@90w;0J?uqRL>h-Wy^ z{vx`N!sydzEa8)G*cVXG$cFpqTDae;sLV3ZA;%S_xj(+X%D43i3w!l;;(9>9^U6w9 zb#>#|O}&fj-@kwFJ_vlyls`a(|D?e?8;N}u#i$vPxyMSuh;oMe#W@i-Wh3zDR`~i9 zdxFOr^QLx`zr!!a7HUr_A>#p1&lJ{K@=l#9&D+_k?6Y^F?Pw4o4{W>TZbS(5OW{pg!K*^2hn7%B~wI zO(YYUbV;457=eP;bJLy~{~BfzxI2o&M=mNVA`r03n@vqk-QC@_wY8IzldY|-_GPx> z|NPe$zHf&f4bwfVHMqA=I+H-15C?ns2*BVR9334Vo;h-@s3?93IKBiBmscqEG}0m; zuOm&$lfNPIr*BAz_I@@1)-^(HMI({wu~FiGLK+EsVr_@#Jlk!De|Y|k)nlxe^klob zxI72oDu&V-L=ayLfQ$k9;t>%p*1Reb0gxz0@CfjBQ-F8j=T|u%5V+${s5i{h!*kVx z&ZM{L*{E6oC3$`0<=Wb|k86J$IlYVe{g#zH89@6sJ=pW-DL$jUgTeV#U%TeG8?A*i zSrXG@zf@z6J%!vJ=hF>jpUI)~SqdxSz^3NE>ka0#<_K`R=glM%A@mLbs1P`wV9R(< zAX#)p<#%S^f~2|r4IaMZmS1RXq^7b8k`x(weRQdbYaC zrwa7l73JorzK?6Ns*HlQ-vkjreq)+wjtaUv1HPCBm%yd2KXeH`J|k(MX+ZVD-?sq@ z6!(b9MoC;ow-T87da*P)BTi(G*x-e9fSB{=(`6;`poj;JJ zrUbXKS5SBO-LU`exyb9eNjg0zH|&+K+4{(ExWg|M1*2mY3e zorWD`tqPysn6h%N0=q)w>#k1hd~tj9 z>k9&TCuj*slCNK3sQ5 zG5_h`KL>#1za^X$k$;O|xkvw&Ux9|j&&+0@yX&`b5JzJ#zb(oMVmu{`>$3J4wHOew!k)`}lAFu&3dt z77qY1tT?_%lyRWn9Ac}S%TTpEZgQc?o#{1WzEyC(S4`&dvoJrG9|pN__cu30>k+iN zAuZl}m9G~^+4|&~BEB@Y4hG&ds~|)5xa;PuTKZYoxtmGbJKjp0qJwQRL?#}VzA8dw z6|ERv7}#QvcuKZ{%nX;~sj-be$riyMkB%=G#YX_6e!%F6aHb_=+g8(c+E+nebT-s?)6=QXZDaTlK$smzRxTf<+62yrk6r6rQ3B82_l-d2P9Q&Kj76pj z@Fyl{h_C{Psg6+e{m#QXpTDG+z#o_GRT*{qgKxF%!1(ICO!@4MqA`5OZv_sT#y2+T zAGDr%L++y*R5w$6Z!z+@fVSLX@6rch(TTBhsjMEMQ)XLQDSp#EsDD&fU%7DAd#}E_{iJegcHQMA?;D2Enw*3y)ZMin}9^!Ns9)ljtk>EUi?_XN8i2N~Q$Ttd%_!ThF zzUEf2_RCdh*s0*xy+y|H9|o2dRAFPeD&1a8jeOLxw?$9)2N+@!H(x$|BaQm$Lv_jo z7EhyR`&zgD!#{egK*?(q2tD?7qzWTdq)@e8(Q@}*jP3n3_K_G+qu*j`;UZrQ9qV>Y z(7KtG3bFY&fN7qm9L z_M#PLf$k@Yl%Ew1h!H*g&DL?;aw<6DtF%?I6Qul{+q*0?x$NV@lylTm(W65__erYg zo%67H&|RYiSwf=@cX1pOoV3=uXqzkdul2${AYmWJn{#bX0Ms0{lFy@>Fv}(-+j=@z z^N9~S#N%>YkF@2N@*8@y)1{e|$}xh`^07$~dHNlI0Jue~DJmfbX!w?>24DN3ZyjS5 zWJJQ5@45r}>}saAun+*md>BM$_D-Wxg~}ho^lQ;q2gYOW!W?6`;^dTDRr}o+5H(0y z&uZutf6Ewn+V z^u{>rTQU+f^U~A~Fd+1KD%=mnE0BC)Uoy?>xzashMU}ic

o8jNMVU4maUck4fR1pId)gunr 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 z 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 literal 0 HcmV?d00001 diff --git a/images/LogInsight.png b/images/LogInsight.png new file mode 100644 index 0000000000000000000000000000000000000000..f70a2c24b1750f3c191a4fc23d905b2d1a6c19e0 GIT binary patch literal 89837 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 Date: Tue, 14 Nov 2023 10:45:09 +0700 Subject: [PATCH 23/28] set up monitoring log by CloudWatch --- pom.xml | 42 +++++++++ .../com/ppn/ppn/config/aws/AWSConfig.java | 19 +++- .../com/ppn/ppn/utils/CloudWatchAppender.java | 87 +++++++++++++++++++ .../resources/application-prod.properties | 3 + src/main/resources/logback.xml | 11 +++ 5 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/ppn/ppn/utils/CloudWatchAppender.java create mode 100644 src/main/resources/logback.xml diff --git a/pom.xml b/pom.xml index 3b2fe6e..8f35cc3 100644 --- a/pom.xml +++ b/pom.xml @@ -28,6 +28,9 @@ 1.12.319 2.11.0 2.13.4 + 1.2.3 + 1.2.3 + 1.7.15 @@ -163,6 +166,37 @@ ${jackson-datatype-jsr310.version} + + + com.amazonaws + aws-java-sdk-logs + 1.12.556 + + + + org.slf4j + slf4j-api + ${slf4j-api.version} + + + + ch.qos.logback + logback-classic + ${logback-classic.version} + + + + ch.qos.logback + logback-core + ${logback-core.version} + + + + software.amazon.awssdk + cloudwatchlogs + 2.20.26 + + @@ -174,6 +208,14 @@ pom import + + + com.amazonaws + aws-java-sdk-bom + 1.11.1000 + pom + import + diff --git a/src/main/java/com/ppn/ppn/config/aws/AWSConfig.java b/src/main/java/com/ppn/ppn/config/aws/AWSConfig.java index 9f0b865..5e5da9c 100644 --- a/src/main/java/com/ppn/ppn/config/aws/AWSConfig.java +++ b/src/main/java/com/ppn/ppn/config/aws/AWSConfig.java @@ -4,6 +4,8 @@ import com.amazonaws.auth.AWSStaticCredentialsProvider; import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.regions.Regions; +import com.amazonaws.services.logs.AWSLogs; +import com.amazonaws.services.logs.AWSLogsClientBuilder; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3ClientBuilder; import org.springframework.beans.factory.annotation.Value; @@ -18,12 +20,27 @@ public class AWSConfig { @Value("${aws.ppn.accessKey}") private String accessKey; + @Value("${aws.ppn.log.stream.name}") + private String logStreamName; + + @Value("${aws.ppn.log.group.name}") + private String logGroupName; + @Bean - public AmazonS3 initS3Client(){ + public AmazonS3 initS3Client() { AWSCredentials credentials = new BasicAWSCredentials(this.accessKey, this.secretKey); return AmazonS3ClientBuilder.standard() .withRegion(Regions.US_EAST_1) .withCredentials(new AWSStaticCredentialsProvider(credentials)) .build(); } + + @Bean + public AWSLogs awsLogs() { + AWSCredentials credentials = new BasicAWSCredentials(this.accessKey, this.secretKey); + return AWSLogsClientBuilder.standard() + .withRegion(Regions.US_EAST_1) + .withCredentials(new AWSStaticCredentialsProvider(credentials)) + .build(); + } } diff --git a/src/main/java/com/ppn/ppn/utils/CloudWatchAppender.java b/src/main/java/com/ppn/ppn/utils/CloudWatchAppender.java new file mode 100644 index 0000000..fb785aa --- /dev/null +++ b/src/main/java/com/ppn/ppn/utils/CloudWatchAppender.java @@ -0,0 +1,87 @@ +package com.ppn.ppn.utils; + +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.UnsynchronizedAppenderBase; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.services.cloudwatchlogs.CloudWatchLogsClient; +import software.amazon.awssdk.services.cloudwatchlogs.model.DescribeLogStreamsRequest; +import software.amazon.awssdk.services.cloudwatchlogs.model.DescribeLogStreamsResponse; +import software.amazon.awssdk.services.cloudwatchlogs.model.InputLogEvent; +import software.amazon.awssdk.services.cloudwatchlogs.model.PutLogEventsRequest; + +import java.util.LinkedList; +import java.util.Queue; + +import static software.amazon.awssdk.regions.Region.US_EAST_1; + +public class CloudWatchAppender extends UnsynchronizedAppenderBase { + private CloudWatchLogsClient client; + private String logGroupName = "ppn-project-logs"; + private String logStreamName = "prod-log-app-stream"; + + private Queue eventQueue; + + public CloudWatchAppender() { + client = CloudWatchLogsClient + .builder() + .region(US_EAST_1) + .build(); + eventQueue = new LinkedList<>(); + } + + @Override + protected void append(ILoggingEvent event) { + InputLogEvent logEvent = InputLogEvent + .builder() + .message(event.getLevel().levelStr + " " + event.getFormattedMessage()) + .timestamp(event.getTimeStamp()).build(); + + eventQueue.add(logEvent); + flushEvents(); + } + + public void flushEvents() { + // Retrieve the existing log events + DescribeLogStreamsResponse describeLogStreamsResponse = client + .describeLogStreams(DescribeLogStreamsRequest.builder().logGroupName(logGroupName) + .logStreamNamePrefix(logStreamName) + .build()); + + String sequenceToken = describeLogStreamsResponse.logStreams().get(0).uploadSequenceToken(); + + // Batch up the next 10 events + LinkedList logEventsBatch = new LinkedList<>(); + while (!eventQueue.isEmpty() && logEventsBatch.size() < 10) { + logEventsBatch.add(eventQueue.poll()); + } + + // Check if logEventsBatch is empty + if (logEventsBatch.isEmpty()) { + return; + } + + // Put the log events into the CloudWatch stream + PutLogEventsRequest putLogEventsRequest = PutLogEventsRequest + .builder() + .logGroupName(logGroupName) + .logStreamName(logStreamName) + .logEvents(logEventsBatch) + .sequenceToken(sequenceToken).build(); + + client.putLogEvents(putLogEventsRequest); + } + + @Override + public void stop() { + // Flush any remaining events before stopping + flushEvents(); + + // Clean up the AWS CloudWatchLogs client + client.close(); + super.stop(); + } +} diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties index 2266648..fe22635 100644 --- a/src/main/resources/application-prod.properties +++ b/src/main/resources/application-prod.properties @@ -30,4 +30,7 @@ aws.ppn.bucketName=${bucket-name} ##config max size of file spring.servlet.multipart.max-file-size=10MB spring.servlet.multipart.max-request-size=10MB +##send log to cloud watch +aws.ppn.log.stream.name=${logs-stream-name} +aws.ppn.log.group.name=${logs-group-name} diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..1960568 --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,11 @@ + + + + INFO + + + + + + + \ No newline at end of file From c8d026de598f03efb9e6e0171d3313eeacf48cf2 Mon Sep 17 00:00:00 2001 From: letung999 Date: Tue, 14 Nov 2023 13:07:24 +0700 Subject: [PATCH 24/28] add document to set up cloudWatch --- README.md | 1 + src/main/resources/logback.xml | 11 ----------- 2 files changed, 1 insertion(+), 11 deletions(-) delete mode 100644 src/main/resources/logback.xml diff --git a/README.md b/README.md index 98e6365..7147293 100644 --- a/README.md +++ b/README.md @@ -287,6 +287,7 @@ jobs: * Step 4: When you can set up successfully, you can monitoring logs of application in cloud watch, if you want to look for a log, only click Logs insights tab and use the command line with filter by correlationId attached each request. + diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml deleted file mode 100644 index 1960568..0000000 --- a/src/main/resources/logback.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - INFO - - - - - - - \ No newline at end of file From 79773d59534ad7c8fe3fc143093619891213380b Mon Sep 17 00:00:00 2001 From: letung999 Date: Fri, 17 Nov 2023 11:08:39 +0700 Subject: [PATCH 25/28] create update users api and invalidate cache --- .../com/ppn/ppn/constant/MessageStatus.java | 2 +- .../ppn/ppn/controller/UserController.java | 13 +++++++ .../exception/CacheNotDeletedException.java | 17 +++++++++ .../ppn/exception/GlobalExceptionHandler.java | 15 ++++++-- .../com/ppn/ppn/service/UsersServiceImpl.java | 35 +++++++++++++++++++ .../ppn/service/constract/IUsersService.java | 2 ++ 6 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/ppn/ppn/exception/CacheNotDeletedException.java diff --git a/src/main/java/com/ppn/ppn/constant/MessageStatus.java b/src/main/java/com/ppn/ppn/constant/MessageStatus.java index 6e81f65..1d14b44 100644 --- a/src/main/java/com/ppn/ppn/constant/MessageStatus.java +++ b/src/main/java/com/ppn/ppn/constant/MessageStatus.java @@ -15,5 +15,5 @@ public class MessageStatus { public static final String ERR_MSG_DOWN_LOAD_FILE = "Bucket is not exist or empty"; public static final String ERR_MSG_UP_LOAD_FILE = "Invalid file. File extension or file name is not supported"; - + public static final String ERR_MSG_CAN_NOT_DELETE_CACHE = "Can not delete cache with prefix"; } diff --git a/src/main/java/com/ppn/ppn/controller/UserController.java b/src/main/java/com/ppn/ppn/controller/UserController.java index 766eb21..d9d3b7c 100644 --- a/src/main/java/com/ppn/ppn/controller/UserController.java +++ b/src/main/java/com/ppn/ppn/controller/UserController.java @@ -183,4 +183,17 @@ public ResponseEntity search(@RequestBody SearchUserRequest request) throws J .build(); return ResponseEntity.ok(apiResponse); } + + @PutMapping("/update") + public ResponseEntity update(@RequestBody @Valid UsersDto usersDto) { + UsersDto responseData = usersService.updateUsers(usersDto); + APIResponse apiResponse = APIResponse.builder() + .message(INF_MSG_SUCCESSFULLY) + .statusCode(200) + .isSuccess(true) + .timeStamp(LocalDateTime.now()) + .data(responseData) + .build(); + return ResponseEntity.ok(apiResponse); + } } diff --git a/src/main/java/com/ppn/ppn/exception/CacheNotDeletedException.java b/src/main/java/com/ppn/ppn/exception/CacheNotDeletedException.java new file mode 100644 index 0000000..4b34dfb --- /dev/null +++ b/src/main/java/com/ppn/ppn/exception/CacheNotDeletedException.java @@ -0,0 +1,17 @@ +package com.ppn.ppn.exception; + +import lombok.Getter; +import lombok.Setter; + +import static com.ppn.ppn.constant.MessageStatus.ERR_MSG_CAN_NOT_DELETE_CACHE; + +@Getter +@Setter +public class CacheNotDeletedException extends RuntimeException { + private String prefix; + + public CacheNotDeletedException(String prefix) { + super(String.format(ERR_MSG_CAN_NOT_DELETE_CACHE + prefix)); + this.prefix = prefix; + } +} diff --git a/src/main/java/com/ppn/ppn/exception/GlobalExceptionHandler.java b/src/main/java/com/ppn/ppn/exception/GlobalExceptionHandler.java index 086b0c6..0d4d77e 100644 --- a/src/main/java/com/ppn/ppn/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/ppn/ppn/exception/GlobalExceptionHandler.java @@ -115,7 +115,7 @@ public ResponseEntity handleFileEmptyException(FileEmptyException // handle exception occur when the call was transmitted successfully but Amazon S3 can't process request. @ExceptionHandler(AmazonServiceException.class) - public ResponseEntity handleAmazonServiceException(AmazonServiceException ex, WebRequest webRequest){ + public ResponseEntity handleAmazonServiceException(AmazonServiceException ex, WebRequest webRequest) { ErrorResponse errorResponse = ErrorResponse.builder() .localDateTime(LocalDateTime.now()) .message(ex.getMessage()) @@ -127,7 +127,7 @@ public ResponseEntity handleAmazonServiceException(AmazonServiceExceptio //handle exception occur when can't contact with Amazon S3 @ExceptionHandler(SdkClientException.class) - public ResponseEntity handleSdkClientException(SdkClientException ex, WebRequest webRequest){ + public ResponseEntity handleSdkClientException(SdkClientException ex, WebRequest webRequest) { ErrorResponse errorResponse = ErrorResponse.builder() .localDateTime(LocalDateTime.now()) .message(ex.getMessage()) @@ -137,4 +137,15 @@ public ResponseEntity handleSdkClientException(SdkClientException ex, We return new ResponseEntity<>(errorResponse, HttpStatus.SERVICE_UNAVAILABLE); } + @ExceptionHandler(CacheNotDeletedException.class) + public ResponseEntity handleCacheNotDeletedException(CacheNotDeletedException ex, WebRequest webRequest) { + ErrorResponse errorResponse = ErrorResponse.builder() + .localDateTime(LocalDateTime.now()) + .message(ex.getMessage()) + .statusCode(HttpStatusCode.valueOf(400).toString()) + .path(webRequest.getDescription(false)) + .build(); + return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); + } + } diff --git a/src/main/java/com/ppn/ppn/service/UsersServiceImpl.java b/src/main/java/com/ppn/ppn/service/UsersServiceImpl.java index 6df76b1..b24c26c 100644 --- a/src/main/java/com/ppn/ppn/service/UsersServiceImpl.java +++ b/src/main/java/com/ppn/ppn/service/UsersServiceImpl.java @@ -1,6 +1,7 @@ package com.ppn.ppn.service; import com.ppn.ppn.dto.UsersDto; +import com.ppn.ppn.entities.CacheData; import com.ppn.ppn.entities.Role; import com.ppn.ppn.entities.Users; import com.ppn.ppn.exception.ResourceDuplicateException; @@ -8,6 +9,7 @@ import com.ppn.ppn.mapper.UsersMapper; import com.ppn.ppn.payload.SearchUserRequest; import com.ppn.ppn.payload.SearchUserResponse; +import com.ppn.ppn.repository.CacheDataRepository; import com.ppn.ppn.repository.RoleRepository; import com.ppn.ppn.repository.UsersRepository; import com.ppn.ppn.service.constract.IUsersService; @@ -39,6 +41,10 @@ public class UsersServiceImpl implements IUsersService { @Autowired private PasswordEncoder passwordEncoder; + + @Autowired + private CacheDataRepository cacheDataRepository; + private UsersMapper usersMapper = UsersMapper.INSTANCE; @Override @@ -130,6 +136,35 @@ public SearchUserResponse search(SearchUserRequest request, Pageable pageable) { return searchUserResponse; } + @Override + public UsersDto updateUsers(UsersDto usersDto) { + Optional users = userRepository.findById(usersDto.getUserId()); + if (users.isEmpty()) { + throw new ResourcesNotFoundException("emails", usersDto.getEmail()); + } + + List emails = userRepository.findAll().stream() + .filter(u -> !u.getEmail().equals(users.get().getEmail())) + .map(u -> { + return u.getEmail(); + }).collect(Collectors.toList()); + + if (emails.contains(usersDto.getEmail())) { + throw new ResourceDuplicateException("email", usersDto.getEmail()); + } + Users dataSave = userRepository.save(usersMapper.usersDtoUsers(usersDto)); + + //deleted Cache for users: + List cacheDataList = (List) cacheDataRepository.findAll(); + List cacheKeyList = cacheDataList.stream() + .map(CacheData::getKey) + .filter(k -> k.substring(0, k.indexOf("-")).toLowerCase() + .matches(".*" + "users" + ".*")) + .collect(Collectors.toList()); + cacheDataRepository.deleteAllById(cacheKeyList); + + return usersMapper.usersToUsersDto(dataSave); + } public Users checkLogin(Users user) { Users userCheck = null; diff --git a/src/main/java/com/ppn/ppn/service/constract/IUsersService.java b/src/main/java/com/ppn/ppn/service/constract/IUsersService.java index 0d6744d..ea9e4e9 100644 --- a/src/main/java/com/ppn/ppn/service/constract/IUsersService.java +++ b/src/main/java/com/ppn/ppn/service/constract/IUsersService.java @@ -15,4 +15,6 @@ public interface IUsersService { List all(Pageable pageable); SearchUserResponse search(SearchUserRequest request, Pageable pageable); + + UsersDto updateUsers(UsersDto usersDto); } From 223f42d8377612f310a09173b695dd7826856404 Mon Sep 17 00:00:00 2001 From: letung999 Date: Sat, 18 Nov 2023 14:31:21 +0700 Subject: [PATCH 26/28] add payment class --- src/main/java/com/ppn/ppn/dto/OrderDto.java | 28 +++++++++++++++ src/main/java/com/ppn/ppn/dto/PaymentDto.java | 5 +++ src/main/java/com/ppn/ppn/entities/Order.java | 36 +++++++++++++++++++ .../java/com/ppn/ppn/entities/Payment.java | 10 ++++++ .../java/com/ppn/ppn/mapper/OrderMapper.java | 15 ++++++++ 5 files changed, 94 insertions(+) create mode 100644 src/main/java/com/ppn/ppn/dto/OrderDto.java create mode 100644 src/main/java/com/ppn/ppn/entities/Order.java create mode 100644 src/main/java/com/ppn/ppn/mapper/OrderMapper.java diff --git a/src/main/java/com/ppn/ppn/dto/OrderDto.java b/src/main/java/com/ppn/ppn/dto/OrderDto.java new file mode 100644 index 0000000..28afe06 --- /dev/null +++ b/src/main/java/com/ppn/ppn/dto/OrderDto.java @@ -0,0 +1,28 @@ +package com.ppn.ppn.dto; + +import com.ppn.ppn.entities.Payment; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; + +import java.math.BigDecimal; +import java.util.List; + +@Getter +@Setter +public class OrderDto { + + private long orderId; + + private String orderTrackingNumber; + + private int totalQuantity; + + private BigDecimal totalPrice; + + private String status; + + private long shoppingCardId; + + private List paymentList; +} diff --git a/src/main/java/com/ppn/ppn/dto/PaymentDto.java b/src/main/java/com/ppn/ppn/dto/PaymentDto.java index 66ef6ea..8370e94 100644 --- a/src/main/java/com/ppn/ppn/dto/PaymentDto.java +++ b/src/main/java/com/ppn/ppn/dto/PaymentDto.java @@ -1,6 +1,8 @@ package com.ppn.ppn.dto; +import com.ppn.ppn.entities.Order; import com.ppn.ppn.entities.Users; +import jakarta.persistence.Column; import lombok.Getter; import lombok.Setter; @@ -15,6 +17,9 @@ public class PaymentDto extends BaseEntityDto{ private BigDecimal amount; private LocalDate dateOfPayment; private LocalTime timeOfPayment; + private String cardName; + private String cardNumber; private String paymentMethod; private Users users; + private Order order; } diff --git a/src/main/java/com/ppn/ppn/entities/Order.java b/src/main/java/com/ppn/ppn/entities/Order.java new file mode 100644 index 0000000..2020957 --- /dev/null +++ b/src/main/java/com/ppn/ppn/entities/Order.java @@ -0,0 +1,36 @@ +package com.ppn.ppn.entities; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; + +import java.math.BigDecimal; +import java.util.List; + +@Getter +@Setter +@Entity +@Table(name = "order") +public class Order extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long orderId; + + @Column(name = "orderTrackingNumber") + private String orderTrackingNumber; + + @Column(name = "totalQuantity") + private int totalQuantity; + + @Column(name = "totalPrice") + private BigDecimal totalPrice; + + @Column(name = "status") + private String status; + + @Column(name = "shoppingCardId") + private long shoppingCardId; + + @OneToMany(mappedBy = "order") + private List paymentList; +} diff --git a/src/main/java/com/ppn/ppn/entities/Payment.java b/src/main/java/com/ppn/ppn/entities/Payment.java index 81d3565..a08425d 100644 --- a/src/main/java/com/ppn/ppn/entities/Payment.java +++ b/src/main/java/com/ppn/ppn/entities/Payment.java @@ -30,8 +30,18 @@ public class Payment extends BaseEntity { @Column(name = "paymentMethod") private String paymentMethod; + @Column(name = "cardName") + private String cardName; + + @Column(name = "cardNumber") + private String cardNumber; + @JsonIgnore @ManyToOne @JoinColumn(name = "userId") private Users users; + + @ManyToOne + @JoinColumn(name = "orderId") + private Order order; } diff --git a/src/main/java/com/ppn/ppn/mapper/OrderMapper.java b/src/main/java/com/ppn/ppn/mapper/OrderMapper.java new file mode 100644 index 0000000..3a9c4f8 --- /dev/null +++ b/src/main/java/com/ppn/ppn/mapper/OrderMapper.java @@ -0,0 +1,15 @@ +package com.ppn.ppn.mapper; + +import com.ppn.ppn.dto.OrderDto; +import com.ppn.ppn.entities.Order; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper(componentModel = "spring") +public interface OrderMapper { + OrderMapper INSTANCE = Mappers.getMapper(OrderMapper.class); + + OrderDto orderToOrderDto(Order order); + + Order orderDtoToOrder(OrderDto orderDto); +} From cc02263d8916a655e4fddc98e8e0f0736c0d14b5 Mon Sep 17 00:00:00 2001 From: letung999 Date: Sun, 19 Nov 2023 20:09:19 +0700 Subject: [PATCH 27/28] modify relationship between CompanyProfile and CompanyProfileMapping --- .../com/ppn/ppn/entities/CompanyProfile.java | 9 +++----- .../ppn/entities/CompanyProfileMapping.java | 15 +++++++----- src/main/java/com/ppn/ppn/entities/Users.java | 6 +++-- .../CompanyProfileMappingRepository.java | 9 ++++++++ .../ppn/ppn/repository/CompanyRepository.java | 2 +- src/main/resources/application.properties | 23 +++++++++++++++++-- 6 files changed, 47 insertions(+), 17 deletions(-) create mode 100644 src/main/java/com/ppn/ppn/repository/CompanyProfileMappingRepository.java diff --git a/src/main/java/com/ppn/ppn/entities/CompanyProfile.java b/src/main/java/com/ppn/ppn/entities/CompanyProfile.java index ce0760d..6764925 100644 --- a/src/main/java/com/ppn/ppn/entities/CompanyProfile.java +++ b/src/main/java/com/ppn/ppn/entities/CompanyProfile.java @@ -1,10 +1,7 @@ package com.ppn.ppn.entities; import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import lombok.*; import java.util.Date; @@ -14,6 +11,7 @@ @Setter @AllArgsConstructor @NoArgsConstructor +@Builder public class CompanyProfile extends BaseEntity{ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -26,7 +24,6 @@ public class CompanyProfile extends BaseEntity{ @Column(name = "dataOfEstablishment") private Date dateOfEstablishment; - @OneToOne(cascade = CascadeType.ALL) - @JoinColumn(name = "company_profile_mapping_Id") + @OneToOne(mappedBy = "companyProfile", cascade = CascadeType.ALL) private CompanyProfileMapping companyProfileMapping; } diff --git a/src/main/java/com/ppn/ppn/entities/CompanyProfileMapping.java b/src/main/java/com/ppn/ppn/entities/CompanyProfileMapping.java index 41b33cc..e79ba4b 100644 --- a/src/main/java/com/ppn/ppn/entities/CompanyProfileMapping.java +++ b/src/main/java/com/ppn/ppn/entities/CompanyProfileMapping.java @@ -2,25 +2,28 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; -import lombok.Getter; -import lombok.Setter; +import lombok.*; @Entity @Table(name = "company_profile_mapping") @Getter @Setter -public class CompanyProfileMapping extends BaseEntity{ +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CompanyProfileMapping extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer companyProfileMappingId; - @Column(name = "company_id") + @Column(name = "company_id_mapping") private Integer companyId; @Column(name = "parent_company_id") - private String ParentCompanyId; + private String parentCompanyId; @JsonIgnore - @OneToOne(mappedBy = "companyProfileMapping") + @OneToOne(cascade = CascadeType.ALL) + @JoinColumn(name = "company_id") private CompanyProfile companyProfile; } diff --git a/src/main/java/com/ppn/ppn/entities/Users.java b/src/main/java/com/ppn/ppn/entities/Users.java index 603eb0b..5ea68dd 100644 --- a/src/main/java/com/ppn/ppn/entities/Users.java +++ b/src/main/java/com/ppn/ppn/entities/Users.java @@ -2,8 +2,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; -import lombok.Getter; -import lombok.Setter; +import lombok.*; import java.util.List; import java.util.Set; @@ -12,6 +11,9 @@ @Table(name = "Users") @Getter @Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor public class Users extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/ppn/ppn/repository/CompanyProfileMappingRepository.java b/src/main/java/com/ppn/ppn/repository/CompanyProfileMappingRepository.java new file mode 100644 index 0000000..7a82ebd --- /dev/null +++ b/src/main/java/com/ppn/ppn/repository/CompanyProfileMappingRepository.java @@ -0,0 +1,9 @@ +package com.ppn.ppn.repository; + +import com.ppn.ppn.entities.CompanyProfileMapping; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface CompanyProfileMappingRepository extends JpaRepository { +} diff --git a/src/main/java/com/ppn/ppn/repository/CompanyRepository.java b/src/main/java/com/ppn/ppn/repository/CompanyRepository.java index 2fe3cc0..b3a740d 100644 --- a/src/main/java/com/ppn/ppn/repository/CompanyRepository.java +++ b/src/main/java/com/ppn/ppn/repository/CompanyRepository.java @@ -24,6 +24,6 @@ public interface CompanyRepository extends JpaRepository getListCompanyProfileId(Pageable pageable); } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 18a84f4..3c9bfcc 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,16 +1,35 @@ spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=${url-db} -spring.datasource.username=${username-db} +spring.datasource.username=${username} spring.datasource.password=${password-db} spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=false spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL55Dialect -server.port=8080 spring.jpa.properties.hibernate.globally_quoted_identifiers=true +server.port=8080 +#security +ppn.app.jwt.secret=${jwt-secret} +ppn.app.jwt.expiration=${jwt-expiration} +#send log to cloud watch +#mail +spring.mail.host=smtp.gmail.com +spring.mail.port=587 +spring.mail.username=${user-email} +spring.mail.password=${password-email} +spring.mail.properties.mail.smtp.auth=true +spring.mail.properties.mail.smtp.starttls.enable=true +spring.mail.properties.mail.smtp.starttls.required=true +cox.automation.email=cox-automation@gmail.com +cox.name=MANHEIM-COX-AUTOMATION #connect aws aws.ppn.accessKey=${accessKey} aws.ppn.secretKey=${secretKey} +#bucket name +aws.ppn.bucketName=${bucket-name} +##config max size of file +spring.servlet.multipart.max-file-size=10MB +spring.servlet.multipart.max-request-size=10MB #active environment spring.profiles.active=prod #send log to cloud watch From 4b1cba5b1261da1ee818295e4a60598451b34a43 Mon Sep 17 00:00:00 2001 From: letung999 Date: Sun, 19 Nov 2023 20:40:51 +0700 Subject: [PATCH 28/28] =?UTF-8?q?coverage=20unit=20t=C3=83est=20for=20repo?= =?UTF-8?q?sitory=20layer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/CompanyRepositoryTests.java | 105 ++++++++++++++ .../ppn/repository/UserRepositoryTests.java | 137 ++++++++++++++++++ 2 files changed, 242 insertions(+) create mode 100644 src/test/java/com/ppn/ppn/repository/CompanyRepositoryTests.java create mode 100644 src/test/java/com/ppn/ppn/repository/UserRepositoryTests.java diff --git a/src/test/java/com/ppn/ppn/repository/CompanyRepositoryTests.java b/src/test/java/com/ppn/ppn/repository/CompanyRepositoryTests.java new file mode 100644 index 0000000..3282959 --- /dev/null +++ b/src/test/java/com/ppn/ppn/repository/CompanyRepositoryTests.java @@ -0,0 +1,105 @@ +package com.ppn.ppn.repository; + + +import com.ppn.ppn.entities.CompanyProfile; +import com.ppn.ppn.entities.CompanyProfileMapping; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; + +@DataJpaTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +public class CompanyRepositoryTests { + + @Autowired + private CompanyRepository companyRepository; + + @Autowired + private CompanyProfileMappingRepository companyProfileMappingRepository; + + private SimpleDateFormat simpleDateFormat; + private Date inputDate; + private Date inputDate1; + + @BeforeEach + public void setup() throws ParseException { + simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); + inputDate = simpleDateFormat.parse("2009-10-01"); + inputDate1 = simpleDateFormat.parse("2004-10-01"); + + CompanyProfile companyProfile = CompanyProfile.builder() + .companyIdName("001_shopBack") + .dateOfEstablishment(inputDate) + .build(); + + CompanyProfile companyProfile1 = CompanyProfile.builder() + .companyIdName("002_ManHeim") + .dateOfEstablishment(inputDate1) + .build(); + + CompanyProfile companyProfileSaved = companyRepository.save(companyProfile); + CompanyProfile companyProfileSaved1 = companyRepository.save(companyProfile1); + + CompanyProfileMapping companyProfileMapping = CompanyProfileMapping.builder() + .companyId(companyProfileSaved.getCompanyId()) + .parentCompanyId("001_shopBack") + .companyProfile(companyProfileSaved) + .build(); + CompanyProfileMapping companyProfileMapping1 = CompanyProfileMapping.builder() + .companyId(companyProfileSaved1.getCompanyId()) + .parentCompanyId("002_ManHeim") + .companyProfile(companyProfileSaved1) + .build(); + + companyProfileMappingRepository.save(companyProfileMapping); + companyProfileMappingRepository.save(companyProfileMapping1); + } + + @Test + public void givenListCompanyProfile_whenGetListCompanyProfileByDate_thenListCompanyProfile() throws ParseException { + + //action + List resultData = companyRepository.getListCompanyProfile(inputDate1); + + //output + Assertions.assertThat(resultData).isNotNull(); + Assertions.assertThat(resultData.size()).isEqualTo(2); + } + + @Test + public void givenListCompanyProfileId_whenGetListCompanyProfileId_thenListCompanyProfileId() throws ParseException { + + PageRequest pageRequest = PageRequest.of(0, 2); + + //action + Page companyProfilePage = companyRepository.getListCompanyProfileId(pageRequest); + List companyProfiles = companyProfilePage.getContent(); + + //output + Assertions.assertThat(companyProfiles).isNotNull(); + Assertions.assertThat(companyProfiles.size()).isEqualTo(2); + } + + @Test + public void givenListCompanyProfileIdIsEmpty_whenGetListCompanyProfileId_givenListCompanyProfileIdIsEmpty() throws ParseException { + + PageRequest pageRequest = PageRequest.of(1, 2); + + //action + Page companyProfilePage = companyRepository.getListCompanyProfileId(pageRequest); + List companyProfiles = companyProfilePage.getContent(); + + //output + Assertions.assertThat(companyProfiles).isEmpty(); + } +} diff --git a/src/test/java/com/ppn/ppn/repository/UserRepositoryTests.java b/src/test/java/com/ppn/ppn/repository/UserRepositoryTests.java new file mode 100644 index 0000000..61145d2 --- /dev/null +++ b/src/test/java/com/ppn/ppn/repository/UserRepositoryTests.java @@ -0,0 +1,137 @@ +package com.ppn.ppn.repository; + + +import com.ppn.ppn.entities.Users; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +import java.util.List; +import java.util.Optional; + +@DataJpaTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +public class UserRepositoryTests { + @Autowired + private UsersRepository usersRepository; + + + @Test + public void givenUserObject_whenSave_thenReturnSavedUser() { + //set up + Users users = Users.builder() + .email("letung012000@gmail.com") + .firstName("tung") + .phoneNumber("0338257409") + .password("123456") + .status("PENDING") + .gender("Male") + .build(); + + //action + Users dataSaved = usersRepository.save(users); + + //output + Assertions.assertThat(dataSaved).isNotNull(); + Assertions.assertThat(dataSaved.getUserId()).isGreaterThan(0); + } + + @Test + public void givenUsersList_whenFindAll_thenUsersList() { + //setup + Users users = Users.builder() + .email("letung012000@gmail.com") + .firstName("tung") + .phoneNumber("0338257409") + .password("123456") + .status("PENDING") + .gender("Male") + .build(); + + Users users1 = Users.builder() + .email("letung012000@gmail.com") + .firstName("tung") + .phoneNumber("0338257409") + .password("123456") + .status("PENDING") + .gender("Male") + .build(); + + usersRepository.save(users); + usersRepository.save(users1); + + //action + List usersList = usersRepository.findAll(); + + //output + Assertions.assertThat(usersList).isNotNull(); + Assertions.assertThat(usersList.size()).isEqualTo(3); + } + + @Test + public void givenUsersObject_whenFindByUsersById_thenUsersObject() { + //setup + Users users = Users.builder() + .email("letung012000@gmail.com") + .firstName("tung") + .phoneNumber("0338257409") + .password("123456") + .status("PENDING") + .gender("Male") + .build(); + + Users dataSaved = usersRepository.save(users); + + //action + Users data = usersRepository.findById(dataSaved.getUserId()).get(); + + //output + Assertions.assertThat(data).isNotNull(); + } + + @Test + public void givenUsersObject_whenFindByEmail_thenUsersObject() { + //setup + Users users = Users.builder() + .email("letung012000@gmail.com") + .firstName("tung") + .phoneNumber("0338257409") + .password("123456") + .status("PENDING") + .gender("Male") + .build(); + + usersRepository.save(users); + + //action + Optional data = usersRepository.findByEmail(users.getEmail()); + + //output + Assertions.assertThat(data).isNotNull(); + Assertions.assertThat(data.get().getEmail()).isEqualTo(users.getEmail()); + } + + @Test + public void givenUsersObject_whenFindByVerifyCode_thenUserObject() { + //setup + Users users = Users.builder() + .email("letung012000@gmail.com") + .firstName("tung") + .phoneNumber("0338257409") + .password("123456") + .status("PENDING") + .gender("Male") + .verifyCode("C7jgrUGYJNsmvrbGXQSf8SZXPsxlvx") + .build(); + + usersRepository.save(users); + + //action + Optional data = usersRepository.findByVerifyCode(users.getVerifyCode()); + + //output + Assertions.assertThat(data).isNotNull(); + } +}