Skip to content

MarkoG111/marko_blog

Repository files navigation

πŸ“° Marko's Blog

A full-stack blog platform featuring real-time interactions, CQRS-inspired architecture, fine-grained authorization, and hybrid authentication.

πŸ”— Live Demo: https://marko-blog.vercel.app/


πŸ“š Table of Contents


πŸ“Œ Overview

Marko's Blog is a full-stack web application that allows users to create, interact with, and follow blog content in real time.

The system is designed to demonstrate:

  • Scalable backend architecture (CQRS-inspired with Clean Architecture)
  • Real-time communication via SignalR (WebSockets)
  • Custom authorization logic beyond basic role checks
  • Integration of external authentication (Google via Firebase)

🧠 Problem & Motivation

Most blog platforms are simple CRUD applications with minimal real-time capabilities and tightly coupled logic. This project explores how to:

  • Separate read and write operations for better structure and maintainability
  • Handle real-time user interactions (likes, comments, follows, posts)
  • Design a flexible authorization system beyond basic role assignments
  • Integrate external identity providers while maintaining full backend control

πŸš€ Key Features

  • πŸ“ Create, edit, and manage blog posts with categories and images
  • πŸ’¬ Comment system with nested replies (threaded comments)
  • ❀️ Like/unlike posts and comments
  • πŸ‘₯ Follow system - users receive updates from authors they follow
  • πŸ”” Real-time notifications via WebSockets (SignalR)
  • πŸ” Hybrid authentication (JWT + Google via Firebase)
  • 🧠 Use-case driven architecture with centralized execution pipeline
  • πŸ“Š Full audit log of all executed use-case operations
  • πŸ›‘ Fine-grained authorization per action (not just role-based)
  • πŸ“§ Email notification on registration (SMTP)
  • πŸ“‹ Author request system - users can apply to become authors
  • πŸŒ™ Light / Dark theme toggle

πŸ‘€ Demo Credentials

Use these accounts to explore the live demo:

πŸ‘¨β€πŸ’» Admin login:

Username: admin
Password: admin123

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

Username: emily_s
Password: pass123

πŸ›  Tech Stack

Backend

ASP.NET Core C# Entity Framework SignalR JWT FluentValidation Sentry

Frontend

React Vite Redux TailwindCSS Firebase

Database & Deployment

PostgreSQL Vercel Fly.io


🧱 Architecture Explained

The backend follows a layered architecture inspired by Clean Architecture and CQRS principles.

Layers

Layer Responsibility
Domain Core business entities and interfaces - no infrastructure dependencies
EFDataAccess Database access via Entity Framework Core, configurations, migrations
Application Use-case interfaces (CQRS), DTOs, validators, service contracts
Implementation Concrete implementations of commands, queries, services, validators
API ASP.NET Core Web API - controllers, middleware, JWT, SignalR, DI config
Client React frontend - routing, Redux state, real-time layer

Command vs Query Separation

  • Commands β†’ mutate system state (Create, Update, Delete)
  • Queries β†’ read data only
CreatePostCommand   β†’ creates a post + triggers notifications
GetPostsQuery       β†’ fetches filtered and paginated posts

Centralized Execution Pipeline

Every action flows through a single UseCaseExecutor:

HTTP Request
    ↓
UseCaseExecutor
    β”œβ”€β”€ Logs the request (actor, data, use-case name)
    β”œβ”€β”€ Checks permissions (AllowedUseCases)
    └── Executes the use-case

πŸ” Authentication System

The app uses a hybrid authentication approach.

Standard Login

  1. User sends username + password
  2. Backend validates credentials (BCrypt hash check)
  3. JWT is issued and returned

Google OAuth (Firebase)

  1. User signs in via Google popup (Firebase SDK)
  2. Frontend receives user data (email, name, avatar)
  3. Data is sent to /api/auth
  4. Backend checks if user exists - creates one if needed
  5. JWT is issued and returned

⚠️ Firebase is used only as an identity provider. JWT is the sole source of authorization. Firebase tokens are not verified on the backend (a production requirement).


πŸ”” Real-Time Notifications

The system combines REST and WebSocket (SignalR) communication.

Initial Load

Notifications are fetched via REST API on app start.

Real-Time Updates

User performs action (like / comment / follow / post)
    ↓
Backend creates a Notification record
    ↓
SignalR pushes it to the target user's group
    ↓
Frontend Redux state updates instantly
    ↓
UI re-renders

Each user is assigned to a SignalR group by their IdUser, so notifications are targeted and multi-device friendly.


πŸ›‘ Authorization Model

Instead of classic [Authorize(Roles = "Admin")] on controllers, this system uses use-case level permissions.

  • Each user has a list of allowed UseCaseEnum values stored in UserUseCases
  • Permissions are derived from their role (Admin / Author / User) and automatically updated when role changes
  • Every action is validated inside UseCaseExecutor before execution

This enables:

  • Fine-grained control (e.g., delete own comment vs. delete any comment)
  • Easy extension of permissions without touching controllers
  • Full auditability via use-case logs

🧩 Database Schema

database

πŸ“ Folder Structure

My_Blog/
β”œβ”€β”€ Domain/                          # Business entities (Post, User, Comment, etc.)
β”‚   └── *.cs
β”‚
β”œβ”€β”€ EFDataAccess/                    # EF Core DbContext, configurations, migrations
β”‚   β”œβ”€β”€ BlogContext.cs
β”‚   β”œβ”€β”€ Configurations/
β”‚   └── Seed/
β”‚
β”œβ”€β”€ Application/                     # CQRS interfaces, DTOs, validators, services
β”‚   β”œβ”€β”€ Commands/
β”‚   β”œβ”€β”€ Queries/
β”‚   β”œβ”€β”€ DataTransfer/
β”‚   β”œβ”€β”€ Searches/
β”‚   β”œβ”€β”€ Services/
β”‚   └── Exceptions/
β”‚
β”œβ”€β”€ Implementation/                  # Concrete implementations
β”‚   β”œβ”€β”€ Commands/
β”‚   β”œβ”€β”€ Queries/
β”‚   β”œβ”€β”€ Services/
β”‚   β”œβ”€β”€ Validators/
β”‚   β”œβ”€β”€ Logging/
β”‚   └── Extensions/
β”‚
β”œβ”€β”€ API/                             # ASP.NET Core Web API
β”‚   β”œβ”€β”€ Controllers/
β”‚   β”‚   β”œβ”€β”€ PostsController.cs
β”‚   β”‚   β”œβ”€β”€ CommentsController.cs
β”‚   β”‚   β”œβ”€β”€ CategoriesController.cs
β”‚   β”‚   β”œβ”€β”€ LikesController.cs
β”‚   β”‚   β”œβ”€β”€ FollowersController.cs
β”‚   β”‚   β”œβ”€β”€ NotificationsController.cs
β”‚   β”‚   β”œβ”€β”€ UsersController.cs
β”‚   β”‚   β”œβ”€β”€ LoginController.cs
β”‚   β”‚   β”œβ”€β”€ OAuthController.cs
β”‚   β”‚   β”œβ”€β”€ RegisterController.cs
β”‚   β”‚   β”œβ”€β”€ AuthorRequestsController.cs
β”‚   β”‚   β”œβ”€β”€ ImagesController.cs
β”‚   β”‚   └── UseCaseLogsController.cs
β”‚   β”œβ”€β”€ Core/
β”‚   β”‚   β”œβ”€β”€ JWTManager.cs
β”‚   β”‚   β”œβ”€β”€ JWTService.cs
β”‚   β”‚   β”œβ”€β”€ NotificationHub.cs
β”‚   β”‚   β”œβ”€β”€ SignalRNotificationHub.cs
β”‚   β”‚   β”œβ”€β”€ GlobalExceptionHandler.cs
β”‚   β”‚   └── APIExtension.cs
β”‚   β”œβ”€β”€ Services/
β”‚   β”œβ”€β”€ Startup.cs
β”‚   └── Program.cs
β”‚
└── Client/                          # React frontend
    └── src/
        β”œβ”€β”€ pages/
        β”‚   β”œβ”€β”€ Home.jsx
        β”‚   β”œβ”€β”€ PostsPage.jsx
        β”‚   β”œβ”€β”€ PostPage.jsx
        β”‚   β”œβ”€β”€ CreatePost.jsx
        β”‚   β”œβ”€β”€ UpdatePost.jsx
        β”‚   β”œβ”€β”€ Dashboard.jsx
        β”‚   β”œβ”€β”€ SignIn.jsx
        β”‚   β”œβ”€β”€ SignUp.jsx
        β”‚   β”œβ”€β”€ UserPage.jsx
        β”‚   β”œβ”€β”€ Authors.jsx
        β”‚   β”œβ”€β”€ CategoryPage.jsx
        β”‚   β”œβ”€β”€ CreateCategory.jsx
        β”‚   β”œβ”€β”€ NotificationsPage.jsx
        β”‚   └── UserCommentPage.jsx
        β”œβ”€β”€ components/
        β”‚   β”œβ”€β”€ Header.jsx
        β”‚   β”œβ”€β”€ Footer.jsx
        β”‚   β”œβ”€β”€ PostCard.jsx
        β”‚   β”œβ”€β”€ CommentSection.jsx
        β”‚   β”œβ”€β”€ Comment.jsx
        β”‚   β”œβ”€β”€ ChildComment.jsx
        β”‚   β”œβ”€β”€ AdminDashboard.jsx
        β”‚   β”œβ”€β”€ DashPosts.jsx
        β”‚   β”œβ”€β”€ DashComments.jsx
        β”‚   β”œβ”€β”€ DashUsers.jsx
        β”‚   β”œβ”€β”€ DashCategories.jsx
        β”‚   β”œβ”€β”€ DashLogs.jsx
        β”‚   β”œβ”€β”€ DashAuthorRequests.jsx
        β”‚   β”œβ”€β”€ DashProfile.jsx
        β”‚   β”œβ”€β”€ PrivateRoute.jsx
        β”‚   β”œβ”€β”€ OnlyRolePrivateRoute.jsx
        β”‚   β”œβ”€β”€ ThemeProvider.jsx
        β”‚   └── OAuth.jsx
        β”œβ”€β”€ redux/
        β”‚   β”œβ”€β”€ user/userSlice.js
        β”‚   β”œβ”€β”€ theme/themeSlice.js
        β”‚   └── notification/notificationsSlice.js
        β”œβ”€β”€ contexts/
        β”‚   β”œβ”€β”€ ErrorContext.jsx
        β”‚   └── SuccessContext.jsx
        β”œβ”€β”€ api/
        β”œβ”€β”€ services/
        β”œβ”€β”€ utils/
        β”œβ”€β”€ App.jsx
        └── main.jsx

βš™οΈ Setup Instructions

Prerequisites


1. Clone the Repository

git clone https://github.com/your-username/marko-blog.git
cd marko-blog

2. Backend Setup

Configure appsettings.Development.json

Inside the API/ folder, update appsettings.Development.json with your local values:

{
  "ConnectionStrings": {
    "DefaultConnection": "Host=localhost;Port=5432;Database=blog;Username=postgres;Password=postgres;SSL Mode=Disable"
  },
  "JWT": {
    "Issuer": "http://localhost:5000",
    "Audience": "BlogClient",
    "SecretKey": "your-dev-secret-key-min-32-chars",
    "TokenExpiryMinutes": 120
  },
  "SMTP": {
    "SenderEmail": "noreply@yourdomain.com",
    "Host": "smtp.yourdomain.com",
    "Port": 587,
    "Username": "noreply@yourdomain.com",
    "Password": "your-smtp-password"
  }
}

Run Migrations & Start the API

cd API

# Restore dependencies
dotnet restore

# Apply database migrations (creates tables)
dotnet ef database update

# Start the API
dotnet run

The API will be available at http://localhost:5000.


3. Frontend Setup

cd Client

# Install dependencies
npm install

# Start the development server
npm run dev

The frontend will be available at http://localhost:5173.


🌿 Environment Variables

Backend - appsettings.Development.json

Key Description
ConnectionStrings:DefaultConnection PostgreSQL connection string
JWT:Issuer Token issuer (your API URL)
JWT:Audience Token audience (your client name)
JWT:SecretKey Secret key - minimum 32 characters
JWT:TokenExpiryMinutes Token lifetime in minutes
SMTP:Host SMTP server host
SMTP:Port SMTP port (587 for TLS)
SMTP:SenderEmail Email address used for sending
SMTP:Password App password for SMTP auth

Frontend - Client/.env

Create a .env file inside the Client/ folder:

VITE_API_URL=http://localhost:5000/api

VITE_FIREBASE_API_KEY=your_key
VITE_FIREBASE_AUTH_DOMAIN=your_domain
VITE_FIREBASE_PROJECT_ID=your_project

πŸ’‘ The Vite dev server proxies /api requests to http://localhost:5000 automatically - you only need VITE_API_URL for production builds.


🌱 Database Seed

The application automatically seeds the database on first run via DataSeeder.cs.

Seeded data includes:

  • Roles - Admin, Author, User
  • Users - including admin and emily_s accounts with use-case permissions
  • Categories - initial blog categories
  • Posts - sample blog posts with images
  • Post–Category links
  • Comments - sample comments with nested replies
  • Followers - sample follow relationships
  • Likes - sample likes on posts and comments
  • Notifications - sample notification entries

Seeding only runs when the respective tables are empty, so it is safe to re-run.

To manually trigger a fresh seed, drop and recreate the database:

dotnet ef database drop
dotnet ef database update

πŸš€ Deployment

Layer Platform
Frontend Vercel
Backend Fly.io
Database PostgreSQL on Fly.io

The project includes a Dockerfile and fly.toml for backend deployment, and a vercel.json for frontend deployment.


⚠️ Tradeoffs & Limitations

  • Firebase token is not verified on the backend (would be required in production)
  • No recovery mechanism for missed SignalR events (e.g., notifications sent while offline)
  • Commands handle side-effects (like notifications) directly - no event bus or message queue
  • UseCaseEnum may become harder to maintain at scale
  • Images are stored locally on the server (not cloud-optimized - no S3 or CDN)
  • Two sources of truth for auth state: Redux + localStorage (can desync on edge cases)

🎯 Why This Project Matters

This project goes beyond a typical CRUD blog and demonstrates:

  • Real-time full-stack application design with SignalR
  • Clean separation of concerns across multiple backend layers
  • Custom authorization beyond simple role checks
  • Integration of external OAuth providers (Google/Firebase)
  • Practical application of SOLID principles in a layered architecture
  • CQRS approach with centralized use-case execution and audit logging

It represents a step toward production-oriented system design in the .NET + React ecosystem.

Releases

No releases published

Packages

 
 
 

Contributors

Languages