Project Init

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Yaojia Wang
2025-11-02 23:55:18 +01:00
commit 014d62bcc2
169 changed files with 28867 additions and 0 deletions

View File

@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyName>ColaFlow.Modules.ProjectManagement.Infrastructure</AssemblyName>
<RootNamespace>ColaFlow.Modules.ProjectManagement.Infrastructure</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ColaFlow.Modules.ProjectManagement.Domain\ColaFlow.Modules.ProjectManagement.Domain.csproj" />
<ProjectReference Include="..\ColaFlow.Modules.ProjectManagement.Application\ColaFlow.Modules.ProjectManagement.Application.csproj" />
<ProjectReference Include="..\..\..\Shared\ColaFlow.Shared.Kernel\ColaFlow.Shared.Kernel.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,298 @@
// <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("20251102220422_InitialCreate")]
partial class InitialCreate
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("project_management")
.HasAnnotation("ProductVersion", "9.0.0")
.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<Guid?>("ProjectId1")
.HasColumnType("uuid");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("CreatedAt");
b.HasIndex("ProjectId");
b.HasIndex("ProjectId1");
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<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("CreatedAt");
b.HasIndex("OwnerId");
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<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.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<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.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()
.HasForeignKey("ProjectId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate.Project", null)
.WithMany("Epics")
.HasForeignKey("ProjectId1");
});
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()
.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()
.HasForeignKey("StoryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate.Project", b =>
{
b.Navigation("Epics");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,224 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ColaFlow.Modules.ProjectManagement.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class InitialCreate : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.EnsureSchema(
name: "project_management");
migrationBuilder.CreateTable(
name: "Projects",
schema: "project_management",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
Name = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
Description = table.Column<string>(type: "character varying(2000)", maxLength: 2000, nullable: false),
Key = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
Status = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
OwnerId = table.Column<Guid>(type: "uuid", nullable: false),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Projects", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Epics",
schema: "project_management",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
Name = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
Description = table.Column<string>(type: "character varying(2000)", maxLength: 2000, nullable: false),
ProjectId = table.Column<Guid>(type: "uuid", nullable: false),
Status = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
Priority = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
CreatedBy = table.Column<Guid>(type: "uuid", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
ProjectId1 = table.Column<Guid>(type: "uuid", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Epics", x => x.Id);
table.ForeignKey(
name: "FK_Epics_Projects_ProjectId",
column: x => x.ProjectId,
principalSchema: "project_management",
principalTable: "Projects",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_Epics_Projects_ProjectId1",
column: x => x.ProjectId1,
principalSchema: "project_management",
principalTable: "Projects",
principalColumn: "Id");
});
migrationBuilder.CreateTable(
name: "Stories",
schema: "project_management",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
Title = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
Description = table.Column<string>(type: "character varying(4000)", maxLength: 4000, nullable: false),
EpicId = table.Column<Guid>(type: "uuid", nullable: false),
Status = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
Priority = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
EstimatedHours = table.Column<decimal>(type: "numeric", nullable: true),
ActualHours = table.Column<decimal>(type: "numeric", nullable: true),
AssigneeId = table.Column<Guid>(type: "uuid", nullable: true),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
CreatedBy = table.Column<Guid>(type: "uuid", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Stories", x => x.Id);
table.ForeignKey(
name: "FK_Stories_Epics_EpicId",
column: x => x.EpicId,
principalSchema: "project_management",
principalTable: "Epics",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Tasks",
schema: "project_management",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
Title = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
Description = table.Column<string>(type: "character varying(4000)", maxLength: 4000, nullable: false),
StoryId = table.Column<Guid>(type: "uuid", nullable: false),
Status = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
Priority = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
EstimatedHours = table.Column<decimal>(type: "numeric", nullable: true),
ActualHours = table.Column<decimal>(type: "numeric", nullable: true),
AssigneeId = table.Column<Guid>(type: "uuid", nullable: true),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
CreatedBy = table.Column<Guid>(type: "uuid", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Tasks", x => x.Id);
table.ForeignKey(
name: "FK_Tasks_Stories_StoryId",
column: x => x.StoryId,
principalSchema: "project_management",
principalTable: "Stories",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Epics_CreatedAt",
schema: "project_management",
table: "Epics",
column: "CreatedAt");
migrationBuilder.CreateIndex(
name: "IX_Epics_ProjectId",
schema: "project_management",
table: "Epics",
column: "ProjectId");
migrationBuilder.CreateIndex(
name: "IX_Epics_ProjectId1",
schema: "project_management",
table: "Epics",
column: "ProjectId1");
migrationBuilder.CreateIndex(
name: "IX_Projects_CreatedAt",
schema: "project_management",
table: "Projects",
column: "CreatedAt");
migrationBuilder.CreateIndex(
name: "IX_Projects_Key",
schema: "project_management",
table: "Projects",
column: "Key",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Projects_OwnerId",
schema: "project_management",
table: "Projects",
column: "OwnerId");
migrationBuilder.CreateIndex(
name: "IX_Stories_AssigneeId",
schema: "project_management",
table: "Stories",
column: "AssigneeId");
migrationBuilder.CreateIndex(
name: "IX_Stories_CreatedAt",
schema: "project_management",
table: "Stories",
column: "CreatedAt");
migrationBuilder.CreateIndex(
name: "IX_Stories_EpicId",
schema: "project_management",
table: "Stories",
column: "EpicId");
migrationBuilder.CreateIndex(
name: "IX_Tasks_AssigneeId",
schema: "project_management",
table: "Tasks",
column: "AssigneeId");
migrationBuilder.CreateIndex(
name: "IX_Tasks_CreatedAt",
schema: "project_management",
table: "Tasks",
column: "CreatedAt");
migrationBuilder.CreateIndex(
name: "IX_Tasks_StoryId",
schema: "project_management",
table: "Tasks",
column: "StoryId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Tasks",
schema: "project_management");
migrationBuilder.DropTable(
name: "Stories",
schema: "project_management");
migrationBuilder.DropTable(
name: "Epics",
schema: "project_management");
migrationBuilder.DropTable(
name: "Projects",
schema: "project_management");
}
}
}

View File

@@ -0,0 +1,295 @@
// <auto-generated />
using System;
using ColaFlow.Modules.ProjectManagement.Infrastructure.Persistence;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace ColaFlow.Modules.ProjectManagement.Infrastructure.Migrations
{
[DbContext(typeof(PMDbContext))]
partial class PMDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("project_management")
.HasAnnotation("ProductVersion", "9.0.0")
.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<Guid?>("ProjectId1")
.HasColumnType("uuid");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("CreatedAt");
b.HasIndex("ProjectId");
b.HasIndex("ProjectId1");
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<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("CreatedAt");
b.HasIndex("OwnerId");
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<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.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<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.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()
.HasForeignKey("ProjectId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate.Project", null)
.WithMany("Epics")
.HasForeignKey("ProjectId1");
});
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()
.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()
.HasForeignKey("StoryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate.Project", b =>
{
b.Navigation("Epics");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,86 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate;
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
using ColaFlow.Shared.Kernel.Common;
namespace ColaFlow.Modules.ProjectManagement.Infrastructure.Persistence.Configurations;
/// <summary>
/// Entity configuration for Epic entity
/// </summary>
public class EpicConfiguration : IEntityTypeConfiguration<Epic>
{
public void Configure(EntityTypeBuilder<Epic> builder)
{
builder.ToTable("Epics");
// Primary key
builder.HasKey("Id");
// Id conversion
builder.Property(e => e.Id)
.HasConversion(
id => id.Value,
value => EpicId.From(value))
.IsRequired()
.ValueGeneratedNever();
// ProjectId (foreign key)
builder.Property(e => e.ProjectId)
.HasConversion(
id => id.Value,
value => ProjectId.From(value))
.IsRequired();
// Basic properties
builder.Property(e => e.Name)
.HasMaxLength(200)
.IsRequired();
builder.Property(e => e.Description)
.HasMaxLength(2000);
// Status enumeration
builder.Property(e => e.Status)
.HasConversion(
s => s.Name,
name => Enumeration.FromDisplayName<WorkItemStatus>(name))
.HasMaxLength(50)
.IsRequired();
// Priority enumeration
builder.Property(e => e.Priority)
.HasConversion(
p => p.Name,
name => Enumeration.FromDisplayName<TaskPriority>(name))
.HasMaxLength(50)
.IsRequired();
// CreatedBy conversion
builder.Property(e => e.CreatedBy)
.HasConversion(
id => id.Value,
value => UserId.From(value))
.IsRequired();
// Timestamps
builder.Property(e => e.CreatedAt)
.IsRequired();
builder.Property(e => e.UpdatedAt);
// Ignore navigation properties (DDD pattern - access through aggregate)
builder.Ignore(e => e.Stories);
// Foreign key relationship to Project
builder.HasOne<Project>()
.WithMany()
.HasForeignKey(e => e.ProjectId)
.OnDelete(DeleteBehavior.Cascade);
// Indexes
builder.HasIndex(e => e.ProjectId);
builder.HasIndex(e => e.CreatedAt);
}
}

View File

@@ -0,0 +1,79 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate;
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
using ColaFlow.Shared.Kernel.Common;
namespace ColaFlow.Modules.ProjectManagement.Infrastructure.Persistence.Configurations;
/// <summary>
/// Entity configuration for Project aggregate root
/// </summary>
public class ProjectConfiguration : IEntityTypeConfiguration<Project>
{
public void Configure(EntityTypeBuilder<Project> builder)
{
builder.ToTable("Projects");
// Primary key
builder.HasKey(p => p.Id);
// Id conversion (StronglyTypedId to Guid)
builder.Property(p => p.Id)
.HasConversion(
id => id.Value,
value => ProjectId.From(value))
.IsRequired()
.ValueGeneratedNever();
// Basic properties
builder.Property(p => p.Name)
.HasMaxLength(200)
.IsRequired();
builder.Property(p => p.Description)
.HasMaxLength(2000);
// ProjectKey as owned value object
builder.OwnsOne(p => p.Key, kb =>
{
kb.Property(k => k.Value)
.HasColumnName("Key")
.HasMaxLength(20)
.IsRequired();
kb.HasIndex(k => k.Value).IsUnique();
});
// Status enumeration (stored as string)
builder.Property(p => p.Status)
.HasConversion(
s => s.Name,
name => Enumeration.FromDisplayName<ProjectStatus>(name))
.HasMaxLength(50)
.IsRequired();
// OwnerId conversion
builder.Property(p => p.OwnerId)
.HasConversion(
id => id.Value,
value => UserId.From(value))
.IsRequired();
// Timestamps
builder.Property(p => p.CreatedAt)
.IsRequired();
builder.Property(p => p.UpdatedAt);
// Relationships - Epics collection (owned by aggregate)
// Note: We don't expose this as navigation property in DDD, epics are accessed through repository
// Indexes for performance
builder.HasIndex(p => p.CreatedAt);
builder.HasIndex(p => p.OwnerId);
// Ignore DomainEvents (handled separately)
builder.Ignore(p => p.DomainEvents);
}
}

View File

@@ -0,0 +1,97 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate;
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
using ColaFlow.Shared.Kernel.Common;
namespace ColaFlow.Modules.ProjectManagement.Infrastructure.Persistence.Configurations;
/// <summary>
/// Entity configuration for Story entity
/// </summary>
public class StoryConfiguration : IEntityTypeConfiguration<Story>
{
public void Configure(EntityTypeBuilder<Story> builder)
{
builder.ToTable("Stories");
// Primary key
builder.HasKey("Id");
// Id conversion
builder.Property(s => s.Id)
.HasConversion(
id => id.Value,
value => StoryId.From(value))
.IsRequired()
.ValueGeneratedNever();
// EpicId (foreign key)
builder.Property(s => s.EpicId)
.HasConversion(
id => id.Value,
value => EpicId.From(value))
.IsRequired();
// Basic properties
builder.Property(s => s.Title)
.HasMaxLength(200)
.IsRequired();
builder.Property(s => s.Description)
.HasMaxLength(4000);
// Status enumeration
builder.Property(s => s.Status)
.HasConversion(
st => st.Name,
name => Enumeration.FromDisplayName<WorkItemStatus>(name))
.HasMaxLength(50)
.IsRequired();
// Priority enumeration
builder.Property(s => s.Priority)
.HasConversion(
p => p.Name,
name => Enumeration.FromDisplayName<TaskPriority>(name))
.HasMaxLength(50)
.IsRequired();
// CreatedBy conversion
builder.Property(s => s.CreatedBy)
.HasConversion(
id => id.Value,
value => UserId.From(value))
.IsRequired();
// AssigneeId (optional)
builder.Property(s => s.AssigneeId)
.HasConversion(
id => id != null ? id.Value : (Guid?)null,
value => value.HasValue ? UserId.From(value.Value) : null);
// Effort tracking
builder.Property(s => s.EstimatedHours);
builder.Property(s => s.ActualHours);
// Timestamps
builder.Property(s => s.CreatedAt)
.IsRequired();
builder.Property(s => s.UpdatedAt);
// Ignore navigation properties (DDD pattern - access through aggregate)
builder.Ignore(s => s.Tasks);
// Foreign key relationship to Epic
builder.HasOne<Epic>()
.WithMany()
.HasForeignKey(s => s.EpicId)
.OnDelete(DeleteBehavior.Cascade);
// Indexes
builder.HasIndex(s => s.EpicId);
builder.HasIndex(s => s.AssigneeId);
builder.HasIndex(s => s.CreatedAt);
}
}

View File

@@ -0,0 +1,94 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate;
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
using ColaFlow.Shared.Kernel.Common;
namespace ColaFlow.Modules.ProjectManagement.Infrastructure.Persistence.Configurations;
/// <summary>
/// Entity configuration for WorkTask entity
/// </summary>
public class WorkTaskConfiguration : IEntityTypeConfiguration<WorkTask>
{
public void Configure(EntityTypeBuilder<WorkTask> builder)
{
builder.ToTable("Tasks");
// Primary key
builder.HasKey("Id");
// Id conversion
builder.Property(t => t.Id)
.HasConversion(
id => id.Value,
value => TaskId.From(value))
.IsRequired()
.ValueGeneratedNever();
// StoryId (foreign key)
builder.Property(t => t.StoryId)
.HasConversion(
id => id.Value,
value => StoryId.From(value))
.IsRequired();
// Basic properties
builder.Property(t => t.Title)
.HasMaxLength(200)
.IsRequired();
builder.Property(t => t.Description)
.HasMaxLength(4000);
// Status enumeration
builder.Property(t => t.Status)
.HasConversion(
s => s.Name,
name => Enumeration.FromDisplayName<WorkItemStatus>(name))
.HasMaxLength(50)
.IsRequired();
// Priority enumeration
builder.Property(t => t.Priority)
.HasConversion(
p => p.Name,
name => Enumeration.FromDisplayName<TaskPriority>(name))
.HasMaxLength(50)
.IsRequired();
// CreatedBy conversion
builder.Property(t => t.CreatedBy)
.HasConversion(
id => id.Value,
value => UserId.From(value))
.IsRequired();
// AssigneeId (optional)
builder.Property(t => t.AssigneeId)
.HasConversion(
id => id != null ? id.Value : (Guid?)null,
value => value.HasValue ? UserId.From(value.Value) : null);
// Effort tracking
builder.Property(t => t.EstimatedHours);
builder.Property(t => t.ActualHours);
// Timestamps
builder.Property(t => t.CreatedAt)
.IsRequired();
builder.Property(t => t.UpdatedAt);
// Foreign key relationship to Story
builder.HasOne<Story>()
.WithMany()
.HasForeignKey(t => t.StoryId)
.OnDelete(DeleteBehavior.Cascade);
// Indexes
builder.HasIndex(t => t.StoryId);
builder.HasIndex(t => t.AssigneeId);
builder.HasIndex(t => t.CreatedAt);
}
}

View File

@@ -0,0 +1,31 @@
using System.Reflection;
using Microsoft.EntityFrameworkCore;
using ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate;
namespace ColaFlow.Modules.ProjectManagement.Infrastructure.Persistence;
/// <summary>
/// Project Management Module DbContext
/// </summary>
public class PMDbContext : DbContext
{
public PMDbContext(DbContextOptions<PMDbContext> options) : base(options)
{
}
public DbSet<Project> Projects => Set<Project>();
public DbSet<Epic> Epics => Set<Epic>();
public DbSet<Story> Stories => Set<Story>();
public DbSet<WorkTask> Tasks => Set<WorkTask>();
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());
}
}

View File

@@ -0,0 +1,49 @@
using ColaFlow.Modules.ProjectManagement.Domain.Repositories;
using ColaFlow.Shared.Kernel.Common;
namespace ColaFlow.Modules.ProjectManagement.Infrastructure.Persistence;
/// <summary>
/// Unit of Work implementation for ProjectManagement module
/// </summary>
public class UnitOfWork : IUnitOfWork
{
private readonly PMDbContext _context;
public UnitOfWork(PMDbContext context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
}
public async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
// Dispatch domain events before saving
await DispatchDomainEventsAsync(cancellationToken);
// Save changes to database
return await _context.SaveChangesAsync(cancellationToken);
}
private async Task DispatchDomainEventsAsync(CancellationToken cancellationToken)
{
// Get all entities with domain events
var domainEntities = _context.ChangeTracker
.Entries<AggregateRoot>()
.Where(x => x.Entity.DomainEvents.Any())
.Select(x => x.Entity)
.ToList();
// Get all domain events
var domainEvents = domainEntities
.SelectMany(x => x.DomainEvents)
.ToList();
// Clear domain events from entities
domainEntities.ForEach(entity => entity.ClearDomainEvents());
// TODO: Dispatch domain events to handlers
// This will be implemented when we add MediatR
// For now, we just clear the events
await Task.CompletedTask;
}
}

View File

@@ -0,0 +1,55 @@
using Microsoft.EntityFrameworkCore;
using ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate;
using ColaFlow.Modules.ProjectManagement.Domain.Repositories;
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
using ColaFlow.Modules.ProjectManagement.Infrastructure.Persistence;
namespace ColaFlow.Modules.ProjectManagement.Infrastructure.Repositories;
/// <summary>
/// Project repository implementation using EF Core
/// </summary>
public class ProjectRepository : IProjectRepository
{
private readonly PMDbContext _context;
public ProjectRepository(PMDbContext context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
}
public async Task<Project?> GetByIdAsync(ProjectId id, CancellationToken cancellationToken = default)
{
return await _context.Projects
.Include(p => p.Epics)
.FirstOrDefaultAsync(p => p.Id == id, cancellationToken);
}
public async Task<Project?> GetByKeyAsync(string key, CancellationToken cancellationToken = default)
{
return await _context.Projects
.FirstOrDefaultAsync(p => p.Key.Value == key, cancellationToken);
}
public async Task<List<Project>> GetAllAsync(CancellationToken cancellationToken = default)
{
return await _context.Projects
.OrderByDescending(p => p.CreatedAt)
.ToListAsync(cancellationToken);
}
public async Task AddAsync(Project project, CancellationToken cancellationToken = default)
{
await _context.Projects.AddAsync(project, cancellationToken);
}
public void Update(Project project)
{
_context.Projects.Update(project);
}
public void Delete(Project project)
{
_context.Projects.Remove(project);
}
}