{"id":42089876,"url":"https://github.com/vittordeaguiar/blog-api","last_synced_at":"2026-01-26T10:36:49.140Z","repository":{"id":328834655,"uuid":"1116886380","full_name":"vittordeaguiar/blog-api","owner":"vittordeaguiar","description":".NET 9 Web API implementing Clean Architecture, featuring Entity Framework Core, AutoMapper, JWT Authentication, and Redis","archived":false,"fork":false,"pushed_at":"2025-12-18T14:46:37.000Z","size":16500,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-12-18T22:17:27.306Z","etag":null,"topics":["cache","csharp","docker","docker-compose","entity-framework-core","jwt","postgres","redis"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vittordeaguiar.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-15T14:19:26.000Z","updated_at":"2025-12-18T14:46:40.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/vittordeaguiar/blog-api","commit_stats":null,"previous_names":["vittordeaguiar/blog-api"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/vittordeaguiar/blog-api","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vittordeaguiar%2Fblog-api","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vittordeaguiar%2Fblog-api/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vittordeaguiar%2Fblog-api/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vittordeaguiar%2Fblog-api/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vittordeaguiar","download_url":"https://codeload.github.com/vittordeaguiar/blog-api/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vittordeaguiar%2Fblog-api/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28774856,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-26T09:42:00.929Z","status":"ssl_error","status_checked_at":"2026-01-26T09:42:00.591Z","response_time":59,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cache","csharp","docker","docker-compose","entity-framework-core","jwt","postgres","redis"],"created_at":"2026-01-26T10:36:48.487Z","updated_at":"2026-01-26T10:36:49.128Z","avatar_url":"https://github.com/vittordeaguiar.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Blog API\n\n![.NET](https://img.shields.io/badge/.NET-9.0-512BD4?logo=dotnet)\n![C#](https://img.shields.io/badge/C%23-13-239120?logo=csharp)\n![PostgreSQL](https://img.shields.io/badge/PostgreSQL-16-4169E1?logo=postgresql\u0026logoColor=white)\n![Redis](https://img.shields.io/badge/Redis-7-DC382D?logo=redis\u0026logoColor=white)\n![Docker](https://img.shields.io/badge/Docker-Compose-2496ED?logo=docker\u0026logoColor=white)\n![License](https://img.shields.io/badge/License-Educational-blue)\n\n**API RESTful para gerenciamento de blog construída com .NET 9 e Clean Architecture**\n\nEsta é uma API completa para gerenciar um blog, incluindo posts, categorias e usuários. O projeto foi desenvolvido como parte do meu portfólio profissional para demonstrar habilidades em .NET 9, Clean Architecture, padrões de design e boas práticas de desenvolvimento.\n\nA aplicação implementa recursos avançados como cache distribuído com Redis, rate limiting inteligente, autenticação JWT com autorização baseada em roles, validações de domínio usando FluentValidation e uma suite abrangente de testes unitários. Toda a infraestrutura está containerizada com Docker, permitindo deployment rápido e consistente.\n\n## Características Principais\n\n- **Clean Architecture** com separação clara em 4 camadas (Domain, Application, Infrastructure, API)\n- **Autenticação JWT** com autorização baseada em roles (Author, Admin)\n- **CRUD completo** de Posts e Categories com validações de negócio\n- **Relacionamento Many-to-Many** entre Posts e Categories\n- **Cache distribuído** com Redis usando cache-aside pattern (GetOrSetAsync)\n- **Rate limiting distribuído** por IP e usuário autenticado\n- **FluentValidation** para validações de domínio centralizadas\n- **Password hashing** com BCrypt para segurança\n- **Paginação** de resultados com metadata de paginação\n- **Global exception handling** seguindo RFC 7807 (Problem Details)\n- **Entity Framework Core** com PostgreSQL e migrations automáticas\n- **Repository pattern** para abstração de acesso a dados\n- **Testes unitários** com xUnit, Moq e FluentAssertions (17+ testes)\n- **Documentação OpenAPI/Swagger** com interface Scalar UI\n- **Docker Compose** com health checks para PostgreSQL, Redis e API\n- **Graceful degradation** - aplicação funciona mesmo se Redis estiver offline\n\n## Tecnologias Utilizadas\n\n### Backend Framework\n- .NET 9.0\n- ASP.NET Core Web API\n- C# 13\n\n### Database \u0026 Caching\n- PostgreSQL 16 (Alpine)\n- Redis 7 (Alpine)\n- Entity Framework Core 9.0\n- StackExchange.Redis 2.8.16\n\n### Segurança\n- JWT Bearer Authentication\n- BCrypt.Net-Next\n- Role-based Authorization\n\n### Bibliotecas\n- AutoMapper 13.0 (mapeamento de objetos)\n- FluentValidation 11.10 (validações de domínio)\n- xUnit 2.9 (framework de testes)\n- Moq 4.20 (mocking)\n- FluentAssertions 6.12 (assertions expressivas)\n\n### DevOps\n- Docker \u0026 Docker Compose\n- Multi-stage Dockerfile para otimização de imagem\n\n### Documentação\n- OpenAPI\n- Scalar UI\n\n## Arquitetura\n\nEste projeto segue os princípios da **Clean Architecture**, garantindo separação de responsabilidades, testabilidade e manutenibilidade. O fluxo de dependências vai sempre das camadas externas para as internas, com o Domain layer no centro sem nenhuma dependência externa.\n\n```\n┌─────────────────────────────────────────────┐\n│           API Layer (Presentation)          │\n│  Controllers, Middleware, Configuration     │\n└────────────────┬────────────────────────────┘\n                 │ depends on\n                 ▼\n┌─────────────────────────────────────────────┐\n│        Application Layer (Use Cases)        │\n│     Services, DTOs, Mappings, Interfaces    │\n└────────────────┬────────────────────────────┘\n                 │ depends on\n                 ▼\n┌─────────────────────────────────────────────┐\n│           Domain Layer (Core)               │\n│   Entities, Validators, Domain Exceptions   │\n│        (No external dependencies)           │\n└─────────────────────────────────────────────┘\n                 ▲\n                 │ depends on\n┌────────────────┴────────────────────────────┐\n│       Infrastructure Layer (External)       │\n│  Repositories, DbContext, Redis, JWT, etc.  │\n└─────────────────────────────────────────────┘\n```\n\n### Camadas\n\n**Domain Layer (Core)**\nContém as entidades de negócio (Post, Category, User), validadores FluentValidation e exceções de domínio. Esta camada não possui dependências externas e representa as regras de negócio puras da aplicação.\n\n**Application Layer (Use Cases)**\nImplementa os casos de uso através de serviços (PostService, CategoryService, AuthService). Inclui DTOs para transferência de dados, profiles do AutoMapper e interfaces de serviços. Esta camada orquestra o fluxo de dados entre as camadas externa e interna.\n\n**Infrastructure Layer (External)**\nImplementa os detalhes técnicos: repositórios usando Entity Framework Core, DbContext, serviços externos (Redis cache, JWT token service, BCrypt password service) e registro de dependências.\n\n**API Layer (Presentation)**\nExpõe a aplicação através de controllers REST, middleware de tratamento de erros global, configurações (Program.cs) e serviços específicos da camada de apresentação (rate limiters).\n\n## Estrutura do Projeto\n\n```\nblog-api/\n├── src/\n│   ├── BlogAPI.API/              # Presentation Layer\n│   │   ├── Controllers/          # API endpoints\n│   │   ├── Configuration/        # Settings, handlers\n│   │   └── Services/             # Rate limiters\n│   ├── BlogAPI.Application/      # Application Layer\n│   │   ├── DTOs/                 # Data transfer objects\n│   │   ├── Mappings/             # AutoMapper profiles\n│   │   ├── Services/             # Business logic services\n│   │   └── Common/               # Cache keys, shared\n│   ├── BlogAPI.Domain/           # Domain Layer\n│   │   ├── Entities/             # Post, Category, User\n│   │   ├── Validators/           # FluentValidation\n│   │   ├── Interfaces/           # Repository interfaces\n│   │   └── Exceptions/           # Domain exceptions\n│   └── BlogAPI.Infrastructure/   # Infrastructure Layer\n│       ├── Data/                 # DbContext, Configurations\n│       ├── Repositories/         # EF Core repositories\n│       └── Services/             # Redis, JWT, BCrypt\n├── tests/\n│   └── BlogAPI.UnitTests/        # Unit tests (xUnit)\n├── docker-compose.yml             # Container orchestration\n├── Dockerfile                     # Multi-stage build\n```\n\n## Como Executar o Projeto\n\n### Pré-requisitos\n\n- **Opção 1 (Recomendado)**: Docker e Docker Compose\n- **Opção 2**: .NET 9.0 SDK + PostgreSQL 16 + Redis 7\n\n### Opção 1: Docker Compose (Recomendado)\n\nEsta é a forma mais rápida de executar o projeto. O Docker Compose irá subir todos os serviços necessários (PostgreSQL, Redis e a API) com um único comando.\n\n```bash\n# Clone o repositório\ngit clone https://github.com/vittordeaguiar/blog-api.git\ncd blog-api\n\n# Inicie todos os containers (API + PostgreSQL + Redis)\ndocker-compose up -d\n\n# A API estará disponível em:\n# - API: http://localhost:8080\n# - Documentação Scalar: http://localhost:8080/scalar/v1\n# - Swagger JSON: http://localhost:8080/swagger/v1/swagger.json\n\n# Para ver logs da aplicação\ndocker-compose logs -f api\n\n# Para parar todos os containers\ndocker-compose down\n```\n\n### Opção 2: Execução Local\n\nSe preferir executar localmente sem Docker, certifique-se de ter PostgreSQL e Redis instalados e rodando.\n\n```bash\n# Configure as connection strings em appsettings.Development.json:\n# - PostgreSQL: localhost:5432\n# - Redis: localhost:6379\n\n# Restaure as dependências\ndotnet restore\n\n# Execute as migrations (cria o banco automaticamente)\ndotnet ef database update --project src/BlogAPI.Infrastructure --startup-project src/BlogAPI.API\n\n# Execute a aplicação\ndotnet run --project src/BlogAPI.API\n\n# Acesse http://localhost:8080\n```\n\n## Endpoints da API\n\nA API está organizada em três grupos principais de endpoints:\n\n### Autenticação (`/v1/auth`)\n\n- `POST /v1/auth/register` - Registrar novo usuário (role: Author ou Admin)\n- `POST /v1/auth/login` - Fazer login e obter token JWT\n\n### Posts (`/api/v1/posts`)\n\n- `GET /api/v1/posts` - Listar posts paginados (público, com cache de 5 minutos)\n- `GET /api/v1/posts/{slug}` - Obter post por slug (público, com cache de 10 minutos)\n- `POST /api/v1/posts` - Criar novo post (requer autenticação)\n- `PUT /api/v1/posts/{id}` - Atualizar post (apenas owner ou admin)\n- `DELETE /api/v1/posts/{id}` - Deletar post (apenas owner ou admin)\n\n### Categories (`/v1/categories`)\n\n- `GET /v1/categories` - Listar todas categorias (público, cache de 60 minutos)\n- `POST /v1/categories` - Criar categoria (somente admin)\n- `DELETE /v1/categories/{id}` - Deletar categoria (somente admin)\n\nPara explorar todos os endpoints interativamente, acesse a documentação Scalar em `/scalar/v1` após iniciar a aplicação.\n\n## Autenticação e Autorização\n\nA API utiliza JWT (JSON Web Tokens) para autenticação stateless. Aqui está um fluxo completo de como usar:\n\n### 1. Registrar um novo usuário\n\n```bash\ncurl -X POST http://localhost:8080/v1/auth/register \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"name\": \"João Silva\",\n    \"email\": \"joao@example.com\",\n    \"password\": \"SenhaSegura123!\",\n    \"role\": \"Author\"\n  }'\n```\n\n### 2. Fazer login e obter o token\n\n```bash\ncurl -X POST http://localhost:8080/v1/auth/login \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"email\": \"joao@example.com\",\n    \"password\": \"SenhaSegura123!\"\n  }'\n```\n\nResposta: `{\"token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\"}`\n\n### 3. Usar o token em requisições autenticadas\n\n```bash\ncurl -X POST http://localhost:8080/api/v1/posts \\\n  -H \"Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"title\": \"Meu Primeiro Post\",\n    \"content\": \"Conteúdo do post aqui...\",\n    \"categoryIds\": [\"uuid-da-categoria\"]\n  }'\n```\n\n### Roles Disponíveis\n\n- **Author**: Pode criar posts e editar/deletar seus próprios posts\n- **Admin**: Pode gerenciar categorias e editar/deletar qualquer post\n\n## Cache e Performance\n\n### Estratégia de Cache com Redis\n\nA aplicação implementa cache distribuído usando Redis com diferentes TTLs (Time To Live) baseados no tipo de dado:\n\n- **Categories**: 60 minutos - categorias mudam raramente e têm alta taxa de reutilização\n- **Posts (lista paginada)**: 5 minutos - balanceia freshness dos dados vs performance\n- **Posts (detalhe individual)**: 10 minutos - posts específicos mudam menos que listas\n\nO cache utiliza o **cache-aside pattern** através do método `GetOrSetAsync`, que verifica o cache primeiro e, em caso de miss, consulta o banco de dados e armazena o resultado automaticamente.\n\nA **invalidação automática** do cache ocorre em todas as operações de escrita (create, update, delete), garantindo que os dados em cache estejam sempre consistentes com o banco de dados.\n\n### Rate Limiting Distribuído\n\nO rate limiting é implementado usando Redis Sorted Sets com o algoritmo sliding window, permitindo que múltiplas instâncias da API compartilhem os mesmos contadores.\n\n**Limites configurados:**\n\n- **Por IP**: 1000 requests/minuto (ambiente dev), 100 requests/minuto (produção)\n- **Por Usuário Autenticado**: 2000 requests/minuto (ambiente dev), 200 requests/minuto (produção)\n\nQuando um limite é excedido, a API retorna HTTP 429 (Too Many Requests) com o header `Retry-After` indicando quando o cliente pode tentar novamente.\n\nA implementação usa **graceful degradation**: se o Redis estiver offline, as requisições são permitidas (fail-open) para garantir que a API continue funcionando.\n\n## Testes\n\nO projeto possui uma suite abrangente de testes unitários cobrindo as camadas Domain, Application e Infrastructure.\n\n### Executar os testes\n\n```bash\n# Executar todos os testes\ndotnet test\n\n# Executar com detalhes de cobertura\ndotnet test /p:CollectCoverage=true\n```\n\n### Cobertura de Testes\n\n**Domain Layer**\nTestes das entidades de domínio, incluindo comportamentos de publish/unpublish de posts e gerenciamento de categorias.\n\n**Application Layer**\nMais de 17 testes no PostService cobrindo:\n- Criação de posts (validações, author válido/inválido)\n- Listagem paginada (parâmetros válidos/inválidos)\n- Busca por ID e slug\n- Atualização com verificação de autorização (owner/admin)\n- Deleção com verificação de autorização\n- Publish/Unpublish de posts\n- Vinculação de posts com categorias\n\n**Infrastructure Layer**\nTestes do BCryptPasswordService verificando hash e verificação de passwords.\n\n### Frameworks de Teste\n\n- **xUnit**: Framework de testes com suporte a teoria e fixtures\n- **Moq**: Mocking de dependências (repositories, services) para testes isolados\n- **FluentAssertions**: Assertions expressivas e legíveis que facilitam a compreensão dos testes\n\n## Configuração\n\n### Variáveis de Ambiente (Docker Compose)\n\n```yaml\n# PostgreSQL\nConnectionStrings__DefaultConnection=Host=postgres;Port=5432;Database=blogapi_dev;Username=postgres;Password=postgres\n\n# JWT\nJwtSettings__SecretKey=sua-chave-secreta-aqui\n\n# Redis\nRedisSettings__ConnectionString=blogapi-redis:6379\nRedisSettings__Password=redis_dev_password\nRedisSettings__Enabled=true\n\n# ASP.NET Core\nASPNETCORE_ENVIRONMENT=Development\nASPNETCORE_HTTP_PORTS=8080\n```\n\n### Arquivos de Configuração\n\n**appsettings.json**\nConfigurações para ambiente de produção com valores padrão seguros.\n\n**appsettings.Development.json**\nConfigurações específicas para desenvolvimento:\n- CORS liberado para localhost (portas 3000, 3001, 4200, 5173, 8080)\n- Logging detalhado do Entity Framework Core\n- Rate limits mais permissivos para facilitar testes\n- Configurações de conexão para ambiente local\n\n## Progresso do Projeto\n\nEste projeto está **90% completo**, com 9 das 10 issues principais finalizadas:\n\n- Estrutura Clean Architecture\n- Entity Framework Core com PostgreSQL\n- Middleware de tratamento de erros e documentação Swagger/Scalar\n- Domínio de usuário com criptografia BCrypt\n- Autenticação JWT com roles\n- CRUD de Categories e Posts\n- Regras de negócio (publish/unpublish, many-to-many)\n- Controllers da API\n- Testes unitários abrangentes\n- Dockerização (em finalização)\n\nPara acompanhar o progresso detalhado, consulte o [ROADMAP.md](./ROADMAP.md) e o [GitHub Project](https://github.com/vittordeaguiar/blog-api/projects).\n\n## Aprendizados e Decisões Técnicas\n\n### Clean Architecture\n\nEscolhi implementar Clean Architecture para garantir separação clara de responsabilidades e facilitar testes. O Domain layer não possui dependências externas, o que permite testar as regras de negócio de forma completamente isolada. Essa arquitetura também facilitou a substituição de implementações - por exemplo, o NullCacheService serve como fallback automático quando o Redis está offline, sem afetar nenhuma outra parte do código.\n\n### Redis para Cache e Rate Limiting\n\nImplementar cache distribuído com Redis foi fundamental para permitir escalabilidade horizontal da API. Múltiplas instâncias podem compartilhar o mesmo cache e os mesmos contadores de rate limiting, algo impossível com cache in-memory tradicional. A decisão de implementar graceful degradation significa que a API continua funcionando mesmo se o Redis falhar - ela simplesmente opera sem cache até que o Redis volte.\n\n### FluentValidation no Domain Layer\n\nCentralizar validações de negócio nas entidades garante que as regras sejam respeitadas independentemente de onde a entidade é instanciada. Os validadores são reutilizáveis, testáveis e mantêm o código limpo ao separar a lógica de validação da lógica de negócio.\n\n### Repository Pattern\n\nA abstração do acesso a dados através do Repository Pattern facilita enormemente os testes (podemos fazer mock dos repositories) e nos dá flexibilidade para trocar o ORM ou adicionar outras fontes de dados sem afetar as camadas superiores da aplicação.\n\n### JWT Bearer Authentication\n\nOptei por JWT para implementar autenticação stateless, eliminando a necessidade de sessões no servidor. Os tokens JWT carregam claims (user ID, role) e são validados a cada requisição. Esta abordagem é ideal para APIs RESTful e arquiteturas distribuídas, permitindo que qualquer instância da API valide tokens sem consultar um armazenamento centralizado de sessões.\n\n### Cache-Aside Pattern (GetOrSetAsync)\n\nO padrão cache-aside implementado através do método `GetOrSetAsync` simplifica significativamente o código. Em vez de repetir a lógica \"verifica cache, se não tem consulta DB, armazena em cache\" em cada método, encapsulamos isso em um único método reutilizável.\n\n### Rate Limiting Distribuído\n\nImplementei rate limiting usando Redis Sorted Sets com o algoritmo sliding window. Esta abordagem garante que os limites sejam respeitados de forma precisa mesmo com múltiplas instâncias da API rodando simultaneamente, algo crítico para ambientes de produção escaláveis.\n\n## Contato\n\nGitHub: [vittordeaguiar](https://github.com/vittordeaguiar)\n\n## Licença\n\nEste projeto foi desenvolvido para fins educacionais e como parte do meu portfólio profissional. Sinta-se livre para explorar o código e usar como referência para seus próprios projetos.\n\n## Screenshots\n\u003cimg width=\"1892\" height=\"943\" alt=\"image\" src=\"https://github.com/user-attachments/assets/d000ecc9-3d66-4a2a-8b9d-d88e74000acf\" /\u003e\n\u003cimg width=\"1421\" height=\"860\" alt=\"image\" src=\"https://github.com/user-attachments/assets/c0f1ff96-ca01-4aac-adbe-f1e79bed84d0\" /\u003e\n\u003cimg width=\"1677\" height=\"945\" alt=\"image\" src=\"https://github.com/user-attachments/assets/cfc30f6f-5797-4a8e-b4f8-00e94753c89c\" /\u003e\n\u003cimg width=\"1679\" height=\"947\" alt=\"image\" src=\"https://github.com/user-attachments/assets/4ea02c17-aec9-45d5-8031-4977d194379a\" /\u003e\n\u003cimg width=\"1892\" height=\"944\" alt=\"image\" src=\"https://github.com/user-attachments/assets/26c53155-f560-45a9-b1e9-664c9dfe2c0a\" /\u003e\n\u003cimg width=\"817\" height=\"836\" alt=\"image\" src=\"https://github.com/user-attachments/assets/aa1802d5-cf94-41b5-b773-69117a0dcec8\" /\u003e\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvittordeaguiar%2Fblog-api","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvittordeaguiar%2Fblog-api","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvittordeaguiar%2Fblog-api/lists"}