Uma API RESTful moderna para gerenciar seu quadro de visão pessoal, permitindo criar, organizar e categorizar notas com autenticação segura baseada em JWT.
Versão: 0.0.1-SNAPSHOT | Java: 21 | Spring Boot: 4.0.3
- Visão Geral
- Tecnologias
- Pré-requisitos
- Instalação
- Configuração
- Como Executar
- Endpoints da API
- Estrutura do Projeto
- Modelo de Dados
- Autenticação
- CORS
- Documentação Interativa
- Docker
- Desenvolvimento
- Troubleshooting
- Contribuindo
- Licença
- Suporte
- Referências
MyVisionBoard é uma plataforma que permite aos usuários:
- 📝 Criar e gerenciar notas — Escreva, edite e organize suas ideias com paginação e busca por título
- 🏷️ Organizar com tags — Categorize suas notas com etiquetas reutilizáveis
- 👤 Autenticação segura — Sistema de login/registro com JWT tokens stateless
- 🔐 Spring Security — Segurança em nível de aplicação com BCrypt
- 🔄 Redis — Rate limiting distribuído (10 req/min em
/auth, 100 req/min por usuário na API) - 📖 Swagger/OpenAPI — Documentação interativa disponível em
/swagger-ui
| Tecnologia | Versão |
|---|---|
| Java | 21 |
| Spring Boot | 4.0.3 |
| Maven | 3.9 |
| Tecnologia | Versão | Uso |
|---|---|---|
| PostgreSQL | 16 | Armazenamento relacional principal |
| Redis | 7 | Cache distribuído |
- Spring Security — Autenticação e autorização
- JJWT 0.12.6 — Geração e validação de tokens JWT
- BCrypt — Criptografia de senhas
- SpringDoc OpenAPI 2.8.6 — Geração automática da especificação OpenAPI
- Swagger UI — Interface interativa de teste
- Lombok — Redução de boilerplate
- Spring Boot Validation — Bean Validation (JSR-380)
- ✅ Java 21 ou superior
- ✅ Maven 3.9 ou superior
- ✅ Docker e Docker Compose (recomendado para PostgreSQL e Redis)
- ✅ PostgreSQL 16 (local ou via Docker)
- ✅ Redis 7 (local ou via Docker)
# Verificar Java
java -version
# Verificar Maven
mvn -versiongit clone <seu-repositorio>
cd MyVisionBoardmvn clean install -DskipTestsAs variáveis sensíveis são carregadas via arquivo .env na raiz do projeto (nunca commitado). Use .env.example como ponto de partida:
cp .env.example .envDB_URL=jdbc:postgresql://localhost:5432/myvisionboard
DB_USERNAME=postgres
DB_PASSWORD=postgres
JWT_SECRET=your-strong-secret-key-here
JWT_EXPIRATION=86400000
REDIS_HOST=localhost
REDIS_PORT=6379As propriedades sensíveis referenciam as variáveis do .env:
spring.datasource.url=${DB_URL}
spring.datasource.username=${DB_USERNAME}
spring.datasource.password=${DB_PASSWORD}
jwt.secret=${JWT_SECRET}
jwt.expiration=${JWT_EXPIRATION}
spring.data.redis.host=${REDIS_HOST}
spring.data.redis.port=${REDIS_PORT}| Propriedade | Ação recomendada |
|---|---|
JWT_SECRET |
Use uma chave forte e aleatória (mínimo 256 bits) |
DB_PASSWORD |
Use credenciais seguras |
spring.jpa.hibernate.ddl-auto |
Altere para validate |
spring.jpa.show-sql |
Defina como false |
logging.level.org.springframework |
Reduza para WARN ou ERROR |
Suba o PostgreSQL e o Redis com um único comando:
docker-compose up -dEm seguida, execute a aplicação:
mvn spring-boot:runA aplicação estará disponível em: http://localhost:8080
Instale e inicie PostgreSQL e Redis manualmente conforme seu sistema operacional, então:
mvn spring-boot:rundocker-compose logs -fdocker-compose downTodos os endpoints — exceto
/auth/**— exigem autenticação via JWT no header:Authorization: Bearer {seu-token}O usuário autenticado é identificado automaticamente pelo token, sem necessidade de passar
userIdou
POST /auth/register
Content-Type: application/json
{
"name": "João Silva",
"email": "joao@example.com",
"password": "SenhaSegura123"
}Validações:
nameobrigatório ·passwordmínimo 6 caracteres
Resposta 200 OK:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"name": "João Silva",
"email": "joao@example.com"
}Erros:
400— Dados inválidos409— E-mail já cadastrado
POST /auth/login
Content-Type: application/json
{
"email": "joao@example.com",
"password": "SenhaSegura123"
}Resposta 200 OK:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"name": "João Silva",
"email": "joao@example.com"
}Erros:
400— Dados inválidos401— Credenciais incorretas
Suporta paginação e filtragem por título.
GET /notes?page=0&size=10&sort=createdAt,desc
Authorization: Bearer {token}
# Com filtro por título:
GET /notes?title=objetivo&page=0&size=10
Authorization: Bearer {token}Parâmetros de query:
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
title |
string | não | Filtra notas cujo título contenha o valor (case-insensitive) |
page |
int | não | Número da página (padrão: 0) |
size |
int | não | Itens por página (padrão: 20) |
sort |
string | não | Campo e direção, ex: createdAt,desc |
Resposta 200 OK (formato Spring Page):
{
"content": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "Meu Objetivo 2026",
"content": "Descrição detalhada do meu objetivo...",
"tags": [
{ "id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "name": "Carreira" }
],
"createdAt": "2026-03-25T10:30:00",
"updatedAt": "2026-03-25T10:30:00"
}
],
"totalElements": 25,
"totalPages": 3,
"size": 10,
"number": 0,
"first": true,
"last": false,
"empty": false
}GET /notes/550e8400-e29b-41d4-a716-446655440000
Authorization: Bearer {token}Resposta 200 OK: objeto Note completo
Erro 404: nota não encontrada
O autor é definido automaticamente pelo usuário autenticado no token.
POST /notes
Authorization: Bearer {token}
Content-Type: application/json
{
"title": "Nova Nota",
"content": "Conteúdo da nota",
"tagIds": ["6ba7b810-9dad-11d1-80b4-00c04fd430c8"]
}Campos:
titleobrigatório ·contentopcional ·tagIdsopcional (lista de IDs de tags existentes)
Resposta 200 OK:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "Nova Nota",
"content": "Conteúdo da nota",
"tags": [],
"createdAt": "2026-03-25T10:30:00",
"updatedAt": "2026-03-25T10:30:00"
}PUT /notes/550e8400-e29b-41d4-a716-446655440000
Authorization: Bearer {token}
Content-Type: application/json
{
"title": "Nota Atualizada",
"content": "Novo conteúdo"
}Resposta 200 OK:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "Nota Atualizada",
"content": "Novo conteúdo",
"tags": [],
"createdAt": "2026-03-25T10:30:00",
"updatedAt": "2026-03-25T11:00:00"
}Erro 404: nota não encontrada
DELETE /notes/550e8400-e29b-41d4-a716-446655440000
Authorization: Bearer {token}Resposta 200 OK:
{ "message": "Note deleted successfully." }Erro 404:
{ "message": "Note not found." }GET /tags
Authorization: Bearer {token}Resposta 200 OK:
[
{ "id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "name": "Carreira" },
{ "id": "6ba7b811-9dad-11d1-80b4-00c04fd430c8", "name": "Saúde" }
]GET /tags/6ba7b810-9dad-11d1-80b4-00c04fd430c8
Authorization: Bearer {token}Resposta 200 OK:
{ "id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "name": "Carreira" }Erro 404: tag não encontrada
Nomes de tag são únicos no sistema.
POST /tags
Authorization: Bearer {token}
Content-Type: application/json
{ "name": "Educação" }Resposta 200 OK:
{ "id": "uuid-gerado", "name": "Educação" }Erro 400: tag com esse nome já existe
PUT /tags/6ba7b810-9dad-11d1-80b4-00c04fd430c8
Authorization: Bearer {token}
Content-Type: application/json
{ "name": "Desenvolvimento Pessoal" }Resposta 200 OK: tag atualizada
Erro 404: tag não encontrada
DELETE /tags/6ba7b810-9dad-11d1-80b4-00c04fd430c8
Authorization: Bearer {token}Resposta 200 OK:
{ "message": "Tag deleted successfully." }Erro 404:
{ "message": "Tag not found." }O usuário é identificado pelo token JWT. Não é necessário passar
idna URL.
GET /user/me
Authorization: Bearer {token}Resposta 200 OK:
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"name": "João Silva",
"email": "joao@example.com",
"createdAt": "2026-03-25T10:30:00"
}Erro 404: usuário não encontrado
PUT /user/me
Authorization: Bearer {token}
Content-Type: application/json
{ "name": "João Silva Atualizado" }Resposta 200 OK: perfil atualizado (mesmo formato do GET /user/me)
Erro 404: usuário não encontrado
DELETE /user/me
Authorization: Bearer {token}Resposta 200 OK:
{ "message": "Account deleted successfully" }Erros:
401— Não autenticado404— Usuário não encontrado
src/
├── main/
│ ├── java/com/myvisionboard/app/
│ │ ├── Application.java # Ponto de entrada Spring Boot
│ │ ├── config/
│ │ │ ├── RateLimitConfig.java # Rate limiting por IP (auth) e por usuário (API)
│ │ │ ├── RedisConfig.java # Template Redis
│ │ │ ├── SwaggerConfig.java # Configuração OpenAPI/Swagger
│ │ │ └── WebConfig.java # CORS (permite localhost:3000)
│ │ ├── controller/
│ │ │ ├── AuthController.java # POST /auth/register e /auth/login
│ │ │ ├── NoteController.java # CRUD /notes
│ │ │ ├── TagController.java # CRUD /tags
│ │ │ └── UserController.java # Perfil /user/me
│ │ ├── dto/
│ │ │ ├── request/
│ │ │ │ ├── LoginRequest.java # email, password
│ │ │ │ ├── NoteRequest.java # title, content, tagIds
│ │ │ │ ├── RegisterRequest.java # name, email, password
│ │ │ │ ├── TagRequest.java # name
│ │ │ │ └── UserRequest.java # name
│ │ │ └── response/
│ │ │ ├── AuthResponse.java # token, name, email
│ │ │ ├── MessageResponse.java # message
│ │ │ ├── NoteResponse.java # id, title, content, tags, createdAt, updatedAt
│ │ │ ├── TagResponse.java # id, name
│ │ │ └── UserResponse.java # id, name, email, createdAt
│ │ ├── model/
│ │ │ ├── Note.java # Entidade JPA — tabela notes
│ │ │ ├── Tag.java # Entidade JPA — tabela tags
│ │ │ └── User.java # Entidade JPA — tabela users
│ │ ├── repository/
│ │ │ ├── NoteRepository.java # findByUserId, findByUserIdAndTitleContaining
│ │ │ ├── TagRepository.java
│ │ │ └── UserRepository.java
│ │ ├── security/
│ │ │ ├── CustomUserDetailsService.java
│ │ │ ├── JwtAuthFilter.java # Filtro JWT por requisição
│ │ │ ├── JwtService.java # Geração e validação de tokens
│ │ │ └── SecurityConfig.java # Regras de autorização
│ │ └── service/
│ │ ├── AuthService.java
│ │ ├── NoteService.java
│ │ ├── TagService.java
│ │ └── UserService.java
│ └── resources/
│ └── application.properties
├── test/java/...
pom.xml
Dockerfile
docker-compose.yml
.env.example
| Campo | Tipo | Descrição |
|---|---|---|
| id | UUID | Identificador único (auto-gerado) |
| name | VARCHAR(255) | Nome do usuário |
| VARCHAR(255) | E-mail único | |
| password | VARCHAR(255) | Senha criptografada com BCrypt |
| created_at | TIMESTAMP | Data de criação |
| updated_at | TIMESTAMP | Data da última atualização |
| Campo | Tipo | Descrição |
|---|---|---|
| id | UUID | Identificador único (auto-gerado) |
| title | VARCHAR(255) | Título da nota (obrigatório) |
| content | TEXT | Conteúdo da nota |
| user_id | UUID | FK → users.id |
| created_at | TIMESTAMP | Data de criação |
| updated_at | TIMESTAMP | Data da última atualização |
| Campo | Tipo | Descrição |
|---|---|---|
| id | UUID | Identificador único (auto-gerado) |
| name | VARCHAR(255) | Nome único da tag |
| Campo | Tipo | Descrição |
|---|---|---|
| note_id | UUID | FK → notes.id |
| tag_id | UUID | FK → tags.id |
User (1) ──── (N) Note
Note (N) ──── (N) Tag [via note_tags]
1. Cliente envia credenciais → POST /auth/login ou /auth/register
2. Servidor valida e retorna → { token, name, email }
3. Cliente armazena o token
4. Em cada requisição → Authorization: Bearer {token}
5. JwtAuthFilter intercepta → valida assinatura + expiração
6. Usuário é identificado → via subject (email) do token
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...| Propriedade | Valor padrão | Descrição |
|---|---|---|
jwt.secret |
myvisionboard-secret-key-... |
Chave de assinatura HMAC-SHA |
jwt.expiration |
86400000 |
Expiração em ms (24 horas) |
As seguintes rotas não requerem autenticação:
POST /auth/register
POST /auth/login
GET /swagger-ui/**
GET /v3/api-docs/**
GET /error
A aplicação está configurada para aceitar requisições cross-origin de:
http://localhost:3000
Métodos permitidos: GET, POST, PUT, DELETE, OPTIONS
Para adicionar outras origens, edite WebConfig.java:
registry.addMapping("/**")
.allowedOrigins("http://localhost:3000", "https://seu-frontend.com")
...Acesse a interface interativa (sem autenticação necessária):
http://localhost:8080/swagger-ui
Para testar endpoints protegidos no Swagger UI:
- Execute
POST /auth/login - Copie o
tokenda resposta - Clique em Authorize (🔓) no topo
- Insira
Bearer {token}e confirme
http://localhost:8080/v3/api-docs
http://localhost:8080/v3/api-docs.yaml
O build utiliza multi-stage:
# Stage 1: Build
FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn clean package -DskipTests
# Stage 2: Runtime
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: myvisionboard
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_PASSWORD: ${DB_PASSWORD}
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME} -d myvisionboard"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
app:
build: .
ports:
- "8080:8080"
env_file:
- .env
environment:
DB_URL: jdbc:postgresql://postgres:5432/myvisionboard
REDIS_HOST: redis
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
volumes:
postgres_data:
redis_data:# Subir infraestrutura (PostgreSQL + Redis)
docker-compose up -d
# Ver logs
docker-compose logs -f
# Parar e remover containers
docker-compose down
# Parar e remover containers + volumes
docker-compose down -v
# Build manual da imagem da aplicação
docker build -t myvisionboard:latest .# Compile e empacote
mvn clean package
# Sem rodar testes
mvn clean package -DskipTests
# Apenas compilar
mvn clean compile# Todos os testes
mvn test
# Teste específico
mvn test -Dtest=ApplicationTests| Dependência | Versão | Finalidade |
|---|---|---|
| spring-boot-starter-web | 4.0.3 | REST API |
| spring-boot-starter-security | 4.0.3 | Segurança |
| spring-boot-starter-data-jpa | 4.0.3 | Persistência ORM |
| spring-boot-starter-data-redis | 4.0.3 | Cache Redis |
| spring-boot-starter-validation | 4.0.3 | Bean Validation |
| jjwt-api | 0.12.6 | JWT |
| postgresql | runtime | Driver JDBC |
| lombok | — | Boilerplate |
| springdoc-openapi-starter-webmvc-ui | 2.8.6 | Swagger/OpenAPI |
| spring-dotenv | 4.0.0 | Carregamento de variáveis do .env |
- IntelliJ IDEA (Community ou Ultimate) — melhor suporte a Spring
- VS Code com extensões: Extension Pack for Java, Spring Boot Extension Pack
- Eclipse IDE for Enterprise Java Developers
# Descobrir o processo
lsof -i :8080
# Encerrar
kill -9 <PID>
# Ou alterar a porta em application.properties
server.port=8081# Verificar se o container está rodando
docker ps
# Restartar
docker-compose restart postgresdocker-compose restart redis- Verifique se o token não expirou (validade: 24 h)
- Certifique-se de incluir o prefixo
Bearer(com espaço) - Confirme que
jwt.secreté o mesmo usado na geração do token
⚠️ Em produção, altere paravalidatee gerencie as migrações com Flyway ou Liquibase.
- Fork o repositório
- Crie uma branch:
git checkout -b feature/MinhaFeature - Commit:
git commit -m 'feat: adiciona MinhaFeature' - Push:
git push origin feature/MinhaFeature - Abra um Pull Request
- Siga as convenções Java (nomes em camelCase, classes em PascalCase)
- Adicione JavaDoc em métodos públicos de serviço
- Escreva testes para novas features
- Mantenha controllers finos — lógica de negócio nos services
Este projeto está sob licença MIT. Veja o arquivo LICENSE para mais detalhes.
- Abra uma Issue no repositório
- Consulte a documentação Swagger em
http://localhost:8080/swagger-ui
- Spring Boot Documentation
- Spring Security Reference
- JJWT (io.jsonwebtoken)
- PostgreSQL Documentation
- Redis Documentation
- SpringDoc OpenAPI
Última Atualização: 27 de Março de 2026