Terminado todos os controladores e correção de bugs e segurança

This commit is contained in:
2026-03-10 20:47:04 -03:00
commit f131855d84
51 changed files with 2294 additions and 0 deletions

37
.gitignore vendored Normal file
View File

@@ -0,0 +1,37 @@
# Maven
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
.mvn/wrapper/maven-wrapper.jar
# Eclipse
.settings/
.classpath
.project
# IntelliJ IDEA
.idea/
*.iws
*.iml
*.ipr
out/
*.factorypath
# VS Code
.vscode/
# Environment variables
.env
# Logs
*.log
# OS
.DS_Store
Thumbs.db

64
COMOUSARAAPI.md Normal file
View File

@@ -0,0 +1,64 @@
# Guia de Uso da API - Agenda Digital para Estudantes
Esta API foi desenvolvida em **Java (Spring Boot)** com **MongoDB** para auxiliar estudantes na organização de tarefas e compromissos acadêmicos.
## Autenticação
A maioria dos endpoints requer autenticação via **Token JWT**. Para obtê-lo:
1. Realize o login em `POST /api/estudantes/login`.
2. Extraia o campo `token` do corpo da resposta.
3. Inclua o token no cabeçalho de todas as requisições subsequentes:
```
Authorization: Bearer SEU_TOKEN_AQUI
```
---
## Endpoints
### Estudantes
| Método | Rota | Descrição |
|--------|------|-----------|
| `POST` | `/api/estudantes/cadastro` | Cria um novo estudante |
| `POST` | `/api/estudantes/login` | Autentica e retorna o token JWT |
### Tarefas
| Método | Rota | Descrição |
|--------|------|-----------|
| `POST` | `/api/tarefas` | Cria uma nova tarefa |
| `GET` | `/api/tarefas/estudante/{id}` | Lista todas as tarefas de um estudante |
| `GET` | `/api/tarefas/estudante/{id}/pendentes` | Lista apenas as tarefas não concluídas |
| `GET` | `/api/tarefas/{id}` | Retorna os detalhes de uma tarefa específica |
| `PUT` | `/api/tarefas/{id}` | Atualiza os dados de uma tarefa existente |
| `PATCH` | `/api/tarefas/{id}/concluir` | Marca uma tarefa como concluída |
| `DELETE` | `/api/tarefas/{id}` | Remove uma tarefa |
---
## Formato de Resposta
Todas as respostas da API seguem a estrutura abaixo:
```json
{
"data": { ... },
"message": "Sucesso",
"timestamp": "2026-03-01T19:00:00"
}
```
---
## Execução
```bash
# Iniciar a aplicação
mvn spring-boot:run
# Executar os testes
mvn test
```

18
LICENSE Normal file
View File

@@ -0,0 +1,18 @@
MIT License
Copyright (c) 2026 axel
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.

154
README.md Normal file
View File

@@ -0,0 +1,154 @@
<div align="center">
<h1>FocusAgenda</h1>
<p><strong>Plataforma digital de organização de estudos para alunos do ensino médio e técnico</strong></p>
<p>
<img src="https://img.shields.io/badge/Java-ED8B00?style=for-the-badge&logo=openjdk&logoColor=white" />
<img src="https://img.shields.io/badge/MongoDB-4EA94B?style=for-the-badge&logo=mongodb&logoColor=white" />
<img src="https://img.shields.io/badge/HTML5-E34F26?style=for-the-badge&logo=html5&logoColor=white" />
<img src="https://img.shields.io/badge/CSS3-1572B6?style=for-the-badge&logo=css3&logoColor=white" />
<img src="https://img.shields.io/badge/JavaScript-F7DF1E?style=for-the-badge&logo=javascript&logoColor=black" />
</p>
<p>
<img src="https://img.shields.io/badge/Status-Em%20Desenvolvimento-orange?style=for-the-badge" />
<img src="https://img.shields.io/badge/Licença-MIT-blue?style=for-the-badge" />
</p>
</div>
---
## Sobre o Projeto
O **FocusAgenda** é uma plataforma digital desenvolvida como Trabalho de Conclusão de Curso (TCC) na **ETEC Pedro D'Arcádia Neto**, no curso Técnico em Desenvolvimento de Sistemas.
A proposta nasce de um problema real: muitos estudantes do ensino médio e técnico passam horas na escola e, ao chegar em casa, ainda precisam lidar com inúmeras atividades — o que torna difícil manter uma rotina de estudos organizada e eficiente.
O FocusAgenda oferece uma solução simples e prática para isso: um calendário interativo com notificações, lembretes e visualizações diárias, semanais e mensais, ajudando o aluno a gerenciar seu tempo com mais autonomia e produtividade.
---
## Funcionalidades
- **Calendário interativo** com visualizações diária, semanal e mensal
- **Criação e edição de eventos** e afazeres em datas específicas
- **Notificações e lembretes** de tarefas, provas e compromissos acadêmicos
- **Painel informativo (HUD)** com o dia atual, afazeres do dia e feriados do mês
- **Sistema de login e cadastro** de usuários
- **Design responsivo** — funciona bem em celulares e computadores
---
## Tecnologias Utilizadas
### Frontend
| Tecnologia | Uso |
|---|---|
| **HTML5** | Estrutura das páginas (campos, botões, calendário) |
| **CSS3** | Estilização visual, responsividade e identidade do projeto |
| **JavaScript** | Interatividade, navegação sem reload e comunicação com o backend |
### Backend
| Tecnologia | Uso |
|---|---|
| **Java** | Linguagem principal do servidor, processa requisições do frontend |
| **MongoDB** | Banco de dados NoSQL, armazena eventos e dados dos alunos |
---
## Telas do Sistema
### Login
> Acesso seguro com e-mail e senha. Usuários sem conta podem se cadastrar diretamente pela tela.
### Cadastro
> Criação de perfil com e-mail, nome de usuário e senha.
### Calendário Mensal
> Visão geral do mês com todos os eventos cadastrados. Painel lateral em laranja exibe o dia atual, afazeres do dia e feriados.
### Calendário Semanal
> Visualização dos afazeres da semana atual com navegação entre períodos.
### Calendário Diário
> Detalhamento das atividades do dia selecionado com horários e feriados.
---
## Como Executar o Projeto
### Pré-requisitos
- [Java 17+](https://www.oracle.com/java/technologies/downloads/)
- [Maven](https://maven.apache.org/)
- [MongoDB](https://www.mongodb.com/try/download/community) rodando localmente ou via Atlas
### Passos
```bash
# Clone o repositório
git clone https://git.morpheusnox.shop/axel/BackendFocusAgenda.git
# Entre na pasta do projeto
cd projeto
# Instale as dependências e compile
mvn install
# Execute o servidor
mvn spring-boot:run
```
> Após iniciar o servidor, acesse o frontend pelo navegador em `http://localhost:8080`
---
## Estrutura do Projeto
```
projeto/
├── src/
│ └── main/
│ ├── java/ # Código-fonte do backend (Java)
│ └── resources/ # Configurações da aplicação
├── pom.xml # Dependências Maven
├── .gitignore
└── README.md
```
---
## Equipe
| Nome | Função |
|---|---|
| Gabriel H. M. Borges | Desenvolvimento |
| Fernando M. B. da Cruz | Desenvolvimento |
| Gustavo Ferreira Araujo | Desenvolvimento |
| Henry E. de Oliveira | Desenvolvimento |
| Nádia Sakae Habu | Professora Orientadora |
---
## Instituição
**ETEC Pedro D'Arcádia Neto**
Centro Estadual de Educação Tecnológica Paula Souza
Curso: Técnico em Desenvolvimento de Sistemas
---
## Referencias
- CETIC. [TIC Educação 2023](https://cetic.br/)
- MORAN, José. *Educação e tecnologias: mudar para valer.* 2007.
- PROESC. [Engajamento escolar com uma agenda digital.](https://proesc.com/blog/engajamento-escolar-com-uma-agenda-digital-beneficios-e-dicas-praticas/) 2024.
---
<div align="center">
<sub>Desenvolvido na ETEC Pedro D'Arcádia Neto — 2025/2026</sub>
</div>

141
pom.xml Normal file
View File

@@ -0,0 +1,141 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/>
</parent>
<groupId>com.agendaestudantil</groupId>
<artifactId>agenda-digital-estudantes</artifactId>
<version>1.0.0</version>
<name>Agenda Digital para Estudantes</name>
<description>Backend para agenda digital destinado a estudantes com dificuldade de organização</description>
<properties>
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<lombok.version>1.18.30</lombok.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.3</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.6</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>io.github.cdimascio</groupId>
<artifactId>dotenv-java</artifactId>
<version>2.3.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>17</source>
<target>17</target>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,12 @@
package com.agendaestudantil;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class AgendaDigitalEstudantesApplication {
public static void main(String[] args) {
SpringApplication.run(AgendaDigitalEstudantesApplication.class, args);
}
}

View File

@@ -0,0 +1,9 @@
package com.agendaestudantil.configuracao;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.config.EnableMongoAuditing;
@Configuration
@EnableMongoAuditing
public class ConfiguracaoMongo {
}

View File

@@ -0,0 +1,76 @@
package com.agendaestudantil.configuracao;
import com.agendaestudantil.filtro.FiltroJwt;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.List;
@Configuration
@EnableWebSecurity
public class ConfiguracaoSeguranca {
private final FiltroJwt filtroJwt;
private final String corsAllowedOrigins;
public ConfiguracaoSeguranca(FiltroJwt filtroJwt, @Value("${cors.allowed.origins}") String corsAllowedOrigins) {
this.filtroJwt = filtroJwt;
this.corsAllowedOrigins = corsAllowedOrigins;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf.disable())
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/index.html", "/favicon.ico",
"/static/**", "/css/**", "/js/**", "/img/**",
"/*.css", "/*.js", "/*.ico",
"/api/estudantes/cadastro", "/api/estudantes/login",
"/swagger-ui/**", "/v3/api-docs/**")
.permitAll()
.anyRequest().authenticated())
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(filtroJwt, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of(corsAllowedOrigins.split(",")));
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"));
configuration.setAllowedHeaders(List.of("*"));
configuration.setExposedHeaders(List.of("Authorization"));
configuration.setAllowCredentials(true);
configuration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
}

View File

@@ -0,0 +1,13 @@
package com.agendaestudantil.configuracao;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class ResourceController {
@GetMapping("/")
public String index() {
return "forward:/index.html";
}
}

View File

@@ -0,0 +1,74 @@
package com.agendaestudantil.controlador;
import com.agendaestudantil.dto.RequisicaoDisciplinaDTO;
import com.agendaestudantil.dto.RespostaApi;
import com.agendaestudantil.dto.RespostaDisciplinaDTO;
import com.agendaestudantil.entidade.Disciplina;
import com.agendaestudantil.servico.DisciplinaServico;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/disciplinas")
public class DisciplinaControlador {
private final DisciplinaServico disciplinaServico;
public DisciplinaControlador(DisciplinaServico disciplinaServico) {
this.disciplinaServico = disciplinaServico;
}
@PostMapping
public ResponseEntity<RespostaApi<RespostaDisciplinaDTO>> criarDisciplina(
@Valid @RequestBody RequisicaoDisciplinaDTO dto) {
Disciplina disciplina = new Disciplina();
disciplina.setNome(dto.nome());
disciplina.setProfessor(dto.professor());
disciplina.setSala(dto.sala());
disciplina.setCor(dto.cor());
RespostaDisciplinaDTO resposta = disciplinaServico.criarDisciplina(disciplina, dto.estudanteId());
return ResponseEntity.status(HttpStatus.CREATED).body(RespostaApi.sucesso(resposta));
}
@GetMapping("/estudante/{estudanteId}")
public ResponseEntity<RespostaApi<List<RespostaDisciplinaDTO>>> listarPorEstudante(
@PathVariable String estudanteId) {
List<RespostaDisciplinaDTO> disciplinas = disciplinaServico.listarPorEstudante(estudanteId);
return ResponseEntity.ok(RespostaApi.sucesso(disciplinas));
}
@GetMapping("/{id}")
public ResponseEntity<RespostaApi<RespostaDisciplinaDTO>> buscarPorId(
@PathVariable String id,
@RequestParam String estudanteId) {
RespostaDisciplinaDTO disciplina = disciplinaServico.buscarPorId(id, estudanteId);
return ResponseEntity.ok(RespostaApi.sucesso(disciplina));
}
@PutMapping("/{id}")
public ResponseEntity<RespostaApi<RespostaDisciplinaDTO>> atualizarDisciplina(
@PathVariable String id,
@Valid @RequestBody RequisicaoDisciplinaDTO dto) {
Disciplina disciplina = new Disciplina();
disciplina.setNome(dto.nome());
disciplina.setProfessor(dto.professor());
disciplina.setSala(dto.sala());
disciplina.setCor(dto.cor());
RespostaDisciplinaDTO resposta = disciplinaServico.atualizarDisciplina(id, disciplina, dto.estudanteId());
return ResponseEntity.ok(RespostaApi.sucesso(resposta));
}
@DeleteMapping("/{id}")
public ResponseEntity<RespostaApi<Void>> excluirDisciplina(
@PathVariable String id,
@RequestParam String estudanteId) {
disciplinaServico.excluirDisciplina(id, estudanteId);
return ResponseEntity.status(HttpStatus.NO_CONTENT).body(RespostaApi.sucesso(null));
}
}

View File

@@ -0,0 +1,35 @@
package com.agendaestudantil.controlador;
import com.agendaestudantil.dto.RespostaApi;
import com.agendaestudantil.dto.RequisicaoCadastroDTO;
import com.agendaestudantil.dto.RespostaEstudanteDTO;
import com.agendaestudantil.dto.RequisicaoLoginDTO;
import com.agendaestudantil.dto.RespostaLoginDTO;
import com.agendaestudantil.servico.EstudanteServico;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/estudantes")
public class EstudanteControlador {
private final EstudanteServico estudanteServico;
public EstudanteControlador(EstudanteServico estudanteServico) {
this.estudanteServico = estudanteServico;
}
@PostMapping("/cadastro")
public ResponseEntity<RespostaApi<RespostaEstudanteDTO>> cadastrar(@Valid @RequestBody RequisicaoCadastroDTO dto) {
RespostaEstudanteDTO resposta = estudanteServico.cadastrar(dto);
return ResponseEntity.status(HttpStatus.CREATED).body(RespostaApi.sucesso(resposta));
}
@PostMapping("/login")
public ResponseEntity<RespostaApi<RespostaLoginDTO>> login(@Valid @RequestBody RequisicaoLoginDTO dto) {
RespostaLoginDTO resposta = estudanteServico.login(dto);
return ResponseEntity.ok(RespostaApi.sucesso(resposta));
}
}

View File

@@ -0,0 +1,96 @@
package com.agendaestudantil.controlador;
import com.agendaestudantil.dto.RequisicaoEventoDTO;
import com.agendaestudantil.dto.RespostaApi;
import com.agendaestudantil.dto.RespostaEventoDTO;
import com.agendaestudantil.entidade.Evento;
import com.agendaestudantil.servico.EventoServico;
import jakarta.validation.Valid;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.List;
@RestController
@RequestMapping("/api/eventos")
public class EventoControlador {
private final EventoServico eventoServico;
public EventoControlador(EventoServico eventoServico) {
this.eventoServico = eventoServico;
}
@PostMapping
public ResponseEntity<RespostaApi<RespostaEventoDTO>> criarEvento(
@Valid @RequestBody RequisicaoEventoDTO dto) {
Evento evento = new Evento();
evento.setTitulo(dto.titulo());
evento.setDescricao(dto.descricao());
evento.setTipo(dto.tipo());
evento.setLocal(dto.local());
evento.setDataHora(dto.dataHora());
evento.setDisciplinaId(dto.disciplinaId());
RespostaEventoDTO resposta = eventoServico.criarEvento(evento, dto.estudanteId());
return ResponseEntity.status(HttpStatus.CREATED).body(RespostaApi.sucesso(resposta));
}
@GetMapping("/estudante/{estudanteId}")
public ResponseEntity<RespostaApi<List<RespostaEventoDTO>>> listarPorEstudante(
@PathVariable String estudanteId) {
List<RespostaEventoDTO> eventos = eventoServico.listarPorEstudante(estudanteId);
return ResponseEntity.ok(RespostaApi.sucesso(eventos));
}
@GetMapping("/estudante/{estudanteId}/periodo")
public ResponseEntity<RespostaApi<List<RespostaEventoDTO>>> listarPorPeriodo(
@PathVariable String estudanteId,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime inicio,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime fim) {
List<RespostaEventoDTO> eventos = eventoServico.listarPorPeriodo(estudanteId, inicio, fim);
return ResponseEntity.ok(RespostaApi.sucesso(eventos));
}
@GetMapping("/estudante/{estudanteId}/proximos")
public ResponseEntity<RespostaApi<List<RespostaEventoDTO>>> listarProximosEventos(
@PathVariable String estudanteId) {
List<RespostaEventoDTO> eventos = eventoServico.listarProximosEventos(estudanteId);
return ResponseEntity.ok(RespostaApi.sucesso(eventos));
}
@GetMapping("/{id}")
public ResponseEntity<RespostaApi<RespostaEventoDTO>> buscarPorId(
@PathVariable String id,
@RequestParam String estudanteId) {
RespostaEventoDTO evento = eventoServico.buscarPorId(id, estudanteId);
return ResponseEntity.ok(RespostaApi.sucesso(evento));
}
@PutMapping("/{id}")
public ResponseEntity<RespostaApi<RespostaEventoDTO>> atualizarEvento(
@PathVariable String id,
@Valid @RequestBody RequisicaoEventoDTO 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));
}
@DeleteMapping("/{id}")
public ResponseEntity<RespostaApi<Void>> excluirEvento(
@PathVariable String id,
@RequestParam String estudanteId) {
eventoServico.excluirEvento(id, estudanteId);
return ResponseEntity.status(HttpStatus.NO_CONTENT).body(RespostaApi.sucesso(null));
}
}

View File

@@ -0,0 +1,86 @@
package com.agendaestudantil.controlador;
import com.agendaestudantil.dto.RespostaApi;
import com.agendaestudantil.dto.RequisicaoTarefaDTO;
import com.agendaestudantil.dto.RespostaTarefaDTO;
import com.agendaestudantil.servico.TarefaServico;
import jakarta.validation.Valid;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDate;
import java.util.List;
@RestController
@RequestMapping("/api/tarefas")
public class TarefaControlador {
private final TarefaServico tarefaServico;
public TarefaControlador(TarefaServico tarefaServico) {
this.tarefaServico = tarefaServico;
}
@PostMapping
public ResponseEntity<RespostaApi<RespostaTarefaDTO>> criarTarefa(@Valid @RequestBody RequisicaoTarefaDTO dto) {
RespostaTarefaDTO tarefa = tarefaServico.criarTarefa(dto);
return ResponseEntity.status(HttpStatus.CREATED).body(RespostaApi.sucesso(tarefa));
}
@GetMapping("/estudante/{estudanteId}")
public ResponseEntity<RespostaApi<List<RespostaTarefaDTO>>> listarTarefasPorEstudante(
@PathVariable String estudanteId) {
List<RespostaTarefaDTO> tarefas = tarefaServico.listarTarefasPorEstudante(estudanteId);
return ResponseEntity.ok(RespostaApi.sucesso(tarefas));
}
@GetMapping("/estudante/{estudanteId}/pendentes")
public ResponseEntity<RespostaApi<List<RespostaTarefaDTO>>> listarTarefasPendentes(
@PathVariable String estudanteId) {
List<RespostaTarefaDTO> tarefas = tarefaServico.listarTarefasPendentes(estudanteId);
return ResponseEntity.ok(RespostaApi.sucesso(tarefas));
}
@GetMapping("/estudante/{estudanteId}/data")
public ResponseEntity<RespostaApi<List<RespostaTarefaDTO>>> listarTarefasPorData(
@PathVariable String estudanteId,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate data) {
List<RespostaTarefaDTO> tarefas = tarefaServico.listarTarefasPorData(estudanteId, data);
return ResponseEntity.ok(RespostaApi.sucesso(tarefas));
}
@GetMapping("/estudante/{estudanteId}/periodo")
public ResponseEntity<RespostaApi<List<RespostaTarefaDTO>>> listarTarefasPorPeriodo(
@PathVariable String estudanteId,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate inicio,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate fim) {
List<RespostaTarefaDTO> tarefas = tarefaServico.listarTarefasPorPeriodo(estudanteId, inicio, fim);
return ResponseEntity.ok(RespostaApi.sucesso(tarefas));
}
@GetMapping("/{id}")
public ResponseEntity<RespostaApi<RespostaTarefaDTO>> buscarTarefaPorId(@PathVariable String id) {
RespostaTarefaDTO dto = tarefaServico.buscarTarefaPorId(id);
return ResponseEntity.ok(RespostaApi.sucesso(dto));
}
@PutMapping("/{id}")
public ResponseEntity<RespostaApi<RespostaTarefaDTO>> atualizarTarefa(@PathVariable String id,
@Valid @RequestBody RequisicaoTarefaDTO dto) {
RespostaTarefaDTO tarefa = tarefaServico.atualizarTarefa(id, dto);
return ResponseEntity.ok(RespostaApi.sucesso(tarefa));
}
@DeleteMapping("/{id}")
public ResponseEntity<RespostaApi<Void>> excluirTarefa(@PathVariable String id) {
tarefaServico.excluirTarefa(id);
return ResponseEntity.status(HttpStatus.NO_CONTENT).body(RespostaApi.sucesso(null));
}
@PatchMapping("/{id}/concluir")
public ResponseEntity<RespostaApi<RespostaTarefaDTO>> marcarConcluida(@PathVariable String id) {
RespostaTarefaDTO tarefa = tarefaServico.marcarConcluida(id);
return ResponseEntity.ok(RespostaApi.sucesso(tarefa));
}
}

View File

@@ -0,0 +1,16 @@
package com.agendaestudantil.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
public record RequisicaoCadastroDTO(
@NotBlank String nome,
@Email @NotBlank String email,
@NotBlank @Size(min = 6) String senha,
@NotBlank String curso,
@NotNull @Min(1) Integer periodo
) {
}

View File

@@ -0,0 +1,11 @@
package com.agendaestudantil.dto;
import jakarta.validation.constraints.NotBlank;
public record RequisicaoDisciplinaDTO(
@NotBlank String nome,
String professor,
String sala,
String cor,
@NotBlank String estudanteId
) {}

View File

@@ -0,0 +1,17 @@
package com.agendaestudantil.dto;
import com.agendaestudantil.entidade.Evento;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDateTime;
public record RequisicaoEventoDTO(
@NotBlank String titulo,
String descricao,
@NotNull Evento.TipoEvento tipo,
String local,
String disciplinaId,
@NotNull LocalDateTime dataHora,
@NotBlank String estudanteId
) {}

View File

@@ -0,0 +1,10 @@
package com.agendaestudantil.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
public record RequisicaoLoginDTO(
@Email @NotBlank String email,
@NotBlank String senha
) {
}

View File

@@ -0,0 +1,19 @@
package com.agendaestudantil.dto;
import com.agendaestudantil.entidade.Tarefa;
import jakarta.validation.constraints.Future;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDate;
public record RequisicaoTarefaDTO(
@NotBlank String titulo,
String descricao,
@NotNull(message = "Prioridade é obrigatória") Tarefa.Prioridade prioridade,
Tarefa.StatusTarefa status,
@NotNull @Future LocalDate dataEntrega,
String disciplinaId,
@NotBlank(message = "ID do estudante é obrigatório") String estudanteId
) {
}

View File

@@ -0,0 +1,9 @@
package com.agendaestudantil.dto;
import java.time.LocalDateTime;
public record RespostaApi<T>(T data, String message, LocalDateTime timestamp) {
public static <T> RespostaApi<T> sucesso(T data) {
return new RespostaApi<>(data, "Sucesso", LocalDateTime.now());
}
}

View File

@@ -0,0 +1,10 @@
package com.agendaestudantil.dto;
public record RespostaDisciplinaDTO(
String id,
String estudanteId,
String nome,
String professor,
String sala,
String cor
) {}

View File

@@ -0,0 +1,10 @@
package com.agendaestudantil.dto;
public record RespostaEstudanteDTO(
String id,
String nome,
String email,
String curso,
Integer periodo
) {
}

View File

@@ -0,0 +1,14 @@
package com.agendaestudantil.dto;
import java.time.LocalDateTime;
public record RespostaEventoDTO(
String id,
String estudanteId,
String titulo,
String descricao,
String tipo,
String local,
String disciplinaId,
LocalDateTime dataHora
) {}

View File

@@ -0,0 +1,4 @@
package com.agendaestudantil.dto;
public record RespostaLoginDTO(String token, RespostaEstudanteDTO estudante) {
}

View File

@@ -0,0 +1,14 @@
package com.agendaestudantil.dto;
import java.time.LocalDate;
public record RespostaTarefaDTO(
String id,
String titulo,
String descricao,
String prioridade,
String status,
LocalDate dataEntrega,
String disciplinaId,
String estudanteId
) {}

View File

@@ -0,0 +1,28 @@
package com.agendaestudantil.entidade;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.CompoundIndex;
import org.springframework.data.mongodb.core.mapping.Document;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
@Document(collection = "disciplinas")
@CompoundIndex(name = "estudante_nome_idx", def = "{estudanteId: 1, nome: 1}")
public class Disciplina extends EntidadeAuditoria {
@Id
private String id;
private String estudanteId;
private String nome;
private String professor;
private String sala;
private String cor;
}

View File

@@ -0,0 +1,21 @@
package com.agendaestudantil.entidade;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
public abstract class EntidadeAuditoria {
@CreatedDate
private LocalDateTime dataCriacao;
@LastModifiedDate
private LocalDateTime dataAtualizacao;
}

View File

@@ -0,0 +1,33 @@
package com.agendaestudantil.entidade;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
@Document(collection = "estudantes")
public class Estudante extends EntidadeAuditoria {
@Id
private String id;
@Indexed(unique = true)
private String email;
private String nome;
private String senha;
private String curso;
private Integer periodo;
}

View File

@@ -0,0 +1,36 @@
package com.agendaestudantil.entidade;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.CompoundIndex;
import org.springframework.data.mongodb.core.mapping.Document;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
@Document(collection = "eventos")
@CompoundIndex(name = "estudante_data_hora_idx", def = "{estudanteId: 1, dataHora: 1}")
public class Evento extends EntidadeAuditoria {
@Id
private String id;
private String estudanteId;
private String titulo;
private String descricao;
private TipoEvento tipo;
private String local;
private String disciplinaId;
private LocalDateTime dataHora;
public enum TipoEvento {
AULA, PROVA, TRABALHO, ESTUDO, EXAME, OUTRO
}
}

View File

@@ -0,0 +1,44 @@
package com.agendaestudantil.entidade;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.CompoundIndex;
import org.springframework.data.mongodb.core.index.CompoundIndexes;
import org.springframework.data.mongodb.core.mapping.Document;
import java.time.LocalDate;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
@Document(collection = "tarefas")
@CompoundIndexes({
@CompoundIndex(name = "estudante_data_entrega_idx", def = "{estudanteId: 1, dataEntrega: 1}"),
@CompoundIndex(name = "estudante_status_idx", def = "{estudanteId: 1, status: 1}")
})
public class Tarefa extends EntidadeAuditoria {
@Id
private String id;
private String titulo;
private String descricao;
private Prioridade prioridade;
private StatusTarefa status;
private LocalDate dataEntrega;
private String disciplinaId;
private String estudanteId;
public enum Prioridade {
BAIXA, MEDIA, ALTA, URGENTE
}
public enum StatusTarefa {
PENDENTE, EM_ANDAMENTO, CONCLUIDA, ATRASADA
}
}

View File

@@ -0,0 +1,7 @@
package com.agendaestudantil.excecao;
public class ExcecaoNegocio extends RuntimeException {
public ExcecaoNegocio(String message) {
super(message);
}
}

View File

@@ -0,0 +1,7 @@
package com.agendaestudantil.excecao;
public class ExcecaoRecursoNaoEncontrado extends RuntimeException {
public ExcecaoRecursoNaoEncontrado(String message) {
super(message);
}
}

View File

@@ -0,0 +1,53 @@
package com.agendaestudantil.excecao;
import com.agendaestudantil.dto.RespostaApi;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.server.ResponseStatusException;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
@RestControllerAdvice
public class ManipuladorExcecaoGlobal {
@ExceptionHandler(ExcecaoRecursoNaoEncontrado.class)
public ResponseEntity<RespostaApi<Void>> handleResourceNotFound(ExcecaoRecursoNaoEncontrado ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new RespostaApi<>(null, ex.getMessage(), LocalDateTime.now()));
}
@ExceptionHandler(ExcecaoNegocio.class)
public ResponseEntity<RespostaApi<Void>> handleExcecaoNegocio(ExcecaoNegocio ex) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(new RespostaApi<>(null, ex.getMessage(), LocalDateTime.now()));
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<RespostaApi<Map<String, String>>> handleValidationException(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
for (FieldError error : ex.getBindingResult().getFieldErrors()) {
errors.put(error.getField(), error.getDefaultMessage());
}
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(new RespostaApi<>(errors, "Falha na validação", LocalDateTime.now()));
}
@ExceptionHandler(ResponseStatusException.class)
public ResponseEntity<RespostaApi<Void>> handleResponseStatusException(ResponseStatusException ex) {
return ResponseEntity.status(ex.getStatusCode())
.body(new RespostaApi<>(null, ex.getReason(), LocalDateTime.now()));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<RespostaApi<Void>> handleGenericException(Exception ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new RespostaApi<>(null, "Erro interno no servidor: " + ex.getMessage(), LocalDateTime.now()));
}
}

View File

@@ -0,0 +1,62 @@
package com.agendaestudantil.filtro;
import com.agendaestudantil.utilitario.UtilJwt;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
public class FiltroJwt extends OncePerRequestFilter {
private final UtilJwt utilJwt;
private final UserDetailsService userDetailsService;
public FiltroJwt(UtilJwt utilJwt, UserDetailsService userDetailsService) {
this.utilJwt = utilJwt;
this.userDetailsService = userDetailsService;
}
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
String path = request.getRequestURI();
return path.equals("/") || path.equals("/index.html") || path.equals("/favicon.ico")
|| path.startsWith("/static/") || path.startsWith("/css/") || path.startsWith("/js/")
|| path.startsWith("/img/") || path.endsWith(".css") || path.endsWith(".js")
|| path.endsWith(".ico") || path.endsWith(".html");
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String header = request.getHeader("Authorization");
String token = null;
String estudanteId = null;
if (header != null && header.startsWith("Bearer ")) {
token = header.substring(7);
estudanteId = utilJwt.getEstudanteIdFromToken(token);
}
if (estudanteId != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(estudanteId);
if (utilJwt.validateToken(token)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
filterChain.doFilter(request, response);
}
}

View File

@@ -0,0 +1,11 @@
package com.agendaestudantil.repositorio;
import com.agendaestudantil.entidade.Disciplina;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface DisciplinaRepositorio extends MongoRepository<Disciplina, String> {
List<Disciplina> findByEstudanteId(String estudanteId);
}

View File

@@ -0,0 +1,12 @@
package com.agendaestudantil.repositorio;
import com.agendaestudantil.entidade.Estudante;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface EstudanteRepositorio extends MongoRepository<Estudante, String> {
Optional<Estudante> findByEmail(String email);
boolean existsByEmail(String email);
}

View File

@@ -0,0 +1,22 @@
package com.agendaestudantil.repositorio;
import com.agendaestudantil.entidade.Evento;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.stereotype.Repository;
import java.time.LocalDateTime;
import java.util.List;
@Repository
public interface EventoRepositorio extends MongoRepository<Evento, String> {
List<Evento> findByEstudanteId(String estudanteId);
List<Evento> findByDisciplinaId(String disciplinaId);
@Query("{'estudanteId': ?0, 'dataHora': {$gte: ?1, $lte: ?2}}")
List<Evento> findByEstudanteIdAndDataHoraBetween(String estudanteId, LocalDateTime inicio, LocalDateTime fim);
@Query("{'estudanteId': ?0, 'dataHora': {$gte: ?1}}")
List<Evento> findProximosEventosByEstudanteId(String estudanteId, LocalDateTime data);
}

View File

@@ -0,0 +1,27 @@
package com.agendaestudantil.repositorio;
import com.agendaestudantil.entidade.Tarefa;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.stereotype.Repository;
import java.time.LocalDate;
import java.util.List;
@Repository
public interface TarefaRepositorio extends MongoRepository<Tarefa, String> {
List<Tarefa> findByEstudanteId(String estudanteId);
List<Tarefa> findByEstudanteIdAndStatus(String estudanteId, Tarefa.StatusTarefa status);
List<Tarefa> findByDisciplinaId(String disciplinaId);
@Query("{'estudanteId': ?0, 'dataEntrega': ?1}")
List<Tarefa> findByEstudanteIdAndDataEntrega(String estudanteId, LocalDate data);
@Query("{'estudanteId': ?0, 'dataEntrega': {$gte: ?1, $lte: ?2}}")
List<Tarefa> findByEstudanteIdAndDataEntregaBetween(String estudanteId, LocalDate inicio, LocalDate fim);
@Query("{'estudanteId': ?0, 'status': {$ne: ?1}}")
List<Tarefa> findTarefasPendentesByEstudanteId(String estudanteId, Tarefa.StatusTarefa status);
}

View File

@@ -0,0 +1,53 @@
package com.agendaestudantil.seguranca;
import com.agendaestudantil.entidade.Estudante;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.Collections;
public class DetalhesUsuarioPersonalizado implements UserDetails {
private final Estudante estudante;
public DetalhesUsuarioPersonalizado(Estudante estudante) {
this.estudante = estudante;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.singleton(new SimpleGrantedAuthority("ROLE_USER"));
}
@Override
public String getPassword() {
return estudante.getSenha();
}
@Override
public String getUsername() {
return estudante.getId();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}

View File

@@ -0,0 +1,26 @@
package com.agendaestudantil.seguranca;
import com.agendaestudantil.entidade.Estudante;
import com.agendaestudantil.repositorio.EstudanteRepositorio;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
public class ServicoAutenticacaoUsuario implements UserDetailsService {
private final EstudanteRepositorio estudanteRepositorio;
public ServicoAutenticacaoUsuario(EstudanteRepositorio estudanteRepositorio) {
this.estudanteRepositorio = estudanteRepositorio;
}
@Override
public UserDetails loadUserByUsername(String estudanteId) throws UsernameNotFoundException {
Estudante estudante = estudanteRepositorio.findById(estudanteId)
.orElseThrow(() -> new UsernameNotFoundException("Estudante não encontrado"));
return new DetalhesUsuarioPersonalizado(estudante);
}
}

View File

@@ -0,0 +1,92 @@
package com.agendaestudantil.servico;
import com.agendaestudantil.dto.RespostaDisciplinaDTO;
import com.agendaestudantil.entidade.Disciplina;
import com.agendaestudantil.excecao.ExcecaoRecursoNaoEncontrado;
import com.agendaestudantil.repositorio.DisciplinaRepositorio;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import java.util.List;
@Service
public class DisciplinaServico {
private static final Logger log = LoggerFactory.getLogger(DisciplinaServico.class);
private final DisciplinaRepositorio disciplinaRepositorio;
public DisciplinaServico(DisciplinaRepositorio disciplinaRepositorio) {
this.disciplinaRepositorio = disciplinaRepositorio;
}
private void validarAcesso(String estudanteId) {
String authUser = SecurityContextHolder.getContext().getAuthentication().getName();
if (!authUser.equals(estudanteId)) {
log.error("Acesso negado. Usuário {} tentou acessar recurso do estudante {}", authUser, estudanteId);
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Acesso negado");
}
}
public RespostaDisciplinaDTO criarDisciplina(Disciplina disciplina, String estudanteId) {
log.debug("Criando disciplina para estudante: {}", estudanteId);
validarAcesso(estudanteId);
disciplina.setEstudanteId(estudanteId);
Disciplina salva = disciplinaRepositorio.save(disciplina);
return toDTO(salva);
}
public List<RespostaDisciplinaDTO> listarPorEstudante(String estudanteId) {
log.debug("Listando disciplinas para estudante: {}", estudanteId);
validarAcesso(estudanteId);
return disciplinaRepositorio.findByEstudanteId(estudanteId).stream()
.map(this::toDTO)
.toList();
}
public RespostaDisciplinaDTO buscarPorId(String id, String estudanteId) {
Disciplina disciplina = getDisciplinaEntity(id);
validarAcesso(disciplina.getEstudanteId());
return toDTO(disciplina);
}
public RespostaDisciplinaDTO atualizarDisciplina(String id, Disciplina disciplinaAtualizada, String estudanteId) {
Disciplina disciplina = getDisciplinaEntity(id);
validarAcesso(disciplina.getEstudanteId());
disciplina.setNome(disciplinaAtualizada.getNome());
disciplina.setProfessor(disciplinaAtualizada.getProfessor());
disciplina.setSala(disciplinaAtualizada.getSala());
disciplina.setCor(disciplinaAtualizada.getCor());
return toDTO(disciplinaRepositorio.save(disciplina));
}
public void excluirDisciplina(String id, String estudanteId) {
Disciplina disciplina = getDisciplinaEntity(id);
validarAcesso(disciplina.getEstudanteId());
disciplinaRepositorio.delete(disciplina);
}
private Disciplina getDisciplinaEntity(String id) {
return disciplinaRepositorio.findById(id)
.orElseThrow(() -> new ExcecaoRecursoNaoEncontrado("Disciplina não encontrada"));
}
private RespostaDisciplinaDTO toDTO(Disciplina disciplina) {
return new RespostaDisciplinaDTO(
disciplina.getId(),
disciplina.getEstudanteId(),
disciplina.getNome(),
disciplina.getProfessor(),
disciplina.getSala(),
disciplina.getCor()
);
}
}

View File

@@ -0,0 +1,83 @@
package com.agendaestudantil.servico;
import com.agendaestudantil.dto.RequisicaoCadastroDTO;
import com.agendaestudantil.dto.RespostaEstudanteDTO;
import com.agendaestudantil.dto.RequisicaoLoginDTO;
import com.agendaestudantil.dto.RespostaLoginDTO;
import com.agendaestudantil.entidade.Estudante;
import com.agendaestudantil.excecao.ExcecaoNegocio;
import com.agendaestudantil.repositorio.EstudanteRepositorio;
import com.agendaestudantil.utilitario.UtilJwt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import java.util.Optional;
@Service
public class EstudanteServico {
private static final Logger log = LoggerFactory.getLogger(EstudanteServico.class);
private final EstudanteRepositorio estudanteRepositorio;
private final PasswordEncoder passwordEncoder;
private final UtilJwt utilJwt;
public EstudanteServico(EstudanteRepositorio estudanteRepositorio, PasswordEncoder passwordEncoder,
UtilJwt utilJwt) {
this.estudanteRepositorio = estudanteRepositorio;
this.passwordEncoder = passwordEncoder;
this.utilJwt = utilJwt;
}
public RespostaEstudanteDTO cadastrar(RequisicaoCadastroDTO dto) {
log.debug("Acessando método cadastrar para email: {}", dto.email());
Optional<Estudante> existente = estudanteRepositorio.findByEmail(dto.email());
if (existente.isPresent()) {
log.error("Email já cadastrado: {}", dto.email());
throw new ExcecaoNegocio("Email já cadastrado");
}
Estudante estudante = new Estudante();
estudante.setNome(dto.nome());
estudante.setEmail(dto.email());
estudante.setSenha(passwordEncoder.encode(dto.senha()));
estudante.setCurso(dto.curso());
estudante.setPeriodo(dto.periodo());
Estudante salvo = estudanteRepositorio.save(estudante);
return toResponse(salvo);
}
public RespostaLoginDTO login(RequisicaoLoginDTO dto) {
log.debug("Acessando método login para email: {}", dto.email());
Optional<Estudante> estudanteParam = estudanteRepositorio.findByEmail(dto.email());
if (estudanteParam.isEmpty()) {
log.error("Email ou senha incorretos para email: {}", dto.email());
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Email ou senha incorretos");
}
Estudante estudante = estudanteParam.get();
if (!passwordEncoder.matches(dto.senha(), estudante.getSenha())) {
log.error("Falha ao validar senha para email: {}", dto.email());
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Email ou senha incorretos");
}
String token = utilJwt.generateToken(estudante.getId());
return new RespostaLoginDTO(token, toResponse(estudante));
}
private RespostaEstudanteDTO toResponse(Estudante estudante) {
return new RespostaEstudanteDTO(
estudante.getId(),
estudante.getNome(),
estudante.getEmail(),
estudante.getCurso(),
estudante.getPeriodo());
}
}

View File

@@ -0,0 +1,111 @@
package com.agendaestudantil.servico;
import com.agendaestudantil.dto.RespostaEventoDTO;
import com.agendaestudantil.entidade.Evento;
import com.agendaestudantil.excecao.ExcecaoRecursoNaoEncontrado;
import com.agendaestudantil.repositorio.EventoRepositorio;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import java.time.LocalDateTime;
import java.util.List;
@Service
public class EventoServico {
private static final Logger log = LoggerFactory.getLogger(EventoServico.class);
private final EventoRepositorio eventoRepositorio;
public EventoServico(EventoRepositorio eventoRepositorio) {
this.eventoRepositorio = eventoRepositorio;
}
private void validarAcesso(String estudanteId) {
String authUser = SecurityContextHolder.getContext().getAuthentication().getName();
if (!authUser.equals(estudanteId)) {
log.error("Acesso negado. Usuário {} tentou acessar recurso do estudante {}", authUser, estudanteId);
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Acesso negado");
}
}
public RespostaEventoDTO criarEvento(Evento evento, String estudanteId) {
log.debug("Criando evento para estudante: {}", estudanteId);
validarAcesso(estudanteId);
evento.setEstudanteId(estudanteId);
Evento salvo = eventoRepositorio.save(evento);
return toDTO(salvo);
}
public List<RespostaEventoDTO> listarPorEstudante(String estudanteId) {
log.debug("Listando eventos para estudante: {}", estudanteId);
validarAcesso(estudanteId);
return eventoRepositorio.findByEstudanteId(estudanteId).stream()
.map(this::toDTO)
.toList();
}
public List<RespostaEventoDTO> listarPorPeriodo(String estudanteId, LocalDateTime inicio, LocalDateTime fim) {
validarAcesso(estudanteId);
return eventoRepositorio.findByEstudanteIdAndDataHoraBetween(estudanteId, inicio, fim).stream()
.map(this::toDTO)
.toList();
}
public List<RespostaEventoDTO> listarProximosEventos(String estudanteId) {
validarAcesso(estudanteId);
return eventoRepositorio.findProximosEventosByEstudanteId(estudanteId, LocalDateTime.now()).stream()
.map(this::toDTO)
.toList();
}
public RespostaEventoDTO buscarPorId(String id, String estudanteId) {
Evento evento = getEventoEntity(id);
validarAcesso(evento.getEstudanteId());
return toDTO(evento);
}
public RespostaEventoDTO atualizarEvento(String id, Evento eventoAtualizado, String estudanteId) {
Evento evento = getEventoEntity(id);
validarAcesso(evento.getEstudanteId());
evento.setTitulo(eventoAtualizado.getTitulo());
evento.setDescricao(eventoAtualizado.getDescricao());
evento.setTipo(eventoAtualizado.getTipo());
evento.setLocal(eventoAtualizado.getLocal());
evento.setDataHora(eventoAtualizado.getDataHora());
evento.setDisciplinaId(eventoAtualizado.getDisciplinaId());
return toDTO(eventoRepositorio.save(evento));
}
public void excluirEvento(String id, String estudanteId) {
Evento evento = getEventoEntity(id);
validarAcesso(evento.getEstudanteId());
eventoRepositorio.delete(evento);
}
private Evento getEventoEntity(String id) {
return eventoRepositorio.findById(id)
.orElseThrow(() -> new ExcecaoRecursoNaoEncontrado("Evento não encontrado"));
}
private RespostaEventoDTO toDTO(Evento evento) {
return new RespostaEventoDTO(
evento.getId(),
evento.getEstudanteId(),
evento.getTitulo(),
evento.getDescricao(),
evento.getTipo() != null ? evento.getTipo().name() : null,
evento.getLocal(),
evento.getDisciplinaId(),
evento.getDataHora()
);
}
}

View File

@@ -0,0 +1,139 @@
package com.agendaestudantil.servico;
import com.agendaestudantil.dto.RequisicaoTarefaDTO;
import com.agendaestudantil.dto.RespostaTarefaDTO;
import com.agendaestudantil.entidade.Tarefa;
import com.agendaestudantil.excecao.ExcecaoRecursoNaoEncontrado;
import com.agendaestudantil.repositorio.TarefaRepositorio;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import java.time.LocalDate;
import java.util.List;
@Service
public class TarefaServico {
private static final Logger log = LoggerFactory.getLogger(TarefaServico.class);
private final TarefaRepositorio tarefaRepositorio;
public TarefaServico(TarefaRepositorio tarefaRepositorio) {
this.tarefaRepositorio = tarefaRepositorio;
}
private void validarAcesso(String estudanteId) {
String authUser = SecurityContextHolder.getContext().getAuthentication().getName();
if (!authUser.equals(estudanteId)) {
log.error("Acesso negado. Usuário {} tentou acessar recurso do estudante {}", authUser, estudanteId);
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Acesso negado");
}
}
private void validarAcessoTarefa(Tarefa tarefa) {
validarAcesso(tarefa.getEstudanteId());
}
public RespostaTarefaDTO criarTarefa(RequisicaoTarefaDTO dto) {
log.debug("Criando tarefa para estudante: {}", dto.estudanteId());
validarAcesso(dto.estudanteId());
Tarefa tarefa = new Tarefa();
tarefa.setTitulo(dto.titulo());
tarefa.setDescricao(dto.descricao());
tarefa.setPrioridade(dto.prioridade() != null ? dto.prioridade() : Tarefa.Prioridade.MEDIA);
tarefa.setStatus(dto.status() != null ? dto.status() : Tarefa.StatusTarefa.PENDENTE);
tarefa.setDataEntrega(dto.dataEntrega());
tarefa.setEstudanteId(dto.estudanteId());
if (dto.disciplinaId() != null) {
tarefa.setDisciplinaId(dto.disciplinaId());
}
return toDTO(tarefaRepositorio.save(tarefa));
}
public List<RespostaTarefaDTO> listarTarefasPorEstudante(String estudanteId) {
log.debug("Listando tarefas para estudante: {}", estudanteId);
validarAcesso(estudanteId);
return tarefaRepositorio.findByEstudanteId(estudanteId).stream()
.map(this::toDTO)
.toList();
}
public List<RespostaTarefaDTO> listarTarefasPendentes(String estudanteId) {
log.debug("Listando tarefas pendentes para estudante: {}", estudanteId);
validarAcesso(estudanteId);
return tarefaRepositorio.findTarefasPendentesByEstudanteId(estudanteId, Tarefa.StatusTarefa.CONCLUIDA).stream()
.map(this::toDTO)
.toList();
}
public List<RespostaTarefaDTO> listarTarefasPorData(String estudanteId, LocalDate data) {
validarAcesso(estudanteId);
return tarefaRepositorio.findByEstudanteIdAndDataEntrega(estudanteId, data).stream()
.map(this::toDTO)
.toList();
}
public List<RespostaTarefaDTO> listarTarefasPorPeriodo(String estudanteId, LocalDate inicio, LocalDate fim) {
validarAcesso(estudanteId);
return tarefaRepositorio.findByEstudanteIdAndDataEntregaBetween(estudanteId, inicio, fim).stream()
.map(this::toDTO)
.toList();
}
public RespostaTarefaDTO buscarTarefaPorId(String id) {
return toDTO(getTarefaEntity(id));
}
public RespostaTarefaDTO atualizarTarefa(String id, RequisicaoTarefaDTO dto) {
Tarefa tarefa = getTarefaEntity(id);
validarAcesso(tarefa.getEstudanteId());
tarefa.setTitulo(dto.titulo());
tarefa.setDescricao(dto.descricao());
tarefa.setPrioridade(dto.prioridade() != null ? dto.prioridade() : tarefa.getPrioridade());
tarefa.setStatus(dto.status() != null ? dto.status() : tarefa.getStatus());
tarefa.setDataEntrega(dto.dataEntrega());
if (dto.disciplinaId() != null) {
tarefa.setDisciplinaId(dto.disciplinaId());
}
return toDTO(tarefaRepositorio.save(tarefa));
}
public void excluirTarefa(String id) {
tarefaRepositorio.delete(getTarefaEntity(id));
}
public RespostaTarefaDTO marcarConcluida(String id) {
Tarefa tarefa = getTarefaEntity(id);
tarefa.setStatus(Tarefa.StatusTarefa.CONCLUIDA);
return toDTO(tarefaRepositorio.save(tarefa));
}
private Tarefa getTarefaEntity(String id) {
Tarefa tarefa = tarefaRepositorio.findById(id)
.orElseThrow(() -> new ExcecaoRecursoNaoEncontrado("Tarefa não encontrada"));
validarAcessoTarefa(tarefa);
return tarefa;
}
private RespostaTarefaDTO toDTO(Tarefa tarefa) {
return new RespostaTarefaDTO(
tarefa.getId(),
tarefa.getTitulo(),
tarefa.getDescricao(),
tarefa.getPrioridade() != null ? tarefa.getPrioridade().name() : null,
tarefa.getStatus() != null ? tarefa.getStatus().name() : null,
tarefa.getDataEntrega(),
tarefa.getDisciplinaId(),
tarefa.getEstudanteId());
}
}

View File

@@ -0,0 +1,56 @@
package com.agendaestudantil.utilitario;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;
@Component
public class UtilJwt {
private final String secret;
public UtilJwt(@Value("${jwt.secret}") String secret) {
this.secret = secret;
}
private SecretKey getSigningKey() {
return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
}
public String generateToken(String estudanteId) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + 24 * 60 * 60 * 1000L);
return Jwts.builder()
.subject(estudanteId)
.issuedAt(now)
.expiration(expiryDate)
.signWith(getSigningKey())
.compact();
}
public String getEstudanteIdFromToken(String token) {
Claims claims = Jwts.parser()
.verifyWith(getSigningKey())
.build()
.parseSignedClaims(token)
.getPayload();
return claims.getSubject();
}
public boolean validateToken(String token) {
try {
Jwts.parser().verifyWith(getSigningKey()).build().parseSignedClaims(token);
return true;
} catch (Exception e) {
return false;
}
}
}

View File

@@ -0,0 +1,4 @@
spring.data.mongodb.uri=${MONGO_URI:mongodb://localhost:27017/agenda_estudantil}
cors.allowed.origins=${CORS_ORIGINS:http://localhost:3000}
jwt.secret=${JWT_SECRET:8a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t1u2v3w4x5y6z7A8B9C0D1E2F3}
logging.level.org.springframework.data.mongodb=DEBUG

View File

@@ -0,0 +1,5 @@
spring.data.mongodb.uri=${MONGO_URI}
cors.allowed.origins=${CORS_ORIGINS}
jwt.secret=${JWT_SECRET}
logging.level.root=WARN
logging.level.com.agendaestudantil=INFO

View File

@@ -0,0 +1,15 @@
spring.application.name=${APP_NAME:Agenda Digital para Estudantes}
server.port=${SERVER_PORT:8080}
server.servlet.context-path=/
spring.web.resources.static-locations=classpath:/static/
spring.mvc.static-path-pattern=/**
spring.mvc.contentnegotiation.favor-parameter=true
spring.web.resources.add-mappings=true
spring.servlet.multipart.enabled=false
spring.servlet.multipart.file-size-threshold=2KB
spring.http.multipart.enabled=false
spring.web.resources.cache.cachecontrol.max-age=3600
spring.web.resources.cache.cachecontrol.cache-public=true
spring.profiles.active=${SPRING_PROFILES_ACTIVE:dev}

View File

@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html lang="pt-br">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="style.css">
<title>TCC de cria</title>
</head>
<body>
<!--Barrinha que fica no topo-->
<div id="topo">
<h1 id="textotop">Focus Agenda</h1>
</div>
<!--cabo bar-->
<!--Login e outras coisas-->
<div id="log">
<h1 class="mens">Bem Vindo!</h1>
<h3 class="mens">Faça seu login</h3>
<form method="POST" action="login.html">
<div class="campo">
<label for="emailid">Email</label>
<input type="email" placeholder="Digite seu email" name="email" id="emailid" required>
</div>
<div class="campo">
<label for="senhaid">Senha</label>
<input type="password" placeholder="Digite sua senha" name="senha" id="senhaid" required>
</div>
<button type="submit" id="logbtn">Logar</button>
</form>
<p class="mens"><a href="cadastro.html" id="linkcada">Cadastrar-se</a></p>
</div>
<!--cabo log-->
</body>
</html>

View File

@@ -0,0 +1,124 @@
* {
padding: 0;
margin: 0;
}
p,
h1,
h2,
h3 {
color: black;
margin: 0;
font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif
}
label{
display: block;
}
body {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
@media (max-width: 768px) {
body {
padding-left: 20px;
padding-right: 20px;
justify-content: center;
}
}
@media (max-width: 480px) {
#log {
width: 100% !important;
max-width: 320px;
}
}
#topo {
width: 100%;
height: 50px;
background-color: #111;
position: fixed;
top: 0;
left: 0;
background: linear-gradient(to right, #C0392B 47%, #7A4951 73%, #114455 87%);
display: flex;
align-items: center;
justify-content: flex-start;
}
#textotop {
padding-left: 20px;
font-size: 38px;
margin: 0;
font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif;
}
#log {
width: 350px;
display: flex;
flex-direction: column;
gap: 15px;
color: white;
margin-top: 70px; /* Compensa a barra fixa do topo */
}
#campo {
display: flex;
flex-direction: column;
gap: 15px;
align-self: center;
}
#menslog{
font-size: 20px;
}
#emailid, #senhaid {
height: 50px;
width: 100%;
padding: 10px;
font-size: 16px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: white;
}
form {
display: flex;
flex-direction: column;
gap: 15px;
width: 100%;
}
#logbtn {
align-self: center;
width: 50%;
padding: 12px;
font-size: 18px;
font-weight: bold;
background-color: #C0392B;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
}
#logbtn:hover {
background-color: #A03224;
}
.mens {
align-self: center;
}
#linkcada{
color: #111;
}

View File

@@ -0,0 +1,107 @@
package com.agendaestudantil.servico;
import com.agendaestudantil.dto.RequisicaoCadastroDTO;
import com.agendaestudantil.dto.RespostaEstudanteDTO;
import com.agendaestudantil.dto.RequisicaoLoginDTO;
import com.agendaestudantil.dto.RespostaLoginDTO;
import com.agendaestudantil.entidade.Estudante;
import com.agendaestudantil.excecao.ExcecaoNegocio;
import com.agendaestudantil.repositorio.EstudanteRepositorio;
import com.agendaestudantil.utilitario.UtilJwt;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.server.ResponseStatusException;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
public class EstudanteServicoTest {
@Mock
private EstudanteRepositorio estudanteRepositorio;
@Mock
private PasswordEncoder passwordEncoder;
@Mock
private UtilJwt utilJwt;
@InjectMocks
private EstudanteServico estudanteServico;
private RequisicaoCadastroDTO requisicaoCadastro;
private RequisicaoLoginDTO requisicaoLogin;
private Estudante estudante;
@BeforeEach
void setUp() {
requisicaoCadastro = new RequisicaoCadastroDTO("Teste", "teste@teste.com", "123456", "Curso", 1);
requisicaoLogin = new RequisicaoLoginDTO("teste@teste.com", "123456");
estudante = new Estudante();
estudante.setId("1");
estudante.setNome("Teste");
estudante.setEmail("teste@teste.com");
estudante.setSenha("encodedPassword");
estudante.setCurso("Curso");
estudante.setPeriodo(1);
}
@Test
void deveCadastrarEstudanteComSucesso() {
when(estudanteRepositorio.findByEmail(anyString())).thenReturn(Optional.empty());
when(passwordEncoder.encode(anyString())).thenReturn("encodedPassword");
when(estudanteRepositorio.save(any(Estudante.class))).thenReturn(estudante);
RespostaEstudanteDTO resposta = estudanteServico.cadastrar(requisicaoCadastro);
assertNotNull(resposta);
assertEquals("Teste", resposta.nome());
verify(estudanteRepositorio, times(1)).save(any(Estudante.class));
}
@Test
void deveLancarExcecaoAoCadastrarEmailExistente() {
when(estudanteRepositorio.findByEmail(anyString())).thenReturn(Optional.of(estudante));
assertThrows(ExcecaoNegocio.class, () -> estudanteServico.cadastrar(requisicaoCadastro));
verify(estudanteRepositorio, never()).save(any(Estudante.class));
}
@Test
void deveRealizarLoginComSucesso() {
when(estudanteRepositorio.findByEmail(anyString())).thenReturn(Optional.of(estudante));
when(passwordEncoder.matches(anyString(), anyString())).thenReturn(true);
when(utilJwt.generateToken(anyString())).thenReturn("token123");
RespostaLoginDTO resposta = estudanteServico.login(requisicaoLogin);
assertNotNull(resposta);
assertEquals("token123", resposta.token());
assertEquals("Teste", resposta.estudante().nome());
}
@Test
void deveRejeitarSenhaInvalida() {
when(estudanteRepositorio.findByEmail(anyString())).thenReturn(Optional.of(estudante));
when(passwordEncoder.matches(anyString(), anyString())).thenReturn(false);
assertThrows(ResponseStatusException.class, () -> estudanteServico.login(requisicaoLogin));
}
@Test
void deveRejeitarEmailNaoEncontrado() {
when(estudanteRepositorio.findByEmail(anyString())).thenReturn(Optional.empty());
assertThrows(ResponseStatusException.class, () -> estudanteServico.login(requisicaoLogin));
}
}

View File

@@ -0,0 +1,123 @@
package com.agendaestudantil.servico;
import com.agendaestudantil.dto.RequisicaoTarefaDTO;
import com.agendaestudantil.dto.RespostaTarefaDTO;
import com.agendaestudantil.entidade.Tarefa;
import com.agendaestudantil.excecao.ExcecaoRecursoNaoEncontrado;
import com.agendaestudantil.repositorio.TarefaRepositorio;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.server.ResponseStatusException;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
public class TarefaServicoTest {
@Mock
private TarefaRepositorio tarefaRepositorio;
@Mock
private SecurityContext securityContext;
@Mock
private Authentication authentication;
@InjectMocks
private TarefaServico tarefaServico;
private RequisicaoTarefaDTO requisicaoTarefa;
private Tarefa tarefa;
private final String estudanteId = "estudante123";
@BeforeEach
void setUp() {
requisicaoTarefa = new RequisicaoTarefaDTO(
"Estudar Spring",
"Aprender Spring Boot 3",
Tarefa.Prioridade.ALTA,
Tarefa.StatusTarefa.PENDENTE,
LocalDate.now(),
null,
estudanteId);
tarefa = new Tarefa();
tarefa.setId("tarefa1");
tarefa.setTitulo("Estudar Spring");
tarefa.setDescricao("Aprender Spring Boot 3");
tarefa.setPrioridade(Tarefa.Prioridade.ALTA);
tarefa.setStatus(Tarefa.StatusTarefa.PENDENTE);
tarefa.setDataEntrega(LocalDate.now());
tarefa.setEstudanteId(estudanteId);
}
private void mockAuthentication(String user) {
when(securityContext.getAuthentication()).thenReturn(authentication);
when(authentication.getName()).thenReturn(user);
SecurityContextHolder.setContext(securityContext);
}
@Test
void deveCriarTarefaComSucesso() {
mockAuthentication(estudanteId);
when(tarefaRepositorio.save(any(Tarefa.class))).thenReturn(tarefa);
RespostaTarefaDTO resposta = tarefaServico.criarTarefa(requisicaoTarefa);
assertNotNull(resposta);
assertEquals("Estudar Spring", resposta.titulo());
verify(tarefaRepositorio, times(1)).save(any(Tarefa.class));
}
@Test
void deveLancarExcecaoAoCriarTarefaParaOutroUsuario() {
mockAuthentication("outroEstudante");
assertThrows(ResponseStatusException.class, () -> tarefaServico.criarTarefa(requisicaoTarefa));
verify(tarefaRepositorio, never()).save(any(Tarefa.class));
}
@Test
void deveListarTarefasPorEstudanteComSucesso() {
mockAuthentication(estudanteId);
when(tarefaRepositorio.findByEstudanteId(estudanteId)).thenReturn(List.of(tarefa));
List<RespostaTarefaDTO> tarefas = tarefaServico.listarTarefasPorEstudante(estudanteId);
assertFalse(tarefas.isEmpty());
assertEquals(1, tarefas.size());
assertEquals("Estudar Spring", tarefas.get(0).titulo());
}
@Test
void deveMudarStatusTarefaParaConcluidoComSucesso() {
mockAuthentication(estudanteId);
when(tarefaRepositorio.findById(anyString())).thenReturn(Optional.of(tarefa));
when(tarefaRepositorio.save(any(Tarefa.class))).thenReturn(tarefa);
RespostaTarefaDTO resposta = tarefaServico.marcarConcluida("tarefa1");
assertNotNull(resposta);
verify(tarefaRepositorio, times(1)).save(any(Tarefa.class));
}
@Test
void deveLancarExcecaoQuandoTarefaNaoEncontrada() {
when(tarefaRepositorio.findById(anyString())).thenReturn(Optional.empty());
assertThrows(ExcecaoRecursoNaoEncontrado.class, () -> tarefaServico.buscarTarefaPorId("tarefaNaoExistente"));
}
}