Skip to content

emaginebr/NNews

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

37 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

NNews - Multi-Tenant CMS Microservice for News & Blogs

.NET PostgreSQL License NuGet

Overview

NNews is a multi-tenant CMS (Content Management System) microservice for news and blogs with AI-powered content generation via ChatGPT and DALL-E 3. Built using .NET 8, PostgreSQL 16, and Clean Architecture, it provides a complete REST API for managing articles, categories, tags, and images β€” with full tenant isolation through separate databases and per-tenant JWT authentication.

NNews is part of the Emagine ecosystem and integrates with NAuth for authentication/user management and zTools for AI content generation, file uploads, and utility services.


πŸš€ Features

  • 🏒 Multi-Tenant Architecture - Complete tenant isolation with separate databases and per-tenant JWT secrets
  • πŸ€– AI Content Generation - Create and update articles using ChatGPT integration
  • πŸ–ΌοΈ DALL-E 3 Image Generation - AI-powered image creation for articles
  • πŸ“° Full CMS - CRUD for articles, categories (hierarchical), and tags (with merge support)
  • πŸ” Per-Tenant JWT Authentication - Dynamic JWT validation with tenant-specific signing keys
  • πŸ“‚ File Upload - Image upload to S3 via zTools integration
  • 🏷️ Role-Based Access - Article-level role-based access control
  • πŸ“Š Article Status Workflow - Draft, Published, Archived, and Scheduled states
  • πŸ“¦ NuGet Packages - ACL and DTO published as NuGet packages for external consumption
  • πŸ“ Structured Logging - Serilog with console and file sinks
  • 🐳 Docker Ready - Full Docker Compose setup for development and production

πŸ› οΈ Technologies Used

Core Framework

  • ASP.NET Core 8.0 - Web API framework
  • Entity Framework Core 8.0 - ORM with PostgreSQL provider (Npgsql)

Database

  • PostgreSQL 16 - Primary database (one per tenant)

Security

  • NAuth (v0.5.5) - Authentication, user management, and JWT with multi-tenant support
  • Microsoft.IdentityModel.Tokens - Per-tenant JWT validation via IssuerSigningKeyResolver

Additional Libraries

  • zTools (v0.3.6) - ChatGPT, DALL-E, file upload (S3), slug generation, email
  • AutoMapper - Entity-to-DTO mapping
  • Serilog - Structured logging (Console + File sinks)
  • Swashbuckle - Swagger/OpenAPI documentation
  • Newtonsoft.Json - JSON serialization

DevOps

  • Docker & Docker Compose - Containerized deployment
  • GitHub Actions - CI/CD (versioning, NuGet publishing, production deploy)
  • GitVersion - Semantic versioning (ContinuousDelivery mode)

πŸ“ Project Structure

NNews/
β”œβ”€β”€ NNews.API/                    # ASP.NET Core Web API
β”‚   β”œβ”€β”€ Controllers/              # REST controllers (Article, Category, Tag, Image)
β”‚   β”œβ”€β”€ Middlewares/               # TenantMiddleware (multi-tenant resolution)
β”‚   └── Program.cs                # App startup & pipeline configuration
β”œβ”€β”€ NNews.Application/            # DI registration & initialization
β”‚   β”œβ”€β”€ Interfaces/               # ITenantContext, ITenantDbContextFactory
β”‚   β”œβ”€β”€ Services/                 # TenantContext, TenantDbContextFactory, NAuthProviders
β”‚   └── Initializer.cs            # Service registration entry point
β”œβ”€β”€ NNews.Domain/                 # Business logic & entities
β”‚   β”œβ”€β”€ Entities/                 # Domain models (Article, Category, Tag, ArticleRole)
β”‚   β”œβ”€β”€ Enums/                    # ArticleStatus, etc.
β”‚   └── Services/                 # Domain services & interfaces
β”œβ”€β”€ NNews.Infra/                  # Infrastructure layer
β”‚   β”œβ”€β”€ Context/                  # EF Core DbContext (NNewsContext)
β”‚   β”œβ”€β”€ Mapping/                  # AutoMapper profiles
β”‚   β”œβ”€β”€ Migrations/               # EF Core migrations
β”‚   └── Repository/               # Repository implementations
β”œβ”€β”€ NNews.Infra.Interfaces/       # Repository interface contracts
β”œβ”€β”€ NNews/ (NuGet packages)
β”‚   └── NNews.ACL/                # Anti-Corruption Layer HTTP clients
β”‚       β”œβ”€β”€ Handlers/             # TenantHeaderHandler (auto-injects X-Tenant-Id)
β”‚       β”œβ”€β”€ Interfaces/           # ACL client interfaces + ITenantResolver
β”‚       └── Services/             # TenantResolver
β”œβ”€β”€ docs/                         # Project documentation
β”œβ”€β”€ .github/workflows/            # CI/CD pipelines
β”œβ”€β”€ docker-compose.yml            # Development environment
β”œβ”€β”€ docker-compose-prod.yml       # Production environment (multi-tenant)
β”œβ”€β”€ NNews.API.Dockerfile          # Multi-stage Docker build
β”œβ”€β”€ postgres.Dockerfile           # PostgreSQL with extensions
β”œβ”€β”€ nnews.sql                     # Database schema
└── README.md                     # This file

Ecosystem

Project Type Package Description
NNews Microservice NuGet CMS API (ACL + DTO)
NAuth Microservice NuGet Authentication & user management
zTools Microservice NuGet ChatGPT, DALL-E, file upload, utilities

Dependency graph

nnews-app (React SPA)
  └── NNews.ACL (NuGet) ──→ NNews API ──→ PostgreSQL (per tenant)
  └── nauth-react          β”‚
                           β”œβ”€β”€β†’ NAuth API (auth + users)
                           └──→ zTools API (ChatGPT, S3, utils)

πŸ—οΈ System Design

The following diagram illustrates the high-level architecture of NNews:

System Design

The NNews API receives requests with tenant identification via X-Tenant-Id header (for unauthenticated endpoints) or JWT tenant_id claim (for authenticated endpoints). The TenantMiddleware resolves the tenant before authentication, and the TenantDbContextFactory dynamically connects to the correct tenant database. Each tenant has its own PostgreSQL database and JWT signing secret.

πŸ“„ Source: The editable Mermaid source is available at docs/system-design.mmd.


🏒 Multi-Tenant Architecture

NNews implements the database-per-tenant isolation pattern:

Tenant Resolution

Scenario TenantId Source
Unauthenticated endpoints X-Tenant-Id HTTP header
Authenticated endpoints tenant_id JWT claim
ACL consumers (NuGet package) appsettings.json β†’ Tenant:DefaultTenantId (auto-injected via TenantHeaderHandler)

Key Components

Component Layer Responsibility
TenantMiddleware API Extracts X-Tenant-Id from header before authentication
TenantContext Application Resolves TenantId from JWT claim or HTTP header (scoped)
TenantResolver ACL Reads tenant config (ConnectionString, JwtSecret) from appsettings
TenantDbContextFactory Application Creates NNewsContext with dynamic ConnectionString per tenant
TenantHeaderHandler ACL DelegatingHandler that auto-injects X-Tenant-Id in all ACL HTTP requests

Per-Tenant Configuration

{
  "Tenant": {
    "DefaultTenantId": "emagine"
  },
  "Tenants": {
    "emagine": {
      "ConnectionString": "Host=db;Database=nnews_emagine;...",
      "JwtSecret": "your_64char_secret_for_emagine"
    },
    "devblog": {
      "ConnectionString": "Host=db;Database=nnews_devblog;...",
      "JwtSecret": "your_64char_secret_for_devblog"
    }
  }
}

Security Rules

  • TenantId is never accepted from request body β€” only from header or JWT
  • Each tenant has its own JwtSecret β€” JWT validation uses IssuerSigningKeyResolver to dynamically resolve the signing key
  • ACL consumers never pass TenantId as a method parameter β€” it's propagated automatically via TenantHeaderHandler

πŸ“– Additional Documentation

Document Description
MULTI_TENANT_API Multi-tenant implementation guide and patterns
USER_API_DOCUMENTATION User API endpoints reference
ROLE_API_DOCUMENTATION Role API endpoints reference
NUGET_PUBLISHING_GUIDE Guide for publishing NuGet packages

βš™οΈ Environment Configuration

Development

cp .env.example .env

Edit the .env file:

# PostgreSQL Container
POSTGRES_DB=nnews_db
POSTGRES_USER=nnews_user
POSTGRES_PASSWORD=your_secure_password_here
POSTGRES_PORT=5433

# NNews API Port
API_HTTP_PORT=5007

# External Services
ZTOOL_API_URL=http://ztools-api:8080

Production

cp .env.prod.example .env.prod

Edit the .env.prod file:

# HTTPS Certificate
CERTIFICATE_PASSWORD=your_certificate_password_here

# Tenant: emagine
EMAGINE_CONNECTION_STRING=Host=your_db_host;Port=5432;Database=nnews_emagine_db;Username=your_user;Password=your_password
EMAGINE_JWT_SECRET=your_emagine_jwt_secret_at_least_64_characters_long

# Tenant: devblog
DEVBLOG_CONNECTION_STRING=Host=your_db_host;Port=5432;Database=nnews_devblog_db;Username=your_user;Password=your_password
DEVBLOG_JWT_SECRET=your_devblog_jwt_secret_at_least_64_characters_long

⚠️ IMPORTANT:

  • Never commit .env or .env.prod files with real credentials
  • Only .env.example and .env.prod.example should be version controlled
  • Change all default passwords and secrets before deployment

🐳 Docker Setup

Development (with PostgreSQL)

1. Prerequisites

# Create the shared Docker network
docker network create emagine-network

2. Build and Start Services

docker-compose up -d --build

3. Verify Deployment

docker-compose ps
docker-compose logs -f

Production (Multi-Tenant, HTTPS)

docker compose --env-file .env.prod -f docker-compose-prod.yml up --build -d

Accessing the Application

Service URL Environment
NNews API (HTTP) http://localhost:5007 Dev / Prod
NNews API (HTTPS) https://localhost:5008 Prod only
PostgreSQL localhost:5433 Dev only
Swagger UI http://localhost:5007/swagger Dev only

Docker Compose Commands

Action Command
Start services docker-compose up -d
Start with rebuild docker-compose up -d --build
Stop services docker-compose stop
View status docker-compose ps
View logs docker-compose logs -f
Remove containers docker-compose down
Remove containers and volumes (⚠️) docker-compose down -v

πŸ”§ Manual Setup (Without Docker)

Prerequisites

Setup Steps

1. Clone and Restore

git clone https://github.com/emaginebr/NNews.git
cd NNews
dotnet restore NNews.sln

2. Configure Database

Create a PostgreSQL database and update appsettings.Development.json:

{
  "ConnectionStrings": {
    "NNewsContext": "Host=localhost;Port=5432;Database=nnews_db;Username=your_user;Password=your_password"
  }
}

3. Apply Database Schema

psql -U your_user -d nnews_db -f nnews.sql

4. Run the API

dotnet run --project NNews.API

The API will be available at http://localhost:5007 and https://localhost:5008.


πŸ“š API Documentation

Authentication Flow

1. Client sends X-Tenant-Id header β†’ 2. TenantMiddleware resolves tenant
β†’ 3. NAuth validates JWT with tenant-specific secret β†’ 4. Request processed

Key Endpoints

Method Endpoint Description Auth
GET /article List all articles (paginated, filter by categoryId/status) Yes
GET /article/ListByCategory Filter published articles by category & roles No
GET /article/ListByRoles Filter published articles by user roles No
GET /article/ListByTag Filter published articles by tag slug No
GET /article/Search Search articles by keyword No
GET /article/{id} Get article by ID No
POST /article Create new article Yes
POST /article/insertWithAI Create article with AI (ChatGPT) Yes
PUT /article Update article Yes
PUT /article/updateWithAI Update article with AI Yes
DELETE /article/{id} Delete article Yes
GET /category List all categories Yes
GET /category/listByParent Filter categories by parent & roles No
GET /category/{id} Get category by ID No
POST /category Create category Yes
PUT /category Update category Yes
DELETE /category/{id} Delete category Yes
GET /tag List all tags Yes
GET /tag/ListByRoles List tags from published articles No
GET /tag/{id} Get tag by ID No
POST /tag Create tag Yes
PUT /tag Update tag Yes
DELETE /tag/{id} Delete tag Yes
POST /tag/merge/{sourceId}/{targetId} Merge tags Yes
POST /image/uploadImage Upload image (max 100MB) Yes

πŸ”’ Security Features

Multi-Tenant Isolation

  • Database-per-tenant - Each tenant has its own PostgreSQL database
  • Per-tenant JWT secrets - Tokens are signed and validated with tenant-specific keys
  • TenantId never from body - Only accepted from X-Tenant-Id header or JWT claim

Authentication

  • NAuth integration - Centralized authentication and user management
  • BasicAuthentication scheme - JWT Bearer with dynamic key resolution
  • Role-based access control - Article-level permissions via ArticleRole

πŸ’Ύ Backup and Restore

Backup

pg_dump -U your_user -h localhost -p 5432 -d nnews_emagine_db > backup_emagine.sql
pg_dump -U your_user -h localhost -p 5432 -d nnews_devblog_db > backup_devblog.sql

Restore

psql -U your_user -h localhost -p 5432 -d nnews_emagine_db < backup_emagine.sql
psql -U your_user -h localhost -p 5432 -d nnews_devblog_db < backup_devblog.sql

πŸ“¦ Integration

Using NNews ACL in Your Application

Install the NuGet package:

dotnet add package NNews

Configure in your appsettings.json:

{
  "Tenant": {
    "DefaultTenantId": "your-tenant-id"
  },
  "Tenants": {
    "your-tenant-id": {
      "ConnectionString": "...",
      "JwtSecret": "..."
    }
  }
}

Register services with multi-tenant support:

// Register TenantHeaderHandler (auto-injects X-Tenant-Id in all HTTP requests)
services.AddTransient<TenantHeaderHandler>();

// Register ACL clients with tenant propagation
services.AddHttpClient<IArticleClient, ArticleClient>()
    .AddHttpMessageHandler<TenantHeaderHandler>();

services.AddHttpClient<ICategoryClient, CategoryClient>()
    .AddHttpMessageHandler<TenantHeaderHandler>();

Use the ACL (TenantId is propagated automatically):

public class MyService
{
    private readonly IArticleClient _articleClient;

    public MyService(IArticleClient articleClient)
    {
        _articleClient = articleClient; // TenantHeaderHandler auto-injects X-Tenant-Id
    }

    public async Task<List<ArticleDto>> GetArticlesAsync()
    {
        return await _articleClient.GetAllAsync();
    }
}

πŸš€ Deployment

Development Environment

docker-compose up -d --build

Production Environment

docker compose --env-file .env.prod -f docker-compose-prod.yml up --build -d

GitHub Actions (Automated)

Trigger the production deployment workflow manually from GitHub Actions:

Actions β†’ Deploy Production β†’ Run workflow

πŸ”„ CI/CD

GitHub Actions

Workflow Trigger Description
Version and Tag Push to main Calculates version via GitVersion and creates git tag
Create Release After Version and Tag Creates GitHub Release and release branch (for minor/major)
Publish NuGet After Version and Tag Builds and publishes NNews NuGet package
Deploy Production Manual (workflow_dispatch) Deploys to production server via SSH + Docker Compose

Versioning

Uses GitVersion (ContinuousDelivery mode). Commit message prefixes control version bumps:

Prefix Version Bump
major: or breaking: Major (X.0.0)
feature: or minor: Minor (0.X.0)
fix: or patch: Patch (0.0.X)

🀝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Development Setup

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/AmazingFeature)
  3. Make your changes
  4. Commit your changes (git commit -m 'feature: add some AmazingFeature')
  5. Push to the branch (git push origin feature/AmazingFeature)
  6. Open a Pull Request

Coding Standards

  • Follow Clean Architecture layer separation
  • Use Repository Pattern for data access
  • Register all dependencies in Initializer.cs
  • Use AutoMapper profiles for entity-to-DTO mapping
  • Follow commit message conventions for proper versioning

πŸ‘¨β€πŸ’» Author

Developed by Rodrigo Landim Carneiro


πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.


πŸ™ Acknowledgments


πŸ“ž Support


⭐ If you find this project useful, please consider giving it a star!

About

microservice for news and blogs

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages