From b26acfb3079d5c6739d8262c0c0b06340a6de196 Mon Sep 17 00:00:00 2001 From: Gustavo <140917585+Guzitos@users.noreply.github.com> Date: Mon, 6 Apr 2026 20:30:38 -0300 Subject: [PATCH] parte de security feita com sucesso, register e login do usuario --- .idea/modules.xml | 2 +- LibraryLoop/docker-compose.yml | 4 +- LibraryLoop/pom.xml | 37 +++-- .../Repository/userRepository.java | 4 +- .../controller/AuthController.java | 60 +++++++++ .../controller/BookController.java | 30 +---- .../LibraryLoop/dto/book/BookSearchDTO.java | 4 +- .../LibraryLoop/dto/user/LoginRequestDTO.java | 3 + .../dto/user/RegisterRequestDTO.java | 3 + .../LibraryLoop/dto/user/ResponseDTO.java | 3 + .../com/example/LibraryLoop/entity/User.java | 15 ++- .../{ => security}/config/AppConfig.java | 2 +- .../{ => security}/config/CacheConfig.java | 2 +- .../config/CustomUserDetailsService.java | 22 +++ .../security/config/SecurityConfig.java | 48 +++++++ .../security/config/SecurityFilter.java | 48 +++++++ .../security/config/TokenService.java | 52 ++++++++ .../LibraryLoop/service/BookService.java | 126 ++---------------- .../src/main/resources/application.properties | 12 +- 19 files changed, 308 insertions(+), 169 deletions(-) create mode 100644 LibraryLoop/src/main/java/com/example/LibraryLoop/controller/AuthController.java create mode 100644 LibraryLoop/src/main/java/com/example/LibraryLoop/dto/user/LoginRequestDTO.java create mode 100644 LibraryLoop/src/main/java/com/example/LibraryLoop/dto/user/RegisterRequestDTO.java create mode 100644 LibraryLoop/src/main/java/com/example/LibraryLoop/dto/user/ResponseDTO.java rename LibraryLoop/src/main/java/com/example/LibraryLoop/{ => security}/config/AppConfig.java (93%) rename LibraryLoop/src/main/java/com/example/LibraryLoop/{ => security}/config/CacheConfig.java (95%) create mode 100644 LibraryLoop/src/main/java/com/example/LibraryLoop/security/config/CustomUserDetailsService.java create mode 100644 LibraryLoop/src/main/java/com/example/LibraryLoop/security/config/SecurityConfig.java create mode 100644 LibraryLoop/src/main/java/com/example/LibraryLoop/security/config/SecurityFilter.java create mode 100644 LibraryLoop/src/main/java/com/example/LibraryLoop/security/config/TokenService.java diff --git a/.idea/modules.xml b/.idea/modules.xml index a4c960d..47971e9 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,7 +2,7 @@ - + diff --git a/LibraryLoop/docker-compose.yml b/LibraryLoop/docker-compose.yml index f9707ce..541e767 100644 --- a/LibraryLoop/docker-compose.yml +++ b/LibraryLoop/docker-compose.yml @@ -1,5 +1,4 @@ services: - mysql: image: mysql:8.0 container_name: libraryloop-mysql @@ -26,9 +25,10 @@ services: ports: - "8080:8080" environment: - SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/libraryloop?useSSL=false&allowPublicKeyRetrieval=true + SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/libraryloop?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=America/Sao_Paulo SPRING_DATASOURCE_USERNAME: libraryuser SPRING_DATASOURCE_PASSWORD: librarypass + JWT_SECRET: iaowhfdoawfhoqui32gho1ubg24ou1ivb24oi depends_on: mysql: condition: service_healthy diff --git a/LibraryLoop/pom.xml b/LibraryLoop/pom.xml index a573791..a9531f3 100644 --- a/LibraryLoop/pom.xml +++ b/LibraryLoop/pom.xml @@ -26,12 +26,25 @@ + org.apache.httpcomponents.client5 httpclient5 -<<<<<<< HEAD + + + org.springframework.boot + spring-boot-starter-security + + + + + com.auth0 + java-jwt + 4.4.0 + + org.springframework.boot @@ -43,13 +56,6 @@ com.github.ben-manes.caffeine caffeine -======= - - org.springframework.boot - spring-boot-starter-cache - ->>>>>>> origin/main - @@ -63,7 +69,7 @@ spring-boot-starter-validation - + org.projectlombok lombok @@ -71,13 +77,13 @@ true - + org.springframework.boot spring-boot-starter-data-jpa - true + com.mysql mysql-connector-j @@ -91,6 +97,13 @@ test + + + org.springframework.security + spring-security-test + test + + @@ -122,4 +135,4 @@ - + \ No newline at end of file diff --git a/LibraryLoop/src/main/java/com/example/LibraryLoop/Repository/userRepository.java b/LibraryLoop/src/main/java/com/example/LibraryLoop/Repository/userRepository.java index 41e8fc0..06e3c58 100644 --- a/LibraryLoop/src/main/java/com/example/LibraryLoop/Repository/userRepository.java +++ b/LibraryLoop/src/main/java/com/example/LibraryLoop/Repository/userRepository.java @@ -7,5 +7,7 @@ public interface userRepository extends JpaRepository { - Optional findByUsername(String username); + Optional findByEmail(String email); + Optional findByUsername(String username); + } diff --git a/LibraryLoop/src/main/java/com/example/LibraryLoop/controller/AuthController.java b/LibraryLoop/src/main/java/com/example/LibraryLoop/controller/AuthController.java new file mode 100644 index 0000000..ba086d3 --- /dev/null +++ b/LibraryLoop/src/main/java/com/example/LibraryLoop/controller/AuthController.java @@ -0,0 +1,60 @@ +package com.example.LibraryLoop.controller; + +import com.example.LibraryLoop.Repository.userRepository; +import com.example.LibraryLoop.dto.user.LoginRequestDTO; +import com.example.LibraryLoop.dto.user.RegisterRequestDTO; +import com.example.LibraryLoop.dto.user.ResponseDTO; +import com.example.LibraryLoop.entity.User; +import com.example.LibraryLoop.security.config.TokenService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; +import java.util.Optional; + +@RestController +@RequestMapping("/auth") +@RequiredArgsConstructor +public class AuthController { + + private final userRepository repository; + private final PasswordEncoder passwordEncoder; + private final TokenService tokenService; + + @PostMapping("/login") + public ResponseEntity login(@RequestBody LoginRequestDTO body) { + User user = this.repository.findByEmail(body.email()) + .orElseThrow(() -> new RuntimeException("User not found")); + + if (passwordEncoder.matches(body.password(), user.getPassword())) { + String token = this.tokenService.generateToken(user); + return ResponseEntity.ok(new ResponseDTO(user.getUsername(), token)); + } + return ResponseEntity.badRequest().build(); + } + + @PostMapping("/register") + public ResponseEntity register(@RequestBody RegisterRequestDTO body) { + + if (this.repository.findByEmail(body.email()).isPresent()) { + return ResponseEntity.badRequest() + .body(Map.of("error", "Já existe um usuário com esse e-mail cadastrado.")); + } + + if (this.repository.findByUsername(body.username()).isPresent()) { + return ResponseEntity.badRequest() + .body(Map.of("error", "Já existe um usuário com esse username cadastrado.")); + } + + User newUser = new User(); + newUser.setPassword(passwordEncoder.encode(body.password())); + newUser.setEmail(body.email()); + newUser.setUsername(body.username()); + this.repository.save(newUser); + + String token = this.tokenService.generateToken(newUser); + return ResponseEntity.ok(new ResponseDTO(newUser.getUsername(), token)); + } +} \ No newline at end of file diff --git a/LibraryLoop/src/main/java/com/example/LibraryLoop/controller/BookController.java b/LibraryLoop/src/main/java/com/example/LibraryLoop/controller/BookController.java index 70e2c41..559f523 100644 --- a/LibraryLoop/src/main/java/com/example/LibraryLoop/controller/BookController.java +++ b/LibraryLoop/src/main/java/com/example/LibraryLoop/controller/BookController.java @@ -21,31 +21,17 @@ public BookController(BookService bookService) { this.bookService = bookService; } - // 🔎 BUSCAR LIVROS - @GetMapping("/search") - public List searchBooks( - @RequestParam String title, - @RequestParam(defaultValue = "20") int limit - ) { - return bookService.searchBooks(title, limit); - } - - // 📖 LER LIVRO COMPLETO @GetMapping("/{id}/read") public ResponseEntity readBook(@PathVariable Long id) { - - String text = bookService.getFullBook(id); - + String text = bookService.readBook(id); return ResponseEntity.ok(new BookReadResponse(id, text)); } - // 📄 PAGINAÇÃO @GetMapping("/{id}/pages/{page}") - public ResponseEntity getBookPage( + public ResponseEntity getBookPage( @PathVariable Long id, @PathVariable int page ) { - List pages = bookService.getBookPages(id); if (page < 0 || page >= pages.size()) { @@ -55,23 +41,17 @@ public ResponseEntity getBookPage( return ResponseEntity.ok( new PageResponse(page, pages.size(), pages.get(page)) ); -<<<<<<< HEAD - } + } - // 🔎 buscar livros - @GetMapping(value = "/search") + @GetMapping("/search") public List searchBooks( @RequestParam String title, @RequestParam(defaultValue = "20") int limit) { - - return service.searchBooks(title, limit); -======= + return bookService.searchBooks(title, limit); } - // 🔗 LINK EXTERNO (OPCIONAL) @GetMapping("/{id}/link") public ReadLinkDTO getReadLink(@PathVariable String id) { return bookService.getReadLink(id); ->>>>>>> origin/main } } \ No newline at end of file diff --git a/LibraryLoop/src/main/java/com/example/LibraryLoop/dto/book/BookSearchDTO.java b/LibraryLoop/src/main/java/com/example/LibraryLoop/dto/book/BookSearchDTO.java index d476acf..f92ce37 100644 --- a/LibraryLoop/src/main/java/com/example/LibraryLoop/dto/book/BookSearchDTO.java +++ b/LibraryLoop/src/main/java/com/example/LibraryLoop/dto/book/BookSearchDTO.java @@ -26,9 +26,7 @@ public BookSearchDTO( Integer editionCount, List language, Boolean hasFullText, - Integer ratingsAverage, - Boolean readable, - String source + Integer ratingsAverage ) { this.olid = olid; this.title = title; diff --git a/LibraryLoop/src/main/java/com/example/LibraryLoop/dto/user/LoginRequestDTO.java b/LibraryLoop/src/main/java/com/example/LibraryLoop/dto/user/LoginRequestDTO.java new file mode 100644 index 0000000..aef20cb --- /dev/null +++ b/LibraryLoop/src/main/java/com/example/LibraryLoop/dto/user/LoginRequestDTO.java @@ -0,0 +1,3 @@ +package com.example.LibraryLoop.dto.user; + +public record LoginRequestDTO(String email, String password) {} \ No newline at end of file diff --git a/LibraryLoop/src/main/java/com/example/LibraryLoop/dto/user/RegisterRequestDTO.java b/LibraryLoop/src/main/java/com/example/LibraryLoop/dto/user/RegisterRequestDTO.java new file mode 100644 index 0000000..cf1f345 --- /dev/null +++ b/LibraryLoop/src/main/java/com/example/LibraryLoop/dto/user/RegisterRequestDTO.java @@ -0,0 +1,3 @@ +package com.example.LibraryLoop.dto.user; + +public record RegisterRequestDTO(String username, String email, String password) {} \ No newline at end of file diff --git a/LibraryLoop/src/main/java/com/example/LibraryLoop/dto/user/ResponseDTO.java b/LibraryLoop/src/main/java/com/example/LibraryLoop/dto/user/ResponseDTO.java new file mode 100644 index 0000000..cbd76ce --- /dev/null +++ b/LibraryLoop/src/main/java/com/example/LibraryLoop/dto/user/ResponseDTO.java @@ -0,0 +1,3 @@ +package com.example.LibraryLoop.dto.user; + +public record ResponseDTO(String username, String token) {} \ No newline at end of file diff --git a/LibraryLoop/src/main/java/com/example/LibraryLoop/entity/User.java b/LibraryLoop/src/main/java/com/example/LibraryLoop/entity/User.java index 7b7fe2d..fdcda13 100644 --- a/LibraryLoop/src/main/java/com/example/LibraryLoop/entity/User.java +++ b/LibraryLoop/src/main/java/com/example/LibraryLoop/entity/User.java @@ -1,12 +1,10 @@ package com.example.LibraryLoop.entity; import jakarta.persistence.*; -import lombok.Getter; -import lombok.Setter; - - import lombok.*; +import java.util.List; + @Entity @Table(name = "app_user") @Getter @@ -20,6 +18,13 @@ public class User { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(unique = true) private String username; + + @Column(unique = true) + private String email; + + @Column(nullable = false) + private String password; + + private String savedBook; } \ No newline at end of file diff --git a/LibraryLoop/src/main/java/com/example/LibraryLoop/config/AppConfig.java b/LibraryLoop/src/main/java/com/example/LibraryLoop/security/config/AppConfig.java similarity index 93% rename from LibraryLoop/src/main/java/com/example/LibraryLoop/config/AppConfig.java rename to LibraryLoop/src/main/java/com/example/LibraryLoop/security/config/AppConfig.java index 4df5f43..7147944 100644 --- a/LibraryLoop/src/main/java/com/example/LibraryLoop/config/AppConfig.java +++ b/LibraryLoop/src/main/java/com/example/LibraryLoop/security/config/AppConfig.java @@ -1,4 +1,4 @@ -package com.example.LibraryLoop.config; +package com.example.LibraryLoop.security.config; import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; diff --git a/LibraryLoop/src/main/java/com/example/LibraryLoop/config/CacheConfig.java b/LibraryLoop/src/main/java/com/example/LibraryLoop/security/config/CacheConfig.java similarity index 95% rename from LibraryLoop/src/main/java/com/example/LibraryLoop/config/CacheConfig.java rename to LibraryLoop/src/main/java/com/example/LibraryLoop/security/config/CacheConfig.java index 393c91e..b4841a3 100644 --- a/LibraryLoop/src/main/java/com/example/LibraryLoop/config/CacheConfig.java +++ b/LibraryLoop/src/main/java/com/example/LibraryLoop/security/config/CacheConfig.java @@ -1,4 +1,4 @@ -package com.example.LibraryLoop.config; +package com.example.LibraryLoop.security.config; import com.github.benmanes.caffeine.cache.Caffeine; import org.springframework.cache.CacheManager; diff --git a/LibraryLoop/src/main/java/com/example/LibraryLoop/security/config/CustomUserDetailsService.java b/LibraryLoop/src/main/java/com/example/LibraryLoop/security/config/CustomUserDetailsService.java new file mode 100644 index 0000000..b5d51a7 --- /dev/null +++ b/LibraryLoop/src/main/java/com/example/LibraryLoop/security/config/CustomUserDetailsService.java @@ -0,0 +1,22 @@ +package com.example.LibraryLoop.security.config; + +import com.example.LibraryLoop.Repository.userRepository; +import com.example.LibraryLoop.entity.User; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; + +@Component +public class CustomUserDetailsService implements UserDetailsService { + @Autowired + private userRepository repository; + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + User user = this.repository.findByEmail(username).orElseThrow(() -> new UsernameNotFoundException("User not found")); + return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(), new ArrayList<>()); + } +} diff --git a/LibraryLoop/src/main/java/com/example/LibraryLoop/security/config/SecurityConfig.java b/LibraryLoop/src/main/java/com/example/LibraryLoop/security/config/SecurityConfig.java new file mode 100644 index 0000000..fcb3a5a --- /dev/null +++ b/LibraryLoop/src/main/java/com/example/LibraryLoop/security/config/SecurityConfig.java @@ -0,0 +1,48 @@ +package com.example.LibraryLoop.security.config; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class SecurityConfig { + + private final SecurityFilter securityFilter; + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + + http + // desabilita CSRF (necessário para API REST) + .csrf(csrf -> csrf.disable()) + + // API stateless (JWT) + .sessionManagement(session -> + session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + + // configuração de rotas + .authorizeHttpRequests(auth -> auth + .requestMatchers("/auth/**").permitAll() + .anyRequest().authenticated() + ) + + // adiciona filtro JWT antes do filtro padrão + .addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class); + + return http.build(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} \ No newline at end of file diff --git a/LibraryLoop/src/main/java/com/example/LibraryLoop/security/config/SecurityFilter.java b/LibraryLoop/src/main/java/com/example/LibraryLoop/security/config/SecurityFilter.java new file mode 100644 index 0000000..1b986a6 --- /dev/null +++ b/LibraryLoop/src/main/java/com/example/LibraryLoop/security/config/SecurityFilter.java @@ -0,0 +1,48 @@ +package com.example.LibraryLoop.security.config; + +import com.example.LibraryLoop.Repository.userRepository; +import com.example.LibraryLoop.entity.User; +import jakarta.annotation.Nonnull; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; +import java.util.Collections; + +@Component +public class SecurityFilter extends OncePerRequestFilter { + + @Autowired + TokenService tokenService; + @Autowired + userRepository clienteRepository; + + @Override + protected void doFilterInternal(@Nonnull HttpServletRequest request, @Nonnull HttpServletResponse response, @Nonnull FilterChain filterChain) throws ServletException, IOException { + var token = this.recoverToken(request); + var login = tokenService.validateToken(token); + + if (login != null) { + User user = clienteRepository.findByEmail(login) + .orElseThrow(() -> new RuntimeException("User Not Found")); + var authorities = Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")); + var authentication = new UsernamePasswordAuthenticationToken(user, null, authorities); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + filterChain.doFilter(request, response); + } + + private String recoverToken(HttpServletRequest request) { + var authHeader = request.getHeader("Authorization"); + if (authHeader == null) return null; + return authHeader.replace("Bearer ", ""); + } +} \ No newline at end of file diff --git a/LibraryLoop/src/main/java/com/example/LibraryLoop/security/config/TokenService.java b/LibraryLoop/src/main/java/com/example/LibraryLoop/security/config/TokenService.java new file mode 100644 index 0000000..df0aced --- /dev/null +++ b/LibraryLoop/src/main/java/com/example/LibraryLoop/security/config/TokenService.java @@ -0,0 +1,52 @@ +package com.example.LibraryLoop.security.config; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.JWTCreationException; +import com.auth0.jwt.exceptions.JWTVerificationException; +import com.example.LibraryLoop.entity.User; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; + +@Service +public class TokenService { + + @Value("${api.security.token.secret}") + private String secret; + + public String generateToken(User user){ + try { + Algorithm algorithm = Algorithm.HMAC256(secret); + + String token = JWT.create() + .withIssuer("login-auth-api") + .withSubject(user.getEmail()) + .withExpiresAt(this.generateExpirationDate()) + .sign(algorithm); + return token; + } catch (JWTCreationException exception){ + throw new RuntimeException("Error while authenticating"); + } + } + + public String validateToken(String token){ + try { + Algorithm algorithm = Algorithm.HMAC256(secret); + return JWT.require(algorithm) + .withIssuer("login-auth-api") + .build() + .verify(token) + .getSubject(); + } catch (JWTVerificationException exception) { + return null; + } + } + + private Instant generateExpirationDate(){ + return LocalDateTime.now().plusHours(2).toInstant(ZoneOffset.of("-03:00")); + } +} \ No newline at end of file diff --git a/LibraryLoop/src/main/java/com/example/LibraryLoop/service/BookService.java b/LibraryLoop/src/main/java/com/example/LibraryLoop/service/BookService.java index 226c142..e57f8bb 100644 --- a/LibraryLoop/src/main/java/com/example/LibraryLoop/service/BookService.java +++ b/LibraryLoop/src/main/java/com/example/LibraryLoop/service/BookService.java @@ -4,17 +4,17 @@ import com.example.LibraryLoop.dto.book.BookSearchDTO; import com.example.LibraryLoop.dto.gutendex.GutendexAuthor; import com.example.LibraryLoop.dto.gutendex.GutendexBookResponse; +import com.example.LibraryLoop.dto.gutendex.GutendexResponse; import com.example.LibraryLoop.dto.read.ReadLinkDTO; import lombok.RequiredArgsConstructor; -<<<<<<< HEAD import org.springframework.cache.annotation.CacheEvict; -======= ->>>>>>> origin/main import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; @Service @RequiredArgsConstructor @@ -23,42 +23,20 @@ public class BookService { private final GutendexClient gutendexClient; private final RestTemplate restTemplate; -<<<<<<< HEAD @Cacheable(value = "books", key = "#title + '_' + #limit") - public List searchBooks(String title, int limit) { - GutendexResponse response = gutendexClient.searchBooks(title); - - return response.getResults() -======= - // 🔎 BUSCA LIVROS - @Cacheable(value = "books", key = "#title + '-' + #limit") public List searchBooks(String title, int limit) { return gutendexClient.searchBooks(title) .getResults() ->>>>>>> origin/main .stream() .limit(limit) .map(book -> { String cover = null; - if (book.getFormats() != null) { cover = book.getFormats() .entrySet() .stream() -<<<<<<< HEAD - .map(GutendexAuthor::getName) - .toList(), - null, null, null, null, true, null - )) - .toList(); - } - - @Cacheable(value = "bookPages", key = "#id") - public List getBookPages(Long id) { - String fullText = readBook(id); -======= .filter(f -> f.getKey().contains("image")) .map(Map.Entry::getValue) .findFirst() @@ -77,23 +55,14 @@ public List getBookPages(Long id) { .map(GutendexAuthor::getName) .toList(), cover, - null, - null, - null, - true, - null, - readable, - "GUTENDEX" + null, null, null, true, null ); }) .toList(); } - // ========================= -// 📖 TEXTO COMPLETO (CACHEADO) -// ========================= @Cacheable(value = "bookText", key = "#id") - public String getFullBook(Long id) { + public String readBook(Long id) { String url = "https://gutendex.com/books/" + id; @@ -107,30 +76,27 @@ public String getFullBook(Long id) { String textUrl = response.getFormats() .entrySet() .stream() - .filter(f -> f.getKey().contains("text/plain")) - .map(Map.Entry::getValue) + .filter(f -> f.getKey().contains("text/plain; charset=utf-8")) .findFirst() + .map(Map.Entry::getValue) .orElse(null); if (textUrl == null) { - throw new RuntimeException("Livro não possui versão para leitura"); + throw new RuntimeException("Livro não encontrado"); } textUrl = textUrl.replace("http://", "https://"); - String book = restTemplate.getForObject(textUrl, String.class); + if (book == null) return "Erro ao carregar o livro"; + return cleanText(book); } - // ========================= -// 📄 PAGINAÇÃO (CACHEADA) -// ========================= - @Cacheable(value = "pages", key = "#id") + @Cacheable(value = "bookPages", key = "#id") public List getBookPages(Long id) { - String fullText = getFullBook(id); ->>>>>>> origin/main + String fullText = readBook(id); int pageSize = 1500; List pages = new ArrayList<>(); @@ -151,44 +117,8 @@ public List getBookPages(Long id) { return pages; } -<<<<<<< HEAD - @Cacheable(value = "bookText", key = "#id") - public String readBook(Long id) { - String url = "https://gutendex.com/books/" + id; - - GutendexBookResponse response = - restTemplate.getForObject(url, GutendexBookResponse.class); - - if (response == null || response.getFormats() == null) { - return "Livro não encontrado"; - } - - String textUrl = response.getFormats() - .entrySet() - .stream() - .filter(f -> f.getKey().contains("text/plain; charset=utf-8")) - .findFirst() - .map(Map.Entry::getValue) - .orElse(null); - - if (textUrl == null) { - throw new RuntimeException("Livro não encontrado"); - } - - textUrl = textUrl.replace("http://", "https://"); - String book = restTemplate.getForObject(textUrl, String.class); - - if (book == null) return "Erro ao carregar o livro"; - - book = book.replace("\uFEFF", ""); - return cleanText(book); - } - - // ⚠️ use este método para limpar o cache de um livro específico se necessário @CacheEvict(value = {"bookText", "bookPages"}, key = "#id") - public void evictBookCache(Long id) { - // apenas limpa o cache, sem lógica adicional - } + public void evictBookCache(Long id) {} public ReadLinkDTO getReadLink(String bookId) { return new ReadLinkDTO(true, "https://www.gutenberg.org/ebooks/" + bookId); @@ -209,34 +139,6 @@ private String cleanText(String text) { if (end != -1) text = text.substring(0, end); text = text.replaceAll("\n{3,}", "\n\n"); - return text.trim(); -======= - private String cleanText(String text) { - - if (text == null) return ""; - - text = text.replace("\uFEFF", ""); - text = text.replace("\r\n", "\n"); - - int start = text.indexOf("*** START OF"); - if (start != -1) { - int firstLineBreak = text.indexOf("\n", start); - text = text.substring(firstLineBreak + 1); - } - - int end = text.indexOf("*** END OF"); - if (end != -1) { - text = text.substring(0, end); - } - return text.trim(); } - - // 🔗 LINK - public ReadLinkDTO getReadLink(String bookId) { - String link = "https://www.gutenberg.org/ebooks/" + bookId; - return new ReadLinkDTO(true, link); ->>>>>>> origin/main - } - } \ No newline at end of file diff --git a/LibraryLoop/src/main/resources/application.properties b/LibraryLoop/src/main/resources/application.properties index d181d58..5828d5a 100644 --- a/LibraryLoop/src/main/resources/application.properties +++ b/LibraryLoop/src/main/resources/application.properties @@ -1,13 +1,13 @@ -# ===== DATASOURCE ===== -spring.datasource.url=${SPRING_DATASOURCE_URL:jdbc:mysql://localhost:3306/libraryloop?useSSL=false&allowPublicKeyRetrieval=true} +spring.datasource.url=${SPRING_DATASOURCE_URL:jdbc:mysql://localhost:3307/libraryloop?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=America/Sao_Paulo} spring.datasource.username=${SPRING_DATASOURCE_USERNAME:libraryuser} spring.datasource.password=${SPRING_DATASOURCE_PASSWORD:librarypass} spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver -# ===== JPA ===== spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true -spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect +spring.jpa.open-in-view=false -# ===== CACHE ===== -spring.cache.type=caffeine \ No newline at end of file +api.security.token.secret=${JWT_SECRET:iaowhfdoawfhoqui32gho1ubg24ou1ivb24oi} + +spring.cache.type=caffeine +spring.cache.caffeine.spec=maximumSize=500,expireAfterAccess=10m \ No newline at end of file