Initial commit: TrueCV CV verification platform

Clean architecture solution with:
- Domain: Entities (User, CVCheck, CVFlag, CompanyCache) and Enums
- Application: Service interfaces, DTOs, and models
- Infrastructure: EF Core, Identity, Hangfire, external API clients, services
- Web: Blazor Server UI with pages and components

Features:
- CV upload and parsing (PDF/DOCX) using Claude API
- Employment verification against Companies House API
- Timeline analysis for gaps and overlaps
- Veracity scoring algorithm
- Background job processing with Hangfire
- Azure Blob Storage for file storage

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-18 19:20:50 +01:00
commit 6d514e01b2
70 changed files with 5996 additions and 0 deletions

View File

@@ -0,0 +1,114 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using TrueCV.Domain.Entities;
using TrueCV.Domain.Enums;
using TrueCV.Infrastructure.Identity;
namespace TrueCV.Infrastructure.Data;
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, IdentityRole<Guid>, Guid>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<CVCheck> CVChecks => Set<CVCheck>();
public DbSet<CVFlag> CVFlags => Set<CVFlag>();
public DbSet<CompanyCache> CompanyCache => Set<CompanyCache>();
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
ConfigureApplicationUser(builder);
ConfigureCVCheck(builder);
ConfigureCVFlag(builder);
ConfigureCompanyCache(builder);
}
private static void ConfigureApplicationUser(ModelBuilder builder)
{
builder.Entity<ApplicationUser>(entity =>
{
entity.Property(u => u.Plan)
.HasConversion<string>()
.HasMaxLength(32);
entity.Property(u => u.StripeCustomerId)
.HasMaxLength(256);
entity.HasMany(u => u.CVChecks)
.WithOne()
.HasForeignKey(c => c.UserId)
.OnDelete(DeleteBehavior.Cascade);
});
}
private static void ConfigureCVCheck(ModelBuilder builder)
{
builder.Entity<CVCheck>(entity =>
{
entity.Property(c => c.Status)
.HasConversion<string>()
.HasMaxLength(32);
entity.HasIndex(c => c.UserId)
.HasDatabaseName("IX_CVChecks_UserId");
entity.HasIndex(c => c.Status)
.HasDatabaseName("IX_CVChecks_Status");
entity.HasMany(c => c.Flags)
.WithOne(f => f.CVCheck)
.HasForeignKey(f => f.CVCheckId)
.OnDelete(DeleteBehavior.Cascade);
// Ignore the User navigation property since we're using ApplicationUser
entity.Ignore(c => c.User);
});
}
private static void ConfigureCVFlag(ModelBuilder builder)
{
builder.Entity<CVFlag>(entity =>
{
entity.Property(f => f.Category)
.HasConversion<string>()
.HasMaxLength(32);
entity.Property(f => f.Severity)
.HasConversion<string>()
.HasMaxLength(32);
entity.HasIndex(f => f.CVCheckId)
.HasDatabaseName("IX_CVFlags_CVCheckId");
});
}
private static void ConfigureCompanyCache(ModelBuilder builder)
{
builder.Entity<CompanyCache>(entity =>
{
entity.HasKey(c => c.CompanyNumber);
entity.Property(c => c.CompanyNumber)
.HasMaxLength(32);
});
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
var newCVChecks = ChangeTracker.Entries<CVCheck>()
.Where(e => e.State == EntityState.Added)
.Select(e => e.Entity);
foreach (var cvCheck in newCVChecks)
{
cvCheck.CreatedAt = DateTime.UtcNow;
}
return await base.SaveChangesAsync(cancellationToken);
}
}