Skip to content
Dev-Sams1012Public

About

Projeto para a disciplina de Engenharia de Software I 2026.1, ministrada pela Professora Rossana

Resources

Stars

1 star

Watchers

0 watching

Forks

 
 

Latest commit

 

History

98 Commits

Folders and files

Repository files navigation

NeoQuest 🎓

Trabalho Final - Disciplina de Engenharia de Software I - CK0247 2026.1 API REST para plataforma de preparação para pré-vestibulandos, com questões ENEM, turmas, simulados e gamificação.


Sumário

  1. Visão Geral
  2. Funcionalidades
  3. Arquitetura
  4. Stack Tecnológica
  5. Estrutura do Projeto
  6. Modelo de Dados
  7. Endpoints da API
  8. Segurança
  9. Integração com API Externa
  10. Configuração e Execução
  11. Variáveis de Ambiente
  12. Documentação Swagger
  13. Decisões de Design
  14. Equipe

Visão Geral

O NeoQuest é uma API REST desenvolvida em Java com Spring Boot voltada para estudantes que se preparam para o ENEM. A plataforma permite que alunos acessem questões de provas anteriores, organizem grupos de estudo (turmas), realizem simulados cronometrados e acompanhem seu desempenho por meio de um sistema de gamificação com pontos e streaks.

O projeto integra uma API pública do ENEM para importar e armazenar questões reais de provas anteriores (2009–2023) em cache local, garantindo alta disponibilidade e performance nas consultas.


Funcionalidades

Autenticação e Usuários

  • Cadastro de novos usuários com validação de senha forte
  • Login com geração de token JWT
  • Consulta e edição de perfil (nome, e-mail, senha, foto, data de nascimento)
  • Deleção de conta com confirmação de senha

Questões

  • Listagem de questões com filtros por ano, disciplina, idioma e termo de busca
  • Paginação e ordenação por número de questão
  • Favoritar/desfavoritar questões (toggle)
  • Listagem de questões favoritas do usuário autenticado
  • Registro e histórico de respostas por questão
  • Criação, Edição e exclusão de questões personalizadas criadas dentro de turmas
  • Endpoints utilitários para importação de questões da API externa por ano (completo ou paginado)

Turmas

  • Criação e gestão de turmas com código de convite único
  • Ingresso em turmas via código
  • Roles por turma: MEMBRO e ADMINISTRADOR
  • Promoção, rebaixamento, saída e remoção de membros
  • Publicações na turma com comentários
  • Associação de questões personalizadas e simulados à turma

Simulados

  • Criação de simulados vinculados a uma turma
  • Aplicação de simulado e envio de respostas pelo aluno
  • Registro de resultado com acertos, tempo gasto e desempenho detalhado
  • Consulta de desempenho por turma e histórico de tentativas

Gamificação

  • Pontos acumulados por acertos e atividades
  • Streak de dias consecutivos de acesso
  • Ranking de usuários dentro de turmas

Arquitetura

O NeoQuest segue uma arquitetura em camadas clássica do ecossistema Spring:

Controller → Service → Repository → Database (Supabase / PostgreSQL)
                ↓
         Feign Client → API Externa (api.enem.dev)

Princípios aplicados:

  • Separação de responsabilidades: lógica de negócio exclusivamente nos Services
  • DTOs na borda: entidades JPA nunca são expostas diretamente; DTOs controlam entrada e saída
  • Cache local: questões da API externa são persistidas no banco para evitar dependência contínua do serviço externo
  • Stateless: autenticação via JWT, sem sessão no servidor
  • Tratamento centralizado de erros: GlobalExceptionHandler cobre todos os cenários de exceção

Stack Tecnológica

Camada Tecnologia
Linguagem Java 17
Framework Spring Boot 3.5.14
Segurança Spring Security + JWT (jjwt 0.12.6)
Persistência Spring Data JPA / Hibernate
Banco de Dados PostgreSQL (via Supabase)
Cliente HTTP Spring Cloud OpenFeign
Validação Jakarta Validation (Bean Validation 3)
E-mail Spring Mail
Mapeamento Mapper manual (QuestaoMapper, SimuladoMapper, TurmaMapper, UsuarioMapper)
Documentação Não configurada no estado atual do projeto
Build Maven
Variáveis de Ambiente java-dotenv
Testes JUnit 5 + H2 em memória
Utilitários Lombok

Estrutura do Projeto

src/main/java/NeoQuestGroup/NeoQuest/
│
├── NeoQuestApplication.java
├── client/                          # integração com a API externa do ENEM
│   ├── FeignClientConfig.java
│   └── QuestaoApiClient.java
├── controller/                      # endpoints REST da aplicação
│   ├── AuthController.java
│   ├── QuestaoController.java
│   ├── SimuladoController.java
│   ├── TurmaController.java
│   └── UsuarioController.java
├── dto/                             # contratos de entrada e saída
│   ├── AlternativaDTO.java
│   ├── CadastroDTO.java
│   ├── ComentarioTurmaDTO.java
│   ├── ComentarioTurmaResponseDTO.java
│   ├── CriarSimuladoDTO.java
│   ├── CriarTurmaDTO.java
│   ├── DeletarContaDTO.java
│   ├── DesempenhoAlunoSimuladoDTO.java
│   ├── EditarQuestaoDTO.java
│   ├── EditarTurmaDTO.java
│   ├── EditarUsuarioDTO.java
│   ├── FavoritoResponseDTO.java
│   ├── HistoricoRespostaDTO.java
│   ├── LoginDTO.java
│   ├── MembroResponseDTO.java
│   ├── PublicacaoTurmaDTO.java
│   ├── PublicacaoTurmaResponseDTO.java
│   ├── QuestaoDTO.java
│   ├── QuestaoResponseDTO.java
│   ├── RespostaDTO.java
│   ├── RespostaQuestaoResponseDTO.java
│   ├── RespostaSimuladoResponseDTO.java
│   ├── ResultadoDetalhadoResponseDTO.java
│   ├── ResultadoResponseDTO.java
│   ├── SimuladoResponseDTO.java
│   ├── SubmissaoDTO.java
│   ├── TokenResponseDTO.java
│   ├── TurmaResponseDTO.java
│   ├── UsuarioResponseDTO.java
│   └── externa/
├── exception/                       # tratamento centralizado de erros
│   ├── ApiError.java
│   └── GlobalExceptionHandler.java
├── mapper/                          # conversão entre DTOs e entidades
│   ├── QuestaoMapper.java
│   ├── SimuladoMapper.java
│   ├── TurmaMapper.java
│   └── UsuarioMapper.java
├── model/                           # entidades JPA, enums e relacionamentos
│   ├── AlternativaCache.java
│   ├── ComentarioPost.java
│   ├── ComentarioTurma.java
│   ├── Favorito.java
│   ├── Post.java
│   ├── PublicacaoTurma.java
│   ├── QuestaoCache.java
│   ├── RespostaQuestao.java
│   ├── ResultadoSimulado.java
│   ├── ResultadoSimuladoResposta.java
│   ├── RoleTurmaEnum.java
│   ├── Simulado.java
│   ├── TipoPublicacaoEnum.java
│   ├── TipoQuestaoEnum.java
│   ├── Turma.java
│   ├── Usuario.java
│   └── UsuarioTurma.java
├── repository/                      # acesso e consulta de dados
│   ├── ComentarioPostRepository.java
│   ├── ComentarioTurmaRepository.java
│   ├── FavoritoRepository.java
│   ├── PostRepository.java
│   ├── PublicacaoTurmaRepository.java
│   ├── QuestaoCacheRepository.java
│   ├── RespostaQuestaoRepository.java
│   ├── ResultadoSimuladoRepository.java
│   ├── SimuladoRepository.java
│   ├── TurmaRepository.java
│   ├── UsuarioRepository.java
│   └── UsuarioTurmaRepository.java
├── security/                        # autenticação, JWT e regras de acesso
│   ├── JwtAuthFilter.java
│   ├── SecurityConfig.java
│   ├── UserDetailsServiceImpl.java
│   ├── UsuarioPrincipal.java
│   └── JwtUtil.java
├── service/                         # regras de negócio da aplicação
│   ├── AuthService.java
│   ├── GamificacaoService.java
│   ├── QuestaoService.java
│   ├── SimuladoService.java
│   ├── TurmaService.java
│   └── UsuarioService.java
└── util/                            # helpers e utilidades auxiliares
    ├── AuthUtils.java
    └── JwtUtil.java

Modelo de Dados

Entidades principais e relacionamentos

Usuario (1) ──── (N) UsuarioTurma (N) ──── (1) Turma
                        └── RoleTurmaEnum {MEMBRO, ADMINISTRADOR}

Turma (1) ──── (N) PublicacaoTurma (1) ──── (N) ComentarioTurma
Turma (1) ──── (N) Simulado (N) ──── (M) QuestaoCache
Turma (1) ──── (N) QuestaoCache [turmaOrigem]

Usuario (1) ──── (N) ResultadoSimulado (N) ──── (1) Simulado
Usuario (1) ──── (N) Favorito (N) ──── (1) QuestaoCache
Usuario (1) ──── (N) RespostaQuestao (N) ──── (1) QuestaoCache
Usuario (1) ──── (N) Post (1) ──── (N) ComentarioPost
Usuario (1) ──── (N) Post [curtidas - ManyToMany]

QuestaoCache (1) ──── (N) AlternativaCache
QuestaoCache ──── ElementCollection: files (URLs de imagens)

Campos de destaque

questao_cache possui constraint única em (ano, index_questao, lingua) para evitar duplicatas durante a importação da API externa.

resposta_questao possui constraint única em (usuario_id, questao_cache_id) para impedir que o mesmo usuário responda a mesma questão mais de uma vez.


Endpoints da API

Autenticação (/auth)

Método Endpoint Auth Descrição
POST /auth/register ❌ Cadastrar novo usuário
POST /auth/login ❌ Autenticar e obter JWT

Exemplo — Cadastro:

POST /auth/register
{
  "nome": "João Silva",
  "email": "joao@email.com",
  "senha": "Senha@123"
}

Exemplo — Login:

POST /auth/login
{
  "email": "joao@email.com",
  "senha": "Senha@123"
}
// Resposta: { "token": "eyJhbGci..." }

Usuários (/usuarios)

Método Endpoint Auth Descrição
GET /usuarios/me ✅ Retornar perfil do usuário autenticado
PUT /usuarios/me ✅ Editar nome, e-mail, senha, foto, data de nascimento
DELETE /usuarios/me ✅ Deletar conta (requer confirmação de senha)

Regra de negócio: Alterar e-mail ou senha exige que senhaAtual seja fornecida e correta.


Questões (/questoes)

Método Endpoint Auth Descrição
GET /questoes Opcional Listar questões com filtros e paginação
POST /questoes/{id}/favoritar ✅ Toggle favorito (favorita ou desfavorita)
GET /questoes/favoritas ✅ Listar questões favoritas do usuário
POST /questoes/{id}/responder ✅ Registrar resposta do aluno para uma questão
GET /questoes/historico ✅ Consultar histórico de respostas do usuário
PUT /questoes/{id} ✅ Editar questão personalizada da turma
DELETE /questoes/{id} ✅ Excluir questão personalizada da turma
POST /questoes/admin/download-completo ✅ Importar todas as questões de um ano (utilitário de desenvolvimento)
POST /questoes/admin/download-parcial ✅ Importar um bloco de questões a partir de um offset

Parâmetros de filtro (GET /questoes):

Parâmetro Tipo Obrigatório Valores válidos
ano Integer ✅ 2009 a 2023
disciplina String ❌ linguagens, matematica, ciencias-natureza, ciencias-humanas
language String ❌ ingles, espanhol
termo String ❌ Busca textual no enunciado e contexto
page int ❌ Default: 0
size int ❌ Default: 10

Exemplo de resposta:

{
  "content": [
    {
      "id": 42,
      "ano": 2022,
      "index": 1,
      "disciplina": "matematica",
      "lingua": null,
      "contexto": "Um investidor...",
      "comandoQuestao": "Qual é o rendimento...",
      "alternativaCorreta": "C",
      "files": [],
      "alternativas": [
        { "letter": "A", "text": "...", "file": null, "isCorrect": false },
        { "letter": "C", "text": "...", "file": null, "isCorrect": true }
      ],
      "favoritada": false
    }
  ],
  "totalElements": 180,
  "totalPages": 18
}

Nota: O campo favoritada é null para usuários anônimos e true/false para autenticados.

Simulados (/simulados)

Método Endpoint Auth Descrição
POST /simulados ✅ Criar um novo simulado
GET /simulados/{id} ✅ Abrir um simulado para resposta
POST /simulados/{id}/respostas ✅ Enviar as respostas e processar o resultado
GET /simulados/{id}/resultado ✅ Consultar resultado detalhado
GET /simulados/{id}/desempenho ✅ Ver desempenho da turma no simulado
DELETE /simulados/{id} ✅ Excluir simulado

Turmas (/turmas)

Método Endpoint Auth Descrição
POST /turmas ✅ Criar uma nova turma
POST /turmas/entrar?codigo=... ✅ Entrar em uma turma por código
GET /turmas/participo ✅ Listar turmas das quais o usuário participa
GET /turmas/{id}/membros ✅ Consultar membros da turma
PATCH /turmas/{id}/membros/{userId}/promover ✅ Promover membro a administrador
PATCH /turmas/{id}/membros/{userId}/rebaixar ✅ Rebaixar administrador a membro
POST /turmas/{id}/publicacoes ✅ Criar publicação na turma
GET /turmas/{id}/publicacoes ✅ Listar publicações da turma
POST /turmas/{id}/publicacoes/{pubId}/comentarios ✅ Adicionar comentário em uma publicação
POST /turmas/{turmaId}/questoes ✅ Criar questão personalizada para a turma

Segurança

Fluxo de Autenticação JWT

1. Cliente → POST /auth/login com {email, senha}
2. Spring Security autentica via DaoAuthenticationProvider
3. JwtUtil gera token HS256 assinado (claim: email + userId)
4. Cliente inclui token em requisições: Authorization: Bearer <token>
5. JwtAuthFilter extrai e valida o token a cada requisição
6. UsuarioPrincipal wraps a entidade Usuario — sem second query ao banco

Configuração de rotas

/auth/**            público (sem autenticação)
/public/**          público
/**                 requer token JWT válido
OPTIONS /**         permitido para pré-flight CORS

Requisitos de senha

A senha deve conter entre 8 e 30 caracteres e incluir obrigatoriamente:

  • Ao menos uma letra maiúscula
  • Ao menos uma letra minúscula
  • Ao menos um número
  • Ao menos um caractere especial (@$!%*?&)

Tratamento de Erros

Todos os erros seguem o formato padronizado ApiError:

{
  "timestamp": "2024-11-15T10:30:00.000Z",
  "status": 400,
  "error": "Validation Failed",
  "message": "Um ou mais campos são inválidos.",
  "path": "/auth/register",
  "errors": {
    "senha": "A senha deve conter entre 8 e 30 caracteres..."
  }
}

Exceções tratadas pelo GlobalExceptionHandler:

Exceção Status HTTP
MethodArgumentNotValidException 400 Bad Request
ConstraintViolationException 400 Bad Request
HttpMessageNotReadableException 400 Bad Request
MethodArgumentTypeMismatchException 400 Bad Request
IllegalArgumentException 400 Bad Request
EntityNotFoundException 404 Not Found
IllegalStateException 409 Conflict
DataIntegrityViolationException 409 Conflict
HttpRequestMethodNotSupportedException 405 Method Not Allowed
BadCredentialsException / UsernameNotFoundException 401 Unauthorized
AccessDeniedException 403 Forbidden
FeignException 502 Bad Gateway
feign.RetryableException 504 Gateway Timeout
Exception (fallback) 500 Internal Server Error

Em ambiente dev (spring.profiles.active=dev), o campo detail da resposta expõe a mensagem técnica da exceção.


Integração com API Externa

O NeoQuest consome uma API pública do ENEM via Spring Cloud OpenFeign.

Estratégia de Cache

Para evitar dependência contínua da API externa e garantir performance nas consultas:

  1. As questões são baixadas sob demanda pelos endpoints admin (/questoes/admin/download-completo ou /questoes/admin/download-parcial).
  2. Cada questão é persistida na tabela questao_cache com constraint de unicidade por (ano, index_questao, lingua).
  3. Nas consultas públicas (GET /questoes), o banco local é sempre consultado com queries nativas usando ILIKE do PostgreSQL para busca case-insensitive.

Configuração do Feign (FeignClientConfig)

Parâmetro Valor
Timeout de conexão 3 segundos
Timeout de leitura 30 segundos
Tentativas de retry 3 (em erros de rede)
Intervalo entre retries 1s inicial, máx 3s
Log level BASIC

Fluxo de Download

POST /questoes/admin/download-completo?ano=2022
    └── QuestaoService.forcarDownloadCompleto()
        └── Loop: QuestaoApiClient.listarQuestoesPorAno(ano, limit=50, offset)
            └── Até que metadata.hasMore == false
        └── TransactionTemplate: persistirNovasQuestoes()
            └── Filtra duplicatas por chave (ano-index-lingua)
            └── QuestaoCacheRepository.saveAll()

Configuração e Execução

Pré-requisitos

  • Java 17+
  • Maven 3.8+
  • Instância PostgreSQL (local ou Supabase)
  • Arquivo .env na raiz do projeto

Instalação

# Clonar o repositório
git clone https://github.com/neoquest/NeoQuestBack.git
cd NeoQuestBack

# Instalar dependências e compilar
mvn clean install -DskipTests

# Executar a aplicação
mvn spring-boot:run

Arquivo .env (na raiz do projeto)

JWT_SECRET=sua_chave_secreta_muito_longa_e_segura_aqui
SUPABASE_URL=jdbc:postgresql://<host>:<porta>/<banco>
SUPABASE_USERNAME=postgres
SUPABASE_PASSWORD=sua_senha_aqui

application.yaml (exemplo)

spring:
  datasource:
    url: ${SUPABASE_URL}
    username: ${SUPABASE_USERNAME}
    password: ${SUPABASE_PASSWORD}
  sql:
    init:
      mode: never
  jpa:
    hibernate:
      ddl-auto: none
    show-sql: true

server:
  port: 8080

jwt:
  secret: ${JWT_SECRET}
  expiration: 7200000

enem:
  api:
    url: https://api.enem.dev/v1

Variáveis de Ambiente

Variável Descrição Obrigatória
JWT_SECRET Chave secreta para assinatura dos tokens JWT (mín. 32 caracteres) ✅
SUPABASE_URL URL JDBC de conexão ao banco PostgreSQL ✅
SUPABASE_USERNAME Usuário do banco de dados ✅
SUPABASE_PASSWORD Senha do banco de dados ✅

As variáveis são carregadas pelo java-dotenv no método main() de NeoQuestApplication e injetadas como System.setProperty, ficando disponíveis para o Spring via ${...}.


Documentação Swagger

No estado atual do projeto, não há documentação Swagger/OpenAPI habilitada. A API é consumida diretamente pelos controllers e os contratos principais ficam representados nos DTOs e no próprio README.


Decisões de Design

1. Lógica de negócio nos Services, não nas Entidades

Métodos residem nos Services, mantendo as entidades JPA como simples mapeamentos do banco.

2. ResultadoSimulado separado de Simulado

Simulado representa o template (questões + configurações). ResultadoSimulado registra a tentativa de um usuário específico. Essa separação permite múltiplos usuários realizando o mesmo simulado com resultados independentes.

3. UsuarioTurma como entidade de junção com semântica de papel

A relação Usuário-Turma é muitos-para-muitos (N-N) com dados adicionais (role e último acesso). Modelar como entidade explícita UsuarioTurma com RoleTurmaEnum é mais expressivo que uma tabela de junção simples.

4. UsuarioPrincipal como wrapper do UserDetails

Ao carregar a entidade Usuario completa durante a autenticação e encapsulá-la no UsuarioPrincipal, elimina-se a necessidade de consultas adicionais ao banco para obter dados do usuário nas requisições subsequentes.

5. TransactionTemplate no QuestaoService

O QuestaoService usa TransactionTemplate em vez de @Transactional porque o download das questões (chamadas HTTP) não deve ocorrer dentro de uma transação de banco de dados aberta — apenas a persistência deve ser transacional.

6. Cache de questões com sincronização

Os métodos de download e persistência são synchronized para evitar condições de corrida quando múltiplas requisições admin tentam importar o mesmo ano simultaneamente.

7. Queries nativas com ILIKE

A busca textual usa SQL nativo com ILIKE (extensão PostgreSQL) em vez de JPQL para garantir busca case-insensitive eficiente em colunas TEXT sem problemas de cast.


Equipe

Equipe M — Disciplina de Engenharia de Software

Membro Papel
Aluizio Almendra DevOps / Analista de requisitos
Américo Vitor Database Dev / FullStack Dev
Gabriel Teixeira UI/UX / Frontend Dev
João Guilherme UI/UX
Jonathan Duarte QA Tester / Backend Dev
Samuel Augusto Backend Dev / Gerente de projeto

Licença

Este projeto foi desenvolvido para fins acadêmicos no contexto da disciplina de Engenharia de Software.


Última atualização: 03 de Julho de 2026

About

Projeto para a disciplina de Engenharia de Software I 2026.1, ministrada pela Professora Rossana

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages