From a69341c9e6d6f8944f8da49484ae791f1e8a2286 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 26 Jun 2026 04:41:25 +0000 Subject: [PATCH] feat: add Identity persistence layer with EF Core migration - Configure IdentityDbContext with AppUser and AppRole entity mappings - Add unique indexes on UserName and Email columns - Generate InitialCreate migration (Users + Roles tables) - Add auto-migration at startup in Program.cs --- src/Services/Identity/Identity.API/Program.cs | 6 ++ .../Data/IdentityDbContext.cs | 23 ++++- .../20260626044104_InitialCreate.Designer.cs | 94 +++++++++++++++++++ .../20260626044104_InitialCreate.cs | 69 ++++++++++++++ .../IdentityDbContextModelSnapshot.cs | 91 ++++++++++++++++++ 5 files changed, 282 insertions(+), 1 deletion(-) create mode 100644 src/Services/Identity/Identity.Infrastructure/Data/Migrations/20260626044104_InitialCreate.Designer.cs create mode 100644 src/Services/Identity/Identity.Infrastructure/Data/Migrations/20260626044104_InitialCreate.cs create mode 100644 src/Services/Identity/Identity.Infrastructure/Data/Migrations/IdentityDbContextModelSnapshot.cs diff --git a/src/Services/Identity/Identity.API/Program.cs b/src/Services/Identity/Identity.API/Program.cs index b475a08..c8312eb 100644 --- a/src/Services/Identity/Identity.API/Program.cs +++ b/src/Services/Identity/Identity.API/Program.cs @@ -13,6 +13,12 @@ var app = builder.Build(); +using (var scope = app.Services.CreateScope()) +{ + var db = scope.ServiceProvider.GetRequiredService(); + db.Database.Migrate(); +} + if (app.Environment.IsDevelopment()) { app.UseSwagger(); diff --git a/src/Services/Identity/Identity.Infrastructure/Data/IdentityDbContext.cs b/src/Services/Identity/Identity.Infrastructure/Data/IdentityDbContext.cs index 52c3b5f..72a9c79 100644 --- a/src/Services/Identity/Identity.Infrastructure/Data/IdentityDbContext.cs +++ b/src/Services/Identity/Identity.Infrastructure/Data/IdentityDbContext.cs @@ -1,3 +1,4 @@ +using Identity.Domain.Entities; using Microsoft.EntityFrameworkCore; namespace Identity.Infrastructure.Data; @@ -8,9 +9,29 @@ public IdentityDbContext(DbContextOptions options) : base(opt { } + public DbSet Users { get; set; } + public DbSet Roles { get; set; } + protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); - // TODO: Configure entity mappings migrated from monolith + + modelBuilder.Entity(entity => + { + entity.ToTable("Users"); + entity.HasKey(e => e.Id); + entity.Property(e => e.UserName).IsRequired().HasMaxLength(100); + entity.HasIndex(e => e.UserName).IsUnique(); + entity.Property(e => e.Email).IsRequired().HasMaxLength(100); + entity.HasIndex(e => e.Email).IsUnique(); + entity.Property(e => e.PasswordHash).IsRequired(); + }); + + modelBuilder.Entity(entity => + { + entity.ToTable("Roles"); + entity.HasKey(e => e.Id); + entity.Property(e => e.Name).IsRequired(); + }); } } diff --git a/src/Services/Identity/Identity.Infrastructure/Data/Migrations/20260626044104_InitialCreate.Designer.cs b/src/Services/Identity/Identity.Infrastructure/Data/Migrations/20260626044104_InitialCreate.Designer.cs new file mode 100644 index 0000000..b8f0109 --- /dev/null +++ b/src/Services/Identity/Identity.Infrastructure/Data/Migrations/20260626044104_InitialCreate.Designer.cs @@ -0,0 +1,94 @@ +// +using System; +using Identity.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Identity.Infrastructure.Data.Migrations +{ + [DbContext(typeof(IdentityDbContext))] + [Migration("20260626044104_InitialCreate")] + partial class InitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Identity.Domain.Entities.AppRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Roles", (string)null); + }); + + modelBuilder.Entity("Identity.Domain.Entities.AppUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("FullName") + .HasColumnType("text"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("JobTitle") + .HasColumnType("text"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("Users", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Services/Identity/Identity.Infrastructure/Data/Migrations/20260626044104_InitialCreate.cs b/src/Services/Identity/Identity.Infrastructure/Data/Migrations/20260626044104_InitialCreate.cs new file mode 100644 index 0000000..4afc6ce --- /dev/null +++ b/src/Services/Identity/Identity.Infrastructure/Data/Migrations/20260626044104_InitialCreate.cs @@ -0,0 +1,69 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Identity.Infrastructure.Data.Migrations +{ + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Roles", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "text", nullable: false), + Description = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Roles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + UserName = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + Email = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + PasswordHash = table.Column(type: "text", nullable: false), + FullName = table.Column(type: "text", nullable: true), + JobTitle = table.Column(type: "text", nullable: true), + IsEnabled = table.Column(type: "boolean", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_Users_Email", + table: "Users", + column: "Email", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Users_UserName", + table: "Users", + column: "UserName", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Roles"); + + migrationBuilder.DropTable( + name: "Users"); + } + } +} diff --git a/src/Services/Identity/Identity.Infrastructure/Data/Migrations/IdentityDbContextModelSnapshot.cs b/src/Services/Identity/Identity.Infrastructure/Data/Migrations/IdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..d76a5e1 --- /dev/null +++ b/src/Services/Identity/Identity.Infrastructure/Data/Migrations/IdentityDbContextModelSnapshot.cs @@ -0,0 +1,91 @@ +// +using System; +using Identity.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Identity.Infrastructure.Data.Migrations +{ + [DbContext(typeof(IdentityDbContext))] + partial class IdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Identity.Domain.Entities.AppRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Roles", (string)null); + }); + + modelBuilder.Entity("Identity.Domain.Entities.AppUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("FullName") + .HasColumnType("text"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("JobTitle") + .HasColumnType("text"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("Users", (string)null); + }); +#pragma warning restore 612, 618 + } + } +}