Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 0 additions & 46 deletions .github/workflows/cicd.yml

This file was deleted.

5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@
<artifactId>firebase-admin</artifactId>
<version>9.3.0</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>

</dependencies>

Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/api/Configuration/FirebaseConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -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:}")
Expand Down
18 changes: 18 additions & 0 deletions src/main/java/com/api/Util/SHA256PasswordEncoder.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
2 changes: 1 addition & 1 deletion src/main/java/com/api/controller/AdminController.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, Object> updates) {
public ResponseEntity<?> atualizarAdminParcial(@PathVariable Long id,@RequestBody Map<String, Object> updates) {
adminService.updateAdminPartial(id, updates);
return ResponseEntity.ok("Admin atualizado parcialmente com sucesso!");
}
Expand Down
23 changes: 23 additions & 0 deletions src/main/java/com/api/controller/ShowcaseController.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;

Expand All @@ -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;
Expand Down Expand Up @@ -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<ShowcaseResponseDTO> 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");
}
}
}
5 changes: 3 additions & 2 deletions src/main/java/com/api/openapi/AdminOpenApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, Object> updates);
ResponseEntity<?> atualizarAdminParcial(Long id,Map<String, Object> updates);

}
4 changes: 4 additions & 0 deletions src/main/java/com/api/repository/ShowcaseRepository.java
Original file line number Diff line number Diff line change
@@ -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<Showcase, Long> {
Expand Down Expand Up @@ -38,5 +40,7 @@ public interface ShowcaseRepository extends JpaRepository<Showcase, Long> {
WHERE e.id = :enterpriseId
""")
List<ShowcaseListDTO> findShowcaseWithProductByEnterpriseId(long enterpriseId);
List<Showcase> findAllByEntranceDateAfter(LocalDateTime entranceDate);


}
44 changes: 44 additions & 0 deletions src/main/java/com/api/security/CustomAuthenticationProvider.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
12 changes: 8 additions & 4 deletions src/main/java/com/api/security/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -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());
Expand All @@ -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("*"));
Expand All @@ -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()
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/com/api/service/ShowcaseService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -132,4 +133,15 @@ public ShowcaseResponseDTO updateShowcasePartial(Long id, Map<String, Object> up
showcaseRepository.save(showcase);
return mapToDTO(showcase);
}
public List<ShowcaseResponseDTO> listNewShowcases(LocalDateTime lastCheck) {
List<Showcase> novasVitrines = showcaseRepository.findAllByEntranceDateAfter(lastCheck);
List<ShowcaseResponseDTO> resposta = new ArrayList<>();

for (Showcase s : novasVitrines) {
resposta.add(mapToDTO(s));
}

return resposta;
}

}
5 changes: 4 additions & 1 deletion src/test/java/com/api/ApiApplicationTests.java
Original file line number Diff line number Diff line change
@@ -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
}

}
17 changes: 17 additions & 0 deletions src/test/java/com/api/SecurityTestConfig.java
Original file line number Diff line number Diff line change
@@ -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();
}
}
23 changes: 23 additions & 0 deletions src/test/java/com/api/TestSecurityConfig.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
6 changes: 6 additions & 0 deletions src/test/resources/application-test.properties
Original file line number Diff line number Diff line change
@@ -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=