Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 771ea7e9a9 | |||
| 02cfd71cf4 |
-28
@@ -1,28 +0,0 @@
|
|||||||
FROM maven:3.9-eclipse-temurin-17 AS build
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
COPY pom.xml .
|
|
||||||
RUN mvn dependency:go-offline -B
|
|
||||||
|
|
||||||
COPY src ./src
|
|
||||||
RUN mvn clean package -DskipTests
|
|
||||||
|
|
||||||
FROM eclipse-temurin:17-jre-alpine
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
|
|
||||||
|
|
||||||
COPY --from=build /app/target/*.jar app.jar
|
|
||||||
|
|
||||||
RUN chown appuser:appgroup app.jar
|
|
||||||
|
|
||||||
USER appuser
|
|
||||||
|
|
||||||
EXPOSE 8080
|
|
||||||
|
|
||||||
ENV SPRING_PROFILES_ACTIVE=prod
|
|
||||||
ENV SERVER_PORT=8080
|
|
||||||
|
|
||||||
ENTRYPOINT ["java", "-jar", "app.jar"]
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
# Focus Agenda - Agenda Digital para Estudantes
|
|
||||||
|
|
||||||
CENTRO ESTADUAL DE EDUCACAO TECNOLOGICA "PAULA SOUZA"
|
|
||||||
ETEC PEDRO D'ARCADIA NETO
|
|
||||||
Tecnico em Desenvolvimento de Sistemas
|
|
||||||
|
|
||||||
## Autores
|
|
||||||
|
|
||||||
- BORGES, Gabriel H. M.
|
|
||||||
- CRUZ, Fernando M. B. da
|
|
||||||
- ARAUJO, Gustavo Ferreira
|
|
||||||
- OLIVEIRA, Henry E. de
|
|
||||||
- HABU, Nadia Sakae
|
|
||||||
|
|
||||||
##Descricao
|
|
||||||
|
|
||||||
Plataforma digital para organizacao de estudos destinada a alunos do ensino medio e tecnico. A ferramenta auxilia na gestao de rotinas academicas, enviando notificacoes sobre atividades diarias, horarios de estudo, datas de provas e outros compromissos academicos.
|
|
||||||
|
|
||||||
## Funcionalidades
|
|
||||||
|
|
||||||
- Cadastro e autenticacao de usuarios
|
|
||||||
- Calendario mensal, semanal e diario
|
|
||||||
- Criacao e gerenciamentos de eventos
|
|
||||||
- Criacao e gerenciamento de tarefas com prioridades
|
|
||||||
- Gerenciamento de disciplinas
|
|
||||||
- Sistema de notificacoes
|
|
||||||
- Tema claro e escuro
|
|
||||||
- Painel informativo com feriados nacionais
|
|
||||||
|
|
||||||
## Tecnologias
|
|
||||||
|
|
||||||
### Frontend
|
|
||||||
- HTML5
|
|
||||||
- CSS3
|
|
||||||
- JavaScript
|
|
||||||
|
|
||||||
### Backend
|
|
||||||
- Java 17
|
|
||||||
- Spring Boot 3.2.0
|
|
||||||
- Spring Security
|
|
||||||
- JWT (JSON Web Token)
|
|
||||||
- MongoDB
|
|
||||||
|
|
||||||
## Requisitos
|
|
||||||
|
|
||||||
- Java 17 ou superior
|
|
||||||
- Maven 3.8+
|
|
||||||
- MongoDB
|
|
||||||
|
|
||||||
## Execucao
|
|
||||||
|
|
||||||
### Build do projeto
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mvn clean package
|
|
||||||
```
|
|
||||||
|
|
||||||
### Execucao com Maven
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mvn spring-boot:run
|
|
||||||
```
|
|
||||||
|
|
||||||
### Execucao com JAR
|
|
||||||
|
|
||||||
```bash
|
|
||||||
java -jar target/agenda-digital-estudantes-1.0.0.jar
|
|
||||||
```
|
|
||||||
|
|
||||||
### Variaveis de ambiente
|
|
||||||
|
|
||||||
| Variavel | Descricao | Valor padrao |
|
|
||||||
|---|---|---|
|
|
||||||
| APP_NAME | Nome da aplicacao | Focus Agenda |
|
|
||||||
| SERVER_PORT | Porta do servidor | 8080 |
|
|
||||||
| SPRING_PROFILES_ACTIVE | Perfil ativo | dev |
|
|
||||||
| MONGO_URI | URI de conexao com MongoDB | mongodb://localhost:27017/agenda_estudantil |
|
|
||||||
| CORS_ORIGINS | Origens permitidas para CORS | http://localhost:8080,http://localhost:3000 |
|
|
||||||
| JWT_SECRET | Chave secreta para JWT | (chave padrao) |
|
|
||||||
| JWT_EXPIRATION | Expiracao do token em milissegundos | 86400000 |
|
|
||||||
|
|
||||||
## Docker
|
|
||||||
|
|
||||||
### Build da imagem
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker build -t focus-agenda .
|
|
||||||
```
|
|
||||||
|
|
||||||
### Execucao do container
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker run -d -p 8080:8080 --name focus-agenda focus-agenda
|
|
||||||
```
|
|
||||||
|
|
||||||
### Execucao com MongoDB
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker run -d -p 8080:8080 -e MONGO_URI=mongodb://host.docker.internal:27017/agenda_estudantil --name focus-agenda focus-agenda
|
|
||||||
```
|
|
||||||
|
|
||||||
## Acesso
|
|
||||||
|
|
||||||
Apos iniciar a aplicacao, acesse:
|
|
||||||
|
|
||||||
- Aplicacao: http://localhost:8080
|
|
||||||
- Swagger UI: http://localhost:8080/swagger-ui.html
|
|
||||||
- API Docs: http://localhost:8080/v3/api-docs
|
|
||||||
|
|
||||||
## Estrutura do Projeto
|
|
||||||
|
|
||||||
```
|
|
||||||
src/
|
|
||||||
├── main/
|
|
||||||
│ ├── java/com/agendaestudantil/
|
|
||||||
│ │ ├── configuracao/ # Configuracoes de seguranca e MongoDB
|
|
||||||
│ │ ├── controlador/ # Controladores REST
|
|
||||||
│ │ ├── dto/ # Objetos de transferencia de dados
|
|
||||||
│ │ ├── entidade/ # Entidades do dominio
|
|
||||||
│ │ ├── excecao/ # Excecoes personalizadas e manipulador global
|
|
||||||
│ │ ├── filtro/ # Filtro JWT
|
|
||||||
│ │ ├── repositorio/ # Interfaces de repositorio
|
|
||||||
│ │ ├── seguranca/ # Autenticacao e detalhes do usuario
|
|
||||||
│ │ ├── servico/ # Regras de negocio
|
|
||||||
│ │ └── utilitario/ # Utilitarios (JWT)
|
|
||||||
│ └── resources/
|
|
||||||
│ ├── static/ # Frontend (HTML, CSS, JS)
|
|
||||||
│ └── application*.properties
|
|
||||||
```
|
|
||||||
|
|
||||||
## Licenca
|
|
||||||
|
|
||||||
Projeto academico desenvolvido para o Curso Tecnico em Desenvolvimento de Sistemas da ETEC Pedro D'Arcadia Neto.
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
services:
|
|
||||||
mongodb:
|
|
||||||
image: mongo:7
|
|
||||||
ports:
|
|
||||||
- "27017:27017"
|
|
||||||
volumes:
|
|
||||||
- mongo_data:/data/db
|
|
||||||
|
|
||||||
app:
|
|
||||||
build: .
|
|
||||||
ports:
|
|
||||||
- "8080:8080"
|
|
||||||
depends_on:
|
|
||||||
- mongodb
|
|
||||||
environment:
|
|
||||||
MONGO_URI: mongodb://mongodb:27017/agenda_estudantil
|
|
||||||
JWT_SECRET: 4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b
|
|
||||||
CORS_ORIGINS: http://localhost:8080
|
|
||||||
SPRING_PROFILES_ACTIVE: prod
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
mongo_data:
|
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.jsonwebtoken</groupId>
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
<artifactId>jjwt-api</artifactId>
|
<artifactId>jjwt-api</artifactId>
|
||||||
<version>0.12.6</version>
|
<version>0.12.3</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -63,7 +63,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.jsonwebtoken</groupId>
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
<artifactId>jjwt-jackson</artifactId>
|
<artifactId>jjwt-jackson</artifactId>
|
||||||
<version>0.12.6</version>
|
<version>0.12.3</version>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,8 @@ package com.agendaestudantil;
|
|||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
@EnableScheduling
|
|
||||||
public class AgendaDigitalEstudantesApplication {
|
public class AgendaDigitalEstudantesApplication {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
package com.agendaestudantil.configuracao;
|
package com.agendaestudantil.configuracao;
|
||||||
|
|
||||||
import com.agendaestudantil.dto.RespostaApi;
|
|
||||||
import com.agendaestudantil.filtro.FiltroJwt;
|
import com.agendaestudantil.filtro.FiltroJwt;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
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.builders.HttpSecurity;
|
||||||
@@ -21,8 +17,6 @@ import org.springframework.web.cors.CorsConfiguration;
|
|||||||
import org.springframework.web.cors.CorsConfigurationSource;
|
import org.springframework.web.cors.CorsConfigurationSource;
|
||||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@@ -31,43 +25,25 @@ public class ConfiguracaoSeguranca {
|
|||||||
|
|
||||||
private final FiltroJwt filtroJwt;
|
private final FiltroJwt filtroJwt;
|
||||||
private final String corsAllowedOrigins;
|
private final String corsAllowedOrigins;
|
||||||
private final String perfilAtivo;
|
|
||||||
|
|
||||||
public ConfiguracaoSeguranca(FiltroJwt filtroJwt,
|
public ConfiguracaoSeguranca(FiltroJwt filtroJwt, @Value("${cors.allowed.origins}") String corsAllowedOrigins) {
|
||||||
@Value("${cors.allowed.origins}") String corsAllowedOrigins,
|
|
||||||
@Value("${spring.profiles.active:dev}") String perfilAtivo) {
|
|
||||||
this.filtroJwt = filtroJwt;
|
this.filtroJwt = filtroJwt;
|
||||||
this.corsAllowedOrigins = corsAllowedOrigins;
|
this.corsAllowedOrigins = corsAllowedOrigins;
|
||||||
this.perfilAtivo = perfilAtivo;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
List<String> pathsPublicos = new ArrayList<>(List.of(
|
|
||||||
"/", "/index.html", "/login.html", "/cadastro.html", "/calendario.html", "/configuracoes.html",
|
|
||||||
"/politica-privacidade.html",
|
|
||||||
"/favicon.ico", "/imagens/**",
|
|
||||||
"/*.css", "/*.js", "/*.ico", "/*.png",
|
|
||||||
"/api/estudantes/cadastro", "/api/estudantes/login"));
|
|
||||||
|
|
||||||
if ("dev".equals(perfilAtivo)) {
|
|
||||||
pathsPublicos.add("/swagger-ui/**");
|
|
||||||
pathsPublicos.add("/v3/api-docs/**");
|
|
||||||
}
|
|
||||||
|
|
||||||
http.csrf(csrf -> csrf.disable())
|
http.csrf(csrf -> csrf.disable())
|
||||||
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
|
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
|
||||||
.authorizeHttpRequests(auth -> auth
|
.authorizeHttpRequests(auth -> auth
|
||||||
.requestMatchers(pathsPublicos.toArray(new String[0]))
|
.requestMatchers(
|
||||||
|
"/", "/index.html", "/login.html", "/cadastro.html",
|
||||||
|
"/favicon.ico", "/imagens/**",
|
||||||
|
"/*.css", "/*.js", "/*.ico", "/*.png",
|
||||||
|
"/api/estudantes/cadastro", "/api/estudantes/login",
|
||||||
|
"/swagger-ui/**", "/v3/api-docs/**")
|
||||||
.permitAll()
|
.permitAll()
|
||||||
.anyRequest().authenticated())
|
.anyRequest().authenticated())
|
||||||
.exceptionHandling(ex -> ex
|
|
||||||
.authenticationEntryPoint((request, response, authException) -> {
|
|
||||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
|
||||||
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
|
||||||
RespostaApi<Void> body = new RespostaApi<>(null, "Acesso nao autorizado", LocalDateTime.now());
|
|
||||||
new ObjectMapper().writeValue(response.getOutputStream(), body);
|
|
||||||
}))
|
|
||||||
.sessionManagement(session -> session
|
.sessionManagement(session -> session
|
||||||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
.addFilterBefore(filtroJwt, UsernamePasswordAuthenticationFilter.class);
|
.addFilterBefore(filtroJwt, UsernamePasswordAuthenticationFilter.class);
|
||||||
|
|||||||
@@ -15,9 +15,4 @@ public class ResourceController {
|
|||||||
public String app() {
|
public String app() {
|
||||||
return "forward:/calendario.html";
|
return "forward:/calendario.html";
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/config")
|
|
||||||
public String config() {
|
|
||||||
return "forward:/configuracoes.html";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import com.agendaestudantil.servico.DisciplinaServico;
|
|||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
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 org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -26,23 +24,20 @@ public class DisciplinaControlador {
|
|||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
public ResponseEntity<RespostaApi<RespostaDisciplinaDTO>> criarDisciplina(
|
public ResponseEntity<RespostaApi<RespostaDisciplinaDTO>> criarDisciplina(
|
||||||
@Valid @RequestBody RequisicaoDisciplinaDTO dto,
|
@Valid @RequestBody RequisicaoDisciplinaDTO dto) {
|
||||||
@AuthenticationPrincipal UserDetails userDetails) {
|
|
||||||
String estudanteId = userDetails.getUsername();
|
|
||||||
Disciplina disciplina = new Disciplina();
|
Disciplina disciplina = new Disciplina();
|
||||||
disciplina.setNome(dto.nome());
|
disciplina.setNome(dto.nome());
|
||||||
disciplina.setProfessor(dto.professor());
|
disciplina.setProfessor(dto.professor());
|
||||||
disciplina.setSala(dto.sala());
|
disciplina.setSala(dto.sala());
|
||||||
disciplina.setCor(dto.cor());
|
disciplina.setCor(dto.cor());
|
||||||
|
|
||||||
RespostaDisciplinaDTO resposta = disciplinaServico.criarDisciplina(disciplina, estudanteId);
|
RespostaDisciplinaDTO resposta = disciplinaServico.criarDisciplina(disciplina, dto.estudanteId());
|
||||||
return ResponseEntity.status(HttpStatus.CREATED).body(RespostaApi.sucesso(resposta));
|
return ResponseEntity.status(HttpStatus.CREATED).body(RespostaApi.sucesso(resposta));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/me")
|
@GetMapping("/estudante/{estudanteId}")
|
||||||
public ResponseEntity<RespostaApi<List<RespostaDisciplinaDTO>>> listarPorEstudante(
|
public ResponseEntity<RespostaApi<List<RespostaDisciplinaDTO>>> listarPorEstudante(
|
||||||
@AuthenticationPrincipal UserDetails userDetails) {
|
@PathVariable String estudanteId) {
|
||||||
String estudanteId = userDetails.getUsername();
|
|
||||||
List<RespostaDisciplinaDTO> disciplinas = disciplinaServico.listarPorEstudante(estudanteId);
|
List<RespostaDisciplinaDTO> disciplinas = disciplinaServico.listarPorEstudante(estudanteId);
|
||||||
return ResponseEntity.ok(RespostaApi.sucesso(disciplinas));
|
return ResponseEntity.ok(RespostaApi.sucesso(disciplinas));
|
||||||
}
|
}
|
||||||
@@ -50,8 +45,7 @@ public class DisciplinaControlador {
|
|||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
public ResponseEntity<RespostaApi<RespostaDisciplinaDTO>> buscarPorId(
|
public ResponseEntity<RespostaApi<RespostaDisciplinaDTO>> buscarPorId(
|
||||||
@PathVariable String id,
|
@PathVariable String id,
|
||||||
@AuthenticationPrincipal UserDetails userDetails) {
|
@RequestParam String estudanteId) {
|
||||||
String estudanteId = userDetails.getUsername();
|
|
||||||
RespostaDisciplinaDTO disciplina = disciplinaServico.buscarPorId(id, estudanteId);
|
RespostaDisciplinaDTO disciplina = disciplinaServico.buscarPorId(id, estudanteId);
|
||||||
return ResponseEntity.ok(RespostaApi.sucesso(disciplina));
|
return ResponseEntity.ok(RespostaApi.sucesso(disciplina));
|
||||||
}
|
}
|
||||||
@@ -59,24 +53,21 @@ public class DisciplinaControlador {
|
|||||||
@PutMapping("/{id}")
|
@PutMapping("/{id}")
|
||||||
public ResponseEntity<RespostaApi<RespostaDisciplinaDTO>> atualizarDisciplina(
|
public ResponseEntity<RespostaApi<RespostaDisciplinaDTO>> atualizarDisciplina(
|
||||||
@PathVariable String id,
|
@PathVariable String id,
|
||||||
@Valid @RequestBody RequisicaoDisciplinaDTO dto,
|
@Valid @RequestBody RequisicaoDisciplinaDTO dto) {
|
||||||
@AuthenticationPrincipal UserDetails userDetails) {
|
|
||||||
String estudanteId = userDetails.getUsername();
|
|
||||||
Disciplina disciplina = new Disciplina();
|
Disciplina disciplina = new Disciplina();
|
||||||
disciplina.setNome(dto.nome());
|
disciplina.setNome(dto.nome());
|
||||||
disciplina.setProfessor(dto.professor());
|
disciplina.setProfessor(dto.professor());
|
||||||
disciplina.setSala(dto.sala());
|
disciplina.setSala(dto.sala());
|
||||||
disciplina.setCor(dto.cor());
|
disciplina.setCor(dto.cor());
|
||||||
|
|
||||||
RespostaDisciplinaDTO resposta = disciplinaServico.atualizarDisciplina(id, disciplina, estudanteId);
|
RespostaDisciplinaDTO resposta = disciplinaServico.atualizarDisciplina(id, disciplina, dto.estudanteId());
|
||||||
return ResponseEntity.ok(RespostaApi.sucesso(resposta));
|
return ResponseEntity.ok(RespostaApi.sucesso(resposta));
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/{id}")
|
@DeleteMapping("/{id}")
|
||||||
public ResponseEntity<RespostaApi<Void>> excluirDisciplina(
|
public ResponseEntity<RespostaApi<Void>> excluirDisciplina(
|
||||||
@PathVariable String id,
|
@PathVariable String id,
|
||||||
@AuthenticationPrincipal UserDetails userDetails) {
|
@RequestParam String estudanteId) {
|
||||||
String estudanteId = userDetails.getUsername();
|
|
||||||
disciplinaServico.excluirDisciplina(id, estudanteId);
|
disciplinaServico.excluirDisciplina(id, estudanteId);
|
||||||
return ResponseEntity.status(HttpStatus.NO_CONTENT).body(RespostaApi.sucesso(null));
|
return ResponseEntity.status(HttpStatus.NO_CONTENT).body(RespostaApi.sucesso(null));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
package com.agendaestudantil.controlador;
|
package com.agendaestudantil.controlador;
|
||||||
|
|
||||||
import com.agendaestudantil.dto.RespostaApi;
|
import com.agendaestudantil.dto.RespostaApi;
|
||||||
import com.agendaestudantil.dto.RequisicaoAtualizacaoEstudanteDTO;
|
|
||||||
import com.agendaestudantil.dto.RequisicaoCadastroDTO;
|
import com.agendaestudantil.dto.RequisicaoCadastroDTO;
|
||||||
import com.agendaestudantil.dto.RespostaDadosCompletoDTO;
|
|
||||||
import com.agendaestudantil.dto.RespostaEstudanteDTO;
|
import com.agendaestudantil.dto.RespostaEstudanteDTO;
|
||||||
import com.agendaestudantil.dto.RequisicaoLoginDTO;
|
import com.agendaestudantil.dto.RequisicaoLoginDTO;
|
||||||
import com.agendaestudantil.dto.RespostaLoginDTO;
|
import com.agendaestudantil.dto.RespostaLoginDTO;
|
||||||
import com.agendaestudantil.dto.RequisicaoTrocaSenhaDTO;
|
|
||||||
import com.agendaestudantil.servico.EstudanteServico;
|
import com.agendaestudantil.servico.EstudanteServico;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
@@ -38,38 +35,10 @@ public class EstudanteControlador {
|
|||||||
return ResponseEntity.ok(RespostaApi.sucesso(resposta));
|
return ResponseEntity.ok(RespostaApi.sucesso(resposta));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retorna dados do usuário autenticado via JWT (sem precisar do ID na URL)
|
||||||
@GetMapping("/me")
|
@GetMapping("/me")
|
||||||
public ResponseEntity<RespostaApi<RespostaEstudanteDTO>> me(@AuthenticationPrincipal UserDetails userDetails) {
|
public ResponseEntity<RespostaApi<RespostaEstudanteDTO>> me(@AuthenticationPrincipal UserDetails userDetails) {
|
||||||
RespostaEstudanteDTO resposta = estudanteServico.buscarPorId(userDetails.getUsername());
|
RespostaEstudanteDTO resposta = estudanteServico.buscarPorId(userDetails.getUsername());
|
||||||
return ResponseEntity.ok(RespostaApi.sucesso(resposta));
|
return ResponseEntity.ok(RespostaApi.sucesso(resposta));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/me/dados")
|
|
||||||
public ResponseEntity<RespostaApi<RespostaDadosCompletoDTO>> exportarDados(
|
|
||||||
@AuthenticationPrincipal UserDetails userDetails) {
|
|
||||||
RespostaDadosCompletoDTO dados = estudanteServico.exportarDados(userDetails.getUsername());
|
|
||||||
return ResponseEntity.ok(RespostaApi.sucesso(dados));
|
|
||||||
}
|
|
||||||
|
|
||||||
@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));
|
|
||||||
}
|
|
||||||
|
|
||||||
@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));
|
|
||||||
}
|
|
||||||
|
|
||||||
@DeleteMapping("/me")
|
|
||||||
public ResponseEntity<RespostaApi<Void>> excluirConta(@AuthenticationPrincipal UserDetails userDetails) {
|
|
||||||
estudanteServico.excluirConta(userDetails.getUsername());
|
|
||||||
return ResponseEntity.ok(RespostaApi.sucesso(null));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ import jakarta.validation.Valid;
|
|||||||
import org.springframework.format.annotation.DateTimeFormat;
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
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 org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@@ -28,9 +26,7 @@ public class EventoControlador {
|
|||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
public ResponseEntity<RespostaApi<RespostaEventoDTO>> criarEvento(
|
public ResponseEntity<RespostaApi<RespostaEventoDTO>> criarEvento(
|
||||||
@Valid @RequestBody RequisicaoEventoDTO dto,
|
@Valid @RequestBody RequisicaoEventoDTO dto) {
|
||||||
@AuthenticationPrincipal UserDetails userDetails) {
|
|
||||||
String estudanteId = userDetails.getUsername();
|
|
||||||
Evento evento = new Evento();
|
Evento evento = new Evento();
|
||||||
evento.setTitulo(dto.titulo());
|
evento.setTitulo(dto.titulo());
|
||||||
evento.setDescricao(dto.descricao());
|
evento.setDescricao(dto.descricao());
|
||||||
@@ -39,39 +35,38 @@ public class EventoControlador {
|
|||||||
evento.setDataHora(dto.dataHora());
|
evento.setDataHora(dto.dataHora());
|
||||||
evento.setDisciplinaId(dto.disciplinaId());
|
evento.setDisciplinaId(dto.disciplinaId());
|
||||||
|
|
||||||
RespostaEventoDTO resposta = eventoServico.criarEvento(evento, estudanteId);
|
RespostaEventoDTO resposta = eventoServico.criarEvento(evento, dto.estudanteId());
|
||||||
return ResponseEntity.status(HttpStatus.CREATED).body(RespostaApi.sucesso(resposta));
|
return ResponseEntity.status(HttpStatus.CREATED).body(RespostaApi.sucesso(resposta));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/me")
|
@GetMapping("/estudante/{estudanteId}")
|
||||||
public ResponseEntity<RespostaApi<List<RespostaEventoDTO>>> listarPorEstudante(
|
public ResponseEntity<RespostaApi<List<RespostaEventoDTO>>> listarPorEstudante(
|
||||||
@AuthenticationPrincipal UserDetails userDetails) {
|
@PathVariable String estudanteId) {
|
||||||
String estudanteId = userDetails.getUsername();
|
|
||||||
List<RespostaEventoDTO> eventos = eventoServico.listarPorEstudante(estudanteId);
|
List<RespostaEventoDTO> eventos = eventoServico.listarPorEstudante(estudanteId);
|
||||||
return ResponseEntity.ok(RespostaApi.sucesso(eventos));
|
return ResponseEntity.ok(RespostaApi.sucesso(eventos));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/me/periodo")
|
@GetMapping("/estudante/{estudanteId}/periodo")
|
||||||
public ResponseEntity<RespostaApi<List<RespostaEventoDTO>>> listarPorPeriodo(
|
public ResponseEntity<RespostaApi<List<RespostaEventoDTO>>> listarPorPeriodo(
|
||||||
@AuthenticationPrincipal UserDetails userDetails,
|
@PathVariable String estudanteId,
|
||||||
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime inicio,
|
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime inicio,
|
||||||
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime fim) {
|
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime fim) {
|
||||||
String estudanteId = userDetails.getUsername();
|
|
||||||
List<RespostaEventoDTO> eventos = eventoServico.listarPorPeriodo(estudanteId, inicio, fim);
|
List<RespostaEventoDTO> eventos = eventoServico.listarPorPeriodo(estudanteId, inicio, fim);
|
||||||
return ResponseEntity.ok(RespostaApi.sucesso(eventos));
|
return ResponseEntity.ok(RespostaApi.sucesso(eventos));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/me/proximos")
|
@GetMapping("/estudante/{estudanteId}/proximos")
|
||||||
public ResponseEntity<RespostaApi<List<RespostaEventoDTO>>> listarProximosEventos(
|
public ResponseEntity<RespostaApi<List<RespostaEventoDTO>>> listarProximosEventos(
|
||||||
@AuthenticationPrincipal UserDetails userDetails) {
|
@PathVariable String estudanteId) {
|
||||||
String estudanteId = userDetails.getUsername();
|
|
||||||
List<RespostaEventoDTO> eventos = eventoServico.listarProximosEventos(estudanteId);
|
List<RespostaEventoDTO> eventos = eventoServico.listarProximosEventos(estudanteId);
|
||||||
return ResponseEntity.ok(RespostaApi.sucesso(eventos));
|
return ResponseEntity.ok(RespostaApi.sucesso(eventos));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
public ResponseEntity<RespostaApi<RespostaEventoDTO>> buscarPorId(@PathVariable String id) {
|
public ResponseEntity<RespostaApi<RespostaEventoDTO>> buscarPorId(
|
||||||
RespostaEventoDTO evento = eventoServico.buscarPorId(id);
|
@PathVariable String id,
|
||||||
|
@RequestParam String estudanteId) {
|
||||||
|
RespostaEventoDTO evento = eventoServico.buscarPorId(id, estudanteId);
|
||||||
return ResponseEntity.ok(RespostaApi.sucesso(evento));
|
return ResponseEntity.ok(RespostaApi.sucesso(evento));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,19 +74,23 @@ public class EventoControlador {
|
|||||||
public ResponseEntity<RespostaApi<RespostaEventoDTO>> atualizarEvento(
|
public ResponseEntity<RespostaApi<RespostaEventoDTO>> atualizarEvento(
|
||||||
@PathVariable String id,
|
@PathVariable String id,
|
||||||
@Valid @RequestBody RequisicaoEventoDTO dto) {
|
@Valid @RequestBody RequisicaoEventoDTO dto) {
|
||||||
RespostaEventoDTO resposta = eventoServico.atualizarEvento(id, dto);
|
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.atualizarEvento(id, evento, dto.estudanteId());
|
||||||
return ResponseEntity.ok(RespostaApi.sucesso(resposta));
|
return ResponseEntity.ok(RespostaApi.sucesso(resposta));
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/{id}")
|
@DeleteMapping("/{id}")
|
||||||
public ResponseEntity<RespostaApi<Void>> excluirEvento(@PathVariable String id) {
|
public ResponseEntity<RespostaApi<Void>> excluirEvento(
|
||||||
eventoServico.excluirEvento(id);
|
@PathVariable String id,
|
||||||
|
@RequestParam String estudanteId) {
|
||||||
|
eventoServico.excluirEvento(id, estudanteId);
|
||||||
return ResponseEntity.status(HttpStatus.NO_CONTENT).body(RespostaApi.sucesso(null));
|
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,8 +8,6 @@ import jakarta.validation.Valid;
|
|||||||
import org.springframework.format.annotation.DateTimeFormat;
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
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 org.springframework.web.bind.annotation.*;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -30,37 +28,33 @@ public class TarefaControlador {
|
|||||||
return ResponseEntity.status(HttpStatus.CREATED).body(RespostaApi.sucesso(tarefa));
|
return ResponseEntity.status(HttpStatus.CREATED).body(RespostaApi.sucesso(tarefa));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/me")
|
@GetMapping("/estudante/{estudanteId}")
|
||||||
public ResponseEntity<RespostaApi<List<RespostaTarefaDTO>>> listarTarefasPorEstudante(
|
public ResponseEntity<RespostaApi<List<RespostaTarefaDTO>>> listarTarefasPorEstudante(
|
||||||
@AuthenticationPrincipal UserDetails userDetails) {
|
@PathVariable String estudanteId) {
|
||||||
String estudanteId = userDetails.getUsername();
|
|
||||||
List<RespostaTarefaDTO> tarefas = tarefaServico.listarTarefasPorEstudante(estudanteId);
|
List<RespostaTarefaDTO> tarefas = tarefaServico.listarTarefasPorEstudante(estudanteId);
|
||||||
return ResponseEntity.ok(RespostaApi.sucesso(tarefas));
|
return ResponseEntity.ok(RespostaApi.sucesso(tarefas));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/me/pendentes")
|
@GetMapping("/estudante/{estudanteId}/pendentes")
|
||||||
public ResponseEntity<RespostaApi<List<RespostaTarefaDTO>>> listarTarefasPendentes(
|
public ResponseEntity<RespostaApi<List<RespostaTarefaDTO>>> listarTarefasPendentes(
|
||||||
@AuthenticationPrincipal UserDetails userDetails) {
|
@PathVariable String estudanteId) {
|
||||||
String estudanteId = userDetails.getUsername();
|
|
||||||
List<RespostaTarefaDTO> tarefas = tarefaServico.listarTarefasPendentes(estudanteId);
|
List<RespostaTarefaDTO> tarefas = tarefaServico.listarTarefasPendentes(estudanteId);
|
||||||
return ResponseEntity.ok(RespostaApi.sucesso(tarefas));
|
return ResponseEntity.ok(RespostaApi.sucesso(tarefas));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/me/data")
|
@GetMapping("/estudante/{estudanteId}/data")
|
||||||
public ResponseEntity<RespostaApi<List<RespostaTarefaDTO>>> listarTarefasPorData(
|
public ResponseEntity<RespostaApi<List<RespostaTarefaDTO>>> listarTarefasPorData(
|
||||||
@AuthenticationPrincipal UserDetails userDetails,
|
@PathVariable String estudanteId,
|
||||||
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate data) {
|
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate data) {
|
||||||
String estudanteId = userDetails.getUsername();
|
|
||||||
List<RespostaTarefaDTO> tarefas = tarefaServico.listarTarefasPorData(estudanteId, data);
|
List<RespostaTarefaDTO> tarefas = tarefaServico.listarTarefasPorData(estudanteId, data);
|
||||||
return ResponseEntity.ok(RespostaApi.sucesso(tarefas));
|
return ResponseEntity.ok(RespostaApi.sucesso(tarefas));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/me/periodo")
|
@GetMapping("/estudante/{estudanteId}/periodo")
|
||||||
public ResponseEntity<RespostaApi<List<RespostaTarefaDTO>>> listarTarefasPorPeriodo(
|
public ResponseEntity<RespostaApi<List<RespostaTarefaDTO>>> listarTarefasPorPeriodo(
|
||||||
@AuthenticationPrincipal UserDetails userDetails,
|
@PathVariable String estudanteId,
|
||||||
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate inicio,
|
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate inicio,
|
||||||
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate fim) {
|
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate fim) {
|
||||||
String estudanteId = userDetails.getUsername();
|
|
||||||
List<RespostaTarefaDTO> tarefas = tarefaServico.listarTarefasPorPeriodo(estudanteId, inicio, fim);
|
List<RespostaTarefaDTO> tarefas = tarefaServico.listarTarefasPorPeriodo(estudanteId, inicio, fim);
|
||||||
return ResponseEntity.ok(RespostaApi.sucesso(tarefas));
|
return ResponseEntity.ok(RespostaApi.sucesso(tarefas));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
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
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
@@ -11,7 +11,6 @@ public record RequisicaoCadastroDTO(
|
|||||||
@Email @NotBlank String email,
|
@Email @NotBlank String email,
|
||||||
@NotBlank @Size(min = 6) String senha,
|
@NotBlank @Size(min = 6) String senha,
|
||||||
@NotBlank String curso,
|
@NotBlank String curso,
|
||||||
@NotNull @Min(1) Integer periodo,
|
@NotNull @Min(1) Integer periodo
|
||||||
Boolean consentimentoLgpd
|
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,5 +6,6 @@ public record RequisicaoDisciplinaDTO(
|
|||||||
@NotBlank String nome,
|
@NotBlank String nome,
|
||||||
String professor,
|
String professor,
|
||||||
String sala,
|
String sala,
|
||||||
String cor
|
String cor,
|
||||||
|
@NotBlank String estudanteId
|
||||||
) {}
|
) {}
|
||||||
|
|||||||
@@ -12,5 +12,6 @@ public record RequisicaoEventoDTO(
|
|||||||
@NotNull Evento.TipoEvento tipo,
|
@NotNull Evento.TipoEvento tipo,
|
||||||
String local,
|
String local,
|
||||||
String disciplinaId,
|
String disciplinaId,
|
||||||
@NotNull LocalDateTime dataHora
|
@NotNull LocalDateTime dataHora,
|
||||||
|
@NotBlank String estudanteId
|
||||||
) {}
|
) {}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ public record RequisicaoTarefaDTO(
|
|||||||
@NotNull(message = "Prioridade é obrigatória") Tarefa.Prioridade prioridade,
|
@NotNull(message = "Prioridade é obrigatória") Tarefa.Prioridade prioridade,
|
||||||
Tarefa.StatusTarefa status,
|
Tarefa.StatusTarefa status,
|
||||||
@NotNull @FutureOrPresent LocalDate dataEntrega,
|
@NotNull @FutureOrPresent LocalDate dataEntrega,
|
||||||
String disciplinaId
|
String disciplinaId,
|
||||||
|
@NotBlank(message = "ID do estudante é obrigatório") String estudanteId
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
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
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package com.agendaestudantil.dto;
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public record RespostaDadosCompletoDTO(
|
|
||||||
RespostaEstudanteDTO estudante,
|
|
||||||
List<RespostaTarefaDTO> tarefas,
|
|
||||||
List<RespostaEventoDTO> eventos,
|
|
||||||
List<RespostaDisciplinaDTO> disciplinas,
|
|
||||||
LocalDateTime geradoEm
|
|
||||||
) {}
|
|
||||||
@@ -10,7 +10,5 @@ public record RespostaEventoDTO(
|
|||||||
String tipo,
|
String tipo,
|
||||||
String local,
|
String local,
|
||||||
String disciplinaId,
|
String disciplinaId,
|
||||||
LocalDateTime dataHora,
|
LocalDateTime dataHora
|
||||||
String status,
|
|
||||||
String nomeDisciplina
|
|
||||||
) {}
|
) {}
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
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
|
|
||||||
) {}
|
|
||||||
@@ -7,7 +7,6 @@ import lombok.EqualsAndHashCode;
|
|||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import org.springframework.data.annotation.Id;
|
import org.springframework.data.annotation.Id;
|
||||||
import org.springframework.data.mongodb.core.index.CompoundIndex;
|
import org.springframework.data.mongodb.core.index.CompoundIndex;
|
||||||
import org.springframework.data.mongodb.core.index.Indexed;
|
|
||||||
import org.springframework.data.mongodb.core.mapping.Document;
|
import org.springframework.data.mongodb.core.mapping.Document;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@@ -21,7 +20,6 @@ public class Disciplina extends EntidadeAuditoria {
|
|||||||
|
|
||||||
@Id
|
@Id
|
||||||
private String id;
|
private String id;
|
||||||
@Indexed
|
|
||||||
private String estudanteId;
|
private String estudanteId;
|
||||||
private String nome;
|
private String nome;
|
||||||
private String professor;
|
private String professor;
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ import org.springframework.data.annotation.Id;
|
|||||||
import org.springframework.data.mongodb.core.index.Indexed;
|
import org.springframework.data.mongodb.core.index.Indexed;
|
||||||
import org.springframework.data.mongodb.core.mapping.Document;
|
import org.springframework.data.mongodb.core.mapping.Document;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@Builder
|
@Builder
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@@ -32,10 +30,4 @@ public class Estudante extends EntidadeAuditoria {
|
|||||||
private String curso;
|
private String curso;
|
||||||
|
|
||||||
private Integer periodo;
|
private Integer periodo;
|
||||||
|
|
||||||
private Boolean consentimentoLgpd;
|
|
||||||
|
|
||||||
private LocalDateTime dataConsentimento;
|
|
||||||
|
|
||||||
private String versaoTermos;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import lombok.EqualsAndHashCode;
|
|||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import org.springframework.data.annotation.Id;
|
import org.springframework.data.annotation.Id;
|
||||||
import org.springframework.data.mongodb.core.index.CompoundIndex;
|
import org.springframework.data.mongodb.core.index.CompoundIndex;
|
||||||
import org.springframework.data.mongodb.core.index.Indexed;
|
|
||||||
import org.springframework.data.mongodb.core.mapping.Document;
|
import org.springframework.data.mongodb.core.mapping.Document;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@@ -23,7 +22,6 @@ public class Evento extends EntidadeAuditoria {
|
|||||||
|
|
||||||
@Id
|
@Id
|
||||||
private String id;
|
private String id;
|
||||||
@Indexed
|
|
||||||
private String estudanteId;
|
private String estudanteId;
|
||||||
private String titulo;
|
private String titulo;
|
||||||
private String descricao;
|
private String descricao;
|
||||||
@@ -31,13 +29,8 @@ public class Evento extends EntidadeAuditoria {
|
|||||||
private String local;
|
private String local;
|
||||||
private String disciplinaId;
|
private String disciplinaId;
|
||||||
private LocalDateTime dataHora;
|
private LocalDateTime dataHora;
|
||||||
private StatusEvento status;
|
|
||||||
|
|
||||||
public enum TipoEvento {
|
public enum TipoEvento {
|
||||||
AULA, PROVA, TRABALHO, ESTUDO, EXAME, OUTRO
|
AULA, PROVA, TRABALHO, ESTUDO, EXAME, OUTRO
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum StatusEvento {
|
|
||||||
ATIVO, CANCELADO
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
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.Indexed;
|
|
||||||
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;
|
|
||||||
@Indexed
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,6 @@ import lombok.NoArgsConstructor;
|
|||||||
import org.springframework.data.annotation.Id;
|
import org.springframework.data.annotation.Id;
|
||||||
import org.springframework.data.mongodb.core.index.CompoundIndex;
|
import org.springframework.data.mongodb.core.index.CompoundIndex;
|
||||||
import org.springframework.data.mongodb.core.index.CompoundIndexes;
|
import org.springframework.data.mongodb.core.index.CompoundIndexes;
|
||||||
import org.springframework.data.mongodb.core.index.Indexed;
|
|
||||||
import org.springframework.data.mongodb.core.mapping.Document;
|
import org.springframework.data.mongodb.core.mapping.Document;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
@@ -33,7 +32,6 @@ public class Tarefa extends EntidadeAuditoria {
|
|||||||
private StatusTarefa status;
|
private StatusTarefa status;
|
||||||
private LocalDate dataEntrega;
|
private LocalDate dataEntrega;
|
||||||
private String disciplinaId;
|
private String disciplinaId;
|
||||||
@Indexed
|
|
||||||
private String estudanteId;
|
private String estudanteId;
|
||||||
|
|
||||||
public enum Prioridade {
|
public enum Prioridade {
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package com.agendaestudantil.excecao;
|
package com.agendaestudantil.excecao;
|
||||||
|
|
||||||
import com.agendaestudantil.dto.RespostaApi;
|
import com.agendaestudantil.dto.RespostaApi;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.validation.FieldError;
|
import org.springframework.validation.FieldError;
|
||||||
@@ -18,8 +16,6 @@ import java.util.Map;
|
|||||||
@RestControllerAdvice
|
@RestControllerAdvice
|
||||||
public class ManipuladorExcecaoGlobal {
|
public class ManipuladorExcecaoGlobal {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(ManipuladorExcecaoGlobal.class);
|
|
||||||
|
|
||||||
@ExceptionHandler(ExcecaoRecursoNaoEncontrado.class)
|
@ExceptionHandler(ExcecaoRecursoNaoEncontrado.class)
|
||||||
public ResponseEntity<RespostaApi<Void>> handleResourceNotFound(ExcecaoRecursoNaoEncontrado ex) {
|
public ResponseEntity<RespostaApi<Void>> handleResourceNotFound(ExcecaoRecursoNaoEncontrado ex) {
|
||||||
return ResponseEntity.status(HttpStatus.NOT_FOUND)
|
return ResponseEntity.status(HttpStatus.NOT_FOUND)
|
||||||
@@ -45,18 +41,13 @@ public class ManipuladorExcecaoGlobal {
|
|||||||
|
|
||||||
@ExceptionHandler(ResponseStatusException.class)
|
@ExceptionHandler(ResponseStatusException.class)
|
||||||
public ResponseEntity<RespostaApi<Void>> handleResponseStatusException(ResponseStatusException ex) {
|
public ResponseEntity<RespostaApi<Void>> handleResponseStatusException(ResponseStatusException ex) {
|
||||||
String reason = ex.getReason();
|
|
||||||
if (reason == null || reason.isBlank()) {
|
|
||||||
reason = "Acesso não autorizado";
|
|
||||||
}
|
|
||||||
return ResponseEntity.status(ex.getStatusCode())
|
return ResponseEntity.status(ex.getStatusCode())
|
||||||
.body(new RespostaApi<>(null, reason, LocalDateTime.now()));
|
.body(new RespostaApi<>(null, ex.getReason(), LocalDateTime.now()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(Exception.class)
|
@ExceptionHandler(Exception.class)
|
||||||
public ResponseEntity<RespostaApi<Void>> handleGenericException(Exception ex) {
|
public ResponseEntity<RespostaApi<Void>> handleGenericException(Exception ex) {
|
||||||
log.error("Erro interno no servidor", ex);
|
|
||||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
.body(new RespostaApi<>(null, "Erro interno no servidor", LocalDateTime.now()));
|
.body(new RespostaApi<>(null, "Erro interno no servidor: " + ex.getMessage(), LocalDateTime.now()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,14 +3,9 @@ package com.agendaestudantil.repositorio;
|
|||||||
import com.agendaestudantil.entidade.Disciplina;
|
import com.agendaestudantil.entidade.Disciplina;
|
||||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface DisciplinaRepositorio extends MongoRepository<Disciplina, String> {
|
public interface DisciplinaRepositorio extends MongoRepository<Disciplina, String> {
|
||||||
List<Disciplina> findByEstudanteId(String estudanteId);
|
List<Disciplina> findByEstudanteId(String estudanteId);
|
||||||
|
|
||||||
List<Disciplina> findByIdIn(Collection<String> ids);
|
|
||||||
|
|
||||||
void deleteByEstudanteId(String estudanteId);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,9 +19,4 @@ public interface EventoRepositorio extends MongoRepository<Evento, String> {
|
|||||||
|
|
||||||
@Query("{'estudanteId': ?0, 'dataHora': {$gte: ?1}}")
|
@Query("{'estudanteId': ?0, 'dataHora': {$gte: ?1}}")
|
||||||
List<Evento> findProximosEventosByEstudanteId(String estudanteId, LocalDateTime data);
|
List<Evento> findProximosEventosByEstudanteId(String estudanteId, LocalDateTime data);
|
||||||
|
|
||||||
@Query("{'dataHora': {$gte: ?0, $lte: ?1}}")
|
|
||||||
List<Evento> findEventosNasProximas24h(LocalDateTime inicio, LocalDateTime fim);
|
|
||||||
|
|
||||||
void deleteByEstudanteId(String estudanteId);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
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);
|
|
||||||
|
|
||||||
boolean existsByEstudanteIdAndReferenciaIdAndTipo(String estudanteId, String referenciaId, Notificacao.TipoNotificacao tipo);
|
|
||||||
|
|
||||||
List<Notificacao> findByEstudanteIdAndReferenciaIdAndTipo(String estudanteId, String referenciaId, Notificacao.TipoNotificacao tipo);
|
|
||||||
}
|
|
||||||
@@ -24,9 +24,4 @@ public interface TarefaRepositorio extends MongoRepository<Tarefa, String> {
|
|||||||
|
|
||||||
@Query("{'estudanteId': ?0, 'status': {$ne: ?1}}")
|
@Query("{'estudanteId': ?0, 'status': {$ne: ?1}}")
|
||||||
List<Tarefa> findTarefasPendentesByEstudanteId(String estudanteId, Tarefa.StatusTarefa status);
|
List<Tarefa> findTarefasPendentesByEstudanteId(String estudanteId, Tarefa.StatusTarefa status);
|
||||||
|
|
||||||
@Query("{'status': {$ne: 'CONCLUIDA'}, 'dataEntrega': {$gte: ?0, $lte: ?1}}")
|
|
||||||
List<Tarefa> findTarefasNaoConcluidasComDataEntre(LocalDate inicio, LocalDate fim);
|
|
||||||
|
|
||||||
void deleteByEstudanteId(String estudanteId);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import com.agendaestudantil.dto.RespostaDisciplinaDTO;
|
|||||||
import com.agendaestudantil.entidade.Disciplina;
|
import com.agendaestudantil.entidade.Disciplina;
|
||||||
import com.agendaestudantil.excecao.ExcecaoRecursoNaoEncontrado;
|
import com.agendaestudantil.excecao.ExcecaoRecursoNaoEncontrado;
|
||||||
import com.agendaestudantil.repositorio.DisciplinaRepositorio;
|
import com.agendaestudantil.repositorio.DisciplinaRepositorio;
|
||||||
import com.agendaestudantil.repositorio.TarefaRepositorio;
|
|
||||||
import com.agendaestudantil.repositorio.EventoRepositorio;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
@@ -21,14 +19,9 @@ public class DisciplinaServico {
|
|||||||
private static final Logger log = LoggerFactory.getLogger(DisciplinaServico.class);
|
private static final Logger log = LoggerFactory.getLogger(DisciplinaServico.class);
|
||||||
|
|
||||||
private final DisciplinaRepositorio disciplinaRepositorio;
|
private final DisciplinaRepositorio disciplinaRepositorio;
|
||||||
private final TarefaRepositorio tarefaRepositorio;
|
|
||||||
private final EventoRepositorio eventoRepositorio;
|
|
||||||
|
|
||||||
public DisciplinaServico(DisciplinaRepositorio disciplinaRepositorio, TarefaRepositorio tarefaRepositorio,
|
public DisciplinaServico(DisciplinaRepositorio disciplinaRepositorio) {
|
||||||
EventoRepositorio eventoRepositorio) {
|
|
||||||
this.disciplinaRepositorio = disciplinaRepositorio;
|
this.disciplinaRepositorio = disciplinaRepositorio;
|
||||||
this.tarefaRepositorio = tarefaRepositorio;
|
|
||||||
this.eventoRepositorio = eventoRepositorio;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validarAcesso(String estudanteId) {
|
private void validarAcesso(String estudanteId) {
|
||||||
@@ -78,14 +71,6 @@ public class DisciplinaServico {
|
|||||||
public void excluirDisciplina(String id, String estudanteId) {
|
public void excluirDisciplina(String id, String estudanteId) {
|
||||||
Disciplina disciplina = getDisciplinaEntity(id);
|
Disciplina disciplina = getDisciplinaEntity(id);
|
||||||
validarAcesso(disciplina.getEstudanteId());
|
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);
|
disciplinaRepositorio.delete(disciplina);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,13 @@
|
|||||||
package com.agendaestudantil.servico;
|
package com.agendaestudantil.servico;
|
||||||
|
|
||||||
import com.agendaestudantil.dto.RequisicaoAtualizacaoEstudanteDTO;
|
|
||||||
import com.agendaestudantil.dto.RequisicaoCadastroDTO;
|
import com.agendaestudantil.dto.RequisicaoCadastroDTO;
|
||||||
import com.agendaestudantil.dto.RespostaDadosCompletoDTO;
|
|
||||||
import com.agendaestudantil.dto.RespostaEstudanteDTO;
|
import com.agendaestudantil.dto.RespostaEstudanteDTO;
|
||||||
import com.agendaestudantil.dto.RequisicaoLoginDTO;
|
import com.agendaestudantil.dto.RequisicaoLoginDTO;
|
||||||
import com.agendaestudantil.dto.RespostaLoginDTO;
|
import com.agendaestudantil.dto.RespostaLoginDTO;
|
||||||
import com.agendaestudantil.dto.RequisicaoTrocaSenhaDTO;
|
|
||||||
import com.agendaestudantil.dto.RespostaDisciplinaDTO;
|
|
||||||
import com.agendaestudantil.dto.RespostaEventoDTO;
|
|
||||||
import com.agendaestudantil.dto.RespostaTarefaDTO;
|
|
||||||
import com.agendaestudantil.entidade.Estudante;
|
import com.agendaestudantil.entidade.Estudante;
|
||||||
import com.agendaestudantil.excecao.ExcecaoNegocio;
|
import com.agendaestudantil.excecao.ExcecaoNegocio;
|
||||||
import com.agendaestudantil.excecao.ExcecaoRecursoNaoEncontrado;
|
import com.agendaestudantil.excecao.ExcecaoRecursoNaoEncontrado;
|
||||||
import com.agendaestudantil.repositorio.EstudanteRepositorio;
|
import com.agendaestudantil.repositorio.EstudanteRepositorio;
|
||||||
import com.agendaestudantil.repositorio.EventoRepositorio;
|
|
||||||
import com.agendaestudantil.repositorio.TarefaRepositorio;
|
|
||||||
import com.agendaestudantil.repositorio.DisciplinaRepositorio;
|
|
||||||
import com.agendaestudantil.repositorio.NotificacaoRepositorio;
|
|
||||||
import com.agendaestudantil.utilitario.UtilJwt;
|
import com.agendaestudantil.utilitario.UtilJwt;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -26,8 +16,6 @@ import org.springframework.security.crypto.password.PasswordEncoder;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.web.server.ResponseStatusException;
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@@ -38,32 +26,20 @@ public class EstudanteServico {
|
|||||||
private final EstudanteRepositorio estudanteRepositorio;
|
private final EstudanteRepositorio estudanteRepositorio;
|
||||||
private final PasswordEncoder passwordEncoder;
|
private final PasswordEncoder passwordEncoder;
|
||||||
private final UtilJwt utilJwt;
|
private final UtilJwt utilJwt;
|
||||||
private final TarefaRepositorio tarefaRepositorio;
|
|
||||||
private final EventoRepositorio eventoRepositorio;
|
|
||||||
private final DisciplinaRepositorio disciplinaRepositorio;
|
|
||||||
private final NotificacaoRepositorio notificacaoRepositorio;
|
|
||||||
|
|
||||||
public EstudanteServico(EstudanteRepositorio estudanteRepositorio, PasswordEncoder passwordEncoder,
|
public EstudanteServico(EstudanteRepositorio estudanteRepositorio, PasswordEncoder passwordEncoder,
|
||||||
UtilJwt utilJwt, TarefaRepositorio tarefaRepositorio, EventoRepositorio eventoRepositorio,
|
UtilJwt utilJwt) {
|
||||||
DisciplinaRepositorio disciplinaRepositorio, NotificacaoRepositorio notificacaoRepositorio) {
|
|
||||||
this.estudanteRepositorio = estudanteRepositorio;
|
this.estudanteRepositorio = estudanteRepositorio;
|
||||||
this.passwordEncoder = passwordEncoder;
|
this.passwordEncoder = passwordEncoder;
|
||||||
this.utilJwt = utilJwt;
|
this.utilJwt = utilJwt;
|
||||||
this.tarefaRepositorio = tarefaRepositorio;
|
|
||||||
this.eventoRepositorio = eventoRepositorio;
|
|
||||||
this.disciplinaRepositorio = disciplinaRepositorio;
|
|
||||||
this.notificacaoRepositorio = notificacaoRepositorio;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public RespostaEstudanteDTO cadastrar(RequisicaoCadastroDTO dto) {
|
public RespostaEstudanteDTO cadastrar(RequisicaoCadastroDTO dto) {
|
||||||
log.debug("Acessando metodo cadastrar para email: {}", dto.email());
|
log.debug("Acessando método cadastrar para email: {}", dto.email());
|
||||||
if (!Boolean.TRUE.equals(dto.consentimentoLgpd())) {
|
|
||||||
throw new ExcecaoNegocio("E necessario aceitar os termos para se cadastrar");
|
|
||||||
}
|
|
||||||
Optional<Estudante> existente = estudanteRepositorio.findByEmail(dto.email());
|
Optional<Estudante> existente = estudanteRepositorio.findByEmail(dto.email());
|
||||||
if (existente.isPresent()) {
|
if (existente.isPresent()) {
|
||||||
log.error("Email ja cadastrado: {}", dto.email());
|
log.error("Email já cadastrado: {}", dto.email());
|
||||||
throw new ExcecaoNegocio("Email ja cadastrado");
|
throw new ExcecaoNegocio("Email já cadastrado");
|
||||||
}
|
}
|
||||||
|
|
||||||
Estudante estudante = new Estudante();
|
Estudante estudante = new Estudante();
|
||||||
@@ -72,16 +48,13 @@ public class EstudanteServico {
|
|||||||
estudante.setSenha(passwordEncoder.encode(dto.senha()));
|
estudante.setSenha(passwordEncoder.encode(dto.senha()));
|
||||||
estudante.setCurso(dto.curso());
|
estudante.setCurso(dto.curso());
|
||||||
estudante.setPeriodo(dto.periodo());
|
estudante.setPeriodo(dto.periodo());
|
||||||
estudante.setConsentimentoLgpd(dto.consentimentoLgpd());
|
|
||||||
estudante.setDataConsentimento(LocalDateTime.now());
|
|
||||||
estudante.setVersaoTermos("1.0");
|
|
||||||
|
|
||||||
Estudante salvo = estudanteRepositorio.save(estudante);
|
Estudante salvo = estudanteRepositorio.save(estudante);
|
||||||
return toResponse(salvo);
|
return toResponse(salvo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public RespostaLoginDTO login(RequisicaoLoginDTO dto) {
|
public RespostaLoginDTO login(RequisicaoLoginDTO dto) {
|
||||||
log.debug("Acessando metodo login para email: {}", dto.email());
|
log.debug("Acessando método login para email: {}", dto.email());
|
||||||
Optional<Estudante> estudanteParam = estudanteRepositorio.findByEmail(dto.email());
|
Optional<Estudante> estudanteParam = estudanteRepositorio.findByEmail(dto.email());
|
||||||
|
|
||||||
if (estudanteParam.isEmpty()) {
|
if (estudanteParam.isEmpty()) {
|
||||||
@@ -102,77 +75,10 @@ public class EstudanteServico {
|
|||||||
|
|
||||||
public RespostaEstudanteDTO buscarPorId(String id) {
|
public RespostaEstudanteDTO buscarPorId(String id) {
|
||||||
Estudante estudante = estudanteRepositorio.findById(id)
|
Estudante estudante = estudanteRepositorio.findById(id)
|
||||||
.orElseThrow(() -> new ExcecaoRecursoNaoEncontrado("Estudante nao encontrado"));
|
.orElseThrow(() -> new ExcecaoRecursoNaoEncontrado("Estudante não encontrado"));
|
||||||
return toResponse(estudante);
|
return toResponse(estudante);
|
||||||
}
|
}
|
||||||
|
|
||||||
public RespostaEstudanteDTO atualizar(String id, RequisicaoAtualizacaoEstudanteDTO dto) {
|
|
||||||
Estudante estudante = estudanteRepositorio.findById(id)
|
|
||||||
.orElseThrow(() -> new ExcecaoRecursoNaoEncontrado("Estudante nao 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 nao 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 nao encontrado");
|
|
||||||
}
|
|
||||||
tarefaRepositorio.deleteByEstudanteId(id);
|
|
||||||
eventoRepositorio.deleteByEstudanteId(id);
|
|
||||||
disciplinaRepositorio.deleteByEstudanteId(id);
|
|
||||||
notificacaoRepositorio.deleteByEstudanteId(id);
|
|
||||||
estudanteRepositorio.deleteById(id);
|
|
||||||
log.info("Conta e todos os dados do estudante {} removidos (LGPD Art. 18-VI)", id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RespostaDadosCompletoDTO exportarDados(String estudanteId) {
|
|
||||||
RespostaEstudanteDTO estudante = buscarPorId(estudanteId);
|
|
||||||
|
|
||||||
List<RespostaTarefaDTO> tarefas = tarefaRepositorio
|
|
||||||
.findByEstudanteId(estudanteId).stream()
|
|
||||||
.map(t -> new RespostaTarefaDTO(
|
|
||||||
t.getId(), t.getTitulo(), t.getDescricao(),
|
|
||||||
t.getPrioridade() != null ? t.getPrioridade().name() : null,
|
|
||||||
t.getStatus() != null ? t.getStatus().name() : null,
|
|
||||||
t.getDataEntrega(), t.getDisciplinaId(), t.getEstudanteId()))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
List<RespostaEventoDTO> eventos = eventoRepositorio
|
|
||||||
.findByEstudanteId(estudanteId).stream()
|
|
||||||
.map(e -> new RespostaEventoDTO(
|
|
||||||
e.getId(), e.getEstudanteId(), e.getTitulo(), e.getDescricao(),
|
|
||||||
e.getTipo() != null ? e.getTipo().name() : null,
|
|
||||||
e.getLocal(), e.getDisciplinaId(), e.getDataHora(),
|
|
||||||
e.getStatus() != null ? e.getStatus().name() : null,
|
|
||||||
null))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
List<RespostaDisciplinaDTO> disciplinas = disciplinaRepositorio
|
|
||||||
.findByEstudanteId(estudanteId).stream()
|
|
||||||
.map(d -> new RespostaDisciplinaDTO(
|
|
||||||
d.getId(), d.getEstudanteId(), d.getNome(), d.getProfessor(), d.getSala(), d.getCor()))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
return new RespostaDadosCompletoDTO(estudante, tarefas, eventos, disciplinas, LocalDateTime.now());
|
|
||||||
}
|
|
||||||
|
|
||||||
private RespostaEstudanteDTO toResponse(Estudante estudante) {
|
private RespostaEstudanteDTO toResponse(Estudante estudante) {
|
||||||
return new RespostaEstudanteDTO(
|
return new RespostaEstudanteDTO(
|
||||||
estudante.getId(),
|
estudante.getId(),
|
||||||
|
|||||||
@@ -1,22 +1,18 @@
|
|||||||
package com.agendaestudantil.servico;
|
package com.agendaestudantil.servico;
|
||||||
|
|
||||||
import com.agendaestudantil.dto.RequisicaoEventoDTO;
|
|
||||||
import com.agendaestudantil.dto.RespostaEventoDTO;
|
import com.agendaestudantil.dto.RespostaEventoDTO;
|
||||||
import com.agendaestudantil.entidade.Disciplina;
|
|
||||||
import com.agendaestudantil.entidade.Evento;
|
import com.agendaestudantil.entidade.Evento;
|
||||||
import com.agendaestudantil.excecao.ExcecaoNegocio;
|
|
||||||
import com.agendaestudantil.excecao.ExcecaoRecursoNaoEncontrado;
|
import com.agendaestudantil.excecao.ExcecaoRecursoNaoEncontrado;
|
||||||
import com.agendaestudantil.repositorio.DisciplinaRepositorio;
|
|
||||||
import com.agendaestudantil.repositorio.EventoRepositorio;
|
import com.agendaestudantil.repositorio.EventoRepositorio;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class EventoServico {
|
public class EventoServico {
|
||||||
@@ -24,29 +20,16 @@ public class EventoServico {
|
|||||||
private static final Logger log = LoggerFactory.getLogger(EventoServico.class);
|
private static final Logger log = LoggerFactory.getLogger(EventoServico.class);
|
||||||
|
|
||||||
private final EventoRepositorio eventoRepositorio;
|
private final EventoRepositorio eventoRepositorio;
|
||||||
private final DisciplinaRepositorio disciplinaRepositorio;
|
|
||||||
|
|
||||||
public EventoServico(EventoRepositorio eventoRepositorio, DisciplinaRepositorio disciplinaRepositorio) {
|
public EventoServico(EventoRepositorio eventoRepositorio) {
|
||||||
this.eventoRepositorio = eventoRepositorio;
|
this.eventoRepositorio = eventoRepositorio;
|
||||||
this.disciplinaRepositorio = disciplinaRepositorio;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validarAcesso(String estudanteId) {
|
private void validarAcesso(String estudanteId) {
|
||||||
String authUser = SecurityContextHolder.getContext().getAuthentication().getName();
|
String authUser = SecurityContextHolder.getContext().getAuthentication().getName();
|
||||||
if (!authUser.equals(estudanteId)) {
|
if (!authUser.equals(estudanteId)) {
|
||||||
log.error("Acesso negado. Usuario {} tentou acessar recurso do estudante {}", authUser, estudanteId);
|
log.error("Acesso negado. Usuário {} tentou acessar recurso do estudante {}", authUser, estudanteId);
|
||||||
throw new ExcecaoNegocio("Acesso negado");
|
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");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,106 +37,66 @@ public class EventoServico {
|
|||||||
log.debug("Criando evento para estudante: {}", estudanteId);
|
log.debug("Criando evento para estudante: {}", estudanteId);
|
||||||
validarAcesso(estudanteId);
|
validarAcesso(estudanteId);
|
||||||
|
|
||||||
if (evento.getDisciplinaId() != null) {
|
|
||||||
validarDisciplina(evento.getDisciplinaId(), estudanteId);
|
|
||||||
}
|
|
||||||
|
|
||||||
evento.setEstudanteId(estudanteId);
|
evento.setEstudanteId(estudanteId);
|
||||||
Evento salvo = eventoRepositorio.save(evento);
|
Evento salvo = eventoRepositorio.save(evento);
|
||||||
return toDTO(salvo, Map.of());
|
return toDTO(salvo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<RespostaEventoDTO> listarPorEstudante(String estudanteId) {
|
public List<RespostaEventoDTO> listarPorEstudante(String estudanteId) {
|
||||||
log.debug("Listando eventos para estudante: {}", estudanteId);
|
log.debug("Listando eventos para estudante: {}", estudanteId);
|
||||||
validarAcesso(estudanteId);
|
validarAcesso(estudanteId);
|
||||||
|
|
||||||
List<Evento> eventos = eventoRepositorio.findByEstudanteId(estudanteId);
|
return eventoRepositorio.findByEstudanteId(estudanteId).stream()
|
||||||
Map<String, String> disciplinasMap = resolverDisciplinas(eventos);
|
.map(this::toDTO)
|
||||||
return eventos.stream()
|
|
||||||
.map(e -> toDTO(e, disciplinasMap))
|
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<RespostaEventoDTO> listarPorPeriodo(String estudanteId, java.time.LocalDateTime inicio, java.time.LocalDateTime fim) {
|
public List<RespostaEventoDTO> listarPorPeriodo(String estudanteId, LocalDateTime inicio, LocalDateTime fim) {
|
||||||
validarAcesso(estudanteId);
|
validarAcesso(estudanteId);
|
||||||
List<Evento> eventos = eventoRepositorio.findByEstudanteIdAndDataHoraBetween(estudanteId, inicio, fim);
|
return eventoRepositorio.findByEstudanteIdAndDataHoraBetween(estudanteId, inicio, fim).stream()
|
||||||
Map<String, String> disciplinasMap = resolverDisciplinas(eventos);
|
.map(this::toDTO)
|
||||||
return eventos.stream()
|
|
||||||
.map(e -> toDTO(e, disciplinasMap))
|
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<RespostaEventoDTO> listarProximosEventos(String estudanteId) {
|
public List<RespostaEventoDTO> listarProximosEventos(String estudanteId) {
|
||||||
validarAcesso(estudanteId);
|
validarAcesso(estudanteId);
|
||||||
List<Evento> eventos = eventoRepositorio.findProximosEventosByEstudanteId(estudanteId, java.time.LocalDateTime.now());
|
return eventoRepositorio.findProximosEventosByEstudanteId(estudanteId, LocalDateTime.now()).stream()
|
||||||
Map<String, String> disciplinasMap = resolverDisciplinas(eventos);
|
.map(this::toDTO)
|
||||||
return eventos.stream()
|
|
||||||
.map(e -> toDTO(e, disciplinasMap))
|
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public RespostaEventoDTO buscarPorId(String id) {
|
public RespostaEventoDTO buscarPorId(String id, String estudanteId) {
|
||||||
Evento evento = getEventoEntity(id);
|
Evento evento = getEventoEntity(id);
|
||||||
validarAcesso(evento.getEstudanteId());
|
validarAcesso(evento.getEstudanteId());
|
||||||
return toDTO(evento, Map.of());
|
return toDTO(evento);
|
||||||
}
|
}
|
||||||
|
|
||||||
public RespostaEventoDTO atualizarEvento(String id, RequisicaoEventoDTO dto) {
|
public RespostaEventoDTO atualizarEvento(String id, Evento eventoAtualizado, String estudanteId) {
|
||||||
Evento evento = getEventoEntity(id);
|
Evento evento = getEventoEntity(id);
|
||||||
validarAcesso(evento.getEstudanteId());
|
validarAcesso(evento.getEstudanteId());
|
||||||
|
|
||||||
evento.setTitulo(dto.titulo());
|
evento.setTitulo(eventoAtualizado.getTitulo());
|
||||||
evento.setDescricao(dto.descricao());
|
evento.setDescricao(eventoAtualizado.getDescricao());
|
||||||
evento.setTipo(dto.tipo());
|
evento.setTipo(eventoAtualizado.getTipo());
|
||||||
evento.setLocal(dto.local());
|
evento.setLocal(eventoAtualizado.getLocal());
|
||||||
evento.setDataHora(dto.dataHora());
|
evento.setDataHora(eventoAtualizado.getDataHora());
|
||||||
|
evento.setDisciplinaId(eventoAtualizado.getDisciplinaId());
|
||||||
|
|
||||||
if (dto.disciplinaId() != null) {
|
return toDTO(eventoRepositorio.save(evento));
|
||||||
validarDisciplina(dto.disciplinaId(), evento.getEstudanteId());
|
|
||||||
evento.setDisciplinaId(dto.disciplinaId());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return toDTO(eventoRepositorio.save(evento), Map.of());
|
public void excluirEvento(String id, String estudanteId) {
|
||||||
}
|
|
||||||
|
|
||||||
public void excluirEvento(String id) {
|
|
||||||
Evento evento = getEventoEntity(id);
|
Evento evento = getEventoEntity(id);
|
||||||
validarAcesso(evento.getEstudanteId());
|
validarAcesso(evento.getEstudanteId());
|
||||||
eventoRepositorio.delete(evento);
|
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), Map.of());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Evento getEventoEntity(String id) {
|
private Evento getEventoEntity(String id) {
|
||||||
return eventoRepositorio.findById(id)
|
return eventoRepositorio.findById(id)
|
||||||
.orElseThrow(() -> new ExcecaoRecursoNaoEncontrado("Evento não encontrado"));
|
.orElseThrow(() -> new ExcecaoRecursoNaoEncontrado("Evento não encontrado"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, String> resolverDisciplinas(List<Evento> eventos) {
|
private RespostaEventoDTO toDTO(Evento evento) {
|
||||||
Set<String> disciplinaIds = eventos.stream()
|
|
||||||
.map(Evento::getDisciplinaId)
|
|
||||||
.filter(id -> id != null && !id.isBlank())
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
|
|
||||||
if (disciplinaIds.isEmpty()) {
|
|
||||||
return Map.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
return disciplinaRepositorio.findByIdIn(disciplinaIds).stream()
|
|
||||||
.collect(Collectors.toMap(Disciplina::getId, Disciplina::getNome));
|
|
||||||
}
|
|
||||||
|
|
||||||
private RespostaEventoDTO toDTO(Evento evento, Map<String, String> disciplinasMap) {
|
|
||||||
String nomeDisciplina = null;
|
|
||||||
final String disciplinaId = evento.getDisciplinaId();
|
|
||||||
if (disciplinaId != null) {
|
|
||||||
nomeDisciplina = disciplinasMap.get(disciplinaId);
|
|
||||||
}
|
|
||||||
return new RespostaEventoDTO(
|
return new RespostaEventoDTO(
|
||||||
evento.getId(),
|
evento.getId(),
|
||||||
evento.getEstudanteId(),
|
evento.getEstudanteId(),
|
||||||
@@ -162,9 +105,7 @@ public class EventoServico {
|
|||||||
evento.getTipo() != null ? evento.getTipo().name() : null,
|
evento.getTipo() != null ? evento.getTipo().name() : null,
|
||||||
evento.getLocal(),
|
evento.getLocal(),
|
||||||
evento.getDisciplinaId(),
|
evento.getDisciplinaId(),
|
||||||
evento.getDataHora(),
|
evento.getDataHora()
|
||||||
evento.getStatus() != null ? evento.getStatus().name() : null,
|
|
||||||
nomeDisciplina
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,132 +0,0 @@
|
|||||||
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();
|
|
||||||
LocalDate ontem = hoje.minusDays(1);
|
|
||||||
LocalDate tresDiasDepois = hoje.plusDays(3);
|
|
||||||
|
|
||||||
List<Tarefa> tarefas = tarefaRepositorio.findTarefasNaoConcluidasComDataEntre(ontem, tresDiasDepois);
|
|
||||||
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.existsByEstudanteIdAndReferenciaIdAndTipo(
|
|
||||||
tarefa.getEstudanteId(), tarefa.getId(), Notificacao.TipoNotificacao.TAREFA_ATRASADA)) {
|
|
||||||
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) {
|
|
||||||
boolean jaNotificouHoje = notificacaoRepositorio.findByEstudanteIdAndReferenciaIdAndTipo(
|
|
||||||
tarefa.getEstudanteId(), tarefa.getId(), Notificacao.TipoNotificacao.PRAZO_PROXIMO)
|
|
||||||
.stream()
|
|
||||||
.anyMatch(n -> n.getDataGeracao().toLocalDate().equals(hoje));
|
|
||||||
|
|
||||||
if (!jaNotificouHoje) {
|
|
||||||
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.findEventosNasProximas24h(agora, em24Horas);
|
|
||||||
for (Evento evento : eventos) {
|
|
||||||
if (evento.getDataHora() == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!notificacaoRepositorio.existsByEstudanteIdAndReferenciaIdAndTipo(
|
|
||||||
evento.getEstudanteId(), evento.getId(), Notificacao.TipoNotificacao.EVENTO_PROXIMO)) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
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()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,13 +3,14 @@ package com.agendaestudantil.servico;
|
|||||||
import com.agendaestudantil.dto.RequisicaoTarefaDTO;
|
import com.agendaestudantil.dto.RequisicaoTarefaDTO;
|
||||||
import com.agendaestudantil.dto.RespostaTarefaDTO;
|
import com.agendaestudantil.dto.RespostaTarefaDTO;
|
||||||
import com.agendaestudantil.entidade.Tarefa;
|
import com.agendaestudantil.entidade.Tarefa;
|
||||||
import com.agendaestudantil.excecao.ExcecaoNegocio;
|
|
||||||
import com.agendaestudantil.excecao.ExcecaoRecursoNaoEncontrado;
|
import com.agendaestudantil.excecao.ExcecaoRecursoNaoEncontrado;
|
||||||
import com.agendaestudantil.repositorio.TarefaRepositorio;
|
import com.agendaestudantil.repositorio.TarefaRepositorio;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -28,8 +29,8 @@ public class TarefaServico {
|
|||||||
private void validarAcesso(String estudanteId) {
|
private void validarAcesso(String estudanteId) {
|
||||||
String authUser = SecurityContextHolder.getContext().getAuthentication().getName();
|
String authUser = SecurityContextHolder.getContext().getAuthentication().getName();
|
||||||
if (!authUser.equals(estudanteId)) {
|
if (!authUser.equals(estudanteId)) {
|
||||||
log.error("Acesso negado. Usuario {} tentou acessar recurso do estudante {}", authUser, estudanteId);
|
log.error("Acesso negado. Usuário {} tentou acessar recurso do estudante {}", authUser, estudanteId);
|
||||||
throw new ExcecaoNegocio("Acesso negado");
|
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Acesso negado");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,8 +39,8 @@ public class TarefaServico {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public RespostaTarefaDTO criarTarefa(RequisicaoTarefaDTO dto) {
|
public RespostaTarefaDTO criarTarefa(RequisicaoTarefaDTO dto) {
|
||||||
String estudanteId = SecurityContextHolder.getContext().getAuthentication().getName();
|
log.debug("Criando tarefa para estudante: {}", dto.estudanteId());
|
||||||
log.debug("Criando tarefa para estudante: {}", estudanteId);
|
validarAcesso(dto.estudanteId());
|
||||||
|
|
||||||
Tarefa tarefa = new Tarefa();
|
Tarefa tarefa = new Tarefa();
|
||||||
tarefa.setTitulo(dto.titulo());
|
tarefa.setTitulo(dto.titulo());
|
||||||
@@ -47,7 +48,7 @@ public class TarefaServico {
|
|||||||
tarefa.setPrioridade(dto.prioridade() != null ? dto.prioridade() : Tarefa.Prioridade.MEDIA);
|
tarefa.setPrioridade(dto.prioridade() != null ? dto.prioridade() : Tarefa.Prioridade.MEDIA);
|
||||||
tarefa.setStatus(dto.status() != null ? dto.status() : Tarefa.StatusTarefa.PENDENTE);
|
tarefa.setStatus(dto.status() != null ? dto.status() : Tarefa.StatusTarefa.PENDENTE);
|
||||||
tarefa.setDataEntrega(dto.dataEntrega());
|
tarefa.setDataEntrega(dto.dataEntrega());
|
||||||
tarefa.setEstudanteId(estudanteId);
|
tarefa.setEstudanteId(dto.estudanteId());
|
||||||
|
|
||||||
if (dto.disciplinaId() != null) {
|
if (dto.disciplinaId() != null) {
|
||||||
tarefa.setDisciplinaId(dto.disciplinaId());
|
tarefa.setDisciplinaId(dto.disciplinaId());
|
||||||
@@ -67,7 +68,7 @@ public class TarefaServico {
|
|||||||
public List<RespostaTarefaDTO> listarTarefasPendentes(String estudanteId) {
|
public List<RespostaTarefaDTO> listarTarefasPendentes(String estudanteId) {
|
||||||
log.debug("Listando tarefas pendentes para estudante: {}", estudanteId);
|
log.debug("Listando tarefas pendentes para estudante: {}", estudanteId);
|
||||||
validarAcesso(estudanteId);
|
validarAcesso(estudanteId);
|
||||||
return tarefaRepositorio.findByEstudanteIdAndStatus(estudanteId, Tarefa.StatusTarefa.PENDENTE).stream()
|
return tarefaRepositorio.findTarefasPendentesByEstudanteId(estudanteId, Tarefa.StatusTarefa.CONCLUIDA).stream()
|
||||||
.map(this::toDTO)
|
.map(this::toDTO)
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
@@ -92,18 +93,13 @@ public class TarefaServico {
|
|||||||
|
|
||||||
public RespostaTarefaDTO atualizarTarefa(String id, RequisicaoTarefaDTO dto) {
|
public RespostaTarefaDTO atualizarTarefa(String id, RequisicaoTarefaDTO dto) {
|
||||||
Tarefa tarefa = getTarefaEntity(id);
|
Tarefa tarefa = getTarefaEntity(id);
|
||||||
String authUser = SecurityContextHolder.getContext().getAuthentication().getName();
|
validarAcesso(tarefa.getEstudanteId());
|
||||||
if (!authUser.equals(tarefa.getEstudanteId())) {
|
|
||||||
throw new ExcecaoNegocio("Acesso negado");
|
|
||||||
}
|
|
||||||
|
|
||||||
tarefa.setTitulo(dto.titulo());
|
tarefa.setTitulo(dto.titulo());
|
||||||
tarefa.setDescricao(dto.descricao());
|
tarefa.setDescricao(dto.descricao());
|
||||||
tarefa.setPrioridade(dto.prioridade() != null ? dto.prioridade() : tarefa.getPrioridade());
|
tarefa.setPrioridade(dto.prioridade() != null ? dto.prioridade() : tarefa.getPrioridade());
|
||||||
tarefa.setStatus(dto.status() != null ? dto.status() : tarefa.getStatus());
|
tarefa.setStatus(dto.status() != null ? dto.status() : tarefa.getStatus());
|
||||||
if (dto.dataEntrega() != null) {
|
|
||||||
tarefa.setDataEntrega(dto.dataEntrega());
|
tarefa.setDataEntrega(dto.dataEntrega());
|
||||||
}
|
|
||||||
|
|
||||||
if (dto.disciplinaId() != null) {
|
if (dto.disciplinaId() != null) {
|
||||||
tarefa.setDisciplinaId(dto.disciplinaId());
|
tarefa.setDisciplinaId(dto.disciplinaId());
|
||||||
|
|||||||
@@ -14,11 +14,9 @@ import java.util.Date;
|
|||||||
public class UtilJwt {
|
public class UtilJwt {
|
||||||
|
|
||||||
private final String secret;
|
private final String secret;
|
||||||
private final long jwtExpiration;
|
|
||||||
|
|
||||||
public UtilJwt(@Value("${jwt.secret}") String secret, @Value("${jwt.expiration}") long jwtExpiration) {
|
public UtilJwt(@Value("${jwt.secret}") String secret) {
|
||||||
this.secret = secret;
|
this.secret = secret;
|
||||||
this.jwtExpiration = jwtExpiration;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private SecretKey getSigningKey() {
|
private SecretKey getSigningKey() {
|
||||||
@@ -27,7 +25,7 @@ public class UtilJwt {
|
|||||||
|
|
||||||
public String generateToken(String estudanteId) {
|
public String generateToken(String estudanteId) {
|
||||||
Date now = new Date();
|
Date now = new Date();
|
||||||
Date expiryDate = new Date(now.getTime() + jwtExpiration);
|
Date expiryDate = new Date(now.getTime() + 24 * 60 * 60 * 1000L);
|
||||||
|
|
||||||
return Jwts.builder()
|
return Jwts.builder()
|
||||||
.subject(estudanteId)
|
.subject(estudanteId)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
spring.data.mongodb.uri=${MONGO_URI:mongodb://localhost:27017/agenda_estudantil}
|
spring.data.mongodb.uri=${MONGO_URI:mongodb://localhost:27017/agenda_estudantil}
|
||||||
cors.allowed.origins=${CORS_ORIGINS:http://localhost:8080,http://localhost:3000}
|
cors.allowed.origins=${CORS_ORIGINS:http://localhost:8080,http://localhost:3000}
|
||||||
jwt.secret=${JWT_SECRET:8a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t1u2v3w4x5y6z7A8B9C0D1E2F3}
|
jwt.secret=${JWT_SECRET:8a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t1u2v3w4x5y6z7A8B9C0D1E2F3}
|
||||||
jwt.expiration=${JWT_EXPIRATION:86400000}
|
|
||||||
logging.level.org.springframework.data.mongodb=DEBUG
|
logging.level.org.springframework.data.mongodb=DEBUG
|
||||||
springdoc.api-docs.enabled=true
|
springdoc.api-docs.enabled=true
|
||||||
springdoc.swagger-ui.enabled=true
|
springdoc.swagger-ui.enabled=true
|
||||||
|
|||||||
@@ -94,89 +94,3 @@ a:hover { text-decoration: underline; }
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.campo-consentimento {
|
|
||||||
background: #f9fafb;
|
|
||||||
border: 1px solid #e5e7eb;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 12px 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label-checkbox {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 13px;
|
|
||||||
color: #374151;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label-checkbox input[type="checkbox"] {
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
margin-top: 2px;
|
|
||||||
accent-color: #c0392b;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label-checkbox a {
|
|
||||||
color: #c0392b;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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; }
|
|
||||||
|
|
||||||
[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; }
|
|
||||||
|
|
||||||
[data-theme="dark"] .campo-consentimento {
|
|
||||||
background: #1a1a1a;
|
|
||||||
border-color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .label-checkbox { color: #e8e8e8; }
|
|
||||||
|
|||||||
@@ -6,13 +6,11 @@
|
|||||||
<link rel="icon" href="imagens/icone.png">
|
<link rel="icon" href="imagens/icone.png">
|
||||||
<link rel="stylesheet" href="cadastro.css">
|
<link rel="stylesheet" href="cadastro.css">
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
<title>Cadastro - Focus Agenda</title>
|
<title>Cadastro – Focus Agenda</title>
|
||||||
<script src="theme.js"></script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="topo">
|
<div id="topo">
|
||||||
<h1 id="textotop">Focus Agenda</h1>
|
<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>
|
||||||
|
|
||||||
<div id="log">
|
<div id="log">
|
||||||
@@ -34,29 +32,29 @@
|
|||||||
|
|
||||||
<div class="campo">
|
<div class="campo">
|
||||||
<label for="cursoid">Curso</label>
|
<label for="cursoid">Curso</label>
|
||||||
<input type="text" placeholder="Ex: Tecnico em Informatica" id="cursoid" required>
|
<input type="text" placeholder="Ex: Técnico em Informática" id="cursoid" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="linha-dupla">
|
<div class="linha-dupla">
|
||||||
<div class="campo">
|
<div class="campo">
|
||||||
<label for="periodoid">Periodo</label>
|
<label for="periodoid">Período</label>
|
||||||
<select id="periodoid" required>
|
<select id="periodoid" required>
|
||||||
<option value="">Selecione</option>
|
<option value="">Selecione</option>
|
||||||
<option value="1">1</option>
|
<option value="1">1º</option>
|
||||||
<option value="2">2</option>
|
<option value="2">2º</option>
|
||||||
<option value="3">3</option>
|
<option value="3">3º</option>
|
||||||
<option value="4">4</option>
|
<option value="4">4º</option>
|
||||||
<option value="5">5</option>
|
<option value="5">5º</option>
|
||||||
<option value="6">6</option>
|
<option value="6">6º</option>
|
||||||
<option value="7">7</option>
|
<option value="7">7º</option>
|
||||||
<option value="8">8</option>
|
<option value="8">8º</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="campo">
|
<div class="campo">
|
||||||
<label for="senhaid">Senha</label>
|
<label for="senhaid">Senha</label>
|
||||||
<input type="password" placeholder="Minimo 6 caracteres" id="senhaid" autocomplete="new-password" required minlength="6">
|
<input type="password" placeholder="Mínimo 6 caracteres" id="senhaid" autocomplete="new-password" required minlength="6">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="campo">
|
<div class="campo">
|
||||||
@@ -64,25 +62,10 @@
|
|||||||
<input type="password" placeholder="Confirme sua senha" id="csenhaid" autocomplete="new-password" required>
|
<input type="password" placeholder="Confirme sua senha" id="csenhaid" autocomplete="new-password" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="campo campo-consentimento">
|
|
||||||
<label class="label-checkbox">
|
|
||||||
<input type="checkbox" id="consentimentoId" required>
|
|
||||||
<span>
|
|
||||||
Li e concordo com a
|
|
||||||
<a href="politica-privacidade.html" target="_blank">Politica de Privacidade</a>
|
|
||||||
e autorizo o tratamento dos meus dados pessoais para uso do FocusAgenda,
|
|
||||||
conforme a LGPD (Lei n 13.709/2018).
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<span class="erro-inline" id="erroConsentimento" style="display:none; color:#c0392b; font-size:12px;">
|
|
||||||
Voce precisa aceitar os termos para continuar.
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="submit" id="logbtn">Cadastrar</button>
|
<button type="submit" id="logbtn">Cadastrar</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<p class="mens"><a href="login.html">Ja tem uma conta?</a></p>
|
<p class="mens"><a href="login.html">Já tem uma conta?</a></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -114,7 +97,6 @@
|
|||||||
const periodo = parseInt(document.getElementById('periodoid').value, 10);
|
const periodo = parseInt(document.getElementById('periodoid').value, 10);
|
||||||
const senha = document.getElementById('senhaid').value;
|
const senha = document.getElementById('senhaid').value;
|
||||||
const csenha = document.getElementById('csenhaid').value;
|
const csenha = document.getElementById('csenhaid').value;
|
||||||
const consentimento = document.getElementById('consentimentoId').checked;
|
|
||||||
|
|
||||||
if (!nome || !email || !curso || !periodo || !senha) {
|
if (!nome || !email || !curso || !periodo || !senha) {
|
||||||
mostrarErro('Preencha todos os campos.');
|
mostrarErro('Preencha todos os campos.');
|
||||||
@@ -127,17 +109,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (senha !== csenha) {
|
if (senha !== csenha) {
|
||||||
mostrarErro('As senhas nao conferem.');
|
mostrarErro('As senhas não conferem.');
|
||||||
document.getElementById('csenhaid').focus();
|
document.getElementById('csenhaid').focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!consentimento) {
|
|
||||||
document.getElementById('erroConsentimento').style.display = 'block';
|
|
||||||
mostrarErro('Voce precisa aceitar os termos para continuar.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
btn.textContent = 'Cadastrando...';
|
btn.textContent = 'Cadastrando...';
|
||||||
|
|
||||||
@@ -145,12 +121,13 @@
|
|||||||
const res = await fetch('/api/estudantes/cadastro', {
|
const res = await fetch('/api/estudantes/cadastro', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ nome, email, senha, curso, periodo, consentimentoLgpd: true })
|
body: JSON.stringify({ nome, email, senha, curso, periodo })
|
||||||
});
|
});
|
||||||
|
|
||||||
const json = await res.json();
|
const json = await res.json();
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
|
// Erros de validação vêm como objeto
|
||||||
if (typeof json.data === 'object' && json.data !== null) {
|
if (typeof json.data === 'object' && json.data !== null) {
|
||||||
const msgs = Object.values(json.data).join('; ');
|
const msgs = Object.values(json.data).join('; ');
|
||||||
mostrarErro(msgs);
|
mostrarErro(msgs);
|
||||||
@@ -163,7 +140,7 @@
|
|||||||
mostrarSucesso('Conta criada! Redirecionando para o login...');
|
mostrarSucesso('Conta criada! Redirecionando para o login...');
|
||||||
setTimeout(() => window.location.href = 'login.html', 2000);
|
setTimeout(() => window.location.href = 'login.html', 2000);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
mostrarErro('Erro de conexao. Verifique se o servidor esta rodando.');
|
mostrarErro('Erro de conexão. Verifique se o servidor está rodando.');
|
||||||
} finally {
|
} finally {
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
btn.textContent = 'Cadastrar';
|
btn.textContent = 'Cadastrar';
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ body {
|
|||||||
background: #f5f5f5;
|
background: #f5f5f5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== HEADER ===== */
|
||||||
#header {
|
#header {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
@@ -18,6 +19,7 @@ body {
|
|||||||
|
|
||||||
#title { color: #fff; padding-left: 20px; font-size: 28px; }
|
#title { color: #fff; padding-left: 20px; font-size: 28px; }
|
||||||
|
|
||||||
|
/* ===== BARRA ESQUERDA ===== */
|
||||||
#barraesquerda {
|
#barraesquerda {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 50px; left: 0;
|
top: 50px; left: 0;
|
||||||
@@ -33,6 +35,7 @@ body {
|
|||||||
z-index: 5;
|
z-index: 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Mini Calendário */
|
||||||
#calendario { margin-top: 10px; }
|
#calendario { margin-top: 10px; }
|
||||||
.calendariotop { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
|
.calendariotop { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
|
||||||
#mes { font-size: 16px; font-weight: 600; }
|
#mes { font-size: 16px; font-weight: 600; }
|
||||||
@@ -48,6 +51,7 @@ body {
|
|||||||
.calendariodia td.today { background: #fff; color: #c0392b; font-weight: 700; border-radius: 50%; }
|
.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%; }
|
.calendariodia td.selecionado { background: rgba(255,255,255,0.35); border-radius: 50%; }
|
||||||
|
|
||||||
|
/* Agenda */
|
||||||
#agenda { margin-top: 18px; }
|
#agenda { margin-top: 18px; }
|
||||||
.agenda-header { font-size: 12px; font-weight: 700; opacity: 0.8; margin-bottom: 8px; letter-spacing: 0.5px; }
|
.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; }
|
.agenda-empty { font-size: 12px; opacity: 0.7; font-style: italic; }
|
||||||
@@ -56,11 +60,13 @@ body {
|
|||||||
.evento .hora { font-size: 11px; opacity: 0.8; }
|
.evento .hora { font-size: 11px; opacity: 0.8; }
|
||||||
.evento .titulo { font-size: 13px; font-weight: 600; }
|
.evento .titulo { font-size: 13px; font-weight: 600; }
|
||||||
|
|
||||||
|
/* Feriados */
|
||||||
#feriados { margin-top: 16px; }
|
#feriados { margin-top: 16px; }
|
||||||
.feriados-header { font-size: 12px; font-weight: 700; opacity: 0.8; margin-bottom: 8px; letter-spacing: 0.5px; }
|
.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; }
|
.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; }
|
.dot { width: 8px; height: 8px; background: #fff; border-radius: 50%; flex-shrink: 0; }
|
||||||
|
|
||||||
|
/* ===== MAIN ===== */
|
||||||
.main {
|
.main {
|
||||||
margin-left: 280px;
|
margin-left: 280px;
|
||||||
margin-top: 50px;
|
margin-top: 50px;
|
||||||
@@ -68,6 +74,7 @@ body {
|
|||||||
min-height: calc(100vh - 50px);
|
min-height: calc(100vh - 50px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Topbar */
|
||||||
.topbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
.topbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
||||||
.topbar h1 { font-size: 24px; color: #1f2937; }
|
.topbar h1 { font-size: 24px; color: #1f2937; }
|
||||||
|
|
||||||
@@ -81,6 +88,7 @@ body {
|
|||||||
.nome { font-size: 14px; font-weight: 600; color: #1f2937; }
|
.nome { font-size: 14px; font-weight: 600; color: #1f2937; }
|
||||||
.cargo { font-size: 12px; color: #6b7280; }
|
.cargo { font-size: 12px; color: #6b7280; }
|
||||||
|
|
||||||
|
/* Botão logout */
|
||||||
#btnLogout {
|
#btnLogout {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 1px solid #d1d5db;
|
border: 1px solid #d1d5db;
|
||||||
@@ -93,34 +101,7 @@ body {
|
|||||||
}
|
}
|
||||||
#btnLogout:hover { background: #fee2e2; border-color: #c0392b; color: #c0392b; }
|
#btnLogout:hover { background: #fee2e2; border-color: #c0392b; color: #c0392b; }
|
||||||
|
|
||||||
#btnConfig {
|
/* Calendar header */
|
||||||
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; }
|
|
||||||
|
|
||||||
.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 { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; flex-wrap: wrap; gap: 10px; }
|
.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; }
|
.mes-nav { display: flex; align-items: center; gap: 12px; }
|
||||||
@@ -132,6 +113,7 @@ body {
|
|||||||
.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 { 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); }
|
.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 {
|
#btnNovoEvento {
|
||||||
background: #c0392b;
|
background: #c0392b;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
@@ -145,22 +127,11 @@ body {
|
|||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
}
|
}
|
||||||
#btnNovoEvento:hover { background: #a03224; }
|
#btnNovoEvento:hover { background: #a03224; }
|
||||||
#btnGerenciarDisciplinas {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
#btnGerenciarDisciplinas: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; }
|
.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); }
|
.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-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 { min-height: 100px; padding: 8px; border-right: 1px solid #f0f0f0; border-bottom: 1px solid #f0f0f0; cursor: pointer; transition: background 0.1s; }
|
||||||
@@ -177,6 +148,7 @@ body {
|
|||||||
.evento-mini.amarelo { background: #d97706; }
|
.evento-mini.amarelo { background: #d97706; }
|
||||||
.mais-eventos { font-size: 10px; color: #6b7280; margin-top: 2px; cursor: pointer; }
|
.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-view { display: grid; grid-template-columns: repeat(7, 1fr); }
|
||||||
.week-col { border-right: 1px solid #e5e7eb; padding: 10px 8px; min-height: 300px; }
|
.week-col { border-right: 1px solid #e5e7eb; padding: 10px 8px; min-height: 300px; }
|
||||||
.week-col:last-child { border-right: none; }
|
.week-col:last-child { border-right: none; }
|
||||||
@@ -186,11 +158,13 @@ body {
|
|||||||
.week-events { display: flex; flex-direction: column; gap: 6px; }
|
.week-events { display: flex; flex-direction: column; gap: 6px; }
|
||||||
.week-empty { font-size: 11px; color: #d1d5db; font-style: italic; }
|
.week-empty { font-size: 11px; color: #d1d5db; font-style: italic; }
|
||||||
|
|
||||||
|
/* Day view */
|
||||||
.day-panel { padding: 20px; }
|
.day-panel { padding: 20px; }
|
||||||
.day-panel-header { font-size: 16px; font-weight: 600; color: #374151; margin-bottom: 16px; }
|
.day-panel-header { font-size: 16px; font-weight: 600; color: #374151; margin-bottom: 16px; }
|
||||||
.day-events { display: flex; flex-direction: column; gap: 10px; }
|
.day-events { display: flex; flex-direction: column; gap: 10px; }
|
||||||
.day-empty { color: #9ca3af; font-style: italic; font-size: 14px; }
|
.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 { border-radius: 6px; padding: 8px 10px; background: #fee2e2; border-left: 4px solid #c0392b; }
|
||||||
.calendar-event.verde { background: #d1fae5; border-color: #16a34a; }
|
.calendar-event.verde { background: #d1fae5; border-color: #16a34a; }
|
||||||
.calendar-event.azul { background: #dbeafe; border-color: #1d4ed8; }
|
.calendar-event.azul { background: #dbeafe; border-color: #1d4ed8; }
|
||||||
@@ -199,10 +173,12 @@ body {
|
|||||||
.calendar-event-hora { font-size: 11px; color: #6b7280; }
|
.calendar-event-hora { font-size: 11px; color: #6b7280; }
|
||||||
.calendar-event-titulo { font-size: 13px; font-weight: 600; color: #1f2937; }
|
.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; }
|
.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; }
|
.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); } }
|
@keyframes spin { to { transform: rotate(360deg); } }
|
||||||
|
|
||||||
|
/* ===== MODAL ===== */
|
||||||
.modal-overlay {
|
.modal-overlay {
|
||||||
display: none;
|
display: none;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -295,6 +271,7 @@ body {
|
|||||||
}
|
}
|
||||||
.btn-perigo:hover { background: #fee2e2; }
|
.btn-perigo:hover { background: #fee2e2; }
|
||||||
|
|
||||||
|
/* Toast de feedback */
|
||||||
#toast {
|
#toast {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 24px;
|
bottom: 24px;
|
||||||
@@ -314,206 +291,10 @@ body {
|
|||||||
#toast.sucesso { background: #065f46; }
|
#toast.sucesso { background: #065f46; }
|
||||||
#toast.erro { background: #b91c1c; }
|
#toast.erro { background: #b91c1c; }
|
||||||
|
|
||||||
|
/* Responsivo */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
#barraesquerda { display: none; }
|
#barraesquerda { display: none; }
|
||||||
.main { margin-left: 0; }
|
.main { margin-left: 0; }
|
||||||
.month-view { font-size: 12px; }
|
.month-view { font-size: 12px; }
|
||||||
.dia-box { min-height: 60px; padding: 4px; }
|
.dia-box { min-height: 60px; padding: 4px; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[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); }
|
|
||||||
|
|
||||||
[data-theme="dark"] #btnGerenciarDisciplinas {
|
|
||||||
background: #c0392b;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
[data-theme="dark"] #btnGerenciarDisciplinas:hover {
|
|
||||||
background: #a03224;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-disc-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
.modal-disc-header h2 {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #1f2937;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
[data-theme="dark"] .modal-disc-header h2 { color: var(--text-primary); }
|
|
||||||
|
|
||||||
.modal-fechar-btn {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
font-size: 22px;
|
|
||||||
line-height: 1;
|
|
||||||
cursor: pointer;
|
|
||||||
color: #9ca3af;
|
|
||||||
padding: 2px 7px;
|
|
||||||
border-radius: 6px;
|
|
||||||
transition: background 0.15s, color 0.15s;
|
|
||||||
}
|
|
||||||
.modal-fechar-btn:hover { background: #f3f4f6; color: #374151; }
|
|
||||||
[data-theme="dark"] .modal-fechar-btn:hover { background: var(--hover-bg); color: var(--text-primary); }
|
|
||||||
|
|
||||||
.modal-corpo-lista {
|
|
||||||
max-height: 310px;
|
|
||||||
overflow-y: auto;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
.modal-corpo-form { margin-bottom: 4px; }
|
|
||||||
|
|
||||||
.disc-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 11px 8px;
|
|
||||||
border-radius: 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background 0.15s;
|
|
||||||
border-bottom: 1px solid #e5e7eb;
|
|
||||||
}
|
|
||||||
.disc-item:last-child { border-bottom: none; }
|
|
||||||
.disc-item:hover { background: #f9fafb; }
|
|
||||||
[data-theme="dark"] .disc-item { border-bottom-color: var(--border-color); }
|
|
||||||
[data-theme="dark"] .disc-item:hover { background: var(--hover-bg); }
|
|
||||||
|
|
||||||
.disc-cor {
|
|
||||||
width: 13px;
|
|
||||||
height: 13px;
|
|
||||||
border-radius: 50%;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
.disc-nome {
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 14px;
|
|
||||||
flex: 1;
|
|
||||||
color: #111827;
|
|
||||||
}
|
|
||||||
[data-theme="dark"] .disc-nome { color: var(--text-primary); }
|
|
||||||
.disc-info {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #6b7280;
|
|
||||||
}
|
|
||||||
[data-theme="dark"] .disc-info { color: var(--text-secondary); }
|
|
||||||
.disc-seta {
|
|
||||||
color: #9ca3af;
|
|
||||||
font-size: 20px;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,333 +0,0 @@
|
|||||||
* { 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.secao-descricao {
|
|
||||||
font-size: 13px;
|
|
||||||
color: #6b7280;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.acoes-lgpd {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.acao-lgpd {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 16px;
|
|
||||||
padding: 14px 16px;
|
|
||||||
background: #f9fafb;
|
|
||||||
border: 1px solid #e5e7eb;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.acao-lgpd strong {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #111827;
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.acao-lgpd p {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #6b7280;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.acao-lgpd button,
|
|
||||||
.btn-link-politica {
|
|
||||||
flex-shrink: 0;
|
|
||||||
padding: 8px 16px;
|
|
||||||
border: 1px solid #c0392b;
|
|
||||||
border-radius: 6px;
|
|
||||||
background: transparent;
|
|
||||||
color: #c0392b;
|
|
||||||
font-size: 13px;
|
|
||||||
font-family: 'Poppins', sans-serif;
|
|
||||||
cursor: pointer;
|
|
||||||
text-decoration: none;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.acao-lgpd button:hover,
|
|
||||||
.btn-link-politica:hover {
|
|
||||||
background: #c0392b;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
#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;
|
|
||||||
}
|
|
||||||
|
|
||||||
[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"] .secao-descricao { color: #a0a0a0; }
|
|
||||||
|
|
||||||
[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; }
|
|
||||||
|
|
||||||
[data-theme="dark"] .acao-lgpd {
|
|
||||||
background: #1a1a1a;
|
|
||||||
border-color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] .acao-lgpd strong { color: #e8e8e8; }
|
|
||||||
[data-theme="dark"] .acao-lgpd p { color: #a0a0a0; }
|
|
||||||
@@ -1,323 +0,0 @@
|
|||||||
<!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>Configuracoes - 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">Configuracoes</h1>
|
|
||||||
<p class="subtitulo">Gerencie seus dados e preferencias</p>
|
|
||||||
|
|
||||||
<div id="mensagem-erro" role="alert"></div>
|
|
||||||
<div id="mensagem-sucesso" role="status"></div>
|
|
||||||
|
|
||||||
<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: Tecnico em Informatica" id="cursoid" required>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="linha-dupla">
|
|
||||||
<div class="campo">
|
|
||||||
<label for="periodoid">Periodo</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 Alteracoes</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<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="Minimo 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>
|
|
||||||
|
|
||||||
<div class="secao">
|
|
||||||
<div class="secao-titulo">Seus Dados (LGPD)</div>
|
|
||||||
<p class="secao-descricao">
|
|
||||||
Conforme a Lei Geral de Protecao de Dados (Lei n 13.709/2018),
|
|
||||||
voce tem direito de acessar, exportar e excluir seus dados pessoais.
|
|
||||||
</p>
|
|
||||||
<div class="acoes-lgpd">
|
|
||||||
<div class="acao-lgpd">
|
|
||||||
<div>
|
|
||||||
<strong>Exportar meus dados</strong>
|
|
||||||
<p>Baixe um arquivo JSON com todas as suas tarefas, eventos e disciplinas.</p>
|
|
||||||
</div>
|
|
||||||
<button type="button" id="btnExportarDados">Exportar JSON</button>
|
|
||||||
</div>
|
|
||||||
<div class="acao-lgpd">
|
|
||||||
<div>
|
|
||||||
<strong>Politica de Privacidade</strong>
|
|
||||||
<p>Veja como seus dados sao tratados e quais sao seus direitos.</p>
|
|
||||||
</div>
|
|
||||||
<a href="politica-privacidade.html" class="btn-link-politica">Ver politica</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="zona-perigo">
|
|
||||||
<div class="secao-titulo">Zona de Perigo</div>
|
|
||||||
<p>
|
|
||||||
Ao excluir sua conta, exercendo o direito previsto no Art. 18, VI da LGPD,
|
|
||||||
todos os seus dados pessoais (tarefas, eventos, disciplinas e notificacoes)
|
|
||||||
serao removidos <strong>permanentemente e imediatamente</strong> dos nossos servidores.
|
|
||||||
Esta acao e irreversivel.
|
|
||||||
</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 requisicao');
|
|
||||||
}
|
|
||||||
return json.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 Alteracoes';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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 nao 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';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('btnExportarDados').addEventListener('click', async () => {
|
|
||||||
const btn = document.getElementById('btnExportarDados');
|
|
||||||
btn.disabled = true;
|
|
||||||
btn.textContent = 'Gerando...';
|
|
||||||
try {
|
|
||||||
const res = await fetch('/api/estudantes/me/dados', {
|
|
||||||
headers: { 'Authorization': 'Bearer ' + localStorage.getItem('fa_token') }
|
|
||||||
});
|
|
||||||
if (res.status === 401) {
|
|
||||||
localStorage.clear();
|
|
||||||
window.location.href = '/login.html?sessao=expirada';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const data = await res.json();
|
|
||||||
const blob = new Blob([JSON.stringify(data.data, null, 2)], { type: 'application/json' });
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
const a = document.createElement('a');
|
|
||||||
a.href = url;
|
|
||||||
a.download = 'focusagenda-meus-dados.json';
|
|
||||||
a.click();
|
|
||||||
URL.revokeObjectURL(url);
|
|
||||||
} catch (err) {
|
|
||||||
alert('Erro ao exportar dados: ' + err.message);
|
|
||||||
} finally {
|
|
||||||
btn.disabled = false;
|
|
||||||
btn.textContent = 'Exportar JSON';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('btnExcluirConta').addEventListener('click', async () => {
|
|
||||||
if (!confirm('Tem certeza que deseja excluir sua conta? Esta acao e irreversivel!')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const confirmacao = prompt('Digite "EXCLUIR" para confirmar a exclusao da sua conta:');
|
|
||||||
if (confirmacao !== 'EXCLUIR') {
|
|
||||||
mostrarErro('Exclusao 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 excluida. Redirecionando...');
|
|
||||||
setTimeout(() => window.location.href = 'login.html', 2000);
|
|
||||||
} catch(err) {
|
|
||||||
mostrarErro(err.message);
|
|
||||||
btn.disabled = false;
|
|
||||||
btn.textContent = 'Excluir Minha Conta';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
carregarUsuario();
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<?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>
|
|
||||||
|
Before Width: | Height: | Size: 624 B |
@@ -1,4 +0,0 @@
|
|||||||
<?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>
|
|
||||||
|
Before Width: | Height: | Size: 568 B |
@@ -81,46 +81,3 @@ a:hover { text-decoration: underline; }
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.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; }
|
|
||||||
|
|
||||||
[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; }
|
|
||||||
|
|||||||
@@ -1,119 +0,0 @@
|
|||||||
<!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>Politica de Privacidade - Focus Agenda</title>
|
|
||||||
<script src="theme.js"></script>
|
|
||||||
<style>
|
|
||||||
.politica-container {
|
|
||||||
max-width: 720px;
|
|
||||||
margin: 80px auto 40px;
|
|
||||||
padding: 32px 24px;
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 12px;
|
|
||||||
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
|
||||||
font-family: 'Poppins', sans-serif;
|
|
||||||
color: #1f2937;
|
|
||||||
line-height: 1.7;
|
|
||||||
}
|
|
||||||
.politica-container h1 {
|
|
||||||
font-size: 24px;
|
|
||||||
color: #c0392b;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
.politica-container h2 {
|
|
||||||
font-size: 18px;
|
|
||||||
color: #114455;
|
|
||||||
margin-top: 28px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
padding-bottom: 6px;
|
|
||||||
border-bottom: 1px solid #e5e7eb;
|
|
||||||
}
|
|
||||||
.politica-container p, .politica-container li {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #374151;
|
|
||||||
}
|
|
||||||
.politica-container ul {
|
|
||||||
padding-left: 20px;
|
|
||||||
margin: 8px 0;
|
|
||||||
}
|
|
||||||
.politica-container li {
|
|
||||||
margin-bottom: 6px;
|
|
||||||
}
|
|
||||||
.voltar-link {
|
|
||||||
display: inline-block;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
color: #c0392b;
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.voltar-link:hover { text-decoration: underline; }
|
|
||||||
[data-theme="dark"] .politica-container {
|
|
||||||
background: #1e1e1e;
|
|
||||||
color: #e8e8e8;
|
|
||||||
}
|
|
||||||
[data-theme="dark"] .politica-container h1 { color: #e74c3c; }
|
|
||||||
[data-theme="dark"] .politica-container h2 { color: #5dade2; border-bottom-color: #333; }
|
|
||||||
[data-theme="dark"] .politica-container p,
|
|
||||||
[data-theme="dark"] .politica-container li { color: #d0d0d0; }
|
|
||||||
[data-theme="dark"] .voltar-link { color: #e74c3c; }
|
|
||||||
</style>
|
|
||||||
</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 class="politica-container">
|
|
||||||
<a href="cadastro.html" class="voltar-link">← Voltar</a>
|
|
||||||
<h1>Politica de Privacidade</h1>
|
|
||||||
<p>Versao 1.0 - Vigente a partir de maio de 2026</p>
|
|
||||||
|
|
||||||
<h2>1. Quem somos</h2>
|
|
||||||
<p>FocusAgenda e um sistema de agenda estudantil desenvolvido como Trabalho de Conclusao de Curso (TCC) no curso Tecnico em Desenvolvimento de Sistemas - ETEC Pedro D'Arcadia Neto.</p>
|
|
||||||
|
|
||||||
<h2>2. Quais dados coletamos e por que (Art. 9, LGPD)</h2>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Nome completo:</strong> identificacao na plataforma</li>
|
|
||||||
<li><strong>Endereco de email:</strong> autenticacao e comunicacao</li>
|
|
||||||
<li><strong>Senha:</strong> armazenada com criptografia BCrypt (nunca em texto simples)</li>
|
|
||||||
<li><strong>Curso e periodo:</strong> personalizao da experiencia</li>
|
|
||||||
<li><strong>Tarefas, eventos e disciplinas:</strong> funcionalidade principal do servico</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2>3. Base legal para o tratamento (Art. 7, I, LGPD)</h2>
|
|
||||||
<p>Consentimento livre, informado e inequivoco do titular. Ao criar uma conta no FocusAgenda, voce aceita explicitamente o tratamento dos seus dados pessoais conforme descrito nesta politica.</p>
|
|
||||||
|
|
||||||
<h2>4. Como protegemos seus dados (Art. 46, LGPD)</h2>
|
|
||||||
<ul>
|
|
||||||
<li>Senhas criptografadas com BCrypt</li>
|
|
||||||
<li>Comunicacao protegida por HTTPS</li>
|
|
||||||
<li>Autenticacao via JWT com expiracao de 24 horas</li>
|
|
||||||
<li>Acesso restrito aos dados do proprio usuario autenticado</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2>5. Seus direitos como titular (Art. 18, LGPD)</h2>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Acesso:</strong> visualize seus dados em Configuracoes</li>
|
|
||||||
<li><strong>Portabilidade:</strong> exporte todos os seus dados em JSON via Configuracoes</li>
|
|
||||||
<li><strong>Correcao:</strong> edite seus dados em Configuracoes > Dados Pessoais</li>
|
|
||||||
<li><strong>Eliminacao:</strong> exclua sua conta em Configuracoes > Zona de Perigo (todos os dados sao removidos permanentemente e imediatamente)</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2>6. Retencao de dados</h2>
|
|
||||||
<p>Os dados sao mantidos enquanto a conta estiver ativa. Apos a exclusao da conta, todos os dados sao removidos imediatamente e de forma irreversivel.</p>
|
|
||||||
|
|
||||||
<h2>7. Contato</h2>
|
|
||||||
<p>Para duvidas sobre privacidade, entre em contato com a equipe do FocusAgenda.</p>
|
|
||||||
|
|
||||||
<h2>8. Versao e vigencia</h2>
|
|
||||||
<p>Versao 1.0 - vigente a partir de maio de 2026.</p>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -63,7 +63,7 @@ body {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 15px;
|
gap: 15px;
|
||||||
color: white;
|
color: white;
|
||||||
margin-top: 70px;
|
margin-top: 70px; /* Compensa a barra fixa do topo */
|
||||||
}
|
}
|
||||||
#campo {
|
#campo {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
(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();
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
async function apiFetch(url, options = {}) {
|
|
||||||
const token = localStorage.getItem('fa_token');
|
|
||||||
const headers = { 'Content-Type': 'application/json', ...(options.headers || {}) };
|
|
||||||
if (token) headers['Authorization'] = 'Bearer ' + token;
|
|
||||||
|
|
||||||
const res = await fetch(url, { ...options, headers });
|
|
||||||
|
|
||||||
if (res.status === 401) {
|
|
||||||
localStorage.clear();
|
|
||||||
window.location.href = '/login.html?sessao=expirada';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await res.json();
|
|
||||||
if (!res.ok) throw new Error(data.message || 'Erro desconhecido');
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
function showToast(mensagem, tipo = 'info') {
|
|
||||||
const existing = document.querySelector('.fa-toast');
|
|
||||||
if (existing) existing.remove();
|
|
||||||
|
|
||||||
const styleId = 'fa-toast-style';
|
|
||||||
if (!document.getElementById(styleId)) {
|
|
||||||
const style = document.createElement('style');
|
|
||||||
style.id = styleId;
|
|
||||||
style.textContent = `
|
|
||||||
.fa-toast {
|
|
||||||
position: fixed; bottom: 24px; right: 24px; z-index: 99999;
|
|
||||||
padding: 12px 18px; border-radius: 8px; font-size: 14px;
|
|
||||||
font-family: 'Poppins', sans-serif; max-width: 340px;
|
|
||||||
animation: faSlideIn 0.3s ease forwards;
|
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
||||||
}
|
|
||||||
.fa-toast.fa-toast-sai { animation: faSlideOut 0.3s ease forwards; }
|
|
||||||
.fa-toast-sucesso { background: #114455; color: #fff; }
|
|
||||||
.fa-toast-erro { background: #c0392b; color: #fff; }
|
|
||||||
.fa-toast-info { background: #f5f5f5; color: #1f2937; border: 1px solid #c0392b; }
|
|
||||||
@keyframes faSlideIn { from { transform: translateY(30px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
|
|
||||||
@keyframes faSlideOut { from { transform: translateY(0); opacity: 1; } to { transform: translateY(30px); opacity: 0; } }
|
|
||||||
`;
|
|
||||||
document.head.appendChild(style);
|
|
||||||
}
|
|
||||||
|
|
||||||
const toast = document.createElement('div');
|
|
||||||
toast.className = 'fa-toast fa-toast-' + tipo;
|
|
||||||
toast.textContent = mensagem;
|
|
||||||
document.body.appendChild(toast);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
toast.classList.add('fa-toast-sai');
|
|
||||||
toast.addEventListener('animationend', () => toast.remove());
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setButtonLoading(btn, loading, textoOriginal) {
|
|
||||||
if (loading) {
|
|
||||||
btn.disabled = true;
|
|
||||||
btn.dataset.textoOriginal = textoOriginal || btn.textContent;
|
|
||||||
btn.textContent = 'Salvando...';
|
|
||||||
} else {
|
|
||||||
btn.disabled = false;
|
|
||||||
if (btn.dataset.textoOriginal) {
|
|
||||||
btn.textContent = btn.dataset.textoOriginal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
window.addEventListener('offline', () => {
|
|
||||||
let banner = document.getElementById('fa-offline-banner');
|
|
||||||
if (!banner) {
|
|
||||||
banner = document.createElement('div');
|
|
||||||
banner.id = 'fa-offline-banner';
|
|
||||||
banner.textContent = 'Sem conexao — algumas funcoes podem nao funcionar';
|
|
||||||
banner.style.cssText = 'position:fixed;top:0;left:0;width:100%;background:#c0392b;color:#fff;text-align:center;padding:10px;font-family:Poppins,sans-serif;font-size:14px;z-index:9999;';
|
|
||||||
document.body.prepend(banner);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
window.addEventListener('online', () => {
|
|
||||||
document.getElementById('fa-offline-banner')?.remove();
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
@@ -44,7 +44,7 @@ public class EstudanteServicoTest {
|
|||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
requisicaoCadastro = new RequisicaoCadastroDTO("Teste", "teste@teste.com", "123456", "Curso", 1, true);
|
requisicaoCadastro = new RequisicaoCadastroDTO("Teste", "teste@teste.com", "123456", "Curso", 1);
|
||||||
requisicaoLogin = new RequisicaoLoginDTO("teste@teste.com", "123456");
|
requisicaoLogin = new RequisicaoLoginDTO("teste@teste.com", "123456");
|
||||||
|
|
||||||
estudante = new Estudante();
|
estudante = new Estudante();
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package com.agendaestudantil.servico;
|
|||||||
import com.agendaestudantil.dto.RequisicaoTarefaDTO;
|
import com.agendaestudantil.dto.RequisicaoTarefaDTO;
|
||||||
import com.agendaestudantil.dto.RespostaTarefaDTO;
|
import com.agendaestudantil.dto.RespostaTarefaDTO;
|
||||||
import com.agendaestudantil.entidade.Tarefa;
|
import com.agendaestudantil.entidade.Tarefa;
|
||||||
import com.agendaestudantil.excecao.ExcecaoNegocio;
|
|
||||||
import com.agendaestudantil.excecao.ExcecaoRecursoNaoEncontrado;
|
import com.agendaestudantil.excecao.ExcecaoRecursoNaoEncontrado;
|
||||||
import com.agendaestudantil.repositorio.TarefaRepositorio;
|
import com.agendaestudantil.repositorio.TarefaRepositorio;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
@@ -15,6 +14,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
|||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.context.SecurityContext;
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -51,7 +51,8 @@ public class TarefaServicoTest {
|
|||||||
Tarefa.Prioridade.ALTA,
|
Tarefa.Prioridade.ALTA,
|
||||||
Tarefa.StatusTarefa.PENDENTE,
|
Tarefa.StatusTarefa.PENDENTE,
|
||||||
LocalDate.now(),
|
LocalDate.now(),
|
||||||
null);
|
null,
|
||||||
|
estudanteId);
|
||||||
|
|
||||||
tarefa = new Tarefa();
|
tarefa = new Tarefa();
|
||||||
tarefa.setId("tarefa1");
|
tarefa.setId("tarefa1");
|
||||||
@@ -83,18 +84,10 @@ public class TarefaServicoTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void deveLancarExcecaoAoCriarTarefaParaOutroUsuario() {
|
void deveLancarExcecaoAoCriarTarefaParaOutroUsuario() {
|
||||||
String outroId = "outroEstudante";
|
mockAuthentication("outroEstudante");
|
||||||
mockAuthentication(outroId);
|
|
||||||
when(tarefaRepositorio.save(any(Tarefa.class))).thenAnswer(inv -> {
|
|
||||||
Tarefa t = inv.getArgument(0);
|
|
||||||
t.setId("tarefa2");
|
|
||||||
return t;
|
|
||||||
});
|
|
||||||
|
|
||||||
RespostaTarefaDTO resposta = tarefaServico.criarTarefa(requisicaoTarefa);
|
assertThrows(ResponseStatusException.class, () -> tarefaServico.criarTarefa(requisicaoTarefa));
|
||||||
|
verify(tarefaRepositorio, never()).save(any(Tarefa.class));
|
||||||
assertNotNull(resposta);
|
|
||||||
assertEquals(outroId, resposta.estudanteId());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user