diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml deleted file mode 100644 index 6463c16..0000000 --- a/.github/workflows/cicd.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: CI/CD Docker - -on: - push: - branches: [ "main" ] - -jobs: - build-and-push: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - # Build e push da imagem Java - - name: Build and push Java image - uses: docker/build-push-action@v4 - with: - context: . - file: ./Dockerfile - target: java - push: true - tags: | - ${{ secrets.DOCKERHUB_USERNAME }}/apisaveit-java:latest - ${{ secrets.DOCKERHUB_USERNAME }}/apisaveit-java:${{ github.sha }} - - # Build e push da imagem Python (exemplo) - - name: Build and push Python image - uses: docker/build-push-action@v4 - with: - context: . - file: ./Dockerfile - target: python - push: true - tags: | - ${{ secrets.DOCKERHUB_USERNAME }}/apisaveit-python:latest - ${{ secrets.DOCKERHUB_USERNAME }}/apisaveit-python:${{ github.sha }} diff --git a/pom.xml b/pom.xml index 501e693..34c4179 100644 --- a/pom.xml +++ b/pom.xml @@ -85,6 +85,11 @@ firebase-admin 9.3.0 + + com.h2database + h2 + test + diff --git a/src/main/java/com/api/Configuration/FirebaseConfig.java b/src/main/java/com/api/Configuration/FirebaseConfig.java index 72112c7..4bb3513 100644 --- a/src/main/java/com/api/Configuration/FirebaseConfig.java +++ b/src/main/java/com/api/Configuration/FirebaseConfig.java @@ -6,11 +6,13 @@ import com.google.firebase.FirebaseOptions; import jakarta.annotation.PostConstruct; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; import java.io.ByteArrayInputStream; import java.util.Base64; @Configuration +@Profile("!test") // não carrega este bean se o profile for "test" public class FirebaseConfig { @Value("${firebase.credentials.b64:}") diff --git a/src/main/java/com/api/Util/SHA256PasswordEncoder.java b/src/main/java/com/api/Util/SHA256PasswordEncoder.java new file mode 100644 index 0000000..6dae5e6 --- /dev/null +++ b/src/main/java/com/api/Util/SHA256PasswordEncoder.java @@ -0,0 +1,18 @@ +package com.api.Util; + +import com.api.Util.PasswordUtils; +import org.springframework.security.crypto.password.PasswordEncoder; + +public class SHA256PasswordEncoder implements PasswordEncoder { + + @Override + public String encode(CharSequence rawPassword) { + return PasswordUtils.hashPassword(rawPassword.toString()); + } + + @Override + public boolean matches(CharSequence rawPassword, String encodedPassword) { + String hashed = PasswordUtils.hashPassword(rawPassword.toString()); + return hashed.equals(encodedPassword); + } +} diff --git a/src/main/java/com/api/controller/AdminController.java b/src/main/java/com/api/controller/AdminController.java index 8978a1e..a349581 100644 --- a/src/main/java/com/api/controller/AdminController.java +++ b/src/main/java/com/api/controller/AdminController.java @@ -51,7 +51,7 @@ public ResponseEntity updateAdmin(@PathVariable Long id, @Valid @RequestBody return ResponseEntity.ok("Admin atualizado com sucesso!"); } @PatchMapping("/atualizarParcial/{id}") - public ResponseEntity atualizarAdminParcial(@PathVariable Long id, @Valid @RequestBody Map updates) { + public ResponseEntity atualizarAdminParcial(@PathVariable Long id,@RequestBody Map updates) { adminService.updateAdminPartial(id, updates); return ResponseEntity.ok("Admin atualizado parcialmente com sucesso!"); } diff --git a/src/main/java/com/api/controller/ShowcaseController.java b/src/main/java/com/api/controller/ShowcaseController.java index 5074dc5..8f22dc9 100644 --- a/src/main/java/com/api/controller/ShowcaseController.java +++ b/src/main/java/com/api/controller/ShowcaseController.java @@ -1,7 +1,9 @@ package com.api.controller; +import com.api.dto.product.ProductResponseDTO; import com.api.exception.GlobalException; import com.api.openapi.ShowcaseOpenApi; +import com.api.service.ProductService; import com.api.service.ShowcaseService; import com.api.dto.showcase.ShowcaseListDTO; import com.api.dto.showcase.ShowcaseRequestDTO; @@ -12,6 +14,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.time.LocalDateTime; import java.util.List; import java.util.Map; @@ -21,6 +24,8 @@ public class ShowcaseController implements ShowcaseOpenApi { private final ShowcaseService showCaseService; private GlobalException ge; + @Autowired + private ProductService productService; @Autowired public ShowcaseController(ShowcaseService showCaseService) { this.showCaseService = showCaseService; @@ -75,5 +80,23 @@ public ResponseEntity updateShowcasePartial(@PathVariable Long id, @RequestBo return ResponseEntity.ok("Vitrine atualizado parcialmente com sucesso!"); } + @GetMapping("/novos") + public ResponseEntity getProdutosNovos(@RequestParam(required = false) String ultimoCheck) { + try { + LocalDateTime checkTime; + + if (ultimoCheck == null || ultimoCheck.isBlank()) { + checkTime = LocalDateTime.now().minusDays(1); + } else { + checkTime = LocalDateTime.parse(ultimoCheck); + } + List novasVitrines = showCaseService.listNewShowcases(checkTime); + return ResponseEntity.ok(novasVitrines); + + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body("Formato de data inválido. Use o formato: yyyy-MM-dd'T'HH:mm:ss"); + } + } } diff --git a/src/main/java/com/api/openapi/AdminOpenApi.java b/src/main/java/com/api/openapi/AdminOpenApi.java index 0f2e8f9..174bc5f 100644 --- a/src/main/java/com/api/openapi/AdminOpenApi.java +++ b/src/main/java/com/api/openapi/AdminOpenApi.java @@ -46,10 +46,11 @@ public interface AdminOpenApi { ResponseEntity updateAdmin(Long id, AdminRequestDTO adminAtualizado); @Operation(summary = "Atualizar parcialmente um admin pelo ID") @ApiResponses({ - @ApiResponse(responseCode = "200", description = "Admin atualizado com sucesso"), + @ApiResponse(responseCode = "200", description = "Admin atualizado parcialmente com sucesso"), @ApiResponse(responseCode = "400", description = "Dados inválidos fornecidos"), @ApiResponse(responseCode = "404", description = "Admin não encontrado"), @ApiResponse(responseCode = "500", description = "Erro interno do servidor") }) - ResponseEntity atualizarAdminParcial(Long id, Map updates); + ResponseEntity atualizarAdminParcial(Long id,Map updates); + } diff --git a/src/main/java/com/api/repository/ShowcaseRepository.java b/src/main/java/com/api/repository/ShowcaseRepository.java index 8db30c2..d471aef 100644 --- a/src/main/java/com/api/repository/ShowcaseRepository.java +++ b/src/main/java/com/api/repository/ShowcaseRepository.java @@ -1,10 +1,12 @@ package com.api.repository; +import com.api.model.Product; import com.api.model.Showcase; import com.api.dto.showcase.ShowcaseListDTO; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import java.time.LocalDateTime; import java.util.List; public interface ShowcaseRepository extends JpaRepository { @@ -38,5 +40,7 @@ public interface ShowcaseRepository extends JpaRepository { WHERE e.id = :enterpriseId """) List findShowcaseWithProductByEnterpriseId(long enterpriseId); + List findAllByEntranceDateAfter(LocalDateTime entranceDate); + } diff --git a/src/main/java/com/api/security/CustomAuthenticationProvider.java b/src/main/java/com/api/security/CustomAuthenticationProvider.java new file mode 100644 index 0000000..6963c62 --- /dev/null +++ b/src/main/java/com/api/security/CustomAuthenticationProvider.java @@ -0,0 +1,44 @@ +package com.api.security; + +import com.api.Util.PasswordUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.*; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.*; +import org.springframework.stereotype.Component; + +@Component +public class CustomAuthenticationProvider implements AuthenticationProvider { + + private final UserDetailsService userDetailsService; + + @Autowired + public CustomAuthenticationProvider(UserDetailsService userDetailsService) { + this.userDetailsService = userDetailsService; + } + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + String email = authentication.getName(); + String rawPassword = authentication.getCredentials().toString(); + + UserDetails userDetails = userDetailsService.loadUserByUsername(email); + + boolean senhaCorreta = PasswordUtils.verifyPassword(rawPassword, userDetails.getPassword()); + if (!senhaCorreta) { + throw new BadCredentialsException("Credenciais inválidas"); + } + + return new UsernamePasswordAuthenticationToken( + userDetails.getUsername(), + userDetails.getPassword(), + userDetails.getAuthorities() + ); + } + + @Override + public boolean supports(Class authentication) { + return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication); + } +} diff --git a/src/main/java/com/api/security/SecurityConfig.java b/src/main/java/com/api/security/SecurityConfig.java index 3904bc8..1839458 100644 --- a/src/main/java/com/api/security/SecurityConfig.java +++ b/src/main/java/com/api/security/SecurityConfig.java @@ -1,5 +1,6 @@ package com.api.security; +import com.api.Util.SHA256PasswordEncoder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -10,7 +11,6 @@ import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.web.cors.CorsConfiguration; @@ -28,13 +28,16 @@ public class SecurityConfig { @Autowired private TokenFilter tokenFilter; + @Autowired + private UserDetailService userDetailService; + @Bean public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); + return new SHA256PasswordEncoder(); } @Bean - public AuthenticationProvider authenticationProvider(UserDetailService userDetailService) { + public AuthenticationProvider authenticationProvider() { DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); provider.setUserDetailsService(userDetailService); provider.setPasswordEncoder(passwordEncoder()); @@ -49,7 +52,7 @@ public AuthenticationManager authenticationManager(AuthenticationConfiguration c @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); - configuration.setAllowedOrigins(List.of("http://localhost:5173", "https://react-save-it.vercel.app")); + configuration.setAllowedOriginPatterns(List.of("http://localhost:5173", "https://react-save-it.vercel.app")); configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); configuration.setAllowCredentials(true); configuration.setAllowedHeaders(List.of("*")); @@ -64,6 +67,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti http .cors(cors -> cors.configurationSource(corsConfigurationSource())) .csrf(csrf -> csrf.disable()) + .authenticationProvider(authenticationProvider()) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/auth/login", "/api/auth/logout").permitAll() .requestMatchers("/login", "/error").permitAll() diff --git a/src/main/java/com/api/service/ShowcaseService.java b/src/main/java/com/api/service/ShowcaseService.java index 487c168..49bac17 100644 --- a/src/main/java/com/api/service/ShowcaseService.java +++ b/src/main/java/com/api/service/ShowcaseService.java @@ -10,6 +10,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -132,4 +133,15 @@ public ShowcaseResponseDTO updateShowcasePartial(Long id, Map up showcaseRepository.save(showcase); return mapToDTO(showcase); } + public List listNewShowcases(LocalDateTime lastCheck) { + List novasVitrines = showcaseRepository.findAllByEntranceDateAfter(lastCheck); + List resposta = new ArrayList<>(); + + for (Showcase s : novasVitrines) { + resposta.add(mapToDTO(s)); + } + + return resposta; + } + } diff --git a/src/test/java/com/api/ApiApplicationTests.java b/src/test/java/com/api/ApiApplicationTests.java index e3d517d..3674b75 100644 --- a/src/test/java/com/api/ApiApplicationTests.java +++ b/src/test/java/com/api/ApiApplicationTests.java @@ -1,13 +1,16 @@ package com.api; +import com.api.TestSecurityConfig; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Import; @SpringBootTest +@Import(TestSecurityConfig.class) class ApiApplicationTests { @Test void contextLoads() { + // Só verifica se o contexto carrega corretamente } - } diff --git a/src/test/java/com/api/SecurityTestConfig.java b/src/test/java/com/api/SecurityTestConfig.java new file mode 100644 index 0000000..1ce38bb --- /dev/null +++ b/src/test/java/com/api/SecurityTestConfig.java @@ -0,0 +1,17 @@ +package com.api; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Profile; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; + +@TestConfiguration +@Profile("test") +public class SecurityTestConfig { + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { + return config.getAuthenticationManager(); + } +} diff --git a/src/test/java/com/api/TestSecurityConfig.java b/src/test/java/com/api/TestSecurityConfig.java new file mode 100644 index 0000000..bb16d4f --- /dev/null +++ b/src/test/java/com/api/TestSecurityConfig.java @@ -0,0 +1,23 @@ +package com.api; + +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; +import org.springframework.security.authentication.AuthenticationManager; + +import static org.mockito.Mockito.mock; + +/** + * Configuração falsa de segurança só para os testes. + * Impede o erro de AuthenticationConfiguration ausente. + */ +@TestConfiguration +public class TestSecurityConfig { + + @Bean + @Primary + public AuthenticationManager authenticationManager() { + // Retorna um mock para não carregar segurança real + return mock(AuthenticationManager.class); + } +} diff --git a/src/test/resources/application-test.properties b/src/test/resources/application-test.properties new file mode 100644 index 0000000..7474d8d --- /dev/null +++ b/src/test/resources/application-test.properties @@ -0,0 +1,6 @@ +spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration +spring.jpa.hibernate.ddl-auto=none +spring.datasource.url=jdbc:h2:mem:testdb +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password=