Atualização do projeto
This commit is contained in:
+37
@@ -0,0 +1,37 @@
|
|||||||
|
# Maven
|
||||||
|
target/
|
||||||
|
pom.xml.tag
|
||||||
|
pom.xml.releaseBackup
|
||||||
|
pom.xml.versionsBackup
|
||||||
|
pom.xml.next
|
||||||
|
release.properties
|
||||||
|
dependency-reduced-pom.xml
|
||||||
|
buildNumber.properties
|
||||||
|
.mvn/timing.properties
|
||||||
|
.mvn/wrapper/maven-wrapper.jar
|
||||||
|
|
||||||
|
# Eclipse
|
||||||
|
.settings/
|
||||||
|
.classpath
|
||||||
|
.project
|
||||||
|
|
||||||
|
# IntelliJ IDEA
|
||||||
|
.idea/
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
out/
|
||||||
|
*.factorypath
|
||||||
|
|
||||||
|
# VS Code
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>3.2.0</version>
|
||||||
|
<relativePath/>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<groupId>com.agendaestudantil</groupId>
|
||||||
|
<artifactId>agenda-digital-estudantes</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<name>Agenda Digital para Estudantes</name>
|
||||||
|
<description>Backend para agenda digital destinado a estudantes com dificuldade de organização</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>17</java.version>
|
||||||
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
|
<lombok.version>1.18.30</lombok.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-mongodb</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-api</artifactId>
|
||||||
|
<version>0.12.3</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-impl</artifactId>
|
||||||
|
<version>0.12.6</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-jackson</artifactId>
|
||||||
|
<version>0.12.3</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springdoc</groupId>
|
||||||
|
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||||
|
<version>2.3.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.github.cdimascio</groupId>
|
||||||
|
<artifactId>dotenv-java</artifactId>
|
||||||
|
<version>2.3.2</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mockito</groupId>
|
||||||
|
<artifactId>mockito-core</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<source>17</source>
|
||||||
|
<target>17</target>
|
||||||
|
<compilerArgs>
|
||||||
|
<arg>-parameters</arg>
|
||||||
|
</compilerArgs>
|
||||||
|
<annotationProcessorPaths>
|
||||||
|
<path>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>${lombok.version}</version>
|
||||||
|
</path>
|
||||||
|
</annotationProcessorPaths>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<excludes>
|
||||||
|
<exclude>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
</exclude>
|
||||||
|
</excludes>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.agendaestudantil;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
@EnableScheduling
|
||||||
|
public class AgendaDigitalEstudantesApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(AgendaDigitalEstudantesApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.agendaestudantil.configuracao;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.data.mongodb.config.EnableMongoAuditing;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableMongoAuditing
|
||||||
|
public class ConfiguracaoMongo {
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package com.agendaestudantil.configuracao;
|
||||||
|
|
||||||
|
import com.agendaestudantil.filtro.FiltroJwt;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||||
|
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;
|
||||||
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
|
import org.springframework.web.cors.CorsConfigurationSource;
|
||||||
|
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
public class ConfiguracaoSeguranca {
|
||||||
|
|
||||||
|
private final FiltroJwt filtroJwt;
|
||||||
|
private final String corsAllowedOrigins;
|
||||||
|
|
||||||
|
public ConfiguracaoSeguranca(FiltroJwt filtroJwt, @Value("${cors.allowed.origins}") String corsAllowedOrigins) {
|
||||||
|
this.filtroJwt = filtroJwt;
|
||||||
|
this.corsAllowedOrigins = corsAllowedOrigins;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
|
http.csrf(csrf -> csrf.disable())
|
||||||
|
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
|
||||||
|
.authorizeHttpRequests(auth -> auth
|
||||||
|
.requestMatchers(
|
||||||
|
"/", "/index.html", "/login.html", "/cadastro.html", "/calendario.html", "/configuracoes.html",
|
||||||
|
"/favicon.ico", "/imagens/**",
|
||||||
|
"/*.css", "/*.js", "/*.ico", "/*.png",
|
||||||
|
"/api/estudantes/cadastro", "/api/estudantes/login",
|
||||||
|
"/swagger-ui/**", "/v3/api-docs/**")
|
||||||
|
.permitAll()
|
||||||
|
.anyRequest().authenticated())
|
||||||
|
.sessionManagement(session -> session
|
||||||
|
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
|
.addFilterBefore(filtroJwt, UsernamePasswordAuthenticationFilter.class);
|
||||||
|
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public CorsConfigurationSource corsConfigurationSource() {
|
||||||
|
CorsConfiguration configuration = new CorsConfiguration();
|
||||||
|
configuration.setAllowedOrigins(List.of(corsAllowedOrigins.split(",")));
|
||||||
|
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"));
|
||||||
|
configuration.setAllowedHeaders(List.of("*"));
|
||||||
|
configuration.setExposedHeaders(List.of("Authorization"));
|
||||||
|
configuration.setAllowCredentials(true);
|
||||||
|
configuration.setMaxAge(3600L);
|
||||||
|
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||||
|
source.registerCorsConfiguration("/**", configuration);
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PasswordEncoder passwordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
|
||||||
|
return config.getAuthenticationManager();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.agendaestudantil.configuracao;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
public class ResourceController {
|
||||||
|
|
||||||
|
@GetMapping("/")
|
||||||
|
public String index() {
|
||||||
|
return "forward:/login.html";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/app")
|
||||||
|
public String app() {
|
||||||
|
return "forward:/calendario.html";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/config")
|
||||||
|
public String config() {
|
||||||
|
return "forward:/configuracoes.html";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package com.agendaestudantil.controlador;
|
||||||
|
|
||||||
|
import com.agendaestudantil.dto.RequisicaoDisciplinaDTO;
|
||||||
|
import com.agendaestudantil.dto.RespostaApi;
|
||||||
|
import com.agendaestudantil.dto.RespostaDisciplinaDTO;
|
||||||
|
import com.agendaestudantil.entidade.Disciplina;
|
||||||
|
import com.agendaestudantil.servico.DisciplinaServico;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/disciplinas")
|
||||||
|
public class DisciplinaControlador {
|
||||||
|
|
||||||
|
private final DisciplinaServico disciplinaServico;
|
||||||
|
|
||||||
|
public DisciplinaControlador(DisciplinaServico disciplinaServico) {
|
||||||
|
this.disciplinaServico = disciplinaServico;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<RespostaApi<RespostaDisciplinaDTO>> criarDisciplina(
|
||||||
|
@Valid @RequestBody RequisicaoDisciplinaDTO dto,
|
||||||
|
@AuthenticationPrincipal UserDetails userDetails) {
|
||||||
|
String estudanteId = userDetails.getUsername();
|
||||||
|
Disciplina disciplina = new Disciplina();
|
||||||
|
disciplina.setNome(dto.nome());
|
||||||
|
disciplina.setProfessor(dto.professor());
|
||||||
|
disciplina.setSala(dto.sala());
|
||||||
|
disciplina.setCor(dto.cor());
|
||||||
|
|
||||||
|
RespostaDisciplinaDTO resposta = disciplinaServico.criarDisciplina(disciplina, estudanteId);
|
||||||
|
return ResponseEntity.status(HttpStatus.CREATED).body(RespostaApi.sucesso(resposta));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/me")
|
||||||
|
public ResponseEntity<RespostaApi<List<RespostaDisciplinaDTO>>> listarPorEstudante(
|
||||||
|
@AuthenticationPrincipal UserDetails userDetails) {
|
||||||
|
String estudanteId = userDetails.getUsername();
|
||||||
|
List<RespostaDisciplinaDTO> disciplinas = disciplinaServico.listarPorEstudante(estudanteId);
|
||||||
|
return ResponseEntity.ok(RespostaApi.sucesso(disciplinas));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public ResponseEntity<RespostaApi<RespostaDisciplinaDTO>> buscarPorId(
|
||||||
|
@PathVariable String id,
|
||||||
|
@AuthenticationPrincipal UserDetails userDetails) {
|
||||||
|
String estudanteId = userDetails.getUsername();
|
||||||
|
RespostaDisciplinaDTO disciplina = disciplinaServico.buscarPorId(id, estudanteId);
|
||||||
|
return ResponseEntity.ok(RespostaApi.sucesso(disciplina));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public ResponseEntity<RespostaApi<RespostaDisciplinaDTO>> atualizarDisciplina(
|
||||||
|
@PathVariable String id,
|
||||||
|
@Valid @RequestBody RequisicaoDisciplinaDTO dto,
|
||||||
|
@AuthenticationPrincipal UserDetails userDetails) {
|
||||||
|
String estudanteId = userDetails.getUsername();
|
||||||
|
Disciplina disciplina = new Disciplina();
|
||||||
|
disciplina.setNome(dto.nome());
|
||||||
|
disciplina.setProfessor(dto.professor());
|
||||||
|
disciplina.setSala(dto.sala());
|
||||||
|
disciplina.setCor(dto.cor());
|
||||||
|
|
||||||
|
RespostaDisciplinaDTO resposta = disciplinaServico.atualizarDisciplina(id, disciplina, estudanteId);
|
||||||
|
return ResponseEntity.ok(RespostaApi.sucesso(resposta));
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public ResponseEntity<RespostaApi<Void>> excluirDisciplina(
|
||||||
|
@PathVariable String id,
|
||||||
|
@AuthenticationPrincipal UserDetails userDetails) {
|
||||||
|
String estudanteId = userDetails.getUsername();
|
||||||
|
disciplinaServico.excluirDisciplina(id, estudanteId);
|
||||||
|
return ResponseEntity.status(HttpStatus.NO_CONTENT).body(RespostaApi.sucesso(null));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package com.agendaestudantil.controlador;
|
||||||
|
|
||||||
|
import com.agendaestudantil.dto.RespostaApi;
|
||||||
|
import com.agendaestudantil.dto.RequisicaoAtualizacaoEstudanteDTO;
|
||||||
|
import com.agendaestudantil.dto.RequisicaoCadastroDTO;
|
||||||
|
import com.agendaestudantil.dto.RespostaEstudanteDTO;
|
||||||
|
import com.agendaestudantil.dto.RequisicaoLoginDTO;
|
||||||
|
import com.agendaestudantil.dto.RespostaLoginDTO;
|
||||||
|
import com.agendaestudantil.dto.RequisicaoTrocaSenhaDTO;
|
||||||
|
import com.agendaestudantil.servico.EstudanteServico;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/estudantes")
|
||||||
|
public class EstudanteControlador {
|
||||||
|
|
||||||
|
private final EstudanteServico estudanteServico;
|
||||||
|
|
||||||
|
public EstudanteControlador(EstudanteServico estudanteServico) {
|
||||||
|
this.estudanteServico = estudanteServico;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/cadastro")
|
||||||
|
public ResponseEntity<RespostaApi<RespostaEstudanteDTO>> cadastrar(@Valid @RequestBody RequisicaoCadastroDTO dto) {
|
||||||
|
RespostaEstudanteDTO resposta = estudanteServico.cadastrar(dto);
|
||||||
|
return ResponseEntity.status(HttpStatus.CREATED).body(RespostaApi.sucesso(resposta));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/login")
|
||||||
|
public ResponseEntity<RespostaApi<RespostaLoginDTO>> login(@Valid @RequestBody RequisicaoLoginDTO dto) {
|
||||||
|
RespostaLoginDTO resposta = estudanteServico.login(dto);
|
||||||
|
return ResponseEntity.ok(RespostaApi.sucesso(resposta));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retorna dados do usuário autenticado via JWT (sem precisar do ID na URL)
|
||||||
|
@GetMapping("/me")
|
||||||
|
public ResponseEntity<RespostaApi<RespostaEstudanteDTO>> me(@AuthenticationPrincipal UserDetails userDetails) {
|
||||||
|
RespostaEstudanteDTO resposta = estudanteServico.buscarPorId(userDetails.getUsername());
|
||||||
|
return ResponseEntity.ok(RespostaApi.sucesso(resposta));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Atualiza dados do usuário autenticado
|
||||||
|
@PutMapping("/me")
|
||||||
|
public ResponseEntity<RespostaApi<RespostaEstudanteDTO>> atualizar(
|
||||||
|
@AuthenticationPrincipal UserDetails userDetails,
|
||||||
|
@Valid @RequestBody RequisicaoAtualizacaoEstudanteDTO dto) {
|
||||||
|
RespostaEstudanteDTO resposta = estudanteServico.atualizar(userDetails.getUsername(), dto);
|
||||||
|
return ResponseEntity.ok(RespostaApi.sucesso(resposta));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Altera senha do usuário autenticado
|
||||||
|
@PutMapping("/senha")
|
||||||
|
public ResponseEntity<RespostaApi<Void>> trocarSenha(
|
||||||
|
@AuthenticationPrincipal UserDetails userDetails,
|
||||||
|
@Valid @RequestBody RequisicaoTrocaSenhaDTO dto) {
|
||||||
|
estudanteServico.trocarSenha(userDetails.getUsername(), dto);
|
||||||
|
return ResponseEntity.ok(RespostaApi.sucesso(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Excluir conta do usuário autenticado
|
||||||
|
@DeleteMapping("/me")
|
||||||
|
public ResponseEntity<RespostaApi<Void>> excluirConta(@AuthenticationPrincipal UserDetails userDetails) {
|
||||||
|
estudanteServico.excluirConta(userDetails.getUsername());
|
||||||
|
return ResponseEntity.ok(RespostaApi.sucesso(null));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package com.agendaestudantil.controlador;
|
||||||
|
|
||||||
|
import com.agendaestudantil.dto.RequisicaoEventoDTO;
|
||||||
|
import com.agendaestudantil.dto.RespostaApi;
|
||||||
|
import com.agendaestudantil.dto.RespostaEventoDTO;
|
||||||
|
import com.agendaestudantil.entidade.Evento;
|
||||||
|
import com.agendaestudantil.servico.EventoServico;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/eventos")
|
||||||
|
public class EventoControlador {
|
||||||
|
|
||||||
|
private final EventoServico eventoServico;
|
||||||
|
|
||||||
|
public EventoControlador(EventoServico eventoServico) {
|
||||||
|
this.eventoServico = eventoServico;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<RespostaApi<RespostaEventoDTO>> criarEvento(
|
||||||
|
@Valid @RequestBody RequisicaoEventoDTO dto,
|
||||||
|
@AuthenticationPrincipal UserDetails userDetails) {
|
||||||
|
String estudanteId = userDetails.getUsername();
|
||||||
|
Evento evento = new Evento();
|
||||||
|
evento.setTitulo(dto.titulo());
|
||||||
|
evento.setDescricao(dto.descricao());
|
||||||
|
evento.setTipo(dto.tipo());
|
||||||
|
evento.setLocal(dto.local());
|
||||||
|
evento.setDataHora(dto.dataHora());
|
||||||
|
evento.setDisciplinaId(dto.disciplinaId());
|
||||||
|
|
||||||
|
RespostaEventoDTO resposta = eventoServico.criarEvento(evento, estudanteId);
|
||||||
|
return ResponseEntity.status(HttpStatus.CREATED).body(RespostaApi.sucesso(resposta));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/me")
|
||||||
|
public ResponseEntity<RespostaApi<List<RespostaEventoDTO>>> listarPorEstudante(
|
||||||
|
@AuthenticationPrincipal UserDetails userDetails) {
|
||||||
|
String estudanteId = userDetails.getUsername();
|
||||||
|
List<RespostaEventoDTO> eventos = eventoServico.listarPorEstudante(estudanteId);
|
||||||
|
return ResponseEntity.ok(RespostaApi.sucesso(eventos));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/me/periodo")
|
||||||
|
public ResponseEntity<RespostaApi<List<RespostaEventoDTO>>> listarPorPeriodo(
|
||||||
|
@AuthenticationPrincipal UserDetails userDetails,
|
||||||
|
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime inicio,
|
||||||
|
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime fim) {
|
||||||
|
String estudanteId = userDetails.getUsername();
|
||||||
|
List<RespostaEventoDTO> eventos = eventoServico.listarPorPeriodo(estudanteId, inicio, fim);
|
||||||
|
return ResponseEntity.ok(RespostaApi.sucesso(eventos));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/me/proximos")
|
||||||
|
public ResponseEntity<RespostaApi<List<RespostaEventoDTO>>> listarProximosEventos(
|
||||||
|
@AuthenticationPrincipal UserDetails userDetails) {
|
||||||
|
String estudanteId = userDetails.getUsername();
|
||||||
|
List<RespostaEventoDTO> eventos = eventoServico.listarProximosEventos(estudanteId);
|
||||||
|
return ResponseEntity.ok(RespostaApi.sucesso(eventos));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public ResponseEntity<RespostaApi<RespostaEventoDTO>> buscarPorId(@PathVariable String id) {
|
||||||
|
RespostaEventoDTO evento = eventoServico.buscarPorId(id);
|
||||||
|
return ResponseEntity.ok(RespostaApi.sucesso(evento));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public ResponseEntity<RespostaApi<RespostaEventoDTO>> atualizarEvento(
|
||||||
|
@PathVariable String id,
|
||||||
|
@Valid @RequestBody RequisicaoEventoDTO dto) {
|
||||||
|
RespostaEventoDTO resposta = eventoServico.atualizarEvento(id, dto);
|
||||||
|
return ResponseEntity.ok(RespostaApi.sucesso(resposta));
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public ResponseEntity<RespostaApi<Void>> excluirEvento(@PathVariable String id) {
|
||||||
|
eventoServico.excluirEvento(id);
|
||||||
|
return ResponseEntity.status(HttpStatus.NO_CONTENT).body(RespostaApi.sucesso(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PatchMapping("/{id}/cancelar")
|
||||||
|
public ResponseEntity<RespostaApi<RespostaEventoDTO>> cancelarEvento(@PathVariable String id) {
|
||||||
|
RespostaEventoDTO evento = eventoServico.cancelarEvento(id);
|
||||||
|
return ResponseEntity.ok(RespostaApi.sucesso(evento));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package com.agendaestudantil.controlador;
|
||||||
|
|
||||||
|
import com.agendaestudantil.dto.RespostaApi;
|
||||||
|
import com.agendaestudantil.dto.RespostaNotificacaoDTO;
|
||||||
|
import com.agendaestudantil.servico.NotificacaoServico;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/notificacoes")
|
||||||
|
public class NotificacaoControlador {
|
||||||
|
|
||||||
|
private final NotificacaoServico notificacaoServico;
|
||||||
|
|
||||||
|
public NotificacaoControlador(NotificacaoServico notificacaoServico) {
|
||||||
|
this.notificacaoServico = notificacaoServico;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/me")
|
||||||
|
public ResponseEntity<RespostaApi<List<RespostaNotificacaoDTO>>> listarTodas(
|
||||||
|
@AuthenticationPrincipal UserDetails userDetails) {
|
||||||
|
String estudanteId = userDetails.getUsername();
|
||||||
|
List<RespostaNotificacaoDTO> notificacoes = notificacaoServico.listarTodas(estudanteId);
|
||||||
|
return ResponseEntity.ok(RespostaApi.sucesso(notificacoes));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/me/nao-lidas")
|
||||||
|
public ResponseEntity<RespostaApi<List<RespostaNotificacaoDTO>>> listarNaoLidas(
|
||||||
|
@AuthenticationPrincipal UserDetails userDetails) {
|
||||||
|
String estudanteId = userDetails.getUsername();
|
||||||
|
List<RespostaNotificacaoDTO> notificacoes = notificacaoServico.listarNaoLidas(estudanteId);
|
||||||
|
return ResponseEntity.ok(RespostaApi.sucesso(notificacoes));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/me/contagem")
|
||||||
|
public ResponseEntity<RespostaApi<Map<String, Long>>> contarNaoLidas(
|
||||||
|
@AuthenticationPrincipal UserDetails userDetails) {
|
||||||
|
String estudanteId = userDetails.getUsername();
|
||||||
|
long total = notificacaoServico.contarNaoLidas(estudanteId);
|
||||||
|
return ResponseEntity.ok(RespostaApi.sucesso(Map.of("total", total)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PatchMapping("/{id}/ler")
|
||||||
|
public ResponseEntity<RespostaApi<RespostaNotificacaoDTO>> marcarComoLida(@PathVariable String id) {
|
||||||
|
RespostaNotificacaoDTO notificacao = notificacaoServico.marcarComoLida(id);
|
||||||
|
return ResponseEntity.ok(RespostaApi.sucesso(notificacao));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PatchMapping("/me/ler-todas")
|
||||||
|
public ResponseEntity<RespostaApi<Void>> marcarTodasComoLidas(
|
||||||
|
@AuthenticationPrincipal UserDetails userDetails) {
|
||||||
|
String estudanteId = userDetails.getUsername();
|
||||||
|
notificacaoServico.marcarTodasComoLidas(estudanteId);
|
||||||
|
return ResponseEntity.ok(RespostaApi.sucesso(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public ResponseEntity<RespostaApi<Void>> excluirNotificacao(@PathVariable String id) {
|
||||||
|
notificacaoServico.excluirNotificacao(id);
|
||||||
|
return ResponseEntity.status(HttpStatus.NO_CONTENT).body(RespostaApi.sucesso(null));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package com.agendaestudantil.controlador;
|
||||||
|
|
||||||
|
import com.agendaestudantil.dto.RespostaApi;
|
||||||
|
import com.agendaestudantil.dto.RequisicaoTarefaDTO;
|
||||||
|
import com.agendaestudantil.dto.RespostaTarefaDTO;
|
||||||
|
import com.agendaestudantil.servico.TarefaServico;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/tarefas")
|
||||||
|
public class TarefaControlador {
|
||||||
|
|
||||||
|
private final TarefaServico tarefaServico;
|
||||||
|
|
||||||
|
public TarefaControlador(TarefaServico tarefaServico) {
|
||||||
|
this.tarefaServico = tarefaServico;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<RespostaApi<RespostaTarefaDTO>> criarTarefa(@Valid @RequestBody RequisicaoTarefaDTO dto) {
|
||||||
|
RespostaTarefaDTO tarefa = tarefaServico.criarTarefa(dto);
|
||||||
|
return ResponseEntity.status(HttpStatus.CREATED).body(RespostaApi.sucesso(tarefa));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/me")
|
||||||
|
public ResponseEntity<RespostaApi<List<RespostaTarefaDTO>>> listarTarefasPorEstudante(
|
||||||
|
@AuthenticationPrincipal UserDetails userDetails) {
|
||||||
|
String estudanteId = userDetails.getUsername();
|
||||||
|
List<RespostaTarefaDTO> tarefas = tarefaServico.listarTarefasPorEstudante(estudanteId);
|
||||||
|
return ResponseEntity.ok(RespostaApi.sucesso(tarefas));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/me/pendentes")
|
||||||
|
public ResponseEntity<RespostaApi<List<RespostaTarefaDTO>>> listarTarefasPendentes(
|
||||||
|
@AuthenticationPrincipal UserDetails userDetails) {
|
||||||
|
String estudanteId = userDetails.getUsername();
|
||||||
|
List<RespostaTarefaDTO> tarefas = tarefaServico.listarTarefasPendentes(estudanteId);
|
||||||
|
return ResponseEntity.ok(RespostaApi.sucesso(tarefas));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/me/data")
|
||||||
|
public ResponseEntity<RespostaApi<List<RespostaTarefaDTO>>> listarTarefasPorData(
|
||||||
|
@AuthenticationPrincipal UserDetails userDetails,
|
||||||
|
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate data) {
|
||||||
|
String estudanteId = userDetails.getUsername();
|
||||||
|
List<RespostaTarefaDTO> tarefas = tarefaServico.listarTarefasPorData(estudanteId, data);
|
||||||
|
return ResponseEntity.ok(RespostaApi.sucesso(tarefas));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/me/periodo")
|
||||||
|
public ResponseEntity<RespostaApi<List<RespostaTarefaDTO>>> listarTarefasPorPeriodo(
|
||||||
|
@AuthenticationPrincipal UserDetails userDetails,
|
||||||
|
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate inicio,
|
||||||
|
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate fim) {
|
||||||
|
String estudanteId = userDetails.getUsername();
|
||||||
|
List<RespostaTarefaDTO> tarefas = tarefaServico.listarTarefasPorPeriodo(estudanteId, inicio, fim);
|
||||||
|
return ResponseEntity.ok(RespostaApi.sucesso(tarefas));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public ResponseEntity<RespostaApi<RespostaTarefaDTO>> buscarTarefaPorId(@PathVariable String id) {
|
||||||
|
RespostaTarefaDTO dto = tarefaServico.buscarTarefaPorId(id);
|
||||||
|
return ResponseEntity.ok(RespostaApi.sucesso(dto));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public ResponseEntity<RespostaApi<RespostaTarefaDTO>> atualizarTarefa(@PathVariable String id,
|
||||||
|
@Valid @RequestBody RequisicaoTarefaDTO dto) {
|
||||||
|
RespostaTarefaDTO tarefa = tarefaServico.atualizarTarefa(id, dto);
|
||||||
|
return ResponseEntity.ok(RespostaApi.sucesso(tarefa));
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public ResponseEntity<RespostaApi<Void>> excluirTarefa(@PathVariable String id) {
|
||||||
|
tarefaServico.excluirTarefa(id);
|
||||||
|
return ResponseEntity.status(HttpStatus.NO_CONTENT).body(RespostaApi.sucesso(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PatchMapping("/{id}/concluir")
|
||||||
|
public ResponseEntity<RespostaApi<RespostaTarefaDTO>> marcarConcluida(@PathVariable String id) {
|
||||||
|
RespostaTarefaDTO tarefa = tarefaServico.marcarConcluida(id);
|
||||||
|
return ResponseEntity.ok(RespostaApi.sucesso(tarefa));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.agendaestudantil.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import jakarta.validation.constraints.Min;
|
||||||
|
|
||||||
|
public record RequisicaoAtualizacaoEstudanteDTO(
|
||||||
|
@NotBlank String nome,
|
||||||
|
@NotBlank String curso,
|
||||||
|
@NotNull @Min(1) Integer periodo
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.agendaestudantil.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.Email;
|
||||||
|
import jakarta.validation.constraints.Min;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
|
|
||||||
|
public record RequisicaoCadastroDTO(
|
||||||
|
@NotBlank String nome,
|
||||||
|
@Email @NotBlank String email,
|
||||||
|
@NotBlank @Size(min = 6) String senha,
|
||||||
|
@NotBlank String curso,
|
||||||
|
@NotNull @Min(1) Integer periodo
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.agendaestudantil.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
public record RequisicaoDisciplinaDTO(
|
||||||
|
@NotBlank String nome,
|
||||||
|
String professor,
|
||||||
|
String sala,
|
||||||
|
String cor
|
||||||
|
) {}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.agendaestudantil.dto;
|
||||||
|
|
||||||
|
import com.agendaestudantil.entidade.Evento;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
public record RequisicaoEventoDTO(
|
||||||
|
@NotBlank String titulo,
|
||||||
|
String descricao,
|
||||||
|
@NotNull Evento.TipoEvento tipo,
|
||||||
|
String local,
|
||||||
|
String disciplinaId,
|
||||||
|
@NotNull LocalDateTime dataHora
|
||||||
|
) {}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.agendaestudantil.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.Email;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
public record RequisicaoLoginDTO(
|
||||||
|
@Email @NotBlank String email,
|
||||||
|
@NotBlank String senha
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.agendaestudantil.dto;
|
||||||
|
|
||||||
|
import com.agendaestudantil.entidade.Tarefa;
|
||||||
|
import jakarta.validation.constraints.FutureOrPresent;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
public record RequisicaoTarefaDTO(
|
||||||
|
@NotBlank String titulo,
|
||||||
|
String descricao,
|
||||||
|
@NotNull(message = "Prioridade é obrigatória") Tarefa.Prioridade prioridade,
|
||||||
|
Tarefa.StatusTarefa status,
|
||||||
|
@NotNull @FutureOrPresent LocalDate dataEntrega,
|
||||||
|
String disciplinaId
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.agendaestudantil.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
|
|
||||||
|
public record RequisicaoTrocaSenhaDTO(
|
||||||
|
@NotBlank String senhaAtual,
|
||||||
|
@NotBlank @Size(min = 6) String novaSenha
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.agendaestudantil.dto;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
public record RespostaApi<T>(T data, String message, LocalDateTime timestamp) {
|
||||||
|
public static <T> RespostaApi<T> sucesso(T data) {
|
||||||
|
return new RespostaApi<>(data, "Sucesso", LocalDateTime.now());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.agendaestudantil.dto;
|
||||||
|
|
||||||
|
public record RespostaDisciplinaDTO(
|
||||||
|
String id,
|
||||||
|
String estudanteId,
|
||||||
|
String nome,
|
||||||
|
String professor,
|
||||||
|
String sala,
|
||||||
|
String cor
|
||||||
|
) {}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.agendaestudantil.dto;
|
||||||
|
|
||||||
|
public record RespostaEstudanteDTO(
|
||||||
|
String id,
|
||||||
|
String nome,
|
||||||
|
String email,
|
||||||
|
String curso,
|
||||||
|
Integer periodo
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.agendaestudantil.dto;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
public record RespostaEventoDTO(
|
||||||
|
String id,
|
||||||
|
String estudanteId,
|
||||||
|
String titulo,
|
||||||
|
String descricao,
|
||||||
|
String tipo,
|
||||||
|
String local,
|
||||||
|
String disciplinaId,
|
||||||
|
LocalDateTime dataHora,
|
||||||
|
String status,
|
||||||
|
String nomeDisciplina
|
||||||
|
) {}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package com.agendaestudantil.dto;
|
||||||
|
|
||||||
|
public record RespostaLoginDTO(String token, RespostaEstudanteDTO estudante) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.agendaestudantil.dto;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
public record RespostaNotificacaoDTO(
|
||||||
|
String id,
|
||||||
|
String titulo,
|
||||||
|
String mensagem,
|
||||||
|
String tipo,
|
||||||
|
String referenciaId,
|
||||||
|
String tipoReferencia,
|
||||||
|
boolean lida,
|
||||||
|
LocalDateTime dataGeracao
|
||||||
|
) {}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.agendaestudantil.dto;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
public record RespostaTarefaDTO(
|
||||||
|
String id,
|
||||||
|
String titulo,
|
||||||
|
String descricao,
|
||||||
|
String prioridade,
|
||||||
|
String status,
|
||||||
|
LocalDate dataEntrega,
|
||||||
|
String disciplinaId,
|
||||||
|
String estudanteId
|
||||||
|
) {}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.agendaestudantil.entidade;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.springframework.data.annotation.Id;
|
||||||
|
import org.springframework.data.mongodb.core.index.CompoundIndex;
|
||||||
|
import org.springframework.data.mongodb.core.mapping.Document;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
@Document(collection = "disciplinas")
|
||||||
|
@CompoundIndex(name = "estudante_nome_idx", def = "{estudanteId: 1, nome: 1}")
|
||||||
|
public class Disciplina extends EntidadeAuditoria {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
private String id;
|
||||||
|
private String estudanteId;
|
||||||
|
private String nome;
|
||||||
|
private String professor;
|
||||||
|
private String sala;
|
||||||
|
private String cor;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.agendaestudantil.entidade;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.springframework.data.annotation.CreatedDate;
|
||||||
|
import org.springframework.data.annotation.LastModifiedDate;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public abstract class EntidadeAuditoria {
|
||||||
|
|
||||||
|
@CreatedDate
|
||||||
|
private LocalDateTime dataCriacao;
|
||||||
|
|
||||||
|
@LastModifiedDate
|
||||||
|
private LocalDateTime dataAtualizacao;
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.agendaestudantil.entidade;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.springframework.data.annotation.Id;
|
||||||
|
import org.springframework.data.mongodb.core.index.Indexed;
|
||||||
|
import org.springframework.data.mongodb.core.mapping.Document;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
@Document(collection = "estudantes")
|
||||||
|
public class Estudante extends EntidadeAuditoria {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Indexed(unique = true)
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
private String nome;
|
||||||
|
|
||||||
|
private String senha;
|
||||||
|
|
||||||
|
private String curso;
|
||||||
|
|
||||||
|
private Integer periodo;
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package com.agendaestudantil.entidade;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.springframework.data.annotation.Id;
|
||||||
|
import org.springframework.data.mongodb.core.index.CompoundIndex;
|
||||||
|
import org.springframework.data.mongodb.core.mapping.Document;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
@Document(collection = "eventos")
|
||||||
|
@CompoundIndex(name = "estudante_data_hora_idx", def = "{estudanteId: 1, dataHora: 1}")
|
||||||
|
public class Evento extends EntidadeAuditoria {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
private String id;
|
||||||
|
private String estudanteId;
|
||||||
|
private String titulo;
|
||||||
|
private String descricao;
|
||||||
|
private TipoEvento tipo;
|
||||||
|
private String local;
|
||||||
|
private String disciplinaId;
|
||||||
|
private LocalDateTime dataHora;
|
||||||
|
private StatusEvento status;
|
||||||
|
|
||||||
|
public enum TipoEvento {
|
||||||
|
AULA, PROVA, TRABALHO, ESTUDO, EXAME, OUTRO
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum StatusEvento {
|
||||||
|
ATIVO, CANCELADO
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package com.agendaestudantil.entidade;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.springframework.data.annotation.Id;
|
||||||
|
import org.springframework.data.mongodb.core.index.CompoundIndex;
|
||||||
|
import org.springframework.data.mongodb.core.mapping.Document;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
@Document(collection = "notificacoes")
|
||||||
|
@CompoundIndex(name = "estudante_lida_idx", def = "{estudanteId: 1, lida: 1}")
|
||||||
|
public class Notificacao extends EntidadeAuditoria {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
private String id;
|
||||||
|
private String estudanteId;
|
||||||
|
private String titulo;
|
||||||
|
private String mensagem;
|
||||||
|
private TipoNotificacao tipo;
|
||||||
|
private String referenciaId;
|
||||||
|
private TipoReferencia tipoReferencia;
|
||||||
|
private boolean lida;
|
||||||
|
private LocalDateTime dataGeracao;
|
||||||
|
|
||||||
|
public enum TipoNotificacao {
|
||||||
|
PRAZO_PROXIMO, TAREFA_ATRASADA, EVENTO_PROXIMO
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum TipoReferencia {
|
||||||
|
TAREFA, EVENTO
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.agendaestudantil.entidade;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.springframework.data.annotation.Id;
|
||||||
|
import org.springframework.data.mongodb.core.index.CompoundIndex;
|
||||||
|
import org.springframework.data.mongodb.core.index.CompoundIndexes;
|
||||||
|
import org.springframework.data.mongodb.core.mapping.Document;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
@Document(collection = "tarefas")
|
||||||
|
@CompoundIndexes({
|
||||||
|
@CompoundIndex(name = "estudante_data_entrega_idx", def = "{estudanteId: 1, dataEntrega: 1}"),
|
||||||
|
@CompoundIndex(name = "estudante_status_idx", def = "{estudanteId: 1, status: 1}")
|
||||||
|
})
|
||||||
|
public class Tarefa extends EntidadeAuditoria {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
private String id;
|
||||||
|
private String titulo;
|
||||||
|
private String descricao;
|
||||||
|
private Prioridade prioridade;
|
||||||
|
private StatusTarefa status;
|
||||||
|
private LocalDate dataEntrega;
|
||||||
|
private String disciplinaId;
|
||||||
|
private String estudanteId;
|
||||||
|
|
||||||
|
public enum Prioridade {
|
||||||
|
BAIXA, MEDIA, ALTA, URGENTE
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum StatusTarefa {
|
||||||
|
PENDENTE, EM_ANDAMENTO, CONCLUIDA, ATRASADA
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.agendaestudantil.excecao;
|
||||||
|
|
||||||
|
public class ExcecaoNegocio extends RuntimeException {
|
||||||
|
public ExcecaoNegocio(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.agendaestudantil.excecao;
|
||||||
|
|
||||||
|
public class ExcecaoRecursoNaoEncontrado extends RuntimeException {
|
||||||
|
public ExcecaoRecursoNaoEncontrado(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package com.agendaestudantil.excecao;
|
||||||
|
|
||||||
|
import com.agendaestudantil.dto.RespostaApi;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.validation.FieldError;
|
||||||
|
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RestControllerAdvice
|
||||||
|
public class ManipuladorExcecaoGlobal {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(ManipuladorExcecaoGlobal.class);
|
||||||
|
|
||||||
|
@ExceptionHandler(ExcecaoRecursoNaoEncontrado.class)
|
||||||
|
public ResponseEntity<RespostaApi<Void>> handleResourceNotFound(ExcecaoRecursoNaoEncontrado ex) {
|
||||||
|
return ResponseEntity.status(HttpStatus.NOT_FOUND)
|
||||||
|
.body(new RespostaApi<>(null, ex.getMessage(), LocalDateTime.now()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(ExcecaoNegocio.class)
|
||||||
|
public ResponseEntity<RespostaApi<Void>> handleExcecaoNegocio(ExcecaoNegocio ex) {
|
||||||
|
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
|
||||||
|
.body(new RespostaApi<>(null, ex.getMessage(), LocalDateTime.now()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||||
|
public ResponseEntity<RespostaApi<Map<String, String>>> handleValidationException(
|
||||||
|
MethodArgumentNotValidException ex) {
|
||||||
|
Map<String, String> errors = new HashMap<>();
|
||||||
|
for (FieldError error : ex.getBindingResult().getFieldErrors()) {
|
||||||
|
errors.put(error.getField(), error.getDefaultMessage());
|
||||||
|
}
|
||||||
|
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
|
||||||
|
.body(new RespostaApi<>(errors, "Falha na validação", LocalDateTime.now()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(ResponseStatusException.class)
|
||||||
|
public ResponseEntity<RespostaApi<Void>> handleResponseStatusException(ResponseStatusException ex) {
|
||||||
|
return ResponseEntity.status(ex.getStatusCode())
|
||||||
|
.body(new RespostaApi<>(null, ex.getReason(), LocalDateTime.now()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(Exception.class)
|
||||||
|
public ResponseEntity<RespostaApi<Void>> handleGenericException(Exception ex) {
|
||||||
|
log.error("Erro interno no servidor", ex);
|
||||||
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
|
.body(new RespostaApi<>(null, "Erro interno no servidor", LocalDateTime.now()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package com.agendaestudantil.filtro;
|
||||||
|
|
||||||
|
import com.agendaestudantil.utilitario.UtilJwt;
|
||||||
|
import jakarta.servlet.FilterChain;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class FiltroJwt extends OncePerRequestFilter {
|
||||||
|
|
||||||
|
private final UtilJwt utilJwt;
|
||||||
|
private final UserDetailsService userDetailsService;
|
||||||
|
|
||||||
|
public FiltroJwt(UtilJwt utilJwt, UserDetailsService userDetailsService) {
|
||||||
|
this.utilJwt = utilJwt;
|
||||||
|
this.userDetailsService = userDetailsService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean shouldNotFilter(HttpServletRequest request) {
|
||||||
|
String path = request.getRequestURI();
|
||||||
|
return path.equals("/") || path.equals("/index.html") || path.equals("/favicon.ico")
|
||||||
|
|| path.startsWith("/static/") || path.startsWith("/css/") || path.startsWith("/js/")
|
||||||
|
|| path.startsWith("/img/") || path.endsWith(".css") || path.endsWith(".js")
|
||||||
|
|| path.endsWith(".ico") || path.endsWith(".html");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||||
|
throws ServletException, IOException {
|
||||||
|
|
||||||
|
String header = request.getHeader("Authorization");
|
||||||
|
String token = null;
|
||||||
|
String estudanteId = null;
|
||||||
|
|
||||||
|
if (header != null && header.startsWith("Bearer ")) {
|
||||||
|
token = header.substring(7);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token != null && utilJwt.validateToken(token)) {
|
||||||
|
estudanteId = utilJwt.getEstudanteIdFromToken(token);
|
||||||
|
|
||||||
|
if (SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||||
|
UserDetails userDetails = userDetailsService.loadUserByUsername(estudanteId);
|
||||||
|
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
|
||||||
|
userDetails, null, userDetails.getAuthorities());
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.agendaestudantil.repositorio;
|
||||||
|
|
||||||
|
import com.agendaestudantil.entidade.Disciplina;
|
||||||
|
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface DisciplinaRepositorio extends MongoRepository<Disciplina, String> {
|
||||||
|
List<Disciplina> findByEstudanteId(String estudanteId);
|
||||||
|
|
||||||
|
void deleteByEstudanteId(String estudanteId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.agendaestudantil.repositorio;
|
||||||
|
|
||||||
|
import com.agendaestudantil.entidade.Estudante;
|
||||||
|
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface EstudanteRepositorio extends MongoRepository<Estudante, String> {
|
||||||
|
Optional<Estudante> findByEmail(String email);
|
||||||
|
boolean existsByEmail(String email);
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.agendaestudantil.repositorio;
|
||||||
|
|
||||||
|
import com.agendaestudantil.entidade.Evento;
|
||||||
|
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||||
|
import org.springframework.data.mongodb.repository.Query;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface EventoRepositorio extends MongoRepository<Evento, String> {
|
||||||
|
List<Evento> findByEstudanteId(String estudanteId);
|
||||||
|
|
||||||
|
List<Evento> findByDisciplinaId(String disciplinaId);
|
||||||
|
|
||||||
|
@Query("{'estudanteId': ?0, 'dataHora': {$gte: ?1, $lte: ?2}}")
|
||||||
|
List<Evento> findByEstudanteIdAndDataHoraBetween(String estudanteId, LocalDateTime inicio, LocalDateTime fim);
|
||||||
|
|
||||||
|
@Query("{'estudanteId': ?0, 'dataHora': {$gte: ?1}}")
|
||||||
|
List<Evento> findProximosEventosByEstudanteId(String estudanteId, LocalDateTime data);
|
||||||
|
|
||||||
|
void deleteByEstudanteId(String estudanteId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.agendaestudantil.repositorio;
|
||||||
|
|
||||||
|
import com.agendaestudantil.entidade.Notificacao;
|
||||||
|
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface NotificacaoRepositorio extends MongoRepository<Notificacao, String> {
|
||||||
|
|
||||||
|
List<Notificacao> findByEstudanteIdAndLidaFalse(String estudanteId);
|
||||||
|
|
||||||
|
List<Notificacao> findByEstudanteId(String estudanteId);
|
||||||
|
|
||||||
|
long countByEstudanteIdAndLidaFalse(String estudanteId);
|
||||||
|
|
||||||
|
void deleteByEstudanteId(String estudanteId);
|
||||||
|
|
||||||
|
boolean existsByEstudanteIdAndReferenciaId(String estudanteId, String referenciaId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.agendaestudantil.repositorio;
|
||||||
|
|
||||||
|
import com.agendaestudantil.entidade.Tarefa;
|
||||||
|
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||||
|
import org.springframework.data.mongodb.repository.Query;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface TarefaRepositorio extends MongoRepository<Tarefa, String> {
|
||||||
|
List<Tarefa> findByEstudanteId(String estudanteId);
|
||||||
|
|
||||||
|
List<Tarefa> findByEstudanteIdAndStatus(String estudanteId, Tarefa.StatusTarefa status);
|
||||||
|
|
||||||
|
List<Tarefa> findByDisciplinaId(String disciplinaId);
|
||||||
|
|
||||||
|
@Query("{'estudanteId': ?0, 'dataEntrega': ?1}")
|
||||||
|
List<Tarefa> findByEstudanteIdAndDataEntrega(String estudanteId, LocalDate data);
|
||||||
|
|
||||||
|
@Query("{'estudanteId': ?0, 'dataEntrega': {$gte: ?1, $lte: ?2}}")
|
||||||
|
List<Tarefa> findByEstudanteIdAndDataEntregaBetween(String estudanteId, LocalDate inicio, LocalDate fim);
|
||||||
|
|
||||||
|
@Query("{'estudanteId': ?0, 'status': {$ne: ?1}}")
|
||||||
|
List<Tarefa> findTarefasPendentesByEstudanteId(String estudanteId, Tarefa.StatusTarefa status);
|
||||||
|
|
||||||
|
void deleteByEstudanteId(String estudanteId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package com.agendaestudantil.seguranca;
|
||||||
|
|
||||||
|
import com.agendaestudantil.entidade.Estudante;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
public class DetalhesUsuarioPersonalizado implements UserDetails {
|
||||||
|
|
||||||
|
private final Estudante estudante;
|
||||||
|
|
||||||
|
public DetalhesUsuarioPersonalizado(Estudante estudante) {
|
||||||
|
this.estudante = estudante;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||||
|
return Collections.singleton(new SimpleGrantedAuthority("ROLE_USER"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPassword() {
|
||||||
|
return estudante.getSenha();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUsername() {
|
||||||
|
return estudante.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAccountNonExpired() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAccountNonLocked() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCredentialsNonExpired() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.agendaestudantil.seguranca;
|
||||||
|
|
||||||
|
import com.agendaestudantil.entidade.Estudante;
|
||||||
|
import com.agendaestudantil.repositorio.EstudanteRepositorio;
|
||||||
|
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.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class ServicoAutenticacaoUsuario implements UserDetailsService {
|
||||||
|
|
||||||
|
private final EstudanteRepositorio estudanteRepositorio;
|
||||||
|
|
||||||
|
public ServicoAutenticacaoUsuario(EstudanteRepositorio estudanteRepositorio) {
|
||||||
|
this.estudanteRepositorio = estudanteRepositorio;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserDetails loadUserByUsername(String estudanteId) throws UsernameNotFoundException {
|
||||||
|
Estudante estudante = estudanteRepositorio.findById(estudanteId)
|
||||||
|
.orElseThrow(() -> new UsernameNotFoundException("Estudante não encontrado"));
|
||||||
|
|
||||||
|
return new DetalhesUsuarioPersonalizado(estudante);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
package com.agendaestudantil.servico;
|
||||||
|
|
||||||
|
import com.agendaestudantil.dto.RespostaDisciplinaDTO;
|
||||||
|
import com.agendaestudantil.entidade.Disciplina;
|
||||||
|
import com.agendaestudantil.excecao.ExcecaoRecursoNaoEncontrado;
|
||||||
|
import com.agendaestudantil.repositorio.DisciplinaRepositorio;
|
||||||
|
import com.agendaestudantil.repositorio.TarefaRepositorio;
|
||||||
|
import com.agendaestudantil.repositorio.EventoRepositorio;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class DisciplinaServico {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(DisciplinaServico.class);
|
||||||
|
|
||||||
|
private final DisciplinaRepositorio disciplinaRepositorio;
|
||||||
|
private final TarefaRepositorio tarefaRepositorio;
|
||||||
|
private final EventoRepositorio eventoRepositorio;
|
||||||
|
|
||||||
|
public DisciplinaServico(DisciplinaRepositorio disciplinaRepositorio, TarefaRepositorio tarefaRepositorio,
|
||||||
|
EventoRepositorio eventoRepositorio) {
|
||||||
|
this.disciplinaRepositorio = disciplinaRepositorio;
|
||||||
|
this.tarefaRepositorio = tarefaRepositorio;
|
||||||
|
this.eventoRepositorio = eventoRepositorio;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validarAcesso(String estudanteId) {
|
||||||
|
String authUser = SecurityContextHolder.getContext().getAuthentication().getName();
|
||||||
|
if (!authUser.equals(estudanteId)) {
|
||||||
|
log.error("Acesso negado. Usuário {} tentou acessar recurso do estudante {}", authUser, estudanteId);
|
||||||
|
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Acesso negado");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public RespostaDisciplinaDTO criarDisciplina(Disciplina disciplina, String estudanteId) {
|
||||||
|
log.debug("Criando disciplina para estudante: {}", estudanteId);
|
||||||
|
validarAcesso(estudanteId);
|
||||||
|
|
||||||
|
disciplina.setEstudanteId(estudanteId);
|
||||||
|
Disciplina salva = disciplinaRepositorio.save(disciplina);
|
||||||
|
return toDTO(salva);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<RespostaDisciplinaDTO> listarPorEstudante(String estudanteId) {
|
||||||
|
log.debug("Listando disciplinas para estudante: {}", estudanteId);
|
||||||
|
validarAcesso(estudanteId);
|
||||||
|
|
||||||
|
return disciplinaRepositorio.findByEstudanteId(estudanteId).stream()
|
||||||
|
.map(this::toDTO)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public RespostaDisciplinaDTO buscarPorId(String id, String estudanteId) {
|
||||||
|
Disciplina disciplina = getDisciplinaEntity(id);
|
||||||
|
validarAcesso(disciplina.getEstudanteId());
|
||||||
|
return toDTO(disciplina);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RespostaDisciplinaDTO atualizarDisciplina(String id, Disciplina disciplinaAtualizada, String estudanteId) {
|
||||||
|
Disciplina disciplina = getDisciplinaEntity(id);
|
||||||
|
validarAcesso(disciplina.getEstudanteId());
|
||||||
|
|
||||||
|
disciplina.setNome(disciplinaAtualizada.getNome());
|
||||||
|
disciplina.setProfessor(disciplinaAtualizada.getProfessor());
|
||||||
|
disciplina.setSala(disciplinaAtualizada.getSala());
|
||||||
|
disciplina.setCor(disciplinaAtualizada.getCor());
|
||||||
|
|
||||||
|
return toDTO(disciplinaRepositorio.save(disciplina));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void excluirDisciplina(String id, String estudanteId) {
|
||||||
|
Disciplina disciplina = getDisciplinaEntity(id);
|
||||||
|
validarAcesso(disciplina.getEstudanteId());
|
||||||
|
tarefaRepositorio.findByDisciplinaId(id).forEach(tarefa -> {
|
||||||
|
tarefa.setDisciplinaId(null);
|
||||||
|
tarefaRepositorio.save(tarefa);
|
||||||
|
});
|
||||||
|
eventoRepositorio.findByDisciplinaId(id).forEach(evento -> {
|
||||||
|
evento.setDisciplinaId(null);
|
||||||
|
eventoRepositorio.save(evento);
|
||||||
|
});
|
||||||
|
disciplinaRepositorio.delete(disciplina);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Disciplina getDisciplinaEntity(String id) {
|
||||||
|
return disciplinaRepositorio.findById(id)
|
||||||
|
.orElseThrow(() -> new ExcecaoRecursoNaoEncontrado("Disciplina não encontrada"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private RespostaDisciplinaDTO toDTO(Disciplina disciplina) {
|
||||||
|
return new RespostaDisciplinaDTO(
|
||||||
|
disciplina.getId(),
|
||||||
|
disciplina.getEstudanteId(),
|
||||||
|
disciplina.getNome(),
|
||||||
|
disciplina.getProfessor(),
|
||||||
|
disciplina.getSala(),
|
||||||
|
disciplina.getCor()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
package com.agendaestudantil.servico;
|
||||||
|
|
||||||
|
import com.agendaestudantil.dto.RequisicaoAtualizacaoEstudanteDTO;
|
||||||
|
import com.agendaestudantil.dto.RequisicaoCadastroDTO;
|
||||||
|
import com.agendaestudantil.dto.RespostaEstudanteDTO;
|
||||||
|
import com.agendaestudantil.dto.RequisicaoLoginDTO;
|
||||||
|
import com.agendaestudantil.dto.RespostaLoginDTO;
|
||||||
|
import com.agendaestudantil.dto.RequisicaoTrocaSenhaDTO;
|
||||||
|
import com.agendaestudantil.entidade.Estudante;
|
||||||
|
import com.agendaestudantil.excecao.ExcecaoNegocio;
|
||||||
|
import com.agendaestudantil.excecao.ExcecaoRecursoNaoEncontrado;
|
||||||
|
import com.agendaestudantil.repositorio.EstudanteRepositorio;
|
||||||
|
import com.agendaestudantil.repositorio.EventoRepositorio;
|
||||||
|
import com.agendaestudantil.repositorio.TarefaRepositorio;
|
||||||
|
import com.agendaestudantil.repositorio.DisciplinaRepositorio;
|
||||||
|
import com.agendaestudantil.utilitario.UtilJwt;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class EstudanteServico {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(EstudanteServico.class);
|
||||||
|
|
||||||
|
private final EstudanteRepositorio estudanteRepositorio;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
private final UtilJwt utilJwt;
|
||||||
|
private final TarefaRepositorio tarefaRepositorio;
|
||||||
|
private final EventoRepositorio eventoRepositorio;
|
||||||
|
private final DisciplinaRepositorio disciplinaRepositorio;
|
||||||
|
|
||||||
|
public EstudanteServico(EstudanteRepositorio estudanteRepositorio, PasswordEncoder passwordEncoder,
|
||||||
|
UtilJwt utilJwt, TarefaRepositorio tarefaRepositorio, EventoRepositorio eventoRepositorio,
|
||||||
|
DisciplinaRepositorio disciplinaRepositorio) {
|
||||||
|
this.estudanteRepositorio = estudanteRepositorio;
|
||||||
|
this.passwordEncoder = passwordEncoder;
|
||||||
|
this.utilJwt = utilJwt;
|
||||||
|
this.tarefaRepositorio = tarefaRepositorio;
|
||||||
|
this.eventoRepositorio = eventoRepositorio;
|
||||||
|
this.disciplinaRepositorio = disciplinaRepositorio;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RespostaEstudanteDTO cadastrar(RequisicaoCadastroDTO dto) {
|
||||||
|
log.debug("Acessando método cadastrar para email: {}", dto.email());
|
||||||
|
Optional<Estudante> existente = estudanteRepositorio.findByEmail(dto.email());
|
||||||
|
if (existente.isPresent()) {
|
||||||
|
log.error("Email já cadastrado: {}", dto.email());
|
||||||
|
throw new ExcecaoNegocio("Email já cadastrado");
|
||||||
|
}
|
||||||
|
|
||||||
|
Estudante estudante = new Estudante();
|
||||||
|
estudante.setNome(dto.nome());
|
||||||
|
estudante.setEmail(dto.email());
|
||||||
|
estudante.setSenha(passwordEncoder.encode(dto.senha()));
|
||||||
|
estudante.setCurso(dto.curso());
|
||||||
|
estudante.setPeriodo(dto.periodo());
|
||||||
|
|
||||||
|
Estudante salvo = estudanteRepositorio.save(estudante);
|
||||||
|
return toResponse(salvo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RespostaLoginDTO login(RequisicaoLoginDTO dto) {
|
||||||
|
log.debug("Acessando método login para email: {}", dto.email());
|
||||||
|
Optional<Estudante> estudanteParam = estudanteRepositorio.findByEmail(dto.email());
|
||||||
|
|
||||||
|
if (estudanteParam.isEmpty()) {
|
||||||
|
log.error("Email ou senha incorretos para email: {}", dto.email());
|
||||||
|
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Email ou senha incorretos");
|
||||||
|
}
|
||||||
|
|
||||||
|
Estudante estudante = estudanteParam.get();
|
||||||
|
|
||||||
|
if (!passwordEncoder.matches(dto.senha(), estudante.getSenha())) {
|
||||||
|
log.error("Falha ao validar senha para email: {}", dto.email());
|
||||||
|
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Email ou senha incorretos");
|
||||||
|
}
|
||||||
|
|
||||||
|
String token = utilJwt.generateToken(estudante.getId());
|
||||||
|
return new RespostaLoginDTO(token, toResponse(estudante));
|
||||||
|
}
|
||||||
|
|
||||||
|
public RespostaEstudanteDTO buscarPorId(String id) {
|
||||||
|
Estudante estudante = estudanteRepositorio.findById(id)
|
||||||
|
.orElseThrow(() -> new ExcecaoRecursoNaoEncontrado("Estudante não encontrado"));
|
||||||
|
return toResponse(estudante);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RespostaEstudanteDTO atualizar(String id, RequisicaoAtualizacaoEstudanteDTO dto) {
|
||||||
|
Estudante estudante = estudanteRepositorio.findById(id)
|
||||||
|
.orElseThrow(() -> new ExcecaoRecursoNaoEncontrado("Estudante não encontrado"));
|
||||||
|
|
||||||
|
estudante.setNome(dto.nome());
|
||||||
|
estudante.setCurso(dto.curso());
|
||||||
|
estudante.setPeriodo(dto.periodo());
|
||||||
|
|
||||||
|
Estudante atualizado = estudanteRepositorio.save(estudante);
|
||||||
|
return toResponse(atualizado);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void trocarSenha(String id, RequisicaoTrocaSenhaDTO dto) {
|
||||||
|
Estudante estudante = estudanteRepositorio.findById(id)
|
||||||
|
.orElseThrow(() -> new ExcecaoRecursoNaoEncontrado("Estudante não encontrado"));
|
||||||
|
|
||||||
|
if (!passwordEncoder.matches(dto.senhaAtual(), estudante.getSenha())) {
|
||||||
|
throw new ExcecaoNegocio("Senha atual incorreta");
|
||||||
|
}
|
||||||
|
|
||||||
|
estudante.setSenha(passwordEncoder.encode(dto.novaSenha()));
|
||||||
|
estudanteRepositorio.save(estudante);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void excluirConta(String id) {
|
||||||
|
if (!estudanteRepositorio.existsById(id)) {
|
||||||
|
throw new ExcecaoRecursoNaoEncontrado("Estudante não encontrado");
|
||||||
|
}
|
||||||
|
tarefaRepositorio.deleteByEstudanteId(id);
|
||||||
|
eventoRepositorio.deleteByEstudanteId(id);
|
||||||
|
disciplinaRepositorio.deleteByEstudanteId(id);
|
||||||
|
estudanteRepositorio.deleteById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RespostaEstudanteDTO toResponse(Estudante estudante) {
|
||||||
|
return new RespostaEstudanteDTO(
|
||||||
|
estudante.getId(),
|
||||||
|
estudante.getNome(),
|
||||||
|
estudante.getEmail(),
|
||||||
|
estudante.getCurso(),
|
||||||
|
estudante.getPeriodo());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
package com.agendaestudantil.servico;
|
||||||
|
|
||||||
|
import com.agendaestudantil.dto.RequisicaoEventoDTO;
|
||||||
|
import com.agendaestudantil.dto.RespostaEventoDTO;
|
||||||
|
import com.agendaestudantil.entidade.Disciplina;
|
||||||
|
import com.agendaestudantil.entidade.Evento;
|
||||||
|
import com.agendaestudantil.excecao.ExcecaoNegocio;
|
||||||
|
import com.agendaestudantil.excecao.ExcecaoRecursoNaoEncontrado;
|
||||||
|
import com.agendaestudantil.repositorio.DisciplinaRepositorio;
|
||||||
|
import com.agendaestudantil.repositorio.EventoRepositorio;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class EventoServico {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(EventoServico.class);
|
||||||
|
|
||||||
|
private final EventoRepositorio eventoRepositorio;
|
||||||
|
private final DisciplinaRepositorio disciplinaRepositorio;
|
||||||
|
|
||||||
|
public EventoServico(EventoRepositorio eventoRepositorio, DisciplinaRepositorio disciplinaRepositorio) {
|
||||||
|
this.eventoRepositorio = eventoRepositorio;
|
||||||
|
this.disciplinaRepositorio = disciplinaRepositorio;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validarAcesso(String estudanteId) {
|
||||||
|
String authUser = SecurityContextHolder.getContext().getAuthentication().getName();
|
||||||
|
if (!authUser.equals(estudanteId)) {
|
||||||
|
log.error("Acesso negado. Usuário {} tentou acessar recurso do estudante {}", authUser, estudanteId);
|
||||||
|
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Acesso negado");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validarDisciplina(String disciplinaId, String estudanteId) {
|
||||||
|
if (disciplinaId == null || disciplinaId.isBlank()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Disciplina disciplina = disciplinaRepositorio.findById(disciplinaId)
|
||||||
|
.orElseThrow(() -> new ExcecaoRecursoNaoEncontrado("Disciplina não encontrada"));
|
||||||
|
if (!disciplina.getEstudanteId().equals(estudanteId)) {
|
||||||
|
throw new ExcecaoNegocio("Disciplina não pertence ao estudante");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public RespostaEventoDTO criarEvento(Evento evento, String estudanteId) {
|
||||||
|
log.debug("Criando evento para estudante: {}", estudanteId);
|
||||||
|
validarAcesso(estudanteId);
|
||||||
|
|
||||||
|
if (evento.getDisciplinaId() != null) {
|
||||||
|
validarDisciplina(evento.getDisciplinaId(), estudanteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
evento.setEstudanteId(estudanteId);
|
||||||
|
Evento salvo = eventoRepositorio.save(evento);
|
||||||
|
return toDTO(salvo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<RespostaEventoDTO> listarPorEstudante(String estudanteId) {
|
||||||
|
log.debug("Listando eventos para estudante: {}", estudanteId);
|
||||||
|
validarAcesso(estudanteId);
|
||||||
|
|
||||||
|
return eventoRepositorio.findByEstudanteId(estudanteId).stream()
|
||||||
|
.map(this::toDTO)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<RespostaEventoDTO> listarPorPeriodo(String estudanteId, LocalDateTime inicio, LocalDateTime fim) {
|
||||||
|
validarAcesso(estudanteId);
|
||||||
|
return eventoRepositorio.findByEstudanteIdAndDataHoraBetween(estudanteId, inicio, fim).stream()
|
||||||
|
.map(this::toDTO)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<RespostaEventoDTO> listarProximosEventos(String estudanteId) {
|
||||||
|
validarAcesso(estudanteId);
|
||||||
|
return eventoRepositorio.findProximosEventosByEstudanteId(estudanteId, LocalDateTime.now()).stream()
|
||||||
|
.map(this::toDTO)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public RespostaEventoDTO buscarPorId(String id) {
|
||||||
|
Evento evento = getEventoEntity(id);
|
||||||
|
validarAcesso(evento.getEstudanteId());
|
||||||
|
return toDTO(evento);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RespostaEventoDTO atualizarEvento(String id, RequisicaoEventoDTO dto) {
|
||||||
|
Evento evento = getEventoEntity(id);
|
||||||
|
validarAcesso(evento.getEstudanteId());
|
||||||
|
|
||||||
|
evento.setTitulo(dto.titulo());
|
||||||
|
evento.setDescricao(dto.descricao());
|
||||||
|
evento.setTipo(dto.tipo());
|
||||||
|
evento.setLocal(dto.local());
|
||||||
|
evento.setDataHora(dto.dataHora());
|
||||||
|
|
||||||
|
if (dto.disciplinaId() != null) {
|
||||||
|
validarDisciplina(dto.disciplinaId(), evento.getEstudanteId());
|
||||||
|
evento.setDisciplinaId(dto.disciplinaId());
|
||||||
|
}
|
||||||
|
|
||||||
|
return toDTO(eventoRepositorio.save(evento));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void excluirEvento(String id) {
|
||||||
|
Evento evento = getEventoEntity(id);
|
||||||
|
validarAcesso(evento.getEstudanteId());
|
||||||
|
eventoRepositorio.delete(evento);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RespostaEventoDTO cancelarEvento(String id) {
|
||||||
|
Evento evento = getEventoEntity(id);
|
||||||
|
validarAcesso(evento.getEstudanteId());
|
||||||
|
evento.setStatus(Evento.StatusEvento.CANCELADO);
|
||||||
|
return toDTO(eventoRepositorio.save(evento));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Evento getEventoEntity(String id) {
|
||||||
|
return eventoRepositorio.findById(id)
|
||||||
|
.orElseThrow(() -> new ExcecaoRecursoNaoEncontrado("Evento não encontrado"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private RespostaEventoDTO toDTO(Evento evento) {
|
||||||
|
String nomeDisciplina = null;
|
||||||
|
final String disciplinaId = evento.getDisciplinaId();
|
||||||
|
if (disciplinaId != null) {
|
||||||
|
var optDisciplina = disciplinaRepositorio.findById(disciplinaId);
|
||||||
|
if (optDisciplina.isPresent()) {
|
||||||
|
nomeDisciplina = optDisciplina.get().getNome();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new RespostaEventoDTO(
|
||||||
|
evento.getId(),
|
||||||
|
evento.getEstudanteId(),
|
||||||
|
evento.getTitulo(),
|
||||||
|
evento.getDescricao(),
|
||||||
|
evento.getTipo() != null ? evento.getTipo().name() : null,
|
||||||
|
evento.getLocal(),
|
||||||
|
evento.getDisciplinaId(),
|
||||||
|
evento.getDataHora(),
|
||||||
|
evento.getStatus() != null ? evento.getStatus().name() : null,
|
||||||
|
nomeDisciplina
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
package com.agendaestudantil.servico;
|
||||||
|
|
||||||
|
import com.agendaestudantil.entidade.Evento;
|
||||||
|
import com.agendaestudantil.entidade.Notificacao;
|
||||||
|
import com.agendaestudantil.entidade.Tarefa;
|
||||||
|
import com.agendaestudantil.repositorio.EventoRepositorio;
|
||||||
|
import com.agendaestudantil.repositorio.NotificacaoRepositorio;
|
||||||
|
import com.agendaestudantil.repositorio.TarefaRepositorio;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class NotificacaoAgendadorServico {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(NotificacaoAgendadorServico.class);
|
||||||
|
|
||||||
|
private final TarefaRepositorio tarefaRepositorio;
|
||||||
|
private final EventoRepositorio eventoRepositorio;
|
||||||
|
private final NotificacaoRepositorio notificacaoRepositorio;
|
||||||
|
|
||||||
|
public NotificacaoAgendadorServico(TarefaRepositorio tarefaRepositorio, EventoRepositorio eventoRepositorio,
|
||||||
|
NotificacaoRepositorio notificacaoRepositorio) {
|
||||||
|
this.tarefaRepositorio = tarefaRepositorio;
|
||||||
|
this.eventoRepositorio = eventoRepositorio;
|
||||||
|
this.notificacaoRepositorio = notificacaoRepositorio;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Scheduled(fixedRate = 3600000)
|
||||||
|
public void gerarNotificacoes() {
|
||||||
|
log.debug("Iniciando geracao de notificacoes");
|
||||||
|
LocalDate hoje = LocalDate.now();
|
||||||
|
|
||||||
|
List<Tarefa> tarefas = tarefaRepositorio.findAll();
|
||||||
|
for (Tarefa tarefa : tarefas) {
|
||||||
|
if (tarefa.getStatus() == Tarefa.StatusTarefa.CONCLUIDA) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalDate dataEntrega = tarefa.getDataEntrega();
|
||||||
|
if (dataEntrega == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
long diasAteEntrega = ChronoUnit.DAYS.between(hoje, dataEntrega);
|
||||||
|
|
||||||
|
if (dataEntrega.isBefore(hoje)) {
|
||||||
|
if (!tarefa.getStatus().equals(Tarefa.StatusTarefa.ATRASADA)) {
|
||||||
|
tarefa.setStatus(Tarefa.StatusTarefa.ATRASADA);
|
||||||
|
tarefaRepositorio.save(tarefa);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!notificacaoRepositorio.existsByEstudanteIdAndReferenciaId(tarefa.getEstudanteId(), tarefa.getId())) {
|
||||||
|
criarNotificacao(tarefa.getEstudanteId(), tarefa.getId(),
|
||||||
|
"Tarefa atrasada: " + tarefa.getTitulo(),
|
||||||
|
"A tarefa '" + tarefa.getTitulo() + "' esta atrasada desde " + dataEntrega,
|
||||||
|
Notificacao.TipoNotificacao.TAREFA_ATRASADA,
|
||||||
|
Notificacao.TipoReferencia.TAREFA);
|
||||||
|
}
|
||||||
|
} else if (diasAteEntrega == 0 || diasAteEntrega == 1 || diasAteEntrega == 2 || diasAteEntrega == 3) {
|
||||||
|
boolean enviarNotificacao = false;
|
||||||
|
if (diasAteEntrega == 0 || diasAteEntrega == 1) {
|
||||||
|
enviarNotificacao = true;
|
||||||
|
} else if (diasAteEntrega == 2 || diasAteEntrega == 3) {
|
||||||
|
if (tarefa.getPrioridade() == Tarefa.Prioridade.ALTA || tarefa.getPrioridade() == Tarefa.Prioridade.URGENTE) {
|
||||||
|
enviarNotificacao = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enviarNotificacao && !notificacaoRepositorio.existsByEstudanteIdAndReferenciaId(tarefa.getEstudanteId(), tarefa.getId())) {
|
||||||
|
String prazo = diasAteEntrega == 0 ? "hoje" : (diasAteEntrega == 1 ? "amanha" : "em " + diasAteEntrega + " dias");
|
||||||
|
criarNotificacao(tarefa.getEstudanteId(), tarefa.getId(),
|
||||||
|
"Tarefa com prazo proximo: " + tarefa.getTitulo(),
|
||||||
|
"A tarefa '" + tarefa.getTitulo() + "' vence " + prazo,
|
||||||
|
Notificacao.TipoNotificacao.PRAZO_PROXIMO,
|
||||||
|
Notificacao.TipoReferencia.TAREFA);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalDateTime agora = LocalDateTime.now();
|
||||||
|
LocalDateTime em24Horas = agora.plusHours(24);
|
||||||
|
List<Evento> eventos = eventoRepositorio.findAll();
|
||||||
|
for (Evento evento : eventos) {
|
||||||
|
if (evento.getDataHora() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!evento.getDataHora().isBefore(em24Horas) && !evento.getDataHora().isAfter(em24Horas)) {
|
||||||
|
if (!notificacaoRepositorio.existsByEstudanteIdAndReferenciaId(evento.getEstudanteId(), evento.getId())) {
|
||||||
|
criarNotificacao(evento.getEstudanteId(), evento.getId(),
|
||||||
|
"Evento nas proximas 24h: " + evento.getTitulo(),
|
||||||
|
"O evento '" + evento.getTitulo() + "' sera em " + evento.getDataHora(),
|
||||||
|
Notificacao.TipoNotificacao.EVENTO_PROXIMO,
|
||||||
|
Notificacao.TipoReferencia.EVENTO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("Geracao de notificacoes concluida");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void criarNotificacao(String estudanteId, String referenciaId, String titulo, String mensagem,
|
||||||
|
Notificacao.TipoNotificacao tipo, Notificacao.TipoReferencia tipoReferencia) {
|
||||||
|
Notificacao notif = new Notificacao();
|
||||||
|
notif.setEstudanteId(estudanteId);
|
||||||
|
notif.setReferenciaId(referenciaId);
|
||||||
|
notif.setTitulo(titulo);
|
||||||
|
notif.setMensagem(mensagem);
|
||||||
|
notif.setTipo(tipo);
|
||||||
|
notif.setTipoReferencia(tipoReferencia);
|
||||||
|
notif.setLida(false);
|
||||||
|
notif.setDataGeracao(LocalDateTime.now());
|
||||||
|
notificacaoRepositorio.save(notif);
|
||||||
|
log.debug("Notificacao criada para estudante {}", estudanteId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package com.agendaestudantil.servico;
|
||||||
|
|
||||||
|
import com.agendaestudantil.dto.RespostaNotificacaoDTO;
|
||||||
|
import com.agendaestudantil.entidade.Notificacao;
|
||||||
|
import com.agendaestudantil.excecao.ExcecaoRecursoNaoEncontrado;
|
||||||
|
import com.agendaestudantil.repositorio.NotificacaoRepositorio;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class NotificacaoServico {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(NotificacaoServico.class);
|
||||||
|
|
||||||
|
private final NotificacaoRepositorio notificacaoRepositorio;
|
||||||
|
|
||||||
|
public NotificacaoServico(NotificacaoRepositorio notificacaoRepositorio) {
|
||||||
|
this.notificacaoRepositorio = notificacaoRepositorio;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validarAcesso(String estudanteId) {
|
||||||
|
String authUser = SecurityContextHolder.getContext().getAuthentication().getName();
|
||||||
|
if (!authUser.equals(estudanteId)) {
|
||||||
|
log.error("Acesso negado. Usuário {} tentou acessar recurso do estudante {}", authUser, estudanteId);
|
||||||
|
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Acesso negado");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<RespostaNotificacaoDTO> listarNaoLidas(String estudanteId) {
|
||||||
|
log.debug("Listando notificações não lidas para estudante: {}", estudanteId);
|
||||||
|
validarAcesso(estudanteId);
|
||||||
|
return notificacaoRepositorio.findByEstudanteIdAndLidaFalse(estudanteId).stream()
|
||||||
|
.map(this::toDTO)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<RespostaNotificacaoDTO> listarTodas(String estudanteId) {
|
||||||
|
log.debug("Listando todas as notificações para estudante: {}", estudanteId);
|
||||||
|
validarAcesso(estudanteId);
|
||||||
|
return notificacaoRepositorio.findByEstudanteId(estudanteId).stream()
|
||||||
|
.map(this::toDTO)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long contarNaoLidas(String estudanteId) {
|
||||||
|
log.debug("Contando notificações não lidas para estudante: {}", estudanteId);
|
||||||
|
validarAcesso(estudanteId);
|
||||||
|
return notificacaoRepositorio.countByEstudanteIdAndLidaFalse(estudanteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RespostaNotificacaoDTO marcarComoLida(String id) {
|
||||||
|
Notificacao notificacao = getNotificacaoEntity(id);
|
||||||
|
validarAcesso(notificacao.getEstudanteId());
|
||||||
|
notificacao.setLida(true);
|
||||||
|
return toDTO(notificacaoRepositorio.save(notificacao));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void marcarTodasComoLidas(String estudanteId) {
|
||||||
|
log.debug("Marcando todas as notificações como lidas para estudante: {}", estudanteId);
|
||||||
|
validarAcesso(estudanteId);
|
||||||
|
List<Notificacao> notificacoes = notificacaoRepositorio.findByEstudanteIdAndLidaFalse(estudanteId);
|
||||||
|
for (Notificacao n : notificacoes) {
|
||||||
|
n.setLida(true);
|
||||||
|
notificacaoRepositorio.save(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void excluirNotificacao(String id) {
|
||||||
|
Notificacao notificacao = getNotificacaoEntity(id);
|
||||||
|
validarAcesso(notificacao.getEstudanteId());
|
||||||
|
notificacaoRepositorio.delete(notificacao);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Notificacao getNotificacaoEntity(String id) {
|
||||||
|
return notificacaoRepositorio.findById(id)
|
||||||
|
.orElseThrow(() -> new ExcecaoRecursoNaoEncontrado("Notificação não encontrada"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private RespostaNotificacaoDTO toDTO(Notificacao notificacao) {
|
||||||
|
return new RespostaNotificacaoDTO(
|
||||||
|
notificacao.getId(),
|
||||||
|
notificacao.getTitulo(),
|
||||||
|
notificacao.getMensagem(),
|
||||||
|
notificacao.getTipo() != null ? notificacao.getTipo().name() : null,
|
||||||
|
notificacao.getReferenciaId(),
|
||||||
|
notificacao.getTipoReferencia() != null ? notificacao.getTipoReferencia().name() : null,
|
||||||
|
notificacao.isLida(),
|
||||||
|
notificacao.getDataGeracao()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
package com.agendaestudantil.servico;
|
||||||
|
|
||||||
|
import com.agendaestudantil.dto.RequisicaoTarefaDTO;
|
||||||
|
import com.agendaestudantil.dto.RespostaTarefaDTO;
|
||||||
|
import com.agendaestudantil.entidade.Tarefa;
|
||||||
|
import com.agendaestudantil.excecao.ExcecaoRecursoNaoEncontrado;
|
||||||
|
import com.agendaestudantil.repositorio.TarefaRepositorio;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class TarefaServico {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(TarefaServico.class);
|
||||||
|
|
||||||
|
private final TarefaRepositorio tarefaRepositorio;
|
||||||
|
|
||||||
|
public TarefaServico(TarefaRepositorio tarefaRepositorio) {
|
||||||
|
this.tarefaRepositorio = tarefaRepositorio;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validarAcesso(String estudanteId) {
|
||||||
|
String authUser = SecurityContextHolder.getContext().getAuthentication().getName();
|
||||||
|
if (!authUser.equals(estudanteId)) {
|
||||||
|
log.error("Acesso negado. Usuário {} tentou acessar recurso do estudante {}", authUser, estudanteId);
|
||||||
|
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Acesso negado");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validarAcessoTarefa(Tarefa tarefa) {
|
||||||
|
validarAcesso(tarefa.getEstudanteId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public RespostaTarefaDTO criarTarefa(RequisicaoTarefaDTO dto) {
|
||||||
|
String estudanteId = SecurityContextHolder.getContext().getAuthentication().getName();
|
||||||
|
log.debug("Criando tarefa para estudante: {}", estudanteId);
|
||||||
|
|
||||||
|
Tarefa tarefa = new Tarefa();
|
||||||
|
tarefa.setTitulo(dto.titulo());
|
||||||
|
tarefa.setDescricao(dto.descricao());
|
||||||
|
tarefa.setPrioridade(dto.prioridade() != null ? dto.prioridade() : Tarefa.Prioridade.MEDIA);
|
||||||
|
tarefa.setStatus(dto.status() != null ? dto.status() : Tarefa.StatusTarefa.PENDENTE);
|
||||||
|
tarefa.setDataEntrega(dto.dataEntrega());
|
||||||
|
tarefa.setEstudanteId(estudanteId);
|
||||||
|
|
||||||
|
if (dto.disciplinaId() != null) {
|
||||||
|
tarefa.setDisciplinaId(dto.disciplinaId());
|
||||||
|
}
|
||||||
|
|
||||||
|
return toDTO(tarefaRepositorio.save(tarefa));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<RespostaTarefaDTO> listarTarefasPorEstudante(String estudanteId) {
|
||||||
|
log.debug("Listando tarefas para estudante: {}", estudanteId);
|
||||||
|
validarAcesso(estudanteId);
|
||||||
|
return tarefaRepositorio.findByEstudanteId(estudanteId).stream()
|
||||||
|
.map(this::toDTO)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<RespostaTarefaDTO> listarTarefasPendentes(String estudanteId) {
|
||||||
|
log.debug("Listando tarefas pendentes para estudante: {}", estudanteId);
|
||||||
|
validarAcesso(estudanteId);
|
||||||
|
return tarefaRepositorio.findByEstudanteIdAndStatus(estudanteId, Tarefa.StatusTarefa.PENDENTE).stream()
|
||||||
|
.map(this::toDTO)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<RespostaTarefaDTO> listarTarefasPorData(String estudanteId, LocalDate data) {
|
||||||
|
validarAcesso(estudanteId);
|
||||||
|
return tarefaRepositorio.findByEstudanteIdAndDataEntrega(estudanteId, data).stream()
|
||||||
|
.map(this::toDTO)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<RespostaTarefaDTO> listarTarefasPorPeriodo(String estudanteId, LocalDate inicio, LocalDate fim) {
|
||||||
|
validarAcesso(estudanteId);
|
||||||
|
return tarefaRepositorio.findByEstudanteIdAndDataEntregaBetween(estudanteId, inicio, fim).stream()
|
||||||
|
.map(this::toDTO)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public RespostaTarefaDTO buscarTarefaPorId(String id) {
|
||||||
|
return toDTO(getTarefaEntity(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public RespostaTarefaDTO atualizarTarefa(String id, RequisicaoTarefaDTO dto) {
|
||||||
|
Tarefa tarefa = getTarefaEntity(id);
|
||||||
|
String authUser = SecurityContextHolder.getContext().getAuthentication().getName();
|
||||||
|
if (!authUser.equals(tarefa.getEstudanteId())) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Acesso negado");
|
||||||
|
}
|
||||||
|
|
||||||
|
tarefa.setTitulo(dto.titulo());
|
||||||
|
tarefa.setDescricao(dto.descricao());
|
||||||
|
tarefa.setPrioridade(dto.prioridade() != null ? dto.prioridade() : tarefa.getPrioridade());
|
||||||
|
tarefa.setStatus(dto.status() != null ? dto.status() : tarefa.getStatus());
|
||||||
|
if (dto.dataEntrega() != null) {
|
||||||
|
tarefa.setDataEntrega(dto.dataEntrega());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dto.disciplinaId() != null) {
|
||||||
|
tarefa.setDisciplinaId(dto.disciplinaId());
|
||||||
|
}
|
||||||
|
|
||||||
|
return toDTO(tarefaRepositorio.save(tarefa));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void excluirTarefa(String id) {
|
||||||
|
tarefaRepositorio.delete(getTarefaEntity(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public RespostaTarefaDTO marcarConcluida(String id) {
|
||||||
|
Tarefa tarefa = getTarefaEntity(id);
|
||||||
|
tarefa.setStatus(Tarefa.StatusTarefa.CONCLUIDA);
|
||||||
|
return toDTO(tarefaRepositorio.save(tarefa));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Tarefa getTarefaEntity(String id) {
|
||||||
|
Tarefa tarefa = tarefaRepositorio.findById(id)
|
||||||
|
.orElseThrow(() -> new ExcecaoRecursoNaoEncontrado("Tarefa não encontrada"));
|
||||||
|
validarAcessoTarefa(tarefa);
|
||||||
|
return tarefa;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RespostaTarefaDTO toDTO(Tarefa tarefa) {
|
||||||
|
return new RespostaTarefaDTO(
|
||||||
|
tarefa.getId(),
|
||||||
|
tarefa.getTitulo(),
|
||||||
|
tarefa.getDescricao(),
|
||||||
|
tarefa.getPrioridade() != null ? tarefa.getPrioridade().name() : null,
|
||||||
|
tarefa.getStatus() != null ? tarefa.getStatus().name() : null,
|
||||||
|
tarefa.getDataEntrega(),
|
||||||
|
tarefa.getDisciplinaId(),
|
||||||
|
tarefa.getEstudanteId());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package com.agendaestudantil.utilitario;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.Claims;
|
||||||
|
import io.jsonwebtoken.Jwts;
|
||||||
|
import io.jsonwebtoken.security.Keys;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class UtilJwt {
|
||||||
|
|
||||||
|
private final String secret;
|
||||||
|
private final long jwtExpiration;
|
||||||
|
|
||||||
|
public UtilJwt(@Value("${jwt.secret}") String secret, @Value("${jwt.expiration}") long jwtExpiration) {
|
||||||
|
this.secret = secret;
|
||||||
|
this.jwtExpiration = jwtExpiration;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SecretKey getSigningKey() {
|
||||||
|
return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String generateToken(String estudanteId) {
|
||||||
|
Date now = new Date();
|
||||||
|
Date expiryDate = new Date(now.getTime() + jwtExpiration);
|
||||||
|
|
||||||
|
return Jwts.builder()
|
||||||
|
.subject(estudanteId)
|
||||||
|
.issuedAt(now)
|
||||||
|
.expiration(expiryDate)
|
||||||
|
.signWith(getSigningKey())
|
||||||
|
.compact();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEstudanteIdFromToken(String token) {
|
||||||
|
Claims claims = Jwts.parser()
|
||||||
|
.verifyWith(getSigningKey())
|
||||||
|
.build()
|
||||||
|
.parseSignedClaims(token)
|
||||||
|
.getPayload();
|
||||||
|
|
||||||
|
return claims.getSubject();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean validateToken(String token) {
|
||||||
|
try {
|
||||||
|
Jwts.parser().verifyWith(getSigningKey()).build().parseSignedClaims(token);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
spring.data.mongodb.uri=${MONGO_URI:mongodb://localhost:27017/agenda_estudantil}
|
||||||
|
cors.allowed.origins=${CORS_ORIGINS:http://localhost:8080,http://localhost:3000}
|
||||||
|
jwt.secret=${JWT_SECRET:8a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t1u2v3w4x5y6z7A8B9C0D1E2F3}
|
||||||
|
jwt.expiration=${JWT_EXPIRATION:86400000}
|
||||||
|
logging.level.org.springframework.data.mongodb=DEBUG
|
||||||
|
springdoc.api-docs.enabled=true
|
||||||
|
springdoc.swagger-ui.enabled=true
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
spring.data.mongodb.uri=${MONGO_URI}
|
||||||
|
cors.allowed.origins=${CORS_ORIGINS}
|
||||||
|
jwt.secret=${JWT_SECRET}
|
||||||
|
springdoc.api-docs.enabled=false
|
||||||
|
springdoc.swagger-ui.enabled=false
|
||||||
|
logging.level.root=WARN
|
||||||
|
logging.level.root=WARN
|
||||||
|
logging.level.com.agendaestudantil=INFO
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
spring.application.name=${APP_NAME:Focus Agenda}
|
||||||
|
server.port=${SERVER_PORT:8080}
|
||||||
|
server.servlet.context-path=/
|
||||||
|
spring.web.resources.static-locations=classpath:/static/
|
||||||
|
spring.mvc.static-path-pattern=/**
|
||||||
|
spring.mvc.contentnegotiation.favor-parameter=true
|
||||||
|
spring.web.resources.add-mappings=true
|
||||||
|
spring.servlet.multipart.enabled=false
|
||||||
|
|
||||||
|
spring.web.resources.cache.cachecontrol.max-age=3600
|
||||||
|
spring.web.resources.cache.cachecontrol.cache-public=true
|
||||||
|
|
||||||
|
spring.profiles.active=${SPRING_PROFILES_ACTIVE:dev}
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
|
||||||
|
body {
|
||||||
|
min-height: 100vh;
|
||||||
|
font-family: 'Poppins', Arial, sans-serif;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 80px 20px 20px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
#topo {
|
||||||
|
width: 100%;
|
||||||
|
height: 50px;
|
||||||
|
position: fixed;
|
||||||
|
top: 0; left: 0;
|
||||||
|
background: linear-gradient(to right, #c0392b 47%, #7a4951 73%, #114455 87%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
#textotop {
|
||||||
|
padding-left: 20px;
|
||||||
|
font-size: clamp(22px, 5vw, 38px);
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
#log {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mens { text-align: center; color: #1f2937; }
|
||||||
|
.campo { display: flex; flex-direction: column; gap: 8px; }
|
||||||
|
label { font-weight: 700; color: #1f2937; }
|
||||||
|
|
||||||
|
input, select {
|
||||||
|
height: 46px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
border: 1px solid #c7c7c7;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-family: inherit;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
form { display: flex; flex-direction: column; gap: 16px; }
|
||||||
|
|
||||||
|
.linha-dupla { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
||||||
|
|
||||||
|
#logbtn {
|
||||||
|
align-self: center;
|
||||||
|
width: 50%;
|
||||||
|
padding: 12px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: #c0392b;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#logbtn:hover { background-color: #a03224; }
|
||||||
|
#logbtn:disabled { background-color: #ccc; cursor: not-allowed; }
|
||||||
|
|
||||||
|
a { color: #111; text-decoration: none; }
|
||||||
|
a:hover { text-decoration: underline; }
|
||||||
|
|
||||||
|
#mensagem-erro {
|
||||||
|
background: #fee2e2;
|
||||||
|
border: 1px solid #fca5a5;
|
||||||
|
color: #b91c1c;
|
||||||
|
padding: 10px 14px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mensagem-sucesso {
|
||||||
|
background: #d1fae5;
|
||||||
|
border: 1px solid #6ee7b7;
|
||||||
|
color: #065f46;
|
||||||
|
padding: 10px 14px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Botão tema */
|
||||||
|
.theme-toggle-btn {
|
||||||
|
position: absolute;
|
||||||
|
right: 20px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
background: rgba(255,255,255,0.15);
|
||||||
|
border: 1px solid rgba(255,255,255,0.3);
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: background 0.2s, transform 0.2s;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
.theme-toggle-btn:hover { background: rgba(255,255,255,0.3); transform: translateY(-50%) scale(1.1); }
|
||||||
|
.theme-icon { width: 100%; height: 100%; object-fit: contain; }
|
||||||
|
|
||||||
|
/* ===== TEMA ESCURO ===== */
|
||||||
|
[data-theme="dark"] body {
|
||||||
|
background: #121212;
|
||||||
|
color: #e8e8e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .mens { color: #e8e8e8; }
|
||||||
|
[data-theme="dark"] label { color: #e8e8e8; }
|
||||||
|
|
||||||
|
[data-theme="dark"] input,
|
||||||
|
[data-theme="dark"] select {
|
||||||
|
background: #1a1a1a;
|
||||||
|
border-color: #333;
|
||||||
|
color: #e8e8e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] #mensagem-erro {
|
||||||
|
background: rgba(254,226,226,0.1);
|
||||||
|
border-color: rgba(252,165,165,0.2);
|
||||||
|
color: #fca5a5;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] #mensagem-sucesso {
|
||||||
|
background: rgba(209,250,229,0.1);
|
||||||
|
border-color: rgba(110,231,183,0.2);
|
||||||
|
color: #6ee7b7;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] a { color: #e8e8e8; }
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="pt-br">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="icon" href="imagens/icone.png">
|
||||||
|
<link rel="stylesheet" href="cadastro.css">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
<title>Cadastro – Focus Agenda</title>
|
||||||
|
<script src="theme.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="topo">
|
||||||
|
<h1 id="textotop">Focus Agenda</h1>
|
||||||
|
<button class="theme-toggle-btn" onclick="toggleTheme()" title="Tema Escuro"><img src="imagens/moon-svgrepo-com.svg" class="theme-icon" alt="Tema"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="log">
|
||||||
|
<h1 class="mens">Crie Sua Conta</h1>
|
||||||
|
|
||||||
|
<div id="mensagem-erro" role="alert"></div>
|
||||||
|
<div id="mensagem-sucesso" role="status"></div>
|
||||||
|
|
||||||
|
<form id="cadastroForm" novalidate>
|
||||||
|
<div class="campo">
|
||||||
|
<label for="emailid">Email</label>
|
||||||
|
<input type="email" placeholder="Digite seu email" id="emailid" autocomplete="email" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="campo">
|
||||||
|
<label for="nomeid">Nome</label>
|
||||||
|
<input type="text" placeholder="Seu nome completo" id="nomeid" autocomplete="name" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="campo">
|
||||||
|
<label for="cursoid">Curso</label>
|
||||||
|
<input type="text" placeholder="Ex: Técnico em Informática" id="cursoid" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="linha-dupla">
|
||||||
|
<div class="campo">
|
||||||
|
<label for="periodoid">Período</label>
|
||||||
|
<select id="periodoid" required>
|
||||||
|
<option value="">Selecione</option>
|
||||||
|
<option value="1">1º</option>
|
||||||
|
<option value="2">2º</option>
|
||||||
|
<option value="3">3º</option>
|
||||||
|
<option value="4">4º</option>
|
||||||
|
<option value="5">5º</option>
|
||||||
|
<option value="6">6º</option>
|
||||||
|
<option value="7">7º</option>
|
||||||
|
<option value="8">8º</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="campo">
|
||||||
|
<label for="senhaid">Senha</label>
|
||||||
|
<input type="password" placeholder="Mínimo 6 caracteres" id="senhaid" autocomplete="new-password" required minlength="6">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="campo">
|
||||||
|
<label for="csenhaid">Confirmar Senha</label>
|
||||||
|
<input type="password" placeholder="Confirme sua senha" id="csenhaid" autocomplete="new-password" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" id="logbtn">Cadastrar</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<p class="mens"><a href="login.html">Já tem uma conta?</a></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const form = document.getElementById('cadastroForm');
|
||||||
|
const erroEl = document.getElementById('mensagem-erro');
|
||||||
|
const sucessoEl = document.getElementById('mensagem-sucesso');
|
||||||
|
const btn = document.getElementById('logbtn');
|
||||||
|
|
||||||
|
function mostrarErro(msg) {
|
||||||
|
erroEl.textContent = msg;
|
||||||
|
erroEl.style.display = 'block';
|
||||||
|
sucessoEl.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
function mostrarSucesso(msg) {
|
||||||
|
sucessoEl.textContent = msg;
|
||||||
|
sucessoEl.style.display = 'block';
|
||||||
|
erroEl.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
form.addEventListener('submit', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
erroEl.style.display = 'none';
|
||||||
|
sucessoEl.style.display = 'none';
|
||||||
|
|
||||||
|
const nome = document.getElementById('nomeid').value.trim();
|
||||||
|
const email = document.getElementById('emailid').value.trim();
|
||||||
|
const curso = document.getElementById('cursoid').value.trim();
|
||||||
|
const periodo = parseInt(document.getElementById('periodoid').value, 10);
|
||||||
|
const senha = document.getElementById('senhaid').value;
|
||||||
|
const csenha = document.getElementById('csenhaid').value;
|
||||||
|
|
||||||
|
if (!nome || !email || !curso || !periodo || !senha) {
|
||||||
|
mostrarErro('Preencha todos os campos.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (senha.length < 6) {
|
||||||
|
mostrarErro('A senha deve ter pelo menos 6 caracteres.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (senha !== csenha) {
|
||||||
|
mostrarErro('As senhas não conferem.');
|
||||||
|
document.getElementById('csenhaid').focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.textContent = 'Cadastrando...';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/estudantes/cadastro', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ nome, email, senha, curso, periodo })
|
||||||
|
});
|
||||||
|
|
||||||
|
const json = await res.json();
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
// Erros de validação vêm como objeto
|
||||||
|
if (typeof json.data === 'object' && json.data !== null) {
|
||||||
|
const msgs = Object.values(json.data).join('; ');
|
||||||
|
mostrarErro(msgs);
|
||||||
|
} else {
|
||||||
|
mostrarErro(json.message || 'Erro ao cadastrar.');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mostrarSucesso('Conta criada! Redirecionando para o login...');
|
||||||
|
setTimeout(() => window.location.href = 'login.html', 2000);
|
||||||
|
} catch (err) {
|
||||||
|
mostrarErro('Erro de conexão. Verifique se o servidor está rodando.');
|
||||||
|
} finally {
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.textContent = 'Cadastrar';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,446 @@
|
|||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Poppins', sans-serif;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== HEADER ===== */
|
||||||
|
#header {
|
||||||
|
width: 100%;
|
||||||
|
height: 50px;
|
||||||
|
position: fixed;
|
||||||
|
top: 0; left: 0;
|
||||||
|
background: linear-gradient(to right, #c0392b, #114455);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
#title { color: #fff; padding-left: 20px; font-size: 28px; }
|
||||||
|
|
||||||
|
/* ===== BARRA ESQUERDA ===== */
|
||||||
|
#barraesquerda {
|
||||||
|
position: fixed;
|
||||||
|
top: 50px; left: 0;
|
||||||
|
width: 280px;
|
||||||
|
height: calc(100vh - 50px);
|
||||||
|
background: #c0392b;
|
||||||
|
padding: 15px;
|
||||||
|
color: #fff;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
overflow-y: auto;
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mini Calendário */
|
||||||
|
#calendario { margin-top: 10px; }
|
||||||
|
.calendariotop { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
|
||||||
|
#mes { font-size: 16px; font-weight: 600; }
|
||||||
|
#calendarseta { display: flex; gap: 10px; }
|
||||||
|
#calendarseta button { background: transparent; border: none; color: #fff; font-size: 24px; cursor: pointer; padding: 2px 8px; transition: transform 0.2s; }
|
||||||
|
#calendarseta button:hover { transform: scale(1.2); }
|
||||||
|
|
||||||
|
.calendariodia { width: 100%; border-collapse: collapse; }
|
||||||
|
.calendariodia th { font-size: 10px; opacity: 0.8; padding: 3px; text-align: center; }
|
||||||
|
.calendariodia td { text-align: center; padding: 4px; font-size: 11px; border-radius: 50%; cursor: pointer; transition: background 0.15s; }
|
||||||
|
.calendariodia td:hover { background: rgba(255,255,255,0.2); }
|
||||||
|
.calendariodia td.outromes { opacity: 0.4; }
|
||||||
|
.calendariodia td.today { background: #fff; color: #c0392b; font-weight: 700; border-radius: 50%; }
|
||||||
|
.calendariodia td.selecionado { background: rgba(255,255,255,0.35); border-radius: 50%; }
|
||||||
|
|
||||||
|
/* Agenda */
|
||||||
|
#agenda { margin-top: 18px; }
|
||||||
|
.agenda-header { font-size: 12px; font-weight: 700; opacity: 0.8; margin-bottom: 8px; letter-spacing: 0.5px; }
|
||||||
|
.agenda-empty { font-size: 12px; opacity: 0.7; font-style: italic; }
|
||||||
|
.evento { background: rgba(255,255,255,0.15); border-radius: 8px; padding: 8px 10px; margin-bottom: 8px; cursor: pointer; transition: background 0.15s; }
|
||||||
|
.evento:hover { background: rgba(255,255,255,0.25); }
|
||||||
|
.evento .hora { font-size: 11px; opacity: 0.8; }
|
||||||
|
.evento .titulo { font-size: 13px; font-weight: 600; }
|
||||||
|
|
||||||
|
/* Feriados */
|
||||||
|
#feriados { margin-top: 16px; }
|
||||||
|
.feriados-header { font-size: 12px; font-weight: 700; opacity: 0.8; margin-bottom: 8px; letter-spacing: 0.5px; }
|
||||||
|
.feriado { display: flex; align-items: center; gap: 8px; font-size: 12px; margin-bottom: 4px; }
|
||||||
|
.dot { width: 8px; height: 8px; background: #fff; border-radius: 50%; flex-shrink: 0; }
|
||||||
|
|
||||||
|
/* ===== MAIN ===== */
|
||||||
|
.main {
|
||||||
|
margin-left: 280px;
|
||||||
|
margin-top: 50px;
|
||||||
|
padding: 24px;
|
||||||
|
min-height: calc(100vh - 50px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Topbar */
|
||||||
|
.topbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
||||||
|
.topbar h1 { font-size: 24px; color: #1f2937; }
|
||||||
|
|
||||||
|
.user-area { display: flex; align-items: center; gap: 14px; }
|
||||||
|
.icone-img { width: 28px; height: 28px; cursor: pointer; opacity: 0.7; transition: opacity 0.2s; }
|
||||||
|
.icone-img:hover { opacity: 1; }
|
||||||
|
|
||||||
|
.perfil { display: flex; align-items: center; gap: 10px; }
|
||||||
|
.avatar { width: 38px; height: 38px; border-radius: 50%; background: linear-gradient(135deg, #c0392b, #114455); }
|
||||||
|
.info { display: flex; flex-direction: column; }
|
||||||
|
.nome { font-size: 14px; font-weight: 600; color: #1f2937; }
|
||||||
|
.cargo { font-size: 12px; color: #6b7280; }
|
||||||
|
|
||||||
|
/* Botão logout */
|
||||||
|
#btnLogout {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #6b7280;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
#btnLogout:hover { background: #fee2e2; border-color: #c0392b; color: #c0392b; }
|
||||||
|
|
||||||
|
/* Botão configurações */
|
||||||
|
#btnConfig {
|
||||||
|
text-decoration: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
opacity: 0.6;
|
||||||
|
transition: opacity 0.2s, transform 0.2s;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
#btnConfig:hover { opacity: 1; transform: scale(1.15); }
|
||||||
|
.config-icon { width: 22px; height: 22px; object-fit: contain; }
|
||||||
|
|
||||||
|
/* Botão tema */
|
||||||
|
.theme-toggle-btn {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 6px;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.2s;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
.theme-toggle-btn:hover { background: #f3f4f6; transform: scale(1.1); }
|
||||||
|
.theme-icon { width: 100%; height: 100%; object-fit: contain; }
|
||||||
|
|
||||||
|
/* Calendar header */
|
||||||
|
.calendar-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; flex-wrap: wrap; gap: 10px; }
|
||||||
|
|
||||||
|
.mes-nav { display: flex; align-items: center; gap: 12px; }
|
||||||
|
.seta { background: transparent; border: 1px solid #d1d5db; border-radius: 6px; width: 32px; height: 32px; font-size: 20px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: background 0.15s; }
|
||||||
|
.seta:hover { background: #f3f4f6; }
|
||||||
|
.titulo-mes { font-size: 18px; font-weight: 600; color: #1f2937; min-width: 200px; text-align: center; }
|
||||||
|
|
||||||
|
.view-switch { display: flex; gap: 4px; background: #f3f4f6; padding: 4px; border-radius: 8px; }
|
||||||
|
.view-switch button { border: none; background: transparent; padding: 6px 14px; border-radius: 6px; font-size: 14px; cursor: pointer; color: #6b7280; transition: all 0.2s; font-family: inherit; }
|
||||||
|
.view-switch button.active { background: #fff; color: #1f2937; font-weight: 600; box-shadow: 0 1px 3px rgba(0,0,0,0.12); }
|
||||||
|
|
||||||
|
/* Botão novo evento */
|
||||||
|
#btnNovoEvento {
|
||||||
|
background: #c0392b;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 18px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
#btnNovoEvento:hover { background: #a03224; }
|
||||||
|
|
||||||
|
/* Calendar area */
|
||||||
|
.calendar-area { background: #fff; border-radius: 12px; box-shadow: 0 1px 4px rgba(0,0,0,0.08); overflow: hidden; }
|
||||||
|
|
||||||
|
/* Month view */
|
||||||
|
.month-view { display: grid; grid-template-columns: repeat(7, 1fr); }
|
||||||
|
.dia-semana { text-align: center; padding: 10px; font-size: 12px; font-weight: 600; color: #6b7280; background: #f9fafb; border-bottom: 1px solid #e5e7eb; }
|
||||||
|
.dia-box { min-height: 100px; padding: 8px; border-right: 1px solid #f0f0f0; border-bottom: 1px solid #f0f0f0; cursor: pointer; transition: background 0.1s; }
|
||||||
|
.dia-box:hover { background: #fef2f2; }
|
||||||
|
.dia-box:nth-child(7n) { border-right: none; }
|
||||||
|
.dia-box.outro-mes { background: #fafafa; color: #d1d5db; }
|
||||||
|
.dia-box.outro-mes .num-dia { color: #d1d5db; }
|
||||||
|
.num-dia { font-size: 13px; font-weight: 500; color: #374151; margin-bottom: 4px; }
|
||||||
|
.dia-box.today .num-dia { background: #c0392b; color: #fff; width: 24px; height: 24px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 12px; }
|
||||||
|
.dia-box.selecionado { background: #fef2f2; }
|
||||||
|
.evento-mini { font-size: 10px; background: #c0392b; color: #fff; border-radius: 3px; padding: 1px 4px; margin-top: 2px; truncate: clip; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||||
|
.evento-mini.verde { background: #16a34a; }
|
||||||
|
.evento-mini.azul { background: #1d4ed8; }
|
||||||
|
.evento-mini.amarelo { background: #d97706; }
|
||||||
|
.mais-eventos { font-size: 10px; color: #6b7280; margin-top: 2px; cursor: pointer; }
|
||||||
|
|
||||||
|
/* Week view */
|
||||||
|
.week-view { display: grid; grid-template-columns: repeat(7, 1fr); }
|
||||||
|
.week-col { border-right: 1px solid #e5e7eb; padding: 10px 8px; min-height: 300px; }
|
||||||
|
.week-col:last-child { border-right: none; }
|
||||||
|
.week-col.today { background: #fef9f9; }
|
||||||
|
.week-col-head { font-size: 13px; font-weight: 600; color: #6b7280; margin-bottom: 6px; }
|
||||||
|
.week-col-date { display: block; font-size: 11px; font-weight: 400; color: #9ca3af; }
|
||||||
|
.week-events { display: flex; flex-direction: column; gap: 6px; }
|
||||||
|
.week-empty { font-size: 11px; color: #d1d5db; font-style: italic; }
|
||||||
|
|
||||||
|
/* Day view */
|
||||||
|
.day-panel { padding: 20px; }
|
||||||
|
.day-panel-header { font-size: 16px; font-weight: 600; color: #374151; margin-bottom: 16px; }
|
||||||
|
.day-events { display: flex; flex-direction: column; gap: 10px; }
|
||||||
|
.day-empty { color: #9ca3af; font-style: italic; font-size: 14px; }
|
||||||
|
|
||||||
|
/* Event cards */
|
||||||
|
.calendar-event { border-radius: 6px; padding: 8px 10px; background: #fee2e2; border-left: 4px solid #c0392b; }
|
||||||
|
.calendar-event.verde { background: #d1fae5; border-color: #16a34a; }
|
||||||
|
.calendar-event.azul { background: #dbeafe; border-color: #1d4ed8; }
|
||||||
|
.calendar-event.amarelo { background: #fef3c7; border-color: #d97706; }
|
||||||
|
.calendar-event.rosa { background: #fce7f3; border-color: #db2777; }
|
||||||
|
.calendar-event-hora { font-size: 11px; color: #6b7280; }
|
||||||
|
.calendar-event-titulo { font-size: 13px; font-weight: 600; color: #1f2937; }
|
||||||
|
|
||||||
|
/* ===== LOADING SPINNER ===== */
|
||||||
|
.loading { display: flex; align-items: center; justify-content: center; padding: 40px; color: #9ca3af; gap: 10px; font-size: 14px; }
|
||||||
|
.spinner { width: 20px; height: 20px; border: 2px solid #e5e7eb; border-top-color: #c0392b; border-radius: 50%; animation: spin 0.7s linear infinite; }
|
||||||
|
@keyframes spin { to { transform: rotate(360deg); } }
|
||||||
|
|
||||||
|
/* ===== MODAL ===== */
|
||||||
|
.modal-overlay {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0,0,0,0.45);
|
||||||
|
z-index: 100;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.modal-overlay.aberto { display: flex; }
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 28px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 480px;
|
||||||
|
margin: 20px;
|
||||||
|
box-shadow: 0 20px 60px rgba(0,0,0,0.2);
|
||||||
|
max-height: 90vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-titulo { font-size: 18px; font-weight: 700; color: #1f2937; margin-bottom: 20px; }
|
||||||
|
|
||||||
|
.modal-campo { display: flex; flex-direction: column; gap: 6px; margin-bottom: 16px; }
|
||||||
|
.modal-campo label { font-size: 13px; font-weight: 600; color: #374151; }
|
||||||
|
.modal-campo input,
|
||||||
|
.modal-campo select,
|
||||||
|
.modal-campo textarea {
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: inherit;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
.modal-campo input:focus,
|
||||||
|
.modal-campo select:focus,
|
||||||
|
.modal-campo textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #c0392b;
|
||||||
|
}
|
||||||
|
.modal-campo textarea { resize: vertical; min-height: 70px; }
|
||||||
|
|
||||||
|
.modal-linha { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
||||||
|
|
||||||
|
.modal-acoes { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; }
|
||||||
|
|
||||||
|
.btn-secundario {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 9px 18px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #6b7280;
|
||||||
|
font-family: inherit;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
.btn-secundario:hover { background: #f3f4f6; }
|
||||||
|
|
||||||
|
.btn-primario {
|
||||||
|
background: #c0392b;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 9px 18px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
.btn-primario:hover { background: #a03224; }
|
||||||
|
.btn-primario:disabled { background: #ccc; cursor: not-allowed; }
|
||||||
|
|
||||||
|
.btn-perigo {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid #fca5a5;
|
||||||
|
color: #c0392b;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 9px 18px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
transition: all 0.2s;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
.btn-perigo:hover { background: #fee2e2; }
|
||||||
|
|
||||||
|
/* Toast de feedback */
|
||||||
|
#toast {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 24px;
|
||||||
|
right: 24px;
|
||||||
|
background: #1f2937;
|
||||||
|
color: #fff;
|
||||||
|
padding: 12px 18px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
z-index: 200;
|
||||||
|
transform: translateY(80px);
|
||||||
|
opacity: 0;
|
||||||
|
transition: all 0.3s;
|
||||||
|
max-width: 320px;
|
||||||
|
}
|
||||||
|
#toast.visivel { transform: translateY(0); opacity: 1; }
|
||||||
|
#toast.sucesso { background: #065f46; }
|
||||||
|
#toast.erro { background: #b91c1c; }
|
||||||
|
|
||||||
|
/* Responsivo */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
#barraesquerda { display: none; }
|
||||||
|
.main { margin-left: 0; }
|
||||||
|
.month-view { font-size: 12px; }
|
||||||
|
.dia-box { min-height: 60px; padding: 4px; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== TEMA ESCURO ===== */
|
||||||
|
[data-theme="dark"] {
|
||||||
|
--bg-primary: #121212;
|
||||||
|
--bg-secondary: #1e1e1e;
|
||||||
|
--bg-card: #2a2a2a;
|
||||||
|
--bg-input: #1a1a1a;
|
||||||
|
--text-primary: #e8e8e8;
|
||||||
|
--text-secondary: #a0a0a0;
|
||||||
|
--text-muted: #666;
|
||||||
|
--border-color: #333;
|
||||||
|
--border-light: #282828;
|
||||||
|
--shadow: 0 1px 4px rgba(0,0,0,0.5);
|
||||||
|
--hover-bg: rgba(255,255,255,0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] body {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .topbar h1 { color: var(--text-primary); }
|
||||||
|
[data-theme="dark"] .nome { color: var(--text-primary); }
|
||||||
|
[data-theme="dark"] .cargo { color: var(--text-secondary); }
|
||||||
|
|
||||||
|
[data-theme="dark"] #btnLogout {
|
||||||
|
border-color: var(--border-color);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
[data-theme="dark"] #btnLogout:hover { background: rgba(192,57,43,0.2); border-color: #c0392b; color: #e74c3c; }
|
||||||
|
|
||||||
|
[data-theme="dark"] .calendar-header .seta {
|
||||||
|
border-color: var(--border-color);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
[data-theme="dark"] .calendar-header .seta:hover { background: var(--hover-bg); }
|
||||||
|
[data-theme="dark"] .titulo-mes { color: var(--text-primary); }
|
||||||
|
|
||||||
|
[data-theme="dark"] .view-switch { background: var(--bg-secondary); }
|
||||||
|
[data-theme="dark"] .view-switch button { color: var(--text-secondary); }
|
||||||
|
[data-theme="dark"] .view-switch button.active {
|
||||||
|
background: var(--bg-card);
|
||||||
|
color: var(--text-primary);
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .calendar-area {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .dia-semana {
|
||||||
|
background: var(--bg-card);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
border-bottom-color: var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .dia-box {
|
||||||
|
border-right-color: var(--border-light);
|
||||||
|
border-bottom-color: var(--border-light);
|
||||||
|
}
|
||||||
|
[data-theme="dark"] .dia-box:hover { background: rgba(192,57,43,0.1); }
|
||||||
|
[data-theme="dark"] .dia-box.outro-mes { background: var(--bg-primary); color: #444; }
|
||||||
|
[data-theme="dark"] .dia-box.outro-mes .num-dia { color: #444; }
|
||||||
|
[data-theme="dark"] .num-dia { color: var(--text-primary); }
|
||||||
|
[data-theme="dark"] .dia-box.selecionado { background: rgba(192,57,43,0.15); }
|
||||||
|
|
||||||
|
[data-theme="dark"] .week-col { border-right-color: var(--border-color); }
|
||||||
|
[data-theme="dark"] .week-col.today { background: rgba(192,57,43,0.08); }
|
||||||
|
[data-theme="dark"] .week-col-head { color: var(--text-secondary); }
|
||||||
|
[data-theme="dark"] .week-col-date { color: var(--text-muted); }
|
||||||
|
[data-theme="dark"] .week-empty { color: #444; }
|
||||||
|
|
||||||
|
[data-theme="dark"] .day-panel-header { color: var(--text-primary); }
|
||||||
|
[data-theme="dark"] .day-empty { color: var(--text-muted); }
|
||||||
|
|
||||||
|
[data-theme="dark"] .calendar-event { background: rgba(192,57,43,0.2); }
|
||||||
|
[data-theme="dark"] .calendar-event.verde { background: rgba(22,163,74,0.2); }
|
||||||
|
[data-theme="dark"] .calendar-event.azul { background: rgba(29,78,216,0.2); }
|
||||||
|
[data-theme="dark"] .calendar-event.amarelo { background: rgba(217,119,6,0.2); }
|
||||||
|
[data-theme="dark"] .calendar-event.rosa { background: rgba(219,39,119,0.2); }
|
||||||
|
[data-theme="dark"] .calendar-event-hora { color: var(--text-secondary); }
|
||||||
|
[data-theme="dark"] .calendar-event-titulo { color: var(--text-primary); }
|
||||||
|
|
||||||
|
[data-theme="dark"] .loading { color: var(--text-muted); }
|
||||||
|
[data-theme="dark"] .spinner { border-color: var(--border-color); border-top-color: #c0392b; }
|
||||||
|
|
||||||
|
[data-theme="dark"] .modal {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
box-shadow: 0 20px 60px rgba(0,0,0,0.5);
|
||||||
|
}
|
||||||
|
[data-theme="dark"] .modal-titulo { color: var(--text-primary); }
|
||||||
|
[data-theme="dark"] .modal-campo label { color: var(--text-primary); }
|
||||||
|
[data-theme="dark"] .modal-campo input,
|
||||||
|
[data-theme="dark"] .modal-campo select,
|
||||||
|
[data-theme="dark"] .modal-campo textarea {
|
||||||
|
background: var(--bg-input);
|
||||||
|
border-color: var(--border-color);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .btn-secundario {
|
||||||
|
border-color: var(--border-color);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
[data-theme="dark"] .btn-secundario:hover { background: var(--hover-bg); }
|
||||||
|
|
||||||
|
[data-theme="dark"] .btn-perigo {
|
||||||
|
border-color: rgba(252,165,165,0.3);
|
||||||
|
color: #e74c3c;
|
||||||
|
}
|
||||||
|
[data-theme="dark"] .btn-perigo:hover { background: rgba(192,57,43,0.2); }
|
||||||
|
|
||||||
|
[data-theme="dark"] #toast { background: #2a2a2a; }
|
||||||
|
|
||||||
|
[data-theme="dark"] .mais-eventos { color: var(--text-secondary); }
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,266 @@
|
|||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
|
||||||
|
body {
|
||||||
|
min-height: 100vh;
|
||||||
|
font-family: 'Poppins', 'Trebuchet MS', Arial, sans-serif;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 80px 20px 20px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
#topo {
|
||||||
|
width: 100%;
|
||||||
|
height: 50px;
|
||||||
|
position: fixed;
|
||||||
|
top: 0; left: 0;
|
||||||
|
background: linear-gradient(to right, #c0392b 47%, #7a4951 73%, #114455 87%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
#textotop {
|
||||||
|
padding-left: 20px;
|
||||||
|
font-size: clamp(22px, 5vw, 38px);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#voltar {
|
||||||
|
position: absolute;
|
||||||
|
right: 20px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
background: rgba(255,255,255,0.15);
|
||||||
|
border: 1px solid rgba(255,255,255,0.3);
|
||||||
|
color: #fff;
|
||||||
|
padding: 6px 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#voltar:hover { background: rgba(255,255,255,0.3); }
|
||||||
|
|
||||||
|
.theme-toggle-btn {
|
||||||
|
position: absolute;
|
||||||
|
right: 140px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
background: rgba(255,255,255,0.15);
|
||||||
|
border: 1px solid rgba(255,255,255,0.3);
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: background 0.2s, transform 0.2s;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-toggle-btn:hover { background: rgba(255,255,255,0.3); transform: translateY(-50%) scale(1.1); }
|
||||||
|
.theme-icon { width: 100%; height: 100%; object-fit: contain; }
|
||||||
|
|
||||||
|
#log {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 440px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.titulo-pagina { text-align: center; color: #1f2937; }
|
||||||
|
.subtitulo { text-align: center; color: #6b7280; font-weight: 400; font-size: 15px; }
|
||||||
|
|
||||||
|
.campo { display: flex; flex-direction: column; gap: 8px; }
|
||||||
|
label { font-weight: 700; color: #1f2937; }
|
||||||
|
|
||||||
|
input, select {
|
||||||
|
height: 46px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
border: 1px solid #c7c7c7;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-family: inherit;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:disabled {
|
||||||
|
background: #f3f4f6;
|
||||||
|
color: #9ca3af;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
form { display: flex; flex-direction: column; gap: 16px; }
|
||||||
|
|
||||||
|
.linha-dupla { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
||||||
|
|
||||||
|
#logbtn {
|
||||||
|
align-self: center;
|
||||||
|
width: 50%;
|
||||||
|
padding: 12px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: #c0392b;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#logbtn:hover { background-color: #a03224; }
|
||||||
|
#logbtn:disabled { background-color: #ccc; cursor: not-allowed; }
|
||||||
|
|
||||||
|
.secao {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secao-titulo {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1f2937;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 2px solid #f3f4f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
#btnSenha {
|
||||||
|
align-self: center;
|
||||||
|
width: 60%;
|
||||||
|
padding: 12px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: #114455;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#btnSenha:hover { background-color: #0d3644; }
|
||||||
|
#btnSenha:disabled { background-color: #ccc; cursor: not-allowed; }
|
||||||
|
|
||||||
|
.zona-perigo {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #fca5a5;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zona-perigo .secao-titulo {
|
||||||
|
color: #b91c1c;
|
||||||
|
border-bottom-color: #fee2e2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zona-perigo p {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #6b7280;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
#btnExcluirConta {
|
||||||
|
align-self: center;
|
||||||
|
width: 60%;
|
||||||
|
padding: 12px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: transparent;
|
||||||
|
color: #b91c1c;
|
||||||
|
border: 2px solid #b91c1c;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#btnExcluirConta:hover { background-color: #fee2e2; }
|
||||||
|
#btnExcluirConta:disabled { background-color: #ccc; border-color: #ccc; color: #fff; cursor: not-allowed; }
|
||||||
|
|
||||||
|
#mensagem-erro {
|
||||||
|
background: #fee2e2;
|
||||||
|
border: 1px solid #fca5a5;
|
||||||
|
color: #b91c1c;
|
||||||
|
padding: 10px 14px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mensagem-sucesso {
|
||||||
|
background: #d1fae5;
|
||||||
|
border: 1px solid #6ee7b7;
|
||||||
|
color: #065f46;
|
||||||
|
padding: 10px 14px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== TEMA ESCURO ===== */
|
||||||
|
[data-theme="dark"] body {
|
||||||
|
background: #121212;
|
||||||
|
color: #e8e8e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .titulo-pagina { color: #e8e8e8; }
|
||||||
|
[data-theme="dark"] .subtitulo { color: #a0a0a0; }
|
||||||
|
|
||||||
|
[data-theme="dark"] .secao {
|
||||||
|
background: #1e1e1e;
|
||||||
|
box-shadow: 0 1px 4px rgba(0,0,0,0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .secao-titulo {
|
||||||
|
color: #e8e8e8;
|
||||||
|
border-bottom-color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] label { color: #e8e8e8; }
|
||||||
|
|
||||||
|
[data-theme="dark"] input,
|
||||||
|
[data-theme="dark"] select {
|
||||||
|
background: #1a1a1a;
|
||||||
|
border-color: #333;
|
||||||
|
color: #e8e8e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] input:disabled {
|
||||||
|
background: #161616;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .zona-perigo {
|
||||||
|
background: #1e1e1e;
|
||||||
|
border-color: rgba(252,165,165,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .zona-perigo p { color: #a0a0a0; }
|
||||||
|
|
||||||
|
[data-theme="dark"] #mensagem-erro {
|
||||||
|
background: rgba(254,226,226,0.1);
|
||||||
|
border-color: rgba(252,165,165,0.2);
|
||||||
|
color: #fca5a5;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] #mensagem-sucesso {
|
||||||
|
background: rgba(209,250,229,0.1);
|
||||||
|
border-color: rgba(110,231,183,0.2);
|
||||||
|
color: #6ee7b7;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] a { color: #e8e8e8; }
|
||||||
@@ -0,0 +1,281 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="pt-br">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="icon" href="imagens/icone.png">
|
||||||
|
<link rel="stylesheet" href="configuracoes.css">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
<title>Configurações – Focus Agenda</title>
|
||||||
|
<script src="theme.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="topo">
|
||||||
|
<h1 id="textotop">Focus Agenda</h1>
|
||||||
|
<button class="theme-toggle-btn" onclick="toggleTheme()" title="Tema Escuro"><img src="imagens/moon-svgrepo-com.svg" class="theme-icon" alt="Tema"></button>
|
||||||
|
<button id="voltar" onclick="window.location.href='calendario.html'">Voltar</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="log">
|
||||||
|
<h1 class="titulo-pagina">Configurações</h1>
|
||||||
|
<p class="subtitulo">Gerencie seus dados e preferências</p>
|
||||||
|
|
||||||
|
<div id="mensagem-erro" role="alert"></div>
|
||||||
|
<div id="mensagem-sucesso" role="status"></div>
|
||||||
|
|
||||||
|
<!-- DADOS PESSOAIS -->
|
||||||
|
<div class="secao">
|
||||||
|
<div class="secao-titulo">Dados Pessoais</div>
|
||||||
|
<form id="dadosForm" novalidate>
|
||||||
|
<div class="campo">
|
||||||
|
<label for="nomeid">Nome</label>
|
||||||
|
<input type="text" placeholder="Seu nome completo" id="nomeid" autocomplete="name" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="campo">
|
||||||
|
<label for="emailid">Email</label>
|
||||||
|
<input type="email" placeholder="Seu email" id="emailid" autocomplete="email" required disabled>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="campo">
|
||||||
|
<label for="cursoid">Curso</label>
|
||||||
|
<input type="text" placeholder="Ex: Técnico em Informática" id="cursoid" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="linha-dupla">
|
||||||
|
<div class="campo">
|
||||||
|
<label for="periodoid">Período</label>
|
||||||
|
<select id="periodoid" required>
|
||||||
|
<option value="">Selecione</option>
|
||||||
|
<option value="1">1º</option>
|
||||||
|
<option value="2">2º</option>
|
||||||
|
<option value="3">3º</option>
|
||||||
|
<option value="4">4º</option>
|
||||||
|
<option value="5">5º</option>
|
||||||
|
<option value="6">6º</option>
|
||||||
|
<option value="7">7º</option>
|
||||||
|
<option value="8">8º</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" id="logbtn">Salvar Alterações</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ALTERAR SENHA -->
|
||||||
|
<div class="secao">
|
||||||
|
<div class="secao-titulo">Alterar Senha</div>
|
||||||
|
<form id="senhaForm" novalidate>
|
||||||
|
<div class="campo">
|
||||||
|
<label for="senhaAtualid">Senha Atual</label>
|
||||||
|
<input type="password" placeholder="Digite sua senha atual" id="senhaAtualid" autocomplete="current-password" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="campo">
|
||||||
|
<label for="novaSenhaid">Nova Senha</label>
|
||||||
|
<input type="password" placeholder="Mínimo 6 caracteres" id="novaSenhaid" autocomplete="new-password" required minlength="6">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="campo">
|
||||||
|
<label for="confirmaNovaid">Confirmar Nova Senha</label>
|
||||||
|
<input type="password" placeholder="Confirme a nova senha" id="confirmaNovaid" autocomplete="new-password" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" id="btnSenha">Alterar Senha</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ZONA DE PERIGO -->
|
||||||
|
<div class="zona-perigo">
|
||||||
|
<div class="secao-titulo">Zona de Perigo</div>
|
||||||
|
<p>Após excluir sua conta, todos os seus dados, tarefas, eventos e disciplinas serão removidos permanentemente. Esta ação não pode ser desfeita.</p>
|
||||||
|
<button type="button" id="btnExcluirConta">Excluir Minha Conta</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const token = localStorage.getItem('fa_token');
|
||||||
|
const userStr = localStorage.getItem('fa_user');
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
window.location.href = 'login.html';
|
||||||
|
}
|
||||||
|
|
||||||
|
let usuario = null;
|
||||||
|
try { usuario = JSON.parse(userStr); } catch(e) {}
|
||||||
|
|
||||||
|
const erroEl = document.getElementById('mensagem-erro');
|
||||||
|
const sucessoEl = document.getElementById('mensagem-sucesso');
|
||||||
|
|
||||||
|
function mostrarErro(msg) {
|
||||||
|
erroEl.textContent = msg;
|
||||||
|
erroEl.style.display = 'block';
|
||||||
|
sucessoEl.style.display = 'none';
|
||||||
|
erroEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||||
|
}
|
||||||
|
|
||||||
|
function mostrarSucesso(msg) {
|
||||||
|
sucessoEl.textContent = msg;
|
||||||
|
sucessoEl.style.display = 'block';
|
||||||
|
erroEl.style.display = 'none';
|
||||||
|
sucessoEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function api(method, path, body) {
|
||||||
|
const opts = {
|
||||||
|
method,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': 'Bearer ' + token
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (body) opts.body = JSON.stringify(body);
|
||||||
|
|
||||||
|
const res = await fetch(path, opts);
|
||||||
|
const json = await res.json().catch(() => ({}));
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(json.message || 'Erro na requisição');
|
||||||
|
}
|
||||||
|
return json.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// CARREGAR DADOS DO USUÁRIO
|
||||||
|
// =============================================
|
||||||
|
async function carregarUsuario() {
|
||||||
|
try {
|
||||||
|
const u = await api('GET', '/api/estudantes/me');
|
||||||
|
usuario = u;
|
||||||
|
localStorage.setItem('fa_user', JSON.stringify(u));
|
||||||
|
|
||||||
|
document.getElementById('nomeid').value = u.nome || '';
|
||||||
|
document.getElementById('emailid').value = u.email || '';
|
||||||
|
document.getElementById('cursoid').value = u.curso || '';
|
||||||
|
document.getElementById('periodoid').value = u.periodo || '';
|
||||||
|
} catch(e) {
|
||||||
|
mostrarErro('Erro ao carregar dados: ' + e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// SALVAR DADOS PESSOAIS
|
||||||
|
// =============================================
|
||||||
|
document.getElementById('dadosForm').addEventListener('submit', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
erroEl.style.display = 'none';
|
||||||
|
sucessoEl.style.display = 'none';
|
||||||
|
|
||||||
|
const nome = document.getElementById('nomeid').value.trim();
|
||||||
|
const curso = document.getElementById('cursoid').value.trim();
|
||||||
|
const periodo = parseInt(document.getElementById('periodoid').value, 10);
|
||||||
|
|
||||||
|
if (!nome || !curso || !periodo) {
|
||||||
|
mostrarErro('Preencha todos os campos.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const btn = document.getElementById('logbtn');
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.textContent = 'Salvando...';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const atualizado = await api('PUT', '/api/estudantes/me', { nome, curso, periodo });
|
||||||
|
localStorage.setItem('fa_user', JSON.stringify(atualizado));
|
||||||
|
mostrarSucesso('Dados atualizados com sucesso!');
|
||||||
|
} catch(err) {
|
||||||
|
mostrarErro(err.message);
|
||||||
|
} finally {
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.textContent = 'Salvar Alterações';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// ALTERAR SENHA
|
||||||
|
// =============================================
|
||||||
|
document.getElementById('senhaForm').addEventListener('submit', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
erroEl.style.display = 'none';
|
||||||
|
sucessoEl.style.display = 'none';
|
||||||
|
|
||||||
|
const senhaAtual = document.getElementById('senhaAtualid').value;
|
||||||
|
const novaSenha = document.getElementById('novaSenhaid').value;
|
||||||
|
const confirmaNova = document.getElementById('confirmaNovaid').value;
|
||||||
|
|
||||||
|
if (!senhaAtual || !novaSenha || !confirmaNova) {
|
||||||
|
mostrarErro('Preencha todos os campos.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (novaSenha.length < 6) {
|
||||||
|
mostrarErro('A nova senha deve ter pelo menos 6 caracteres.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (novaSenha !== confirmaNova) {
|
||||||
|
mostrarErro('As senhas não conferem.');
|
||||||
|
document.getElementById('confirmaNovaid').focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (senhaAtual === novaSenha) {
|
||||||
|
mostrarErro('A nova senha deve ser diferente da atual.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const btn = document.getElementById('btnSenha');
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.textContent = 'Alterando...';
|
||||||
|
|
||||||
|
try {
|
||||||
|
await api('PUT', '/api/estudantes/senha', { senhaAtual, novaSenha });
|
||||||
|
mostrarSucesso('Senha alterada com sucesso!');
|
||||||
|
document.getElementById('senhaAtualid').value = '';
|
||||||
|
document.getElementById('novaSenhaid').value = '';
|
||||||
|
document.getElementById('confirmaNovaid').value = '';
|
||||||
|
} catch(err) {
|
||||||
|
mostrarErro(err.message);
|
||||||
|
} finally {
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.textContent = 'Alterar Senha';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// EXCLUIR CONTA
|
||||||
|
// =============================================
|
||||||
|
document.getElementById('btnExcluirConta').addEventListener('click', async () => {
|
||||||
|
if (!confirm('Tem certeza que deseja excluir sua conta? Esta ação é irreversível!')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmacao = prompt('Digite "EXCLUIR" para confirmar a exclusão da sua conta:');
|
||||||
|
if (confirmacao !== 'EXCLUIR') {
|
||||||
|
mostrarErro('Exclusão cancelada.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const btn = document.getElementById('btnExcluirConta');
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.textContent = 'Excluindo...';
|
||||||
|
|
||||||
|
try {
|
||||||
|
await api('DELETE', '/api/estudantes/me');
|
||||||
|
localStorage.removeItem('fa_token');
|
||||||
|
localStorage.removeItem('fa_user');
|
||||||
|
mostrarSucesso('Conta excluída. Redirecionando...');
|
||||||
|
setTimeout(() => window.location.href = 'login.html', 2000);
|
||||||
|
} catch(err) {
|
||||||
|
mostrarErro(err.message);
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.textContent = 'Excluir Minha Conta';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Inicializa
|
||||||
|
carregarUsuario();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 7.0 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M3.32031 11.6835C3.32031 16.6541 7.34975 20.6835 12.3203 20.6835C16.1075 20.6835 19.3483 18.3443 20.6768 15.032C19.6402 15.4486 18.5059 15.6834 17.3203 15.6834C12.3497 15.6834 8.32031 11.654 8.32031 6.68342C8.32031 5.50338 8.55165 4.36259 8.96453 3.32996C5.65605 4.66028 3.32031 7.89912 3.32031 11.6835Z" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 624 B |
Binary file not shown.
|
After Width: | Height: | Size: 8.6 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 3V4M12 20V21M4 12H3M6.31412 6.31412L5.5 5.5M17.6859 6.31412L18.5 5.5M6.31412 17.69L5.5 18.5001M17.6859 17.69L18.5 18.5001M21 12H20M16 12C16 14.2091 14.2091 16 12 16C9.79086 16 8 14.2091 8 12C8 9.79086 9.79086 8 12 8C14.2091 8 16 9.79086 16 12Z" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 568 B |
@@ -0,0 +1,11 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="pt-br">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="refresh" content="0;url=login.html">
|
||||||
|
<title>Focus Agenda</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script>window.location.href='login.html';</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
|
||||||
|
body {
|
||||||
|
min-height: 100vh;
|
||||||
|
font-family: 'Poppins', 'Trebuchet MS', Arial, sans-serif;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 80px 20px 20px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
#topo {
|
||||||
|
width: 100%;
|
||||||
|
height: 50px;
|
||||||
|
position: fixed;
|
||||||
|
top: 0; left: 0;
|
||||||
|
background: linear-gradient(to right, #c0392b 47%, #7a4951 73%, #114455 87%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
#textotop {
|
||||||
|
padding-left: 20px;
|
||||||
|
font-size: clamp(22px, 5vw, 38px);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#log {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 360px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mens { text-align: center; color: #1f2937; }
|
||||||
|
|
||||||
|
.campo { display: flex; flex-direction: column; gap: 8px; }
|
||||||
|
label { font-weight: 700; color: #1f2937; }
|
||||||
|
|
||||||
|
input[type="email"], input[type="password"] {
|
||||||
|
height: 46px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
border: 1px solid #c7c7c7;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
form { display: flex; flex-direction: column; gap: 16px; }
|
||||||
|
|
||||||
|
#logbtn {
|
||||||
|
align-self: center;
|
||||||
|
width: 50%;
|
||||||
|
padding: 12px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: #c0392b;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#logbtn:hover { background-color: #a03224; }
|
||||||
|
#logbtn:disabled { background-color: #ccc; cursor: not-allowed; }
|
||||||
|
|
||||||
|
a { color: #111; text-decoration: none; }
|
||||||
|
a:hover { text-decoration: underline; }
|
||||||
|
|
||||||
|
#mensagem-erro {
|
||||||
|
background: #fee2e2;
|
||||||
|
border: 1px solid #fca5a5;
|
||||||
|
color: #b91c1c;
|
||||||
|
padding: 10px 14px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Botão tema */
|
||||||
|
.theme-toggle-btn {
|
||||||
|
position: absolute;
|
||||||
|
right: 20px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
background: rgba(255,255,255,0.15);
|
||||||
|
border: 1px solid rgba(255,255,255,0.3);
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: background 0.2s, transform 0.2s;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
.theme-toggle-btn:hover { background: rgba(255,255,255,0.3); transform: translateY(-50%) scale(1.1); }
|
||||||
|
.theme-icon { width: 100%; height: 100%; object-fit: contain; }
|
||||||
|
|
||||||
|
/* ===== TEMA ESCURO ===== */
|
||||||
|
[data-theme="dark"] body {
|
||||||
|
background: #121212;
|
||||||
|
color: #e8e8e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .mens { color: #e8e8e8; }
|
||||||
|
[data-theme="dark"] label { color: #e8e8e8; }
|
||||||
|
|
||||||
|
[data-theme="dark"] input[type="email"],
|
||||||
|
[data-theme="dark"] input[type="password"] {
|
||||||
|
background: #1a1a1a;
|
||||||
|
border-color: #333;
|
||||||
|
color: #e8e8e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] #mensagem-erro {
|
||||||
|
background: rgba(254,226,226,0.1);
|
||||||
|
border-color: rgba(252,165,165,0.2);
|
||||||
|
color: #fca5a5;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] a { color: #e8e8e8; }
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="pt-br">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="icon" href="imagens/icone.png">
|
||||||
|
<link rel="stylesheet" href="login.css">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
<title>Login – Focus Agenda</title>
|
||||||
|
<script src="theme.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="topo">
|
||||||
|
<h1 id="textotop">Focus Agenda</h1>
|
||||||
|
<button class="theme-toggle-btn" onclick="toggleTheme()" title="Tema Escuro"><img src="imagens/moon-svgrepo-com.svg" class="theme-icon" alt="Tema"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="log">
|
||||||
|
<h1 class="mens">Bem-vindo!</h1>
|
||||||
|
<h3 class="mens">Faça seu login</h3>
|
||||||
|
|
||||||
|
<div id="mensagem-erro" role="alert"></div>
|
||||||
|
|
||||||
|
<form id="loginForm" novalidate>
|
||||||
|
<div class="campo">
|
||||||
|
<label for="emailid">Email</label>
|
||||||
|
<input type="email" placeholder="Digite seu email" id="emailid" autocomplete="email" required>
|
||||||
|
</div>
|
||||||
|
<div class="campo">
|
||||||
|
<label for="senhaid">Senha</label>
|
||||||
|
<input type="password" placeholder="Digite sua senha" id="senhaid" autocomplete="current-password" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" id="logbtn">Entrar</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<p class="mens"><a href="cadastro.html">Cadastrar-se</a></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Se já tiver token válido, redireciona direto
|
||||||
|
(function () {
|
||||||
|
if (localStorage.getItem('fa_token')) {
|
||||||
|
window.location.href = 'calendario.html';
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
const form = document.getElementById('loginForm');
|
||||||
|
const erroEl = document.getElementById('mensagem-erro');
|
||||||
|
const btn = document.getElementById('logbtn');
|
||||||
|
|
||||||
|
function mostrarErro(msg) {
|
||||||
|
erroEl.textContent = msg;
|
||||||
|
erroEl.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
form.addEventListener('submit', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
erroEl.style.display = 'none';
|
||||||
|
|
||||||
|
const email = document.getElementById('emailid').value.trim();
|
||||||
|
const senha = document.getElementById('senhaid').value;
|
||||||
|
|
||||||
|
if (!email || !senha) {
|
||||||
|
mostrarErro('Preencha todos os campos.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.textContent = 'Entrando...';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/estudantes/login', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ email, senha })
|
||||||
|
});
|
||||||
|
|
||||||
|
const json = await res.json();
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
mostrarErro(json.message || 'Email ou senha incorretos.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Salva token e dados do usuário
|
||||||
|
localStorage.setItem('fa_token', json.data.token);
|
||||||
|
localStorage.setItem('fa_user', JSON.stringify(json.data.estudante));
|
||||||
|
|
||||||
|
window.location.href = 'calendario.html';
|
||||||
|
} catch (err) {
|
||||||
|
mostrarErro('Erro de conexão. Verifique se o servidor está rodando.');
|
||||||
|
} finally {
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.textContent = 'Entrar';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
* {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3 {
|
||||||
|
color: black;
|
||||||
|
margin: 0;
|
||||||
|
font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif
|
||||||
|
}
|
||||||
|
|
||||||
|
label{
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
body {
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-right: 20px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
#log {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 320px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#topo {
|
||||||
|
width: 100%;
|
||||||
|
height: 50px;
|
||||||
|
background-color: #111;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background: linear-gradient(to right, #C0392B 47%, #7A4951 73%, #114455 87%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
#textotop {
|
||||||
|
padding-left: 20px;
|
||||||
|
font-size: 38px;
|
||||||
|
margin: 0;
|
||||||
|
font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
#log {
|
||||||
|
width: 350px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
color: white;
|
||||||
|
margin-top: 70px; /* Compensa a barra fixa do topo */
|
||||||
|
}
|
||||||
|
#campo {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
#menslog{
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#emailid, #senhaid {
|
||||||
|
height: 50px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#logbtn {
|
||||||
|
align-self: center;
|
||||||
|
width: 50%;
|
||||||
|
padding: 12px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: #C0392B;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#logbtn:hover {
|
||||||
|
background-color: #A03224;
|
||||||
|
}
|
||||||
|
.mens {
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
#linkcada{
|
||||||
|
color: #111;
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
(function() {
|
||||||
|
var saved = localStorage.getItem('fa_theme');
|
||||||
|
var theme = saved || 'light';
|
||||||
|
document.documentElement.setAttribute('data-theme', theme);
|
||||||
|
})();
|
||||||
|
|
||||||
|
function toggleTheme() {
|
||||||
|
var current = document.documentElement.getAttribute('data-theme');
|
||||||
|
var next = current === 'dark' ? 'light' : 'dark';
|
||||||
|
document.documentElement.setAttribute('data-theme', next);
|
||||||
|
localStorage.setItem('fa_theme', next);
|
||||||
|
updateThemeButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateThemeButtons() {
|
||||||
|
var theme = document.documentElement.getAttribute('data-theme');
|
||||||
|
var moonSrc = 'imagens/moon-svgrepo-com.svg';
|
||||||
|
var sunSrc = 'imagens/sun-svgrepo-com.svg';
|
||||||
|
|
||||||
|
var icons = document.querySelectorAll('.theme-icon');
|
||||||
|
for (var i = 0; i < icons.length; i++) {
|
||||||
|
icons[i].src = theme === 'dark' ? sunSrc : moonSrc;
|
||||||
|
icons[i].alt = theme === 'dark' ? 'Tema Claro' : 'Tema Escuro';
|
||||||
|
}
|
||||||
|
|
||||||
|
var btns = document.querySelectorAll('.theme-toggle-btn');
|
||||||
|
for (var j = 0; j < btns.length; j++) {
|
||||||
|
btns[j].title = theme === 'dark' ? 'Tema Claro' : 'Tema Escuro';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', updateThemeButtons);
|
||||||
|
} else {
|
||||||
|
updateThemeButtons();
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
package com.agendaestudantil.servico;
|
||||||
|
|
||||||
|
import com.agendaestudantil.dto.RequisicaoCadastroDTO;
|
||||||
|
import com.agendaestudantil.dto.RespostaEstudanteDTO;
|
||||||
|
import com.agendaestudantil.dto.RequisicaoLoginDTO;
|
||||||
|
import com.agendaestudantil.dto.RespostaLoginDTO;
|
||||||
|
import com.agendaestudantil.entidade.Estudante;
|
||||||
|
import com.agendaestudantil.excecao.ExcecaoNegocio;
|
||||||
|
import com.agendaestudantil.repositorio.EstudanteRepositorio;
|
||||||
|
import com.agendaestudantil.utilitario.UtilJwt;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
public class EstudanteServicoTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private EstudanteRepositorio estudanteRepositorio;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private UtilJwt utilJwt;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private EstudanteServico estudanteServico;
|
||||||
|
|
||||||
|
private RequisicaoCadastroDTO requisicaoCadastro;
|
||||||
|
private RequisicaoLoginDTO requisicaoLogin;
|
||||||
|
private Estudante estudante;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
requisicaoCadastro = new RequisicaoCadastroDTO("Teste", "teste@teste.com", "123456", "Curso", 1);
|
||||||
|
requisicaoLogin = new RequisicaoLoginDTO("teste@teste.com", "123456");
|
||||||
|
|
||||||
|
estudante = new Estudante();
|
||||||
|
estudante.setId("1");
|
||||||
|
estudante.setNome("Teste");
|
||||||
|
estudante.setEmail("teste@teste.com");
|
||||||
|
estudante.setSenha("encodedPassword");
|
||||||
|
estudante.setCurso("Curso");
|
||||||
|
estudante.setPeriodo(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void deveCadastrarEstudanteComSucesso() {
|
||||||
|
when(estudanteRepositorio.findByEmail(anyString())).thenReturn(Optional.empty());
|
||||||
|
when(passwordEncoder.encode(anyString())).thenReturn("encodedPassword");
|
||||||
|
when(estudanteRepositorio.save(any(Estudante.class))).thenReturn(estudante);
|
||||||
|
|
||||||
|
RespostaEstudanteDTO resposta = estudanteServico.cadastrar(requisicaoCadastro);
|
||||||
|
|
||||||
|
assertNotNull(resposta);
|
||||||
|
assertEquals("Teste", resposta.nome());
|
||||||
|
verify(estudanteRepositorio, times(1)).save(any(Estudante.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void deveLancarExcecaoAoCadastrarEmailExistente() {
|
||||||
|
when(estudanteRepositorio.findByEmail(anyString())).thenReturn(Optional.of(estudante));
|
||||||
|
|
||||||
|
assertThrows(ExcecaoNegocio.class, () -> estudanteServico.cadastrar(requisicaoCadastro));
|
||||||
|
verify(estudanteRepositorio, never()).save(any(Estudante.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void deveRealizarLoginComSucesso() {
|
||||||
|
when(estudanteRepositorio.findByEmail(anyString())).thenReturn(Optional.of(estudante));
|
||||||
|
when(passwordEncoder.matches(anyString(), anyString())).thenReturn(true);
|
||||||
|
when(utilJwt.generateToken(anyString())).thenReturn("token123");
|
||||||
|
|
||||||
|
RespostaLoginDTO resposta = estudanteServico.login(requisicaoLogin);
|
||||||
|
|
||||||
|
assertNotNull(resposta);
|
||||||
|
assertEquals("token123", resposta.token());
|
||||||
|
assertEquals("Teste", resposta.estudante().nome());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void deveRejeitarSenhaInvalida() {
|
||||||
|
when(estudanteRepositorio.findByEmail(anyString())).thenReturn(Optional.of(estudante));
|
||||||
|
when(passwordEncoder.matches(anyString(), anyString())).thenReturn(false);
|
||||||
|
|
||||||
|
assertThrows(ResponseStatusException.class, () -> estudanteServico.login(requisicaoLogin));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void deveRejeitarEmailNaoEncontrado() {
|
||||||
|
when(estudanteRepositorio.findByEmail(anyString())).thenReturn(Optional.empty());
|
||||||
|
|
||||||
|
assertThrows(ResponseStatusException.class, () -> estudanteServico.login(requisicaoLogin));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
package com.agendaestudantil.servico;
|
||||||
|
|
||||||
|
import com.agendaestudantil.dto.RequisicaoTarefaDTO;
|
||||||
|
import com.agendaestudantil.dto.RespostaTarefaDTO;
|
||||||
|
import com.agendaestudantil.entidade.Tarefa;
|
||||||
|
import com.agendaestudantil.excecao.ExcecaoRecursoNaoEncontrado;
|
||||||
|
import com.agendaestudantil.repositorio.TarefaRepositorio;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
public class TarefaServicoTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private TarefaRepositorio tarefaRepositorio;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private SecurityContext securityContext;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private Authentication authentication;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private TarefaServico tarefaServico;
|
||||||
|
|
||||||
|
private RequisicaoTarefaDTO requisicaoTarefa;
|
||||||
|
private Tarefa tarefa;
|
||||||
|
private final String estudanteId = "estudante123";
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
requisicaoTarefa = new RequisicaoTarefaDTO(
|
||||||
|
"Estudar Spring",
|
||||||
|
"Aprender Spring Boot 3",
|
||||||
|
Tarefa.Prioridade.ALTA,
|
||||||
|
Tarefa.StatusTarefa.PENDENTE,
|
||||||
|
LocalDate.now(),
|
||||||
|
null);
|
||||||
|
|
||||||
|
tarefa = new Tarefa();
|
||||||
|
tarefa.setId("tarefa1");
|
||||||
|
tarefa.setTitulo("Estudar Spring");
|
||||||
|
tarefa.setDescricao("Aprender Spring Boot 3");
|
||||||
|
tarefa.setPrioridade(Tarefa.Prioridade.ALTA);
|
||||||
|
tarefa.setStatus(Tarefa.StatusTarefa.PENDENTE);
|
||||||
|
tarefa.setDataEntrega(LocalDate.now());
|
||||||
|
tarefa.setEstudanteId(estudanteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mockAuthentication(String user) {
|
||||||
|
when(securityContext.getAuthentication()).thenReturn(authentication);
|
||||||
|
when(authentication.getName()).thenReturn(user);
|
||||||
|
SecurityContextHolder.setContext(securityContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void deveCriarTarefaComSucesso() {
|
||||||
|
mockAuthentication(estudanteId);
|
||||||
|
when(tarefaRepositorio.save(any(Tarefa.class))).thenReturn(tarefa);
|
||||||
|
|
||||||
|
RespostaTarefaDTO resposta = tarefaServico.criarTarefa(requisicaoTarefa);
|
||||||
|
|
||||||
|
assertNotNull(resposta);
|
||||||
|
assertEquals("Estudar Spring", resposta.titulo());
|
||||||
|
verify(tarefaRepositorio, times(1)).save(any(Tarefa.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void deveLancarExcecaoAoCriarTarefaParaOutroUsuario() {
|
||||||
|
mockAuthentication("outroEstudante");
|
||||||
|
|
||||||
|
assertThrows(ResponseStatusException.class, () -> tarefaServico.criarTarefa(requisicaoTarefa));
|
||||||
|
verify(tarefaRepositorio, never()).save(any(Tarefa.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void deveListarTarefasPorEstudanteComSucesso() {
|
||||||
|
mockAuthentication(estudanteId);
|
||||||
|
when(tarefaRepositorio.findByEstudanteId(estudanteId)).thenReturn(List.of(tarefa));
|
||||||
|
|
||||||
|
List<RespostaTarefaDTO> tarefas = tarefaServico.listarTarefasPorEstudante(estudanteId);
|
||||||
|
|
||||||
|
assertFalse(tarefas.isEmpty());
|
||||||
|
assertEquals(1, tarefas.size());
|
||||||
|
assertEquals("Estudar Spring", tarefas.get(0).titulo());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void deveMudarStatusTarefaParaConcluidoComSucesso() {
|
||||||
|
mockAuthentication(estudanteId);
|
||||||
|
when(tarefaRepositorio.findById(anyString())).thenReturn(Optional.of(tarefa));
|
||||||
|
when(tarefaRepositorio.save(any(Tarefa.class))).thenReturn(tarefa);
|
||||||
|
|
||||||
|
RespostaTarefaDTO resposta = tarefaServico.marcarConcluida("tarefa1");
|
||||||
|
|
||||||
|
assertNotNull(resposta);
|
||||||
|
verify(tarefaRepositorio, times(1)).save(any(Tarefa.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void deveLancarExcecaoQuandoTarefaNaoEncontrada() {
|
||||||
|
when(tarefaRepositorio.findById(anyString())).thenReturn(Optional.empty());
|
||||||
|
|
||||||
|
assertThrows(ExcecaoRecursoNaoEncontrado.class, () -> tarefaServico.buscarTarefaPorId("tarefaNaoExistente"));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user