A production-ready REST API designed for building practical tip discovery and management applications. Lifehacking provides a complete backend for exploring daily life tips, organizing them by categories, and managing user favorites with smooth anonymous-to-authenticated transitions.
Built with .NET 10 and Clean Architecture principles. This project was created from arielbvergara/clean-architecture β a reusable Clean Architecture template developed during lessons and converted into a template. In that project you can see the commits made prior to the creation of this project.
π€ AI-assisted development: Kiro and Warp were used as AI assistants throughout the development of this project.
- Deployed Application
- Project Presentation Slides
- Project Overview
- Technology Stack
- Related Projects
- Key Features
- Installation & Running
- Project Structure
- API Endpoints
- Architecture
- Authentication & Authorization
- Security Features
- Testing
- Development Guidelines
- Roadmap
- Frontend: https://lifehacking.vercel.app/
- Backend API: https://slight-janet-lifehacking-ce47cbe0.koyeb.app/
Lifehacking Master Slides Presentation
The Lifehacking Tips API is a complete and robust backend solution that enables building applications where users can discover, organize, and manage practical tips to improve their daily lives. The API is designed with modern architecture and industry best practices, providing a solid foundation for web and mobile applications.
The API allows developers to build applications where users can:
- Discover tips through advanced search, filtering by categories and tags (no authentication required)
- Save favorites with automatic sync between local storage and server-side persistence
- Manage content through a complete administrative interface for tips and categories
- Manage users with Firebase authentication, role-based access control, and self-service account management
The system is designed to support three user types with different access levels:
- Anonymous Users β Full read access with client-side favorites
- Authenticated Users β Persistent favorites with automatic merge from local storage
- Administrators β Full content and user management capabilities
The project follows the principles of Clean Architecture and Domain-Driven Design (DDD), ensuring:
- Clear separation of concerns between layers
- Business domain independence from frameworks and external technologies
- Maintainable, testable, and scalable code
- Easy addition of new features without affecting existing code
| Technology | Purpose |
|---|---|
| .NET 10 + Clean Architecture | Web API with Domain, Application, Infrastructure, and WebAPI layers |
| Firebase Authentication | JWT Bearer token validation and identity management |
| Firebase Cloud Firestore | Primary NoSQL database |
| AWS S3 | Category image storage |
| AWS CloudFront | CDN for image delivery |
| Docker & Docker Compose | Containerized deployment |
| Koyeb | Cloud deployment platform |
| Dependabot | Automatic weekly dependency updates |
| GitHub Actions | CI pipeline (build, test, lint, security scanning) and code review |
| Kiro | AI assistant used during development |
| Warp | AI assistant used during development |
| Sentry | Error tracking and performance monitoring |
| Swagger / OpenAPI | Interactive API documentation |
| Github Copilot | AI assistant used for code review |
Frontend (lifehacking-app)
| Technology | Purpose |
|---|---|
| Next.js 16 | React-based frontend framework |
| Google Stitch | UI/UX design |
| Firebase Authentication | Authentication and identity |
| Vercel | Frontend deployment |
| Sentry.io | Monitoring and error tracking |
| Docker | Containerized deployment |
| Dependabot | Automatic weekly dependency updates |
| GitHub Actions | CI pipeline and code review |
| Kiro | AI assistant used during development |
| Github Copilot | AI assistant used for code review |
| Project | Description | Deployment |
|---|---|---|
| lifehacking-app | Frontend β Next.js 16, Google Stitch design, Firebase, Docker, Vercel | Vercel |
| lifehacking (this repository) | Backend API β .NET 10, Clean Architecture, Firebase, Docker, AWS | Koyeb |
- Tip exploration with advanced search and filtering (by category, tags, search term)
- Detailed view of tip information including step-by-step instructions
- Category browsing with access to all available categories
- Flexible sorting of results by creation date, update date, or title
- Paginated responses for optimal performance
- Client-side favorites management (local storage)
- All anonymous user capabilities
- Persistent favorites stored server-side
- Automatic merge of local favorites on first login (no duplicates)
- Cross-device sync of favorites
- Self-service profile management (view, update name, delete account)
- All authenticated user capabilities
- Full tip lifecycle management (create, update, delete)
- Category management with cascade deletion
- Complete user administration
- Admin user creation with Firebase integration
- Dashboard with real-time statistics and entity counts
- Audit log for all administrative actions
- Clean Architecture with clear separation of concerns
- In-memory cache with automatic invalidation for performance optimization
- Soft Delete for data preservation and auditing
- Exhaustive input validation with detailed error responses
- Correlation IDs for request traceability in logs and monitoring systems
- Interactive documentation with Swagger/OpenAPI
- Robust security with JWT, rate limiting, security headers, and configurable CORS
Before getting started, make sure you have installed:
- .NET SDK 10.0 or higher
- Docker and Docker Compose
- A Firebase project for authentication (create one at Firebase Console)
- Optional: A Sentry project for monitoring (sign up at sentry.io)
The fastest way to run the API locally with all dependencies configured.
Before running docker compose up, you need:
-
Firebase Admin SDK credentials file
- Download the JSON credentials file from Firebase Console
- Go to: Project Settings β Service Accounts β Generate new private key
- Save the file as
firebase-adminsdk.jsonin~/secrets/
# Create directory if it doesn't exist mkdir -p ~/secrets # Copy your credentials file cp /path/to/your/firebase-adminsdk.json ~/secrets/firebase-adminsdk.json
-
Configure environment variables in docker-compose.yml (already configured by default):
ASPNETCORE_ENVIRONMENT: Developmentβ Development environmentClientApp__Origin: "http://localhost:3000"β Frontend origin for CORSGOOGLE_APPLICATION_CREDENTIALS: /app/firebase-adminsdk.jsonβ Path to credentials inside the container
-
Configure Firebase in appsettings.json or environment variables
Option A: Edit
lifehacking/WebAPI/appsettings.Development.json:{ "Firebase": { "ProjectId": "your-firebase-project", }, "Authentication": { "Authority": "https://securetoken.google.com/your-firebase-project", "Audience": "your-firebase-project" } }Option B: Add environment variables in
docker-compose.yml:environment: Firebase__ProjectId: "your-firebase-project" Authentication__Authority: "https://securetoken.google.com/your-firebase-project" Authentication__Audience: "your-firebase-project"
Once configured:
docker compose up --buildThis will:
- Build the Docker image with .NET 10
- Mount the Firebase credentials file
- Start the WebAPI container
- Configure the API to use Firebase/Firestore
- Expose the API on port 8080
Once running:
- API Base URL:
http://localhost:8080 - Swagger UI:
http://localhost:8080/swagger(interactive API documentation) - Health Check:
http://localhost:8080/health(if configured)
To stop the services:
docker compose downImportant note: The project is designed to use Firebase/Firestore as the database. Docker Compose is configured to automatically connect to your Firebase project.
For faster iteration during development, run the API directly using the .NET SDK:
# Build the solution
dotnet build lifehacking.slnx
# Run the WebAPI project
dotnet run --project lifehacking/WebAPI/WebAPI.csprojThe API reads configuration from lifehacking/WebAPI/appsettings.Development.json and environment variables, connecting to Firebase/Firestore as configured.
To test authenticated and admin endpoints, configure Firebase as your identity provider:
-
Update
appsettings.Development.json:{ "Authentication": { "Authority": "https://securetoken.google.com/<your-firebase-project-id>", "Audience": "<your-firebase-project-id>" }, "Firebase": { "ProjectId": "<your-firebase-project-id>" } } -
Get a Firebase ID token:
- Authenticate a user through Firebase (web, mobile, or REST API)
- Extract the ID token from the authentication response
-
Use the token in API requests:
curl -H "Authorization: Bearer <firebase-id-token>" \ http://localhost:8080/api/user/me
The API validates the JWT token and maps the sub claim to the internal user's ExternalAuthId.
Sentry integration is optional. The API works normally with Sentry disabled.
To enable monitoring, set these environment variables:
export Sentry__Enabled=true
export Sentry__Dsn=<your-sentry-dsn>
export Sentry__Environment=Development
export Sentry__TracesSampleRate=0.2Or configure in appsettings.Development.json:
{
"Sentry": {
"Enabled": true,
"Dsn": "<your-sentry-dsn>",
"Environment": "Development",
"TracesSampleRate": 0.2
}
}When enabled, unhandled errors and performance traces are sent to Sentry with full context (path, user, correlation ID).
Once the API is running, navigate to the Swagger UI for interactive documentation:
Swagger provides:
- Complete endpoint documentation with request/response schemas
- Validation rules and constraints
- Interactive testing (try endpoints directly from the browser)
- Authentication support (add your Bearer token to test protected endpoints)
Note: Swagger is only enabled in non-production environments.
To enable category image uploads, configure AWS S3 and CloudFront:
export AWS_ACCESS_KEY_ID=your-access-key-id
export AWS_SECRET_ACCESS_KEY=your-secret-access-key
export AWS_REGION=us-east-1
export AWS__S3__BucketName=lifehacking-category-images
export AWS__CloudFront__Domain=your-distribution.cloudfront.netFor detailed AWS setup instructions, see docs/AWS-S3-Setup-Guide.md
The project follows Clean Architecture principles with a clear separation of concerns:
lifehacking/
βββ lifehacking.slnx # .NET 10 solution file
βββ README.md # Main documentation (English)
βββ AGENTS.md # Guide for AI agents
βββ docker-compose.yml # Docker Compose configuration
βββ Dockerfile # Application Docker image
β
βββ ADRs/ # Architecture Decision Records
β βββ 001-use-microsoft-testing-platform-runner.md
β βββ 018-replace-postgresql-persistence-with-firebase-database.md
β βββ 020-user-favorites-domain-model-and-storage.md
β βββ ... # More architectural decisions
β
βββ docs/ # Additional documentation
β βββ MVP.md # Product requirements and MVP scope
β βββ AWS-S3-Setup-Guide.md # AWS S3 configuration guide
β βββ Search-Architecture-Decision.md
β
βββ lifehacking/ # Main source code
β
βββ Domain/ # Domain Layer
β βββ Entities/ # Domain entities (User, Tip, Category, UserFavorites)
β βββ ValueObject/ # Value objects (CategoryImage, etc.)
β βββ Primitives/ # Primitive types (Result<T, TE>)
β βββ Constants/ # Domain constants (ImageConstants)
β
βββ Application/ # Application Layer
β βββ UseCases/ # Use cases organized by feature
β β βββ User/ # User use cases
β β βββ Category/ # Category use cases
β β βββ Tip/ # Tip use cases
β β βββ Favorite/ # Favorites use cases
β β βββ Dashboard/ # Dashboard use cases
β βββ Dtos/ # Data Transfer Objects
β β βββ User/ # User DTOs
β β βββ Category/ # Category DTOs
β β βββ Tip/ # Tip DTOs
β β βββ Favorite/ # Favorites DTOs
β β βββ Dashboard/ # Dashboard DTOs
β βββ Interfaces/ # Interfaces (ports)
β β βββ IUserRepository
β β βββ ICategoryRepository
β β βββ IImageStorageService
β β βββ ICacheInvalidationService
β βββ Exceptions/ # Application exceptions
β βββ Validation/ # Validation utilities
β βββ Caching/ # Cache key definitions
β
βββ Infrastructure/ # Infrastructure Layer
β βββ Data/Firestore/ # Firestore implementation
β β βββ Documents/ # Firestore document classes
β β βββ DataStores/ # Data stores (entity-document mapping)
β βββ Repositories/ # Repository implementations
β βββ Storage/ # Cloud storage services
β β βββ S3ImageStorageService.cs
β βββ Configuration/ # Configuration option classes
β
βββ WebAPI/ # Web API Layer
β βββ Program.cs # Entry point and root composition
β βββ Controllers/ # REST controllers
β β βββ UserController.cs
β β βββ AdminCategoryController.cs
β β βββ AdminDashboardController.cs
β β βββ ...
β βββ Filters/ # Global filters
β β βββ GlobalExceptionFilter.cs
β βββ Configuration/ # Service configuration
β βββ appsettings.json # Base configuration
β βββ appsettings.Development.json
β βββ appsettings.Production.json
β
βββ Tests/ # Test projects
βββ Application.Tests/ # Application layer tests
βββ Infrastructure.Tests/ # Infrastructure layer tests
βββ WebAPI.Tests/ # API integration tests
The project strictly follows Clean Architecture dependency rules:
- Domain β No references to other projects (completely independent)
- Application β Depends only on Domain
- Infrastructure β Depends on Application and Domain
- WebAPI β Depends on Application, Domain, and Infrastructure
- Tests β Reference only the layers they are intended to validate
HTTP Client
β
WebAPI Controller (presentation layer)
β
Application Use Case (business logic)
β
Domain Entities/Value Objects (domain model)
β
Infrastructure Repository (data access)
β
Firestore/Firebase (persistence)
β
Result<T, AppException> (response)
β
HTTP Response (mapped to status codes)
All endpoints return JSON and follow RFC 7807 Problem Details for error responses. Each response includes a correlationId for request traceability.
For complete request/response schemas, validation rules, and interactive testing, see the Swagger UI at http://localhost:8080/swagger when running the API.
-
GET /api/tipβ Search and filter tips- Query parameters:
q(search term),categoryId,tags[],orderBy,sortDirection,pageNumber,pageSize - Returns paginated tip summaries with metadata
- Query parameters:
-
GET /api/tip/{id}β Get full tip details- Returns complete tip with title, description, ordered steps, category, tags, and optional video URL
-
GET /api/categoryβ List all available categories- Returns all non-deleted categories
-
GET /api/category/{id}/tipsβ Get tips by category- Query parameters:
orderBy,sortDirection,pageNumber,pageSize - Returns paginated tips for the specified category
- Query parameters:
-
POST /api/userβ Create user profile after authentication- Called once after Firebase authentication to create the internal user record
- External auth ID derived from the JWT token
-
GET /api/user/meβ Get current user profile- User resolved from the JWT token
-
PUT /api/user/me/nameβ Update current user's display name- Self-service profile update
-
DELETE /api/user/meβ Delete current user's account- Soft delete with audit log
-
GET /api/me/favoritesβ List user's favorite tips- Query parameters:
q,categoryId,tags[],orderBy,sortDirection,pageNumber,pageSize - Returns paginated favorites with full tip details
- Query parameters:
-
POST /api/me/favorites/{tipId}β Add tip to favorites- Idempotent operation
-
DELETE /api/me/favorites/{tipId}β Remove tip from favorites -
POST /api/me/favorites/mergeβ Merge local favorites from client storage- Accepts array of tip IDs from local storage
- Returns summary with added, skipped, and failed counts
- Idempotent and supports partial success
-
POST /api/admin/tipsβ Create new tip- Required: title, description, steps (ordered list), categoryId
- Optional: tags (max 10), videoUrl (YouTube/Instagram)
-
PUT /api/admin/tips/{id}β Update existing tip- All fields updatable
-
DELETE /api/admin/tips/{id}β Soft delete tip- Marks the tip as deleted, preserves data
-
POST /api/admin/categories/imagesβ Upload category image- Accepts multipart/form-data with image file
- Validates file size (max 5MB), content type (JPEG, PNG, GIF, WebP), and magic bytes
- Uploads to AWS S3 with GUID-based unique filename
- Returns image metadata including CloudFront CDN URL
- Required before creating categories with images
-
POST /api/admin/categoriesβ Create new category- Required: name (2-100 characters, case-insensitive unique)
- Optional: image metadata from upload endpoint
-
PUT /api/admin/categories/{id}β Update category name- Uniqueness enforced
-
DELETE /api/admin/categories/{id}β Soft delete category- Cascade soft delete to all associated tips
-
POST /api/admin/userβ Create admin user- Creates user in Firebase and internal database
- Required: email, displayName, password
-
GET /api/admin/userβ List users with pagination- Query parameters:
search,orderBy,sortDirection,pageNumber,pageSize,isDeleted - Supports search by email, name, and ID
- Query parameters:
-
GET /api/admin/user/{id}β Get user by internal ID -
GET /api/admin/user/email/{email}β Get user by email address -
PUT /api/admin/user/{id}/nameβ Update user's display name -
DELETE /api/admin/user/{id}β Soft delete user account
GET /api/admin/dashboardβ Get dashboard statistics- Returns entity counts for users, categories, and tips
- Results cached for 1 hour for optimal performance
- Provides a quick overview for administrative monitoring
This API follows Clean Architecture principles with clear separation of concerns:
Responsibility: Contains the core business logic and domain rules.
Features:
- Business entities (User, Tip, Category, UserFavorites)
- Value objects (CategoryImage)
- Domain primitive types (Result<T, TE>)
- Domain constants (ImageConstants)
- No external dependencies (completely independent)
- Persistence and framework agnostic
Principle: The domain is the heart of the application and must not depend on anything external.
Responsibility: Orchestrates use cases and coordinates data flow.
Features:
- Use cases organized by feature (User, Category, Tip, Favorite, Dashboard)
- DTOs (Data Transfer Objects) for communication with the presentation layer
- Interfaces (ports) for external services (IUserRepository, ICategoryRepository, IImageStorageService)
- Validation and transformation logic
- Cache management with automatic invalidation
- Application exception handling
Principle: Defines what the system does without caring about how it does it.
Responsibility: Implements technical details and external services.
Features:
- Repository implementations (UserRepository, CategoryRepository)
- Data access with Firestore (documents, data stores)
- Cloud storage services (S3ImageStorageService)
- Firebase Authentication integration
- External service configuration (AWS, Firebase)
- Mapping between domain entities and persistence documents
Principle: Provides concrete implementations of the abstractions defined in Application.
Responsibility: Exposes functionality through HTTP REST endpoints.
Features:
- REST controllers organized by feature
- Authentication and authorization middleware
- Global filters (GlobalExceptionFilter)
- Service configuration and root composition (Program.cs)
- Swagger/OpenAPI documentation
- Mapping of Result<T, AppException> to HTTP status codes
Principle: Thin layer focused on HTTP concerns, delegating logic to Application.
Instead of throwing exceptions for normal control flow, the Result pattern is used:
Result<TipDetailResponse, AppException> result = await useCase.ExecuteAsync(request);
return result.Match(
success => Ok(success),
error => error.ToActionResult()
);Benefits:
- Explicit error handling
- Better performance (no stack unwinding)
- More predictable and testable code
All dependencies are injected through constructors:
public class CreateTipUseCase
{
private readonly ITipRepository _tipRepository;
private readonly ICategoryRepository _categoryRepository;
public CreateTipUseCase(
ITipRepository tipRepository,
ICategoryRepository categoryRepository)
{
_tipRepository = tipRepository;
_categoryRepository = categoryRepository;
}
}Benefits:
- Facilitates testing with mocks
- Low coupling
- Easy substitution of implementations
Abstracts data access behind interfaces:
public interface ITipRepository
{
Task<Tip?> GetByIdAsync(Guid id);
Task<PagedResult<Tip>> SearchAsync(TipQueryCriteria criteria);
Task<Tip> CreateAsync(Tip tip);
Task UpdateAsync(Tip tip);
Task DeleteAsync(Guid id);
}Benefits:
- Independence from persistence technology
- Facilitates database changes
- Improves testability
Key architectural decisions are documented in ADRs/:
- ADR-018 β Replacement of PostgreSQL with Firebase Firestore
- ADR-020 β User favorites domain model and storage
- ADR-006 β User roles and soft delete lifecycle
- ADR-010 β Hardened production configuration
- ADR-011 β Security headers and rate limiting
- ADR-013 β Standardized error handling and security logging
- ADR-015 β Sentry monitoring and observability integration
The system uses Firebase Authentication with JWT Bearer tokens:
- User authenticates with Firebase (your frontend handles this)
- Frontend receives Firebase ID token (JWT)
- Frontend calls the API with the token in the
Authorization: Bearer <token>header - API validates the token with Firebase and extracts the user's identity
- API maps the Firebase UID to the internal user record
- Access: No authentication required
- Permissions:
- Full read access to tips and categories
- Advanced search and filtering
- Client-side favorites management (local storage)
- Access: Requires valid JWT token
- Permissions:
- All anonymous user permissions
- Server-side persistent favorites
- Profile management (view, update name)
- Account deletion (self-service)
- Local favorites merge
- Access: Requires valid JWT token with Admin role
- Permissions:
- All authenticated user permissions
- Full tip management (create, update, delete)
- Category management (create, update, delete, upload images)
- User administration (create, list, update, delete)
- Dashboard access with statistics
After authenticating with Firebase, users must create their internal profile:
POST /api/user
Authorization: Bearer <firebase-id-token>
Content-Type: application/json
{
"email": "user@example.com",
"name": "John Doe"
}The ExternalAuthId is automatically extracted from the JWT token (sub claim).
Administrators can be created via:
- Seeding on startup β Set
AdminUser:SeedOnStartup=truewith credentials in environment variables - Admin API β Existing administrators can create new admins via
POST /api/admin/user
Example seeding configuration:
{
"AdminUser": {
"SeedOnStartup": true,
"Email": "admin@example.com",
"DisplayName": "Administrator",
"Password": "SecurePassword123!"
}
}The API automatically validates JWT tokens using the Firebase configuration:
{
"Authentication": {
"Authority": "https://securetoken.google.com/<your-project-id>",
"Audience": "<your-project-id>"
}
}Important JWT claims:
subβ Firebase UID (mapped to ExternalAuthId)emailβ User emailemail_verifiedβ Email verification statusroleβ Custom role (User or Admin)
This API is production-ready with exhaustive security measures:
- JWT Authentication β Firebase-based token validation with role-based authorization
- Role-Based Access Control (RBAC) β Clear separation between anonymous, authenticated, and admin users
- Token Validation β Automatic validation of JWT token signature, expiration, and audience
- Secure Claims Mapping β Secure mapping of JWT claims to internal user identity
Two rate limiting policies to protect against abuse:
- Limit: 100 requests per minute
- Applied to: Standard read and write endpoints
- Window: 1-minute sliding window
- Limit: 10 requests per minute
- Applied to: Sensitive operations (create, update, delete)
- Window: 1-minute sliding window
Response when limit is exceeded:
{
"status": 429,
"type": "https://httpstatuses.io/429",
"title": "Too Many Requests",
"detail": "Rate limit exceeded. Please try again later.",
"instance": "/api/admin/tips",
"correlationId": "abc123"
}The API automatically configures HTTP security headers:
- Content-Security-Policy (CSP) β Prevents XSS attacks
- Strict-Transport-Security (HSTS) β Forces HTTPS connections
- X-Frame-Options β Prevents clickjacking
- X-Content-Type-Options β Prevents MIME sniffing
- Referrer-Policy β Controls referrer information
- Permissions-Policy β Controls browser features
Flexible CORS configuration for frontend integration:
{
"ClientApp": {
"Origin": "https://your-app.com"
}
}Features:
- Configurable origins per environment
- Multiple origins supported in production
- Specific allowed headers
- Controlled allowed HTTP methods
Exhaustive validation at multiple levels:
- Data annotations on DTOs
- Automatic validation in the ASP.NET Core pipeline
- Descriptive error messages
- Business rules in entities
- Value objects with built-in validation
- Domain invariant validation
- Magic Byte Validation β Prevents content type spoofing
- Filename Sanitization β Prevents path traversal vulnerabilities
- Size Validation β Limits defined in constants (max 5MB for images)
- MIME Type Validation β Only allowed types (JPEG, PNG, GIF, WebP)
Data preservation with audit log:
- Users β Marked as deleted, data preserved
- Tips β Marked as deleted, relationships preserved
- Categories β Cascade soft delete to related tips
- Audit β Deletion timestamps for traceability
Complete logging system with Sentry integration:
- Correlation IDs β Request traceability across all logs
- Structured Logging β Structured logs with rich context
- Security Events β Logging of security events (authentication, authorization)
- Error Tracking β Automatic capture of unhandled exceptions
- Performance Monitoring β Performance traces with configurable sample rate
Consistent error responses following RFC 7807:
{
"status": 400,
"type": "https://httpstatuses.io/400/validation-error",
"title": "Validation error",
"detail": "One or more validation errors occurred.",
"instance": "/api/admin/tips",
"correlationId": "abc123",
"errors": {
"Title": ["The tip title must be at least 5 characters long"]
}
}Benefits:
- Industry-standard format
- Detailed error information without exposing implementation details
- Correlation IDs for support and debugging
- Consistent responses across the entire API
- SQL Injection β Not applicable (NoSQL with Firestore)
- XSS (Cross-Site Scripting) β CSP headers and input sanitization
- CSRF (Cross-Site Request Forgery) β Stateless JWT tokens
- Path Traversal β Filename sanitization
- Content Type Spoofing β Magic byte validation
- Denial of Service β Rate limiting and configurable timeouts
- Information Disclosure β Generic error messages in production
The project includes exhaustive test coverage across all layers:
- Application.Tests β Use case and domain logic tests
- Infrastructure.Tests β Repository and data access tests with Firestore emulator
- WebAPI.Tests β Integration tests for controllers and middleware
# Run all tests
dotnet test lifehacking.slnx
# Run tests for a specific project
dotnet test lifehacking/Tests/Application.Tests/Application.Tests.csproj
dotnet test lifehacking/Tests/Infrastructure.Tests/Infrastructure.Tests.csproj
dotnet test lifehacking/Tests/WebAPI.Tests/WebAPI.Tests.csproj
# Run a specific test
dotnet test --filter "Name=CreateTip_ShouldReturnValidationError_WhenTitleIsTooShort"
# Run all tests in a class
dotnet test --filter "FullyQualifiedName~CreateTipUseCaseTests"The project uses Microsoft Testing Platform as the modern test runner:
<PropertyGroup>
<DotNetTestRunner>Microsoft.Testing.Platform</DotNetTestRunner>
</PropertyGroup>Benefits:
- Improved performance
- Better integration with development tools
- Support for property-based testing
All tests use xUnit as the testing framework:
[Fact]
public async Task CreateTip_ShouldReturnSuccess_WhenDataIsValid()
{
// Arrange
var request = new CreateTipRequest { /* ... */ };
// Act
var result = await _useCase.ExecuteAsync(request);
// Assert
result.IsSuccess.Should().BeTrue();
}Expressive and readable assertion syntax:
// Instead of
Assert.Equal(expected, actual);
Assert.True(condition);
// We use
actual.Should().Be(expected);
condition.Should().BeTrue();
result.Should().NotBeNull();
list.Should().HaveCount(5);Benefits:
- More descriptive error messages
- More natural and readable syntax
- Better developer experience
Infrastructure tests use the local Firestore emulator:
# Start emulator
firebase emulators:start --only firestore
# Tests connect automatically to the emulator
export FIRESTORE_EMULATOR_HOST=localhost:8080Benefits:
- Realistic integration tests
- No Firebase costs
- Isolated data per test run
- Fast execution speed
All tests follow the pattern:
{MethodName}_Should{DoSomething}_When{Condition}
Examples:
CreateTip_ShouldReturnSuccess_WhenDataIsValid()
CreateTip_ShouldReturnValidationError_WhenTitleIsTooShort()
GetUserById_ShouldReturnNotFound_WhenUserDoesNotExist()
AddFavorite_ShouldBeIdempotent_WhenCalledMultipleTimes()Benefits:
- Self-descriptive names
- Easy identification of scenarios
- Living documentation of behavior
Tests are organized by feature and layer:
Tests/
βββ Application.Tests/
β βββ UseCases/
β β βββ User/
β β β βββ CreateUserUseCaseTests.cs
β β β βββ DeleteUserUseCaseTests.cs
β β β βββ UpdateUserNameUseCaseTests.cs
β β βββ Category/
β β β βββ CreateCategoryUseCaseTests.cs
β β β βββ DeleteCategoryUseCaseTests.cs
β β βββ Tip/
β β β βββ CreateTipUseCaseTests.cs
β β β βββ SearchTipsUseCaseTests.cs
β β βββ Favorite/
β β β βββ AddFavoriteUseCaseTests.cs
β β β βββ MergeFavoritesUseCaseTests.cs
β β βββ Dashboard/
β β βββ GetDashboardUseCaseTests.cs
β βββ MicrosoftTestingPlatformSmokeTests.cs
β
βββ Infrastructure.Tests/
β βββ Repositories/
β β βββ UserRepositoryTests.cs
β β βββ CategoryRepositoryTests.cs
β β βββ TipRepositoryTests.cs
β βββ Storage/
β βββ S3ImageStorageServiceTests.cs
β
βββ WebAPI.Tests/
βββ Controllers/
β βββ UserControllerTests.cs
β βββ AdminCategoryControllerTests.cs
β βββ AdminDashboardControllerTests.cs
βββ Filters/
βββ GlobalExceptionFilterTests.cs
- Test isolated use cases
- Use mocks for dependencies (repositories, services)
- Verify business logic and validation
- Test cache behavior
[Fact]
public async Task GetDashboard_ShouldReturnCachedData_WhenCacheHit()
{
// Arrange
var cachedData = new DashboardResponse { /* ... */ };
_cache.Set(CacheKeys.Dashboard, cachedData);
// Act
var result = await _useCase.ExecuteAsync(new GetDashboardRequest());
// Assert
result.IsSuccess.Should().BeTrue();
_mockRepository.Verify(r => r.GetStatistics(), Times.Never);
}- Test repositories with Firestore emulator
- Verify mapping between entities and documents
- Test complex queries and filters
- Validate persistence behavior
[Fact]
public async Task CreateUser_ShouldPersistToFirestore_WhenDataIsValid()
{
// Arrange
var user = User.Create(/* ... */);
// Act
await _repository.CreateAsync(user);
// Assert
var retrieved = await _repository.GetByIdAsync(user.Id);
retrieved.Should().NotBeNull();
retrieved.Email.Should().Be(user.Email);
}- Test complete HTTP endpoints
- Verify status codes and responses
- Validate authentication and authorization
- Test error handling
[Fact]
public async Task CreateTip_ShouldReturn401_WhenNotAuthenticated()
{
// Arrange
var request = new CreateTipRequest { /* ... */ };
// Act
var response = await _client.PostAsJsonAsync("/api/admin/tips", request);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
}The project maintains high test coverage:
- Use cases: >90% coverage
- Repositories: >85% coverage
- Controllers: >80% coverage
- Domain logic: 100% coverage
To generate a coverage report:
dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=opencoverThe project includes exhaustive documentation to facilitate development:
- AGENTS.md β Complete guide for AI agents working with this code
- ADRs/ β Architecture Decision Records documenting key technical decisions
- docs/MVP.md β Product requirements and MVP scope
- docs/AWS-S3-Setup-Guide.md β Detailed AWS S3 configuration guide
- Maintain strict dependencies between layers
- Domain must not depend on anything
- Application only depends on Domain
- Infrastructure implements Application interfaces
- WebAPI is the composition layer
- Use rich entities with behavior
- Encapsulate business logic in the domain
- Use value objects for concepts without identity
- Keep aggregates consistent
// β Wrong
if (file.Length > 5242880) { /* ... */ }
// β
Correct
if (file.Length > ImageConstants.MaxFileSizeInBytes) { /* ... */ }Rules:
- Define constants with meaningful names
- Centralize reusable values
- Use enums for related value sets
- Self-descriptive names that express intent
public class CreateTipRequest
{
[Required]
[StringLength(200, MinimumLength = 5)]
public string Title { get; set; }
[Required]
[StringLength(2000, MinimumLength = 10)]
public string Description { get; set; }
[Required]
[MinLength(1)]
public List<TipStepRequest> Steps { get; set; }
}// Instead of throwing exceptions
public async Task<Result<TipDetailResponse, AppException>> ExecuteAsync(
CreateTipRequest request)
{
if (!await _categoryRepository.ExistsAsync(request.CategoryId))
{
return new NotFoundException("Category not found");
}
var tip = Tip.Create(/* ... */);
await _repository.CreateAsync(tip);
return tip.ToDetailResponse();
}public class User
{
public Guid Id { get; private set; }
public string Email { get; private set; }
public DateTime CreatedAt { get; private set; }
public DateTime? DeletedAt { get; private set; }
public void Delete()
{
DeletedAt = DateTime.UtcNow;
}
public bool IsDeleted => DeletedAt.HasValue;
}Follow Conventional Commits:
<type>: <description>
<optional body>
<optional footer>
Allowed types:
featβ New featurefixβ Bug fixchoreβ Maintenance tasksrefactorβ Code refactoringdocsβ Documentation changestestβ Adding or modifying tests
Examples:
feat: add user favorites merge endpoint
Implements automatic merge of local favorites when user logs in
for the first time. Handles deduplication and partial failures.
refs: WT-1234
fix: correct cache invalidation on category delete
Categories were not being removed from cache when deleted,
causing stale data to be served.
refs: WT-5678
- Feature branches:
issue-<ticket-id>-<short-description> - No direct commits to the main branch
- Pull requests required for all changes
- Code review before merging
Example:
# Create branch from issue
git checkout -b issue-123-add-favorites-merge
# Make commits
git commit -m "feat: add merge favorites use case"
git commit -m "test: add merge favorites tests"
# Push and create PR
git push origin issue-123-add-favorites-mergePlanned features for future versions:
- Full-text search with Algolia or Elasticsearch
- Comments and tip rating system
- Push notifications for new tips
- Multi-language support for tips
- AI-based personalized recommendations
- Social media sharing integration
- Advanced statistics for administrators
- Native mobile application (iOS and Android)
- Offline mode with synchronization
- Gamification (badges, achievements, levels)
- User community with public profiles