Files
Yaojia Wang ee73d56759 feat(backend): Implement Sprint Repository and EF Core Configuration (Task 2)
Implemented complete Sprint data access layer:
- Extended IProjectRepository with Sprint operations
- Created SprintConfiguration for EF Core mapping
- Added Sprint DbSet and multi-tenant query filter to PMDbContext
- Implemented 4 Sprint repository methods (Get, GetByProject, GetActive, GetProjectWithSprint)
- Created EF Core migration for Sprints table with JSONB TaskIds column
- Multi-tenant isolation enforced via Global Query Filter

Database schema:
- Sprints table with indexes on (TenantId, ProjectId), (TenantId, Status), StartDate, EndDate
- TaskIds stored as JSONB array for performance

Story 3 Task 2/6 completed.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-05 00:10:57 +01:00

75 lines
2.6 KiB
C#

using System.Reflection;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate;
using ColaFlow.Modules.ProjectManagement.Domain.Entities;
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
namespace ColaFlow.Modules.ProjectManagement.Infrastructure.Persistence;
/// <summary>
/// Project Management Module DbContext
/// </summary>
public class PMDbContext : DbContext
{
private readonly IHttpContextAccessor _httpContextAccessor;
public PMDbContext(DbContextOptions<PMDbContext> options, IHttpContextAccessor httpContextAccessor)
: base(options)
{
_httpContextAccessor = httpContextAccessor;
}
public DbSet<Project> Projects => Set<Project>();
public DbSet<Epic> Epics => Set<Epic>();
public DbSet<Story> Stories => Set<Story>();
public DbSet<WorkTask> Tasks => Set<WorkTask>();
public DbSet<Sprint> Sprints => Set<Sprint>();
public DbSet<AuditLog> AuditLogs => Set<AuditLog>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Set default schema for this module (must be before configurations)
modelBuilder.HasDefaultSchema("project_management");
// Apply all entity configurations from this assembly
modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
// Multi-tenant Global Query Filters
modelBuilder.Entity<Project>().HasQueryFilter(p =>
p.TenantId == GetCurrentTenantId());
modelBuilder.Entity<Epic>().HasQueryFilter(e =>
e.TenantId == GetCurrentTenantId());
modelBuilder.Entity<Story>().HasQueryFilter(s =>
s.TenantId == GetCurrentTenantId());
modelBuilder.Entity<WorkTask>().HasQueryFilter(t =>
t.TenantId == GetCurrentTenantId());
modelBuilder.Entity<Sprint>().HasQueryFilter(s =>
s.TenantId == GetCurrentTenantId());
modelBuilder.Entity<AuditLog>().HasQueryFilter(a =>
a.TenantId == GetCurrentTenantId());
}
private TenantId GetCurrentTenantId()
{
var tenantIdClaim = _httpContextAccessor?.HttpContext?.User
.FindFirst("tenant_id")?.Value;
if (Guid.TryParse(tenantIdClaim, out var tenantId) && tenantId != Guid.Empty)
{
return TenantId.From(tenantId);
}
// Return a dummy value for queries outside HTTP context (e.g., migrations)
// These will return no results due to the filter
return TenantId.From(Guid.Empty);
}
}