Files
ColaFlow/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Infrastructure/Migrations/20251104153716_AddTenantIdToEpicStoryTask.Designer.cs
Yaojia Wang 12a4248430 feat(backend): Day 15 Task 1&2 - Add TenantId to Epic/Story/WorkTask and implement TenantContext
This commit completes Day 15's primary objectives:
1. Database Migration - Add TenantId columns to Epic, Story, and WorkTask entities
2. TenantContext Service - Implement tenant context retrieval from JWT claims

Changes:
- Added TenantId property to Epic, Story, and WorkTask domain entities
- Updated entity factory methods to require TenantId parameter
- Modified Project.CreateEpic to pass TenantId from parent aggregate
- Modified Epic.CreateStory and Story.CreateTask to propagate TenantId
- Added EF Core configurations for TenantId mapping with proper indexes
- Created EF Core migration: AddTenantIdToEpicStoryTask
  * Adds tenant_id columns to Epics, Stories, and Tasks tables
  * Creates indexes: ix_epics_tenant_id, ix_stories_tenant_id, ix_tasks_tenant_id
  * Uses default Guid.Empty for existing data (backward compatible)
- Implemented ITenantContext interface in Application layer
- Implemented TenantContext service in Infrastructure layer
  * Retrieves tenant ID from JWT claims (tenant_id or tenantId)
  * Throws UnauthorizedAccessException if tenant context unavailable
- Registered TenantContext as scoped service in DI container
- Added Global Query Filters for Epic, Story, and WorkTask entities
  * Ensures automatic tenant isolation at database query level
  * Prevents cross-tenant data access

Architecture:
- Follows the same pattern as Issue Management Module (Day 14)
- Maintains consistency with Project entity multi-tenancy implementation
- Ensures data isolation through both domain logic and database filters

Note: Unit tests require updates to pass TenantId parameter - will be addressed in follow-up commits

Reference: Day 15 roadmap (DAY15-22-PROJECTMANAGEMENT-ROADMAP.md)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-04 16:44:09 +01:00

326 lines
12 KiB
C#

// <auto-generated />
using System;
using ColaFlow.Modules.ProjectManagement.Infrastructure.Persistence;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace ColaFlow.Modules.ProjectManagement.Infrastructure.Migrations
{
[DbContext(typeof(PMDbContext))]
[Migration("20251104153716_AddTenantIdToEpicStoryTask")]
partial class AddTenantIdToEpicStoryTask
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("project_management")
.HasAnnotation("ProductVersion", "9.0.10")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate.Epic", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("CreatedBy")
.HasColumnType("uuid");
b.Property<string>("Description")
.IsRequired()
.HasMaxLength(2000)
.HasColumnType("character varying(2000)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)");
b.Property<string>("Priority")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<Guid>("ProjectId")
.HasColumnType("uuid");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<Guid>("TenantId")
.HasColumnType("uuid")
.HasColumnName("tenant_id");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("CreatedAt");
b.HasIndex("ProjectId");
b.HasIndex("TenantId")
.HasDatabaseName("ix_epics_tenant_id");
b.ToTable("Epics", "project_management");
});
modelBuilder.Entity("ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate.Project", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Description")
.IsRequired()
.HasMaxLength(2000)
.HasColumnType("character varying(2000)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)");
b.Property<Guid>("OwnerId")
.HasColumnType("uuid");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("CreatedAt");
b.HasIndex("OwnerId");
b.HasIndex("TenantId");
b.ToTable("Projects", "project_management");
});
modelBuilder.Entity("ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate.Story", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid");
b.Property<decimal?>("ActualHours")
.HasColumnType("numeric");
b.Property<Guid?>("AssigneeId")
.HasColumnType("uuid");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("CreatedBy")
.HasColumnType("uuid");
b.Property<string>("Description")
.IsRequired()
.HasMaxLength(4000)
.HasColumnType("character varying(4000)");
b.Property<Guid>("EpicId")
.HasColumnType("uuid");
b.Property<decimal?>("EstimatedHours")
.HasColumnType("numeric");
b.Property<string>("Priority")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<Guid>("TenantId")
.HasColumnType("uuid")
.HasColumnName("tenant_id");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("AssigneeId");
b.HasIndex("CreatedAt");
b.HasIndex("EpicId");
b.HasIndex("TenantId")
.HasDatabaseName("ix_stories_tenant_id");
b.ToTable("Stories", "project_management");
});
modelBuilder.Entity("ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate.WorkTask", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid");
b.Property<decimal?>("ActualHours")
.HasColumnType("numeric");
b.Property<Guid?>("AssigneeId")
.HasColumnType("uuid");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("CreatedBy")
.HasColumnType("uuid");
b.Property<string>("Description")
.IsRequired()
.HasMaxLength(4000)
.HasColumnType("character varying(4000)");
b.Property<decimal?>("EstimatedHours")
.HasColumnType("numeric");
b.Property<string>("Priority")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<Guid>("StoryId")
.HasColumnType("uuid");
b.Property<Guid>("TenantId")
.HasColumnType("uuid")
.HasColumnName("tenant_id");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("AssigneeId");
b.HasIndex("CreatedAt");
b.HasIndex("StoryId");
b.HasIndex("TenantId")
.HasDatabaseName("ix_tasks_tenant_id");
b.ToTable("Tasks", "project_management");
});
modelBuilder.Entity("ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate.Epic", b =>
{
b.HasOne("ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate.Project", null)
.WithMany("Epics")
.HasForeignKey("ProjectId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate.Project", b =>
{
b.OwnsOne("ColaFlow.Modules.ProjectManagement.Domain.ValueObjects.ProjectKey", "Key", b1 =>
{
b1.Property<Guid>("ProjectId")
.HasColumnType("uuid");
b1.Property<string>("Value")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)")
.HasColumnName("Key");
b1.HasKey("ProjectId");
b1.HasIndex("Value")
.IsUnique();
b1.ToTable("Projects", "project_management");
b1.WithOwner()
.HasForeignKey("ProjectId");
});
b.Navigation("Key")
.IsRequired();
});
modelBuilder.Entity("ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate.Story", b =>
{
b.HasOne("ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate.Epic", null)
.WithMany("Stories")
.HasForeignKey("EpicId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate.WorkTask", b =>
{
b.HasOne("ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate.Story", null)
.WithMany("Tasks")
.HasForeignKey("StoryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate.Epic", b =>
{
b.Navigation("Stories");
});
modelBuilder.Entity("ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate.Project", b =>
{
b.Navigation("Epics");
});
modelBuilder.Entity("ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate.Story", b =>
{
b.Navigation("Tasks");
});
#pragma warning restore 612, 618
}
}
}