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=