We are migrating the FleetKeep backend from:
| Category | Count |
|---|---|
| API Endpoints | ~200 |
| Database Tables | 60+ |
| Background Jobs | 11 cron tasks + 2 queue processors |
| Email Templates | 23 types |
| External Integrations | 3 (Stripe, AWS, LeeTrans) |
| Purpose | Technology | Why |
|---|---|---|
| Framework | .NET 8 | Latest LTS, best performance |
| API | ASP.NET Core Web API | Industry standard |
| ORM | Entity Framework Core 8 | Best .NET ORM, great tooling |
| Database | PostgreSQL 15+ | Robust, supports LTREE for hierarchies |
<!-- Core -->
<PackageReference Include="MediatR" Version="12.*" />
<PackageReference Include="FluentValidation.AspNetCore" Version="11.*" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.*" />
<!-- Database -->
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.*" />
<PackageReference Include="EFCore.NamingConventions" Version="8.*" />
<!-- Authentication -->
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.*" />
<PackageReference Include="BCrypt.Net-Next" Version="4.*" />
<!-- Background Jobs -->
<PackageReference Include="Hangfire.AspNetCore" Version="1.*" />
<PackageReference Include="Hangfire.PostgreSql" Version="1.*" />
<!-- AWS & Stripe -->
<PackageReference Include="AWSSDK.S3" Version="3.*" />
<PackageReference Include="AWSSDK.SimpleEmail" Version="3.*" />
<PackageReference Include="Stripe.net" Version="43.*" />
<!-- Utilities -->
<PackageReference Include="StackExchange.Redis" Version="2.*" />
<PackageReference Include="Serilog.AspNetCore" Version="8.*" />
<PackageReference Include="Sentry.AspNetCore" Version="4.*" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.*" />
<PackageReference Include="ClosedXML" Version="0.102.*" />
<PackageReference Include="CsvHelper" Version="31.*" />
<!-- Testing -->
<PackageReference Include="xunit" Version="2.*" />
<PackageReference Include="Moq" Version="4.*" />
<PackageReference Include="FluentAssertions" Version="6.*" />
<PackageReference Include="Testcontainers.PostgreSql" Version="3.*" />
FleetKeep/
├── src/
│ ├── FleetKeep.Api/ # Controllers, Middleware
│ ├── FleetKeep.Application/ # Business Logic, CQRS
│ ├── FleetKeep.Domain/ # Entities, Enums
│ ├── FleetKeep.Infrastructure/ # DB, External Services
│ └── FleetKeep.Shared/ # Utilities
├── tests/
│ ├── FleetKeep.UnitTests/
│ └── FleetKeep.IntegrationTests/
└── docker/
Pure C# classes representing the business domain. No dependencies.
Business logic, CQRS Handlers, DTOs. Depends on Domain.
Database, File Storage, Email, Payments. Depends on Application & Domain.
Controllers, Middleware. Thin layer. Depends on Application & Infrastructure.
dotnet new sln -n FleetKeep
# (Create projects commands omitted for brevity, standard .NET CLI)
{
"ConnectionStrings": {
"DefaultConnection": "Host=localhost;Database=fleetkeep;Username=postgres;Password=pw",
"Redis": "localhost:6379"
},
"JwtSettings": { "Secret": "super-secret-key-min-32-chars", "Issuer": "FleetKeep" }
}
BaseEntity.cs
public abstract class BaseEntity {
public Guid Id { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
}
Implement entities like Company, User, Asset, Inspection, etc.
public class Asset : BaseEntity, ITenantEntity, IAuditable, ISoftDeletable {
public Guid CompanyId { get; set; }
public string Name { get; set; }
public AssetStatus Status { get; set; }
// ... Navigation properties
}
public class ApplicationDbContext : DbContext {
// DB Sets...
public DbSet<Asset> Assets => Set<Asset>();
protected override void OnModelCreating(ModelBuilder builder) {
// Global Query Filters for Multi-tenancy & Soft Delete
builder.Entity<Asset>().HasQueryFilter(e =>
e.CompanyId == _tenantService.CompanyId && !e.IsDeleted);
}
}
Implements ICurrentUserService to retrieve claims (UserId, CompanyId) from HttpContext.
This replicates the complex NestJS constructPermissionFilter() logic.
public async Task<PermissionFilterDto> ConstructPermissionFilterAsync(Guid userId, Guid companyId) {
// Logic to build allowed/excluded asset types and IDs
}
public static IQueryable<Asset> ApplyAuthorizationFilters(this IQueryable<Asset> query, ICurrentUserService user) {
return query
.ApplyLocationFilter(user)
.ApplyPermissionFilter(user.PermissionFilter);
}
Each feature (e.g., Assets) has Commands (Create, Update) and Queries (Get, List).
GetAssetsQueryHandler.cs
public async Task<PaginatedList<AssetDto>> Handle(GetAssetsQuery request, CancellationToken ct) {
var query = _context.Assets
.ApplyAuthorizationFilters(_currentUser) // Critical!
.AsQueryable();
// Apply search/sort...
return await query.ToPaginatedListAsync(...);
}
Replace NestJS Bull queues with Hangfire.
RecurringJob.AddOrUpdate<ScheduledJobsService>(
"send-overdue-notifications",
job => job.SendOverdueNotificationsAsync(),
Cron.Hourly);
X-API-Key for external integrations.| Category | What to Test | Tools |
|---|---|---|
| Unit Tests | Handlers, Validators | xUnit, Moq |
| Integration Tests | API Endpoints, DB | Testcontainers |
Use Docker Compose for the stack. Create a script to migrate data from MongoDB JSON exports to PostgreSQL.
# Migrations
dotnet ef migrations add InitialCreate -s src/FleetKeep.Api
dotnet ef database update -s src/FleetKeep.Api
# Run
dotnet run --project src/FleetKeep.Api
This migration plan provides a complete roadmap for transitioning FleetKeep from NestJS/MongoDB to .NET Core/PostgreSQL. The key points are:
Follow the phases in order, and ensure each phase’s deliverables are complete before moving to the next. This will ensure a smooth migration with minimal risk.