From 88af1d35b13106baea016f71d4a261d25ca53ab6 Mon Sep 17 00:00:00 2001 From: Axel Date: Sun, 1 Mar 2026 19:15:41 -0300 Subject: [PATCH] =?UTF-8?q?Refactor:=20Pacotes=20em=20Portugu=C3=AAs,=20Do?= =?UTF-8?q?cumenta=C3=A7=C3=A3o=20da=20API=20e=20limpeza=20de=20arquivos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .factorypath | 48 ------ .gitignore | 1 + COMOUSARAAPI.md | 64 ++++++++ pom.xml | 66 +++++++++ .../agendaestudantil/config/EnvConfig.java | 24 --- .../configuracao/ConfiguracaoMongo.java | 9 ++ .../configuracao/ConfiguracaoSeguranca.java | 71 +++++++++ .../configuracao/ConfiguracaoSwagger.java | 29 ++++ .../controlador/EstudanteControlador.java | 35 +++++ .../controlador/TarefaControlador.java | 86 +++++++++++ .../controller/EstudanteController.java | 31 ---- .../controller/TarefaController.java | 81 ---------- .../dto/CadastroRequestDTO.java | 16 -- .../dto/EstudanteResponseDTO.java | 16 -- .../agendaestudantil/dto/LoginRequestDTO.java | 13 -- .../dto/RequisicaoCadastroDTO.java | 16 ++ .../dto/RequisicaoLoginDTO.java | 10 ++ .../dto/RequisicaoTarefaDTO.java | 19 +++ .../com/agendaestudantil/dto/RespostaApi.java | 9 ++ .../dto/RespostaDisciplinaDTO.java | 10 ++ .../dto/RespostaEstudanteDTO.java | 10 ++ .../dto/RespostaEventoDTO.java | 14 ++ .../dto/RespostaLoginDTO.java | 4 + .../dto/RespostaTarefaDTO.java | 14 ++ .../dto/TarefaRequestDTO.java | 28 ---- .../agendaestudantil/entidade/Disciplina.java | 97 ++++++++++++ .../entidade/EntidadeAuditoria.java | 30 ++++ .../agendaestudantil/entidade/Estudante.java | 101 +++++++++++++ .../com/agendaestudantil/entidade/Evento.java | 123 +++++++++++++++ .../com/agendaestudantil/entidade/Tarefa.java | 131 ++++++++++++++++ .../agendaestudantil/entity/Disciplina.java | 30 ---- .../agendaestudantil/entity/Estudante.java | 32 ---- .../com/agendaestudantil/entity/Evento.java | 40 ----- .../com/agendaestudantil/entity/Tarefa.java | 45 ------ .../excecao/ExcecaoNegocio.java | 7 + .../excecao/ExcecaoRecursoNaoEncontrado.java | 7 + .../excecao/ManipuladorExcecaoGlobal.java | 53 +++++++ .../agendaestudantil/filtro/FiltroJwt.java | 53 +++++++ .../DisciplinaRepositorio.java} | 6 +- .../EstudanteRepositorio.java} | 6 +- .../EventoRepositorio.java} | 14 +- .../TarefaRepositorio.java} | 22 +-- .../DetalhesUsuarioPersonalizado.java | 53 +++++++ .../seguranca/ServicoAutenticacaoUsuario.java | 26 ++++ .../service/EstudanteService.java | 64 -------- .../service/TarefaService.java | 98 ------------ .../servico/EstudanteServico.java | 83 +++++++++++ .../servico/TarefaServico.java | 140 ++++++++++++++++++ .../agendaestudantil/utilitario/UtilJwt.java | 56 +++++++ src/main/resources/application-dev.properties | 4 + .../resources/application-prod.properties | 5 + src/main/resources/application.properties | 6 +- .../servico/EstudanteServicoTest.java | 107 +++++++++++++ .../servico/TarefaServicoTest.java | 123 +++++++++++++++ 54 files changed, 1691 insertions(+), 595 deletions(-) create mode 100644 COMOUSARAAPI.md delete mode 100644 src/main/java/com/agendaestudantil/config/EnvConfig.java create mode 100644 src/main/java/com/agendaestudantil/configuracao/ConfiguracaoMongo.java create mode 100644 src/main/java/com/agendaestudantil/configuracao/ConfiguracaoSeguranca.java create mode 100644 src/main/java/com/agendaestudantil/configuracao/ConfiguracaoSwagger.java create mode 100644 src/main/java/com/agendaestudantil/controlador/EstudanteControlador.java create mode 100644 src/main/java/com/agendaestudantil/controlador/TarefaControlador.java delete mode 100644 src/main/java/com/agendaestudantil/controller/EstudanteController.java delete mode 100644 src/main/java/com/agendaestudantil/controller/TarefaController.java delete mode 100644 src/main/java/com/agendaestudantil/dto/CadastroRequestDTO.java delete mode 100644 src/main/java/com/agendaestudantil/dto/EstudanteResponseDTO.java delete mode 100644 src/main/java/com/agendaestudantil/dto/LoginRequestDTO.java create mode 100644 src/main/java/com/agendaestudantil/dto/RequisicaoCadastroDTO.java create mode 100644 src/main/java/com/agendaestudantil/dto/RequisicaoLoginDTO.java create mode 100644 src/main/java/com/agendaestudantil/dto/RequisicaoTarefaDTO.java create mode 100644 src/main/java/com/agendaestudantil/dto/RespostaApi.java create mode 100644 src/main/java/com/agendaestudantil/dto/RespostaDisciplinaDTO.java create mode 100644 src/main/java/com/agendaestudantil/dto/RespostaEstudanteDTO.java create mode 100644 src/main/java/com/agendaestudantil/dto/RespostaEventoDTO.java create mode 100644 src/main/java/com/agendaestudantil/dto/RespostaLoginDTO.java create mode 100644 src/main/java/com/agendaestudantil/dto/RespostaTarefaDTO.java delete mode 100644 src/main/java/com/agendaestudantil/dto/TarefaRequestDTO.java create mode 100644 src/main/java/com/agendaestudantil/entidade/Disciplina.java create mode 100644 src/main/java/com/agendaestudantil/entidade/EntidadeAuditoria.java create mode 100644 src/main/java/com/agendaestudantil/entidade/Estudante.java create mode 100644 src/main/java/com/agendaestudantil/entidade/Evento.java create mode 100644 src/main/java/com/agendaestudantil/entidade/Tarefa.java delete mode 100644 src/main/java/com/agendaestudantil/entity/Disciplina.java delete mode 100644 src/main/java/com/agendaestudantil/entity/Estudante.java delete mode 100644 src/main/java/com/agendaestudantil/entity/Evento.java delete mode 100644 src/main/java/com/agendaestudantil/entity/Tarefa.java create mode 100644 src/main/java/com/agendaestudantil/excecao/ExcecaoNegocio.java create mode 100644 src/main/java/com/agendaestudantil/excecao/ExcecaoRecursoNaoEncontrado.java create mode 100644 src/main/java/com/agendaestudantil/excecao/ManipuladorExcecaoGlobal.java create mode 100644 src/main/java/com/agendaestudantil/filtro/FiltroJwt.java rename src/main/java/com/agendaestudantil/{repository/DisciplinaRepository.java => repositorio/DisciplinaRepositorio.java} (55%) rename src/main/java/com/agendaestudantil/{repository/EstudanteRepository.java => repositorio/EstudanteRepositorio.java} (59%) rename src/main/java/com/agendaestudantil/{repository/EventoRepository.java => repositorio/EventoRepositorio.java} (75%) rename src/main/java/com/agendaestudantil/{repository/TarefaRepository.java => repositorio/TarefaRepositorio.java} (72%) create mode 100644 src/main/java/com/agendaestudantil/seguranca/DetalhesUsuarioPersonalizado.java create mode 100644 src/main/java/com/agendaestudantil/seguranca/ServicoAutenticacaoUsuario.java delete mode 100644 src/main/java/com/agendaestudantil/service/EstudanteService.java delete mode 100644 src/main/java/com/agendaestudantil/service/TarefaService.java create mode 100644 src/main/java/com/agendaestudantil/servico/EstudanteServico.java create mode 100644 src/main/java/com/agendaestudantil/servico/TarefaServico.java create mode 100644 src/main/java/com/agendaestudantil/utilitario/UtilJwt.java create mode 100644 src/main/resources/application-dev.properties create mode 100644 src/main/resources/application-prod.properties create mode 100644 src/test/java/com/agendaestudantil/servico/EstudanteServicoTest.java create mode 100644 src/test/java/com/agendaestudantil/servico/TarefaServicoTest.java diff --git a/.factorypath b/.factorypath index b62a4f0..40ad18f 100644 --- a/.factorypath +++ b/.factorypath @@ -1,51 +1,3 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.gitignore b/.gitignore index d1c44b4..7a900d3 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ buildNumber.properties *.iml *.ipr out/ +*.factorypath # VS Code .vscode/ diff --git a/COMOUSARAAPI.md b/COMOUSARAAPI.md new file mode 100644 index 0000000..0f8ba9c --- /dev/null +++ b/COMOUSARAAPI.md @@ -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 +``` \ No newline at end of file diff --git a/pom.xml b/pom.xml index 7f076b8..95125bd 100644 --- a/pom.xml +++ b/pom.xml @@ -19,6 +19,9 @@ 17 + 17 + 17 + 1.18.30 @@ -37,6 +40,39 @@ spring-boot-starter-validation + + + + org.springframework.boot + spring-boot-starter-security + + + + io.jsonwebtoken + jjwt-api + 0.12.3 + + + + io.jsonwebtoken + jjwt-impl + 0.12.3 + runtime + + + + io.jsonwebtoken + jjwt-jackson + 0.12.3 + runtime + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.3.0 + + io.github.cdimascio dotenv-java @@ -54,10 +90,40 @@ spring-boot-starter-test test + + + org.mockito + mockito-core + test + + + + org.springframework.security + spring-security-test + test + + + org.apache.maven.plugins + maven-compiler-plugin + + 17 + 17 + + -parameters + + + + org.projectlombok + lombok + ${lombok.version} + + + + org.springframework.boot spring-boot-maven-plugin diff --git a/src/main/java/com/agendaestudantil/config/EnvConfig.java b/src/main/java/com/agendaestudantil/config/EnvConfig.java deleted file mode 100644 index e5482e4..0000000 --- a/src/main/java/com/agendaestudantil/config/EnvConfig.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.agendaestudantil.config; - -import io.github.cdimascio.dotenv.Dotenv; -import io.github.cdimascio.dotenv.DotenvEntry; -import org.springframework.context.annotation.Configuration; - -import jakarta.annotation.PostConstruct; - -@Configuration -public class EnvConfig { - - @PostConstruct - public void init() { - Dotenv dotenv = Dotenv.configure() - .ignoreIfMissing() - .load(); - - if (dotenv != null) { - for (io.github.cdimascio.dotenv.DotenvEntry entry : dotenv.entries()) { - System.setProperty(entry.getKey(), entry.getValue()); - } - } - } -} diff --git a/src/main/java/com/agendaestudantil/configuracao/ConfiguracaoMongo.java b/src/main/java/com/agendaestudantil/configuracao/ConfiguracaoMongo.java new file mode 100644 index 0000000..3f5f981 --- /dev/null +++ b/src/main/java/com/agendaestudantil/configuracao/ConfiguracaoMongo.java @@ -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 { +} \ No newline at end of file diff --git a/src/main/java/com/agendaestudantil/configuracao/ConfiguracaoSeguranca.java b/src/main/java/com/agendaestudantil/configuracao/ConfiguracaoSeguranca.java new file mode 100644 index 0000000..bca7f13 --- /dev/null +++ b/src/main/java/com/agendaestudantil/configuracao/ConfiguracaoSeguranca.java @@ -0,0 +1,71 @@ +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/**", "/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.setAllowCredentials(true); + 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(); + } +} \ No newline at end of file diff --git a/src/main/java/com/agendaestudantil/configuracao/ConfiguracaoSwagger.java b/src/main/java/com/agendaestudantil/configuracao/ConfiguracaoSwagger.java new file mode 100644 index 0000000..cc6806f --- /dev/null +++ b/src/main/java/com/agendaestudantil/configuracao/ConfiguracaoSwagger.java @@ -0,0 +1,29 @@ +package com.agendaestudantil.configuracao; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ConfiguracaoSwagger { + + @Bean + public OpenAPI customOpenAPI() { + final String securitySchemeName = "bearerAuth"; + return new OpenAPI() + .info(new Info().title("API Agenda Estudantil") + .version("1.0") + .description("Documentação da API da Agenda Estudantil")) + .addSecurityItem(new SecurityRequirement().addList(securitySchemeName)) + .components(new Components().addSecuritySchemes(securitySchemeName, + new SecurityScheme() + .name(securitySchemeName) + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT"))); + } +} diff --git a/src/main/java/com/agendaestudantil/controlador/EstudanteControlador.java b/src/main/java/com/agendaestudantil/controlador/EstudanteControlador.java new file mode 100644 index 0000000..0435285 --- /dev/null +++ b/src/main/java/com/agendaestudantil/controlador/EstudanteControlador.java @@ -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> cadastrar(@Valid @RequestBody RequisicaoCadastroDTO dto) { + RespostaEstudanteDTO resposta = estudanteServico.cadastrar(dto); + return ResponseEntity.status(HttpStatus.CREATED).body(RespostaApi.sucesso(resposta)); + } + + @PostMapping("/login") + public ResponseEntity> login(@Valid @RequestBody RequisicaoLoginDTO dto) { + RespostaLoginDTO resposta = estudanteServico.login(dto); + return ResponseEntity.ok(RespostaApi.sucesso(resposta)); + } +} diff --git a/src/main/java/com/agendaestudantil/controlador/TarefaControlador.java b/src/main/java/com/agendaestudantil/controlador/TarefaControlador.java new file mode 100644 index 0000000..e078a12 --- /dev/null +++ b/src/main/java/com/agendaestudantil/controlador/TarefaControlador.java @@ -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> criarTarefa(@Valid @RequestBody RequisicaoTarefaDTO dto) { + RespostaTarefaDTO tarefa = tarefaServico.criarTarefa(dto); + return ResponseEntity.status(HttpStatus.CREATED).body(RespostaApi.sucesso(tarefa)); + } + + @GetMapping("/estudante/{estudanteId}") + public ResponseEntity>> listarTarefasPorEstudante( + @PathVariable String estudanteId) { + List tarefas = tarefaServico.listarTarefasPorEstudante(estudanteId); + return ResponseEntity.ok(RespostaApi.sucesso(tarefas)); + } + + @GetMapping("/estudante/{estudanteId}/pendentes") + public ResponseEntity>> listarTarefasPendentes( + @PathVariable String estudanteId) { + List tarefas = tarefaServico.listarTarefasPendentes(estudanteId); + return ResponseEntity.ok(RespostaApi.sucesso(tarefas)); + } + + @GetMapping("/estudante/{estudanteId}/data") + public ResponseEntity>> listarTarefasPorData( + @PathVariable String estudanteId, + @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate data) { + List tarefas = tarefaServico.listarTarefasPorData(estudanteId, data); + return ResponseEntity.ok(RespostaApi.sucesso(tarefas)); + } + + @GetMapping("/estudante/{estudanteId}/periodo") + public ResponseEntity>> listarTarefasPorPeriodo( + @PathVariable String estudanteId, + @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate inicio, + @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate fim) { + List tarefas = tarefaServico.listarTarefasPorPeriodo(estudanteId, inicio, fim); + return ResponseEntity.ok(RespostaApi.sucesso(tarefas)); + } + + @GetMapping("/{id}") + public ResponseEntity> buscarTarefaPorId(@PathVariable String id) { + RespostaTarefaDTO dto = tarefaServico.buscarTarefaPorId(id); + return ResponseEntity.ok(RespostaApi.sucesso(dto)); + } + + @PutMapping("/{id}") + public ResponseEntity> atualizarTarefa(@PathVariable String id, + @Valid @RequestBody RequisicaoTarefaDTO dto) { + RespostaTarefaDTO tarefa = tarefaServico.atualizarTarefa(id, dto); + return ResponseEntity.ok(RespostaApi.sucesso(tarefa)); + } + + @DeleteMapping("/{id}") + public ResponseEntity> excluirTarefa(@PathVariable String id) { + tarefaServico.excluirTarefa(id); + return ResponseEntity.status(HttpStatus.NO_CONTENT).body(RespostaApi.sucesso(null)); + } + + @PatchMapping("/{id}/concluir") + public ResponseEntity> marcarConcluida(@PathVariable String id) { + RespostaTarefaDTO tarefa = tarefaServico.marcarConcluida(id); + return ResponseEntity.ok(RespostaApi.sucesso(tarefa)); + } +} diff --git a/src/main/java/com/agendaestudantil/controller/EstudanteController.java b/src/main/java/com/agendaestudantil/controller/EstudanteController.java deleted file mode 100644 index ec30fda..0000000 --- a/src/main/java/com/agendaestudantil/controller/EstudanteController.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.agendaestudantil.controller; - -import com.agendaestudantil.dto.CadastroRequestDTO; -import com.agendaestudantil.dto.EstudanteResponseDTO; -import com.agendaestudantil.dto.LoginRequestDTO; -import com.agendaestudantil.service.EstudanteService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -@RestController -@RequestMapping("/api/estudantes") -@CrossOrigin(origins = "*") -public class EstudanteController { - - @Autowired - private EstudanteService estudanteService; - - @PostMapping("/cadastro") - public ResponseEntity cadastrar(@RequestBody CadastroRequestDTO dto) { - EstudanteResponseDTO resposta = estudanteService.cadastrar(dto); - return ResponseEntity.status(HttpStatus.CREATED).body(resposta); - } - - @PostMapping("/login") - public ResponseEntity login(@RequestBody LoginRequestDTO dto) { - EstudanteResponseDTO resposta = estudanteService.login(dto); - return ResponseEntity.ok(resposta); - } -} diff --git a/src/main/java/com/agendaestudantil/controller/TarefaController.java b/src/main/java/com/agendaestudantil/controller/TarefaController.java deleted file mode 100644 index d9f4931..0000000 --- a/src/main/java/com/agendaestudantil/controller/TarefaController.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.agendaestudantil.controller; - -import com.agendaestudantil.dto.TarefaRequestDTO; -import com.agendaestudantil.entity.Tarefa; -import com.agendaestudantil.service.TarefaService; -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -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") -@RequiredArgsConstructor -public class TarefaController { - - private final TarefaService tarefaService; - - @PostMapping - public ResponseEntity criarTarefa(@Valid @RequestBody TarefaRequestDTO dto) { - Tarefa tarefa = tarefaService.criarTarefa(dto); - return ResponseEntity.status(HttpStatus.CREATED).body(tarefa); - } - - @GetMapping("/estudante/{estudanteId}") - public ResponseEntity> listarTarefasPorEstudante(@PathVariable String estudanteId) { - List tarefas = tarefaService.listarTarefasPorEstudante(estudanteId); - return ResponseEntity.ok(tarefas); - } - - @GetMapping("/estudante/{estudanteId}/pendentes") - public ResponseEntity> listarTarefasPendentes(@PathVariable String estudanteId) { - List tarefas = tarefaService.listarTarefasPendentes(estudanteId); - return ResponseEntity.ok(tarefas); - } - - @GetMapping("/estudante/{estudanteId}/data") - public ResponseEntity> listarTarefasPorData( - @PathVariable String estudanteId, - @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate data) { - List tarefas = tarefaService.listarTarefasPorData(estudanteId, data); - return ResponseEntity.ok(tarefas); - } - - @GetMapping("/estudante/{estudanteId}/periodo") - public ResponseEntity> listarTarefasPorPeriodo( - @PathVariable String estudanteId, - @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate inicio, - @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate fim) { - List tarefas = tarefaService.listarTarefasPorPeriodo(estudanteId, inicio, fim); - return ResponseEntity.ok(tarefas); - } - - @GetMapping("/{id}") - public ResponseEntity buscarTarefaPorId(@PathVariable String id) { - return tarefaService.buscarTarefaPorId(id) - .map(ResponseEntity::ok) - .orElse(ResponseEntity.notFound().build()); - } - - @PutMapping("/{id}") - public ResponseEntity atualizarTarefa(@PathVariable String id, @Valid @RequestBody TarefaRequestDTO dto) { - Tarefa tarefa = tarefaService.atualizarTarefa(id, dto); - return ResponseEntity.ok(tarefa); - } - - @DeleteMapping("/{id}") - public ResponseEntity excluirTarefa(@PathVariable String id) { - tarefaService.excluirTarefa(id); - return ResponseEntity.noContent().build(); - } - - @PatchMapping("/{id}/concluir") - public ResponseEntity marcarConcluida(@PathVariable String id) { - Tarefa tarefa = tarefaService.marcarConcluida(id); - return ResponseEntity.ok(tarefa); - } -} diff --git a/src/main/java/com/agendaestudantil/dto/CadastroRequestDTO.java b/src/main/java/com/agendaestudantil/dto/CadastroRequestDTO.java deleted file mode 100644 index 0b2728e..0000000 --- a/src/main/java/com/agendaestudantil/dto/CadastroRequestDTO.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.agendaestudantil.dto; - -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.AllArgsConstructor; - -@Data -@NoArgsConstructor -@AllArgsConstructor -public class CadastroRequestDTO { - private String nome; - private String email; - private String senha; - private String curso; - private Integer periodo; -} diff --git a/src/main/java/com/agendaestudantil/dto/EstudanteResponseDTO.java b/src/main/java/com/agendaestudantil/dto/EstudanteResponseDTO.java deleted file mode 100644 index 4fc9552..0000000 --- a/src/main/java/com/agendaestudantil/dto/EstudanteResponseDTO.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.agendaestudantil.dto; - -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.AllArgsConstructor; - -@Data -@NoArgsConstructor -@AllArgsConstructor -public class EstudanteResponseDTO { - private String id; - private String nome; - private String email; - private String curso; - private Integer periodo; -} diff --git a/src/main/java/com/agendaestudantil/dto/LoginRequestDTO.java b/src/main/java/com/agendaestudantil/dto/LoginRequestDTO.java deleted file mode 100644 index beb1d90..0000000 --- a/src/main/java/com/agendaestudantil/dto/LoginRequestDTO.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.agendaestudantil.dto; - -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.AllArgsConstructor; - -@Data -@NoArgsConstructor -@AllArgsConstructor -public class LoginRequestDTO { - private String email; - private String senha; -} diff --git a/src/main/java/com/agendaestudantil/dto/RequisicaoCadastroDTO.java b/src/main/java/com/agendaestudantil/dto/RequisicaoCadastroDTO.java new file mode 100644 index 0000000..7c1dee2 --- /dev/null +++ b/src/main/java/com/agendaestudantil/dto/RequisicaoCadastroDTO.java @@ -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 +) { +} diff --git a/src/main/java/com/agendaestudantil/dto/RequisicaoLoginDTO.java b/src/main/java/com/agendaestudantil/dto/RequisicaoLoginDTO.java new file mode 100644 index 0000000..fc73032 --- /dev/null +++ b/src/main/java/com/agendaestudantil/dto/RequisicaoLoginDTO.java @@ -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 +) { +} diff --git a/src/main/java/com/agendaestudantil/dto/RequisicaoTarefaDTO.java b/src/main/java/com/agendaestudantil/dto/RequisicaoTarefaDTO.java new file mode 100644 index 0000000..4295812 --- /dev/null +++ b/src/main/java/com/agendaestudantil/dto/RequisicaoTarefaDTO.java @@ -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 Tarefa.Prioridade prioridade, + Tarefa.StatusTarefa status, + @NotNull @Future LocalDate dataEntrega, + String disciplinaId, + @NotBlank String estudanteId +) { +} diff --git a/src/main/java/com/agendaestudantil/dto/RespostaApi.java b/src/main/java/com/agendaestudantil/dto/RespostaApi.java new file mode 100644 index 0000000..69ef1a2 --- /dev/null +++ b/src/main/java/com/agendaestudantil/dto/RespostaApi.java @@ -0,0 +1,9 @@ +package com.agendaestudantil.dto; + +import java.time.LocalDateTime; + +public record RespostaApi(T data, String message, LocalDateTime timestamp) { + public static RespostaApi sucesso(T data) { + return new RespostaApi<>(data, "Sucesso", LocalDateTime.now()); + } +} \ No newline at end of file diff --git a/src/main/java/com/agendaestudantil/dto/RespostaDisciplinaDTO.java b/src/main/java/com/agendaestudantil/dto/RespostaDisciplinaDTO.java new file mode 100644 index 0000000..360ebf8 --- /dev/null +++ b/src/main/java/com/agendaestudantil/dto/RespostaDisciplinaDTO.java @@ -0,0 +1,10 @@ +package com.agendaestudantil.dto; + +public record RespostaDisciplinaDTO( + String id, + String estudanteId, + String nome, + String professor, + String sala, + String cor +) {} diff --git a/src/main/java/com/agendaestudantil/dto/RespostaEstudanteDTO.java b/src/main/java/com/agendaestudantil/dto/RespostaEstudanteDTO.java new file mode 100644 index 0000000..264d88a --- /dev/null +++ b/src/main/java/com/agendaestudantil/dto/RespostaEstudanteDTO.java @@ -0,0 +1,10 @@ +package com.agendaestudantil.dto; + +public record RespostaEstudanteDTO( + String id, + String nome, + String email, + String curso, + Integer periodo +) { +} diff --git a/src/main/java/com/agendaestudantil/dto/RespostaEventoDTO.java b/src/main/java/com/agendaestudantil/dto/RespostaEventoDTO.java new file mode 100644 index 0000000..82b764d --- /dev/null +++ b/src/main/java/com/agendaestudantil/dto/RespostaEventoDTO.java @@ -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 +) {} diff --git a/src/main/java/com/agendaestudantil/dto/RespostaLoginDTO.java b/src/main/java/com/agendaestudantil/dto/RespostaLoginDTO.java new file mode 100644 index 0000000..3cc2eb2 --- /dev/null +++ b/src/main/java/com/agendaestudantil/dto/RespostaLoginDTO.java @@ -0,0 +1,4 @@ +package com.agendaestudantil.dto; + +public record RespostaLoginDTO(String token, RespostaEstudanteDTO estudante) { +} diff --git a/src/main/java/com/agendaestudantil/dto/RespostaTarefaDTO.java b/src/main/java/com/agendaestudantil/dto/RespostaTarefaDTO.java new file mode 100644 index 0000000..ae18890 --- /dev/null +++ b/src/main/java/com/agendaestudantil/dto/RespostaTarefaDTO.java @@ -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 +) {} diff --git a/src/main/java/com/agendaestudantil/dto/TarefaRequestDTO.java b/src/main/java/com/agendaestudantil/dto/TarefaRequestDTO.java deleted file mode 100644 index bafe02c..0000000 --- a/src/main/java/com/agendaestudantil/dto/TarefaRequestDTO.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.agendaestudantil.dto; - -import com.agendaestudantil.entity.Tarefa; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.Data; -import java.time.LocalDate; - -@Data -public class TarefaRequestDTO { - - @NotBlank(message = "Título é obrigatório") - private String titulo; - - private String descricao; - - private Tarefa.Prioridade prioridade; - - private Tarefa.StatusTarefa status; - - @NotNull(message = "Data de entrega é obrigatória") - private LocalDate dataEntrega; - - private String disciplinaId; - - @NotBlank(message = "ID do estudante é obrigatório") - private String estudanteId; -} diff --git a/src/main/java/com/agendaestudantil/entidade/Disciplina.java b/src/main/java/com/agendaestudantil/entidade/Disciplina.java new file mode 100644 index 0000000..e3b41db --- /dev/null +++ b/src/main/java/com/agendaestudantil/entidade/Disciplina.java @@ -0,0 +1,97 @@ +package com.agendaestudantil.entidade; + +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.index.CompoundIndex; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.util.Objects; + +@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; + + public Disciplina() { + } + + public Disciplina(String id, String estudanteId, String nome, String professor, String sala, String cor) { + this.id = id; + this.estudanteId = estudanteId; + this.nome = nome; + this.professor = professor; + this.sala = sala; + this.cor = cor; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getEstudanteId() { + return estudanteId; + } + + public void setEstudanteId(String estudanteId) { + this.estudanteId = estudanteId; + } + + public String getNome() { + return nome; + } + + public void setNome(String nome) { + this.nome = nome; + } + + public String getProfessor() { + return professor; + } + + public void setProfessor(String professor) { + this.professor = professor; + } + + public String getSala() { + return sala; + } + + public void setSala(String sala) { + this.sala = sala; + } + + public String getCor() { + return cor; + } + + public void setCor(String cor) { + this.cor = cor; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + Disciplina that = (Disciplina) o; + return Objects.equals(id, that.id) && + Objects.equals(estudanteId, that.estudanteId) && + Objects.equals(nome, that.nome); + } + + @Override + public int hashCode() { + return Objects.hash(id, estudanteId, nome); + } +} \ No newline at end of file diff --git a/src/main/java/com/agendaestudantil/entidade/EntidadeAuditoria.java b/src/main/java/com/agendaestudantil/entidade/EntidadeAuditoria.java new file mode 100644 index 0000000..63b8048 --- /dev/null +++ b/src/main/java/com/agendaestudantil/entidade/EntidadeAuditoria.java @@ -0,0 +1,30 @@ +package com.agendaestudantil.entidade; + +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import java.time.LocalDateTime; + +public abstract class EntidadeAuditoria { + + @CreatedDate + private LocalDateTime dataCriacao; + + @LastModifiedDate + private LocalDateTime dataAtualizacao; + + public LocalDateTime getDataCriacao() { + return dataCriacao; + } + + public void setDataCriacao(LocalDateTime dataCriacao) { + this.dataCriacao = dataCriacao; + } + + public LocalDateTime getDataAtualizacao() { + return dataAtualizacao; + } + + public void setDataAtualizacao(LocalDateTime dataAtualizacao) { + this.dataAtualizacao = dataAtualizacao; + } +} diff --git a/src/main/java/com/agendaestudantil/entidade/Estudante.java b/src/main/java/com/agendaestudantil/entidade/Estudante.java new file mode 100644 index 0000000..7295ce8 --- /dev/null +++ b/src/main/java/com/agendaestudantil/entidade/Estudante.java @@ -0,0 +1,101 @@ +package com.agendaestudantil.entidade; + +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.index.Indexed; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.util.Objects; + +@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; + + public Estudante() { + } + + public Estudante(String id, String email, String nome, String senha, String curso, Integer periodo) { + this.id = id; + this.email = email; + this.nome = nome; + this.senha = senha; + this.curso = curso; + this.periodo = periodo; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getNome() { + return nome; + } + + public void setNome(String nome) { + this.nome = nome; + } + + public String getSenha() { + return senha; + } + + public void setSenha(String senha) { + this.senha = senha; + } + + public String getCurso() { + return curso; + } + + public void setCurso(String curso) { + this.curso = curso; + } + + public Integer getPeriodo() { + return periodo; + } + + public void setPeriodo(Integer periodo) { + this.periodo = periodo; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + Estudante estudante = (Estudante) o; + return Objects.equals(id, estudante.id) && + Objects.equals(email, estudante.email); + } + + @Override + public int hashCode() { + return Objects.hash(id, email); + } +} \ No newline at end of file diff --git a/src/main/java/com/agendaestudantil/entidade/Evento.java b/src/main/java/com/agendaestudantil/entidade/Evento.java new file mode 100644 index 0000000..3c35b99 --- /dev/null +++ b/src/main/java/com/agendaestudantil/entidade/Evento.java @@ -0,0 +1,123 @@ +package com.agendaestudantil.entidade; + +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; +import java.util.Objects; + +@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 Evento() { + } + + public Evento(String id, String estudanteId, String titulo, String descricao, TipoEvento tipo, String local, + String disciplinaId, LocalDateTime dataHora) { + this.id = id; + this.estudanteId = estudanteId; + this.titulo = titulo; + this.descricao = descricao; + this.tipo = tipo; + this.local = local; + this.disciplinaId = disciplinaId; + this.dataHora = dataHora; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getEstudanteId() { + return estudanteId; + } + + public void setEstudanteId(String estudanteId) { + this.estudanteId = estudanteId; + } + + public String getTitulo() { + return titulo; + } + + public void setTitulo(String titulo) { + this.titulo = titulo; + } + + public String getDescricao() { + return descricao; + } + + public void setDescricao(String descricao) { + this.descricao = descricao; + } + + public TipoEvento getTipo() { + return tipo; + } + + public void setTipo(TipoEvento tipo) { + this.tipo = tipo; + } + + public String getLocal() { + return local; + } + + public void setLocal(String local) { + this.local = local; + } + + public String getDisciplinaId() { + return disciplinaId; + } + + public void setDisciplinaId(String disciplinaId) { + this.disciplinaId = disciplinaId; + } + + public LocalDateTime getDataHora() { + return dataHora; + } + + public void setDataHora(LocalDateTime dataHora) { + this.dataHora = dataHora; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + Evento evento = (Evento) o; + return Objects.equals(id, evento.id) && + Objects.equals(estudanteId, evento.estudanteId) && + Objects.equals(titulo, evento.titulo); + } + + @Override + public int hashCode() { + return Objects.hash(id, estudanteId, titulo); + } + + public enum TipoEvento { + AULA, PROVA, TRABALHO, ESTUDO, EXAME, OUTRO + } +} \ No newline at end of file diff --git a/src/main/java/com/agendaestudantil/entidade/Tarefa.java b/src/main/java/com/agendaestudantil/entidade/Tarefa.java new file mode 100644 index 0000000..47f37d3 --- /dev/null +++ b/src/main/java/com/agendaestudantil/entidade/Tarefa.java @@ -0,0 +1,131 @@ +package com.agendaestudantil.entidade; + +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; +import java.util.Objects; + +@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 Tarefa() { + } + + public Tarefa(String id, String titulo, String descricao, Prioridade prioridade, StatusTarefa status, + LocalDate dataEntrega, String disciplinaId, String estudanteId) { + this.id = id; + this.titulo = titulo; + this.descricao = descricao; + this.prioridade = prioridade; + this.status = status; + this.dataEntrega = dataEntrega; + this.disciplinaId = disciplinaId; + this.estudanteId = estudanteId; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getTitulo() { + return titulo; + } + + public void setTitulo(String titulo) { + this.titulo = titulo; + } + + public String getDescricao() { + return descricao; + } + + public void setDescricao(String descricao) { + this.descricao = descricao; + } + + public Prioridade getPrioridade() { + return prioridade; + } + + public void setPrioridade(Prioridade prioridade) { + this.prioridade = prioridade; + } + + public StatusTarefa getStatus() { + return status; + } + + public void setStatus(StatusTarefa status) { + this.status = status; + } + + public LocalDate getDataEntrega() { + return dataEntrega; + } + + public void setDataEntrega(LocalDate dataEntrega) { + this.dataEntrega = dataEntrega; + } + + public String getDisciplinaId() { + return disciplinaId; + } + + public void setDisciplinaId(String disciplinaId) { + this.disciplinaId = disciplinaId; + } + + public String getEstudanteId() { + return estudanteId; + } + + public void setEstudanteId(String estudanteId) { + this.estudanteId = estudanteId; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + Tarefa tarefa = (Tarefa) o; + return Objects.equals(id, tarefa.id) && + Objects.equals(estudanteId, tarefa.estudanteId) && + Objects.equals(titulo, tarefa.titulo); + } + + @Override + public int hashCode() { + return Objects.hash(id, estudanteId, titulo); + } + + public enum Prioridade { + BAIXA, MEDIA, ALTA, URGENTE + } + + public enum StatusTarefa { + PENDENTE, EM_ANDAMENTO, CONCLUIDA, ATRASADA + } +} \ No newline at end of file diff --git a/src/main/java/com/agendaestudantil/entity/Disciplina.java b/src/main/java/com/agendaestudantil/entity/Disciplina.java deleted file mode 100644 index 6aecb29..0000000 --- a/src/main/java/com/agendaestudantil/entity/Disciplina.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.agendaestudantil.entity; - -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.AllArgsConstructor; -import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.mapping.Document; -import java.time.LocalDateTime; - -@Document(collection = "disciplinas") -@Data -@NoArgsConstructor -@AllArgsConstructor -public class Disciplina { - - @Id - private String id; - - private String nome; - - private String professor; - - private String sala; - - private String cor; - - private String estudanteId; - - private LocalDateTime dataCriacao; -} diff --git a/src/main/java/com/agendaestudantil/entity/Estudante.java b/src/main/java/com/agendaestudantil/entity/Estudante.java deleted file mode 100644 index 489185d..0000000 --- a/src/main/java/com/agendaestudantil/entity/Estudante.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.agendaestudantil.entity; - -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.AllArgsConstructor; -import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.mapping.Document; -import java.time.LocalDateTime; - -@Document(collection = "estudantes") -@Data -@NoArgsConstructor -@AllArgsConstructor -public class Estudante { - - @Id - private String id; - - private String nome; - - private String email; - - private String senha; - - private String curso; - - private Integer periodo; - - private LocalDateTime dataCriacao; - - private LocalDateTime dataAtualizacao; -} diff --git a/src/main/java/com/agendaestudantil/entity/Evento.java b/src/main/java/com/agendaestudantil/entity/Evento.java deleted file mode 100644 index e319745..0000000 --- a/src/main/java/com/agendaestudantil/entity/Evento.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.agendaestudantil.entity; - -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.AllArgsConstructor; -import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.mapping.Document; -import java.time.LocalDateTime; - -@Document(collection = "eventos") -@Data -@NoArgsConstructor -@AllArgsConstructor -public class Evento { - - @Id - private String id; - - private String titulo; - - private String descricao; - - private TipoEvento tipo; - - private LocalDateTime dataInicio; - - private LocalDateTime dataFim; - - private String local; - - private String disciplinaId; - - private String estudanteId; - - private LocalDateTime dataCriacao; - - public enum TipoEvento { - AULA, PROVA, TRABALHO, ATIVIDADE, EVENTO, LEMBRETE - } -} diff --git a/src/main/java/com/agendaestudantil/entity/Tarefa.java b/src/main/java/com/agendaestudantil/entity/Tarefa.java deleted file mode 100644 index 1b61ed5..0000000 --- a/src/main/java/com/agendaestudantil/entity/Tarefa.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.agendaestudantil.entity; - -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.AllArgsConstructor; -import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.mapping.Document; -import java.time.LocalDate; -import java.time.LocalDateTime; - -@Document(collection = "tarefas") -@Data -@NoArgsConstructor -@AllArgsConstructor -public class Tarefa { - - @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; - - private LocalDateTime dataCriacao; - - private LocalDateTime dataAtualizacao; - - public enum Prioridade { - BAIXA, MEDIA, ALTA, URGENTE - } - - public enum StatusTarefa { - PENDENTE, EM_ANDAMENTO, CONCLUIDA, ATRASADA - } -} diff --git a/src/main/java/com/agendaestudantil/excecao/ExcecaoNegocio.java b/src/main/java/com/agendaestudantil/excecao/ExcecaoNegocio.java new file mode 100644 index 0000000..d516f1e --- /dev/null +++ b/src/main/java/com/agendaestudantil/excecao/ExcecaoNegocio.java @@ -0,0 +1,7 @@ +package com.agendaestudantil.excecao; + +public class ExcecaoNegocio extends RuntimeException { + public ExcecaoNegocio(String message) { + super(message); + } +} diff --git a/src/main/java/com/agendaestudantil/excecao/ExcecaoRecursoNaoEncontrado.java b/src/main/java/com/agendaestudantil/excecao/ExcecaoRecursoNaoEncontrado.java new file mode 100644 index 0000000..a099dd3 --- /dev/null +++ b/src/main/java/com/agendaestudantil/excecao/ExcecaoRecursoNaoEncontrado.java @@ -0,0 +1,7 @@ +package com.agendaestudantil.excecao; + +public class ExcecaoRecursoNaoEncontrado extends RuntimeException { + public ExcecaoRecursoNaoEncontrado(String message) { + super(message); + } +} diff --git a/src/main/java/com/agendaestudantil/excecao/ManipuladorExcecaoGlobal.java b/src/main/java/com/agendaestudantil/excecao/ManipuladorExcecaoGlobal.java new file mode 100644 index 0000000..a3dc5d2 --- /dev/null +++ b/src/main/java/com/agendaestudantil/excecao/ManipuladorExcecaoGlobal.java @@ -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> handleResourceNotFound(ExcecaoRecursoNaoEncontrado ex) { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(new RespostaApi<>(null, ex.getMessage(), LocalDateTime.now())); + } + + @ExceptionHandler(ExcecaoNegocio.class) + public ResponseEntity> handleExcecaoNegocio(ExcecaoNegocio ex) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(new RespostaApi<>(null, ex.getMessage(), LocalDateTime.now())); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity>> handleValidationException( + MethodArgumentNotValidException ex) { + Map 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> handleResponseStatusException(ResponseStatusException ex) { + return ResponseEntity.status(ex.getStatusCode()) + .body(new RespostaApi<>(null, ex.getReason(), LocalDateTime.now())); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity> handleGenericException(Exception ex) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(new RespostaApi<>(null, "Erro interno no servidor: " + ex.getMessage(), LocalDateTime.now())); + } +} diff --git a/src/main/java/com/agendaestudantil/filtro/FiltroJwt.java b/src/main/java/com/agendaestudantil/filtro/FiltroJwt.java new file mode 100644 index 0000000..12713e9 --- /dev/null +++ b/src/main/java/com/agendaestudantil/filtro/FiltroJwt.java @@ -0,0 +1,53 @@ +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 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); + } +} \ No newline at end of file diff --git a/src/main/java/com/agendaestudantil/repository/DisciplinaRepository.java b/src/main/java/com/agendaestudantil/repositorio/DisciplinaRepositorio.java similarity index 55% rename from src/main/java/com/agendaestudantil/repository/DisciplinaRepository.java rename to src/main/java/com/agendaestudantil/repositorio/DisciplinaRepositorio.java index 6aa614b..7443433 100644 --- a/src/main/java/com/agendaestudantil/repository/DisciplinaRepository.java +++ b/src/main/java/com/agendaestudantil/repositorio/DisciplinaRepositorio.java @@ -1,11 +1,11 @@ -package com.agendaestudantil.repository; +package com.agendaestudantil.repositorio; -import com.agendaestudantil.entity.Disciplina; +import com.agendaestudantil.entidade.Disciplina; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; import java.util.List; @Repository -public interface DisciplinaRepository extends MongoRepository { +public interface DisciplinaRepositorio extends MongoRepository { List findByEstudanteId(String estudanteId); } diff --git a/src/main/java/com/agendaestudantil/repository/EstudanteRepository.java b/src/main/java/com/agendaestudantil/repositorio/EstudanteRepositorio.java similarity index 59% rename from src/main/java/com/agendaestudantil/repository/EstudanteRepository.java rename to src/main/java/com/agendaestudantil/repositorio/EstudanteRepositorio.java index 9381975..5124576 100644 --- a/src/main/java/com/agendaestudantil/repository/EstudanteRepository.java +++ b/src/main/java/com/agendaestudantil/repositorio/EstudanteRepositorio.java @@ -1,12 +1,12 @@ -package com.agendaestudantil.repository; +package com.agendaestudantil.repositorio; -import com.agendaestudantil.entity.Estudante; +import com.agendaestudantil.entidade.Estudante; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; import java.util.Optional; @Repository -public interface EstudanteRepository extends MongoRepository { +public interface EstudanteRepositorio extends MongoRepository { Optional findByEmail(String email); boolean existsByEmail(String email); } diff --git a/src/main/java/com/agendaestudantil/repository/EventoRepository.java b/src/main/java/com/agendaestudantil/repositorio/EventoRepositorio.java similarity index 75% rename from src/main/java/com/agendaestudantil/repository/EventoRepository.java rename to src/main/java/com/agendaestudantil/repositorio/EventoRepositorio.java index d8e147b..ef55801 100644 --- a/src/main/java/com/agendaestudantil/repository/EventoRepository.java +++ b/src/main/java/com/agendaestudantil/repositorio/EventoRepositorio.java @@ -1,22 +1,22 @@ -package com.agendaestudantil.repository; +package com.agendaestudantil.repositorio; -import com.agendaestudantil.entity.Evento; +import com.agendaestudantil.entidade.Evento; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; -import org.springframework.data.repository.query.Param; + import org.springframework.stereotype.Repository; import java.time.LocalDateTime; import java.util.List; @Repository -public interface EventoRepository extends MongoRepository { +public interface EventoRepositorio extends MongoRepository { List findByEstudanteId(String estudanteId); - + List findByDisciplinaId(String disciplinaId); - + @Query("{'estudanteId': ?0, 'dataInicio': {$gte: ?1, $lte: ?2}}") List findByEstudanteIdAndDataInicioBetween(String estudanteId, LocalDateTime inicio, LocalDateTime fim); - + @Query("{'estudanteId': ?0, 'dataInicio': {$gte: ?1}}") List findProximosEventosByEstudanteId(String estudanteId, LocalDateTime data); } diff --git a/src/main/java/com/agendaestudantil/repository/TarefaRepository.java b/src/main/java/com/agendaestudantil/repositorio/TarefaRepositorio.java similarity index 72% rename from src/main/java/com/agendaestudantil/repository/TarefaRepository.java rename to src/main/java/com/agendaestudantil/repositorio/TarefaRepositorio.java index 02cc277..cbd6883 100644 --- a/src/main/java/com/agendaestudantil/repository/TarefaRepository.java +++ b/src/main/java/com/agendaestudantil/repositorio/TarefaRepositorio.java @@ -1,27 +1,27 @@ -package com.agendaestudantil.repository; +package com.agendaestudantil.repositorio; -import com.agendaestudantil.entity.Tarefa; +import com.agendaestudantil.entidade.Tarefa; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; -import org.springframework.data.repository.query.Param; + import org.springframework.stereotype.Repository; import java.time.LocalDate; import java.util.List; @Repository -public interface TarefaRepository extends MongoRepository { +public interface TarefaRepositorio extends MongoRepository { List findByEstudanteId(String estudanteId); - + List findByEstudanteIdAndStatus(String estudanteId, Tarefa.StatusTarefa status); - + List findByDisciplinaId(String disciplinaId); - + @Query("{'estudanteId': ?0, 'dataEntrega': ?1}") List findByEstudanteIdAndDataEntrega(String estudanteId, LocalDate data); - + @Query("{'estudanteId': ?0, 'dataEntrega': {$gte: ?1, $lte: ?2}}") List findByEstudanteIdAndDataEntregaBetween(String estudanteId, LocalDate inicio, LocalDate fim); - - @Query("{'estudanteId': ?0, 'status': {$ne: 'CONCLUIDA'}}") - List findTarefasPendentesByEstudanteId(String estudanteId); + + @Query("{'estudanteId': ?0, 'status': {$ne: ?1}}") + List findTarefasPendentesByEstudanteId(String estudanteId, Tarefa.StatusTarefa status); } diff --git a/src/main/java/com/agendaestudantil/seguranca/DetalhesUsuarioPersonalizado.java b/src/main/java/com/agendaestudantil/seguranca/DetalhesUsuarioPersonalizado.java new file mode 100644 index 0000000..d094e09 --- /dev/null +++ b/src/main/java/com/agendaestudantil/seguranca/DetalhesUsuarioPersonalizado.java @@ -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 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; + } +} \ No newline at end of file diff --git a/src/main/java/com/agendaestudantil/seguranca/ServicoAutenticacaoUsuario.java b/src/main/java/com/agendaestudantil/seguranca/ServicoAutenticacaoUsuario.java new file mode 100644 index 0000000..876f7a0 --- /dev/null +++ b/src/main/java/com/agendaestudantil/seguranca/ServicoAutenticacaoUsuario.java @@ -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); + } +} \ No newline at end of file diff --git a/src/main/java/com/agendaestudantil/service/EstudanteService.java b/src/main/java/com/agendaestudantil/service/EstudanteService.java deleted file mode 100644 index 048953c..0000000 --- a/src/main/java/com/agendaestudantil/service/EstudanteService.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.agendaestudantil.service; - -import com.agendaestudantil.dto.CadastroRequestDTO; -import com.agendaestudantil.dto.EstudanteResponseDTO; -import com.agendaestudantil.dto.LoginRequestDTO; -import com.agendaestudantil.entity.Estudante; -import com.agendaestudantil.repository.EstudanteRepository; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Service; -import org.springframework.web.server.ResponseStatusException; - -import java.time.LocalDateTime; -import java.util.Optional; - -@Service -public class EstudanteService { - - @Autowired - private EstudanteRepository estudanteRepository; - - public EstudanteResponseDTO cadastrar(CadastroRequestDTO dto) { - Optional existente = estudanteRepository.findByEmail(dto.getEmail()); - if (existente.isPresent()) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Email já cadastrado"); - } - - Estudante estudante = new Estudante(); - estudante.setNome(dto.getNome()); - estudante.setEmail(dto.getEmail()); - estudante.setSenha(dto.getSenha()); - estudante.setCurso(dto.getCurso()); - estudante.setPeriodo(dto.getPeriodo()); - estudante.setDataCriacao(LocalDateTime.now()); - estudante.setDataAtualizacao(LocalDateTime.now()); - - Estudante salvo = estudanteRepository.save(estudante); - return toResponse(salvo); - } - - public EstudanteResponseDTO login(LoginRequestDTO dto) { - Optional estudante = estudanteRepository.findByEmail(dto.getEmail()); - - if (estudante.isEmpty()) { - throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Email ou senha incorretos"); - } - - if (!estudante.get().getSenha().equals(dto.getSenha())) { - throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Email ou senha incorretos"); - } - - return toResponse(estudante.get()); - } - - private EstudanteResponseDTO toResponse(Estudante estudante) { - return new EstudanteResponseDTO( - estudante.getId(), - estudante.getNome(), - estudante.getEmail(), - estudante.getCurso(), - estudante.getPeriodo() - ); - } -} diff --git a/src/main/java/com/agendaestudantil/service/TarefaService.java b/src/main/java/com/agendaestudantil/service/TarefaService.java deleted file mode 100644 index 01a776c..0000000 --- a/src/main/java/com/agendaestudantil/service/TarefaService.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.agendaestudantil.service; - -import com.agendaestudantil.dto.TarefaRequestDTO; -import com.agendaestudantil.entity.Disciplina; -import com.agendaestudantil.entity.Tarefa; -import com.agendaestudantil.repository.DisciplinaRepository; -import com.agendaestudantil.repository.TarefaRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; - -@Service -@RequiredArgsConstructor -public class TarefaService { - - private final TarefaRepository tarefaRepository; - private final DisciplinaRepository disciplinaRepository; - - @Transactional - public Tarefa criarTarefa(TarefaRequestDTO dto) { - Tarefa tarefa = new Tarefa(); - tarefa.setTitulo(dto.getTitulo()); - tarefa.setDescricao(dto.getDescricao()); - tarefa.setPrioridade(dto.getPrioridade() != null ? dto.getPrioridade() : Tarefa.Prioridade.MEDIA); - tarefa.setStatus(dto.getStatus() != null ? dto.getStatus() : Tarefa.StatusTarefa.PENDENTE); - tarefa.setDataEntrega(dto.getDataEntrega()); - tarefa.setEstudanteId(dto.getEstudanteId()); - tarefa.setDataCriacao(LocalDateTime.now()); - tarefa.setDataAtualizacao(LocalDateTime.now()); - - if (dto.getDisciplinaId() != null) { - Disciplina disciplina = disciplinaRepository.findById(dto.getDisciplinaId()) - .orElseThrow(() -> new RuntimeException("Disciplina não encontrada")); - tarefa.setDisciplinaId(disciplina.getId()); - } - - return tarefaRepository.save(tarefa); - } - - public List listarTarefasPorEstudante(String estudanteId) { - return tarefaRepository.findByEstudanteId(estudanteId); - } - - public List listarTarefasPendentes(String estudanteId) { - return tarefaRepository.findTarefasPendentesByEstudanteId(estudanteId); - } - - public List listarTarefasPorData(String estudanteId, LocalDate data) { - return tarefaRepository.findByEstudanteIdAndDataEntrega(estudanteId, data); - } - - public List listarTarefasPorPeriodo(String estudanteId, LocalDate inicio, LocalDate fim) { - return tarefaRepository.findByEstudanteIdAndDataEntregaBetween(estudanteId, inicio, fim); - } - - public Optional buscarTarefaPorId(String id) { - return tarefaRepository.findById(id); - } - - @Transactional - public Tarefa atualizarTarefa(String id, TarefaRequestDTO dto) { - Tarefa tarefa = tarefaRepository.findById(id) - .orElseThrow(() -> new RuntimeException("Tarefa não encontrada")); - - tarefa.setTitulo(dto.getTitulo()); - tarefa.setDescricao(dto.getDescricao()); - tarefa.setPrioridade(dto.getPrioridade()); - tarefa.setStatus(dto.getStatus()); - tarefa.setDataEntrega(dto.getDataEntrega()); - tarefa.setDataAtualizacao(LocalDateTime.now()); - - if (dto.getDisciplinaId() != null) { - Disciplina disciplina = disciplinaRepository.findById(dto.getDisciplinaId()) - .orElseThrow(() -> new RuntimeException("Disciplina não encontrada")); - tarefa.setDisciplinaId(disciplina.getId()); - } - - return tarefaRepository.save(tarefa); - } - - @Transactional - public void excluirTarefa(String id) { - tarefaRepository.deleteById(id); - } - - @Transactional - public Tarefa marcarConcluida(String id) { - Tarefa tarefa = tarefaRepository.findById(id) - .orElseThrow(() -> new RuntimeException("Tarefa não encontrada")); - tarefa.setStatus(Tarefa.StatusTarefa.CONCLUIDA); - tarefa.setDataAtualizacao(LocalDateTime.now()); - return tarefaRepository.save(tarefa); - } -} diff --git a/src/main/java/com/agendaestudantil/servico/EstudanteServico.java b/src/main/java/com/agendaestudantil/servico/EstudanteServico.java new file mode 100644 index 0000000..56b601a --- /dev/null +++ b/src/main/java/com/agendaestudantil/servico/EstudanteServico.java @@ -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 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 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()); + } +} diff --git a/src/main/java/com/agendaestudantil/servico/TarefaServico.java b/src/main/java/com/agendaestudantil/servico/TarefaServico.java new file mode 100644 index 0000000..a2effef --- /dev/null +++ b/src/main/java/com/agendaestudantil/servico/TarefaServico.java @@ -0,0 +1,140 @@ +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 listarTarefasPorEstudante(String estudanteId) { + log.debug("Listando tarefas para estudante: {}", estudanteId); + validarAcesso(estudanteId); + return tarefaRepositorio.findByEstudanteId(estudanteId).stream() + .map(this::toDTO) + .toList(); + } + + public List 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 listarTarefasPorData(String estudanteId, LocalDate data) { + validarAcesso(estudanteId); + return tarefaRepositorio.findByEstudanteIdAndDataEntrega(estudanteId, data).stream() + .map(this::toDTO) + .toList(); + } + + public List 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(dto.estudanteId()); + + tarefa.setTitulo(dto.titulo()); + tarefa.setDescricao(dto.descricao()); + tarefa.setPrioridade(dto.prioridade()); + tarefa.setStatus(dto.status()); + tarefa.setDataEntrega(dto.dataEntrega()); + tarefa.setEstudanteId(dto.estudanteId()); + + 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()); + } +} diff --git a/src/main/java/com/agendaestudantil/utilitario/UtilJwt.java b/src/main/java/com/agendaestudantil/utilitario/UtilJwt.java new file mode 100644 index 0000000..8fa79a1 --- /dev/null +++ b/src/main/java/com/agendaestudantil/utilitario/UtilJwt.java @@ -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; + } + } +} \ No newline at end of file diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties new file mode 100644 index 0000000..49be875 --- /dev/null +++ b/src/main/resources/application-dev.properties @@ -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 diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties new file mode 100644 index 0000000..a365b3f --- /dev/null +++ b/src/main/resources/application-prod.properties @@ -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 diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 86ead23..3ce5e32 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,8 +1,4 @@ spring.application.name=${APP_NAME:Agenda Digital para Estudantes} server.port=${SERVER_PORT:8080} -spring.data.mongodb.host=${MONGODB_HOST:localhost} -spring.data.mongodb.port=${MONGODB_PORT:27017} -spring.data.mongodb.database=${MONGODB_DATABASE:agenda_estudantil} - -logging.level.org.springframework.data.mongodb=DEBUG +spring.profiles.active=${SPRING_PROFILES_ACTIVE:dev} diff --git a/src/test/java/com/agendaestudantil/servico/EstudanteServicoTest.java b/src/test/java/com/agendaestudantil/servico/EstudanteServicoTest.java new file mode 100644 index 0000000..a501c29 --- /dev/null +++ b/src/test/java/com/agendaestudantil/servico/EstudanteServicoTest.java @@ -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)); + } +} diff --git a/src/test/java/com/agendaestudantil/servico/TarefaServicoTest.java b/src/test/java/com/agendaestudantil/servico/TarefaServicoTest.java new file mode 100644 index 0000000..cf3b160 --- /dev/null +++ b/src/test/java/com/agendaestudantil/servico/TarefaServicoTest.java @@ -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 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")); + } +}