diff --git a/API/ROLAC.API/Data/AppDbContext.cs b/API/ROLAC.API/Data/AppDbContext.cs index 2472e7b..60ff785 100644 --- a/API/ROLAC.API/Data/AppDbContext.cs +++ b/API/ROLAC.API/Data/AppDbContext.cs @@ -9,43 +9,85 @@ public class AppDbContext : IdentityDbContext public AppDbContext(DbContextOptions options) : base(options) { } public DbSet RefreshTokens => Set(); + public DbSet Members => Set(); + public DbSet FamilyUnits => Set(); protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); + // ── RefreshToken (unchanged) ──────────────────────────────────────── builder.Entity(entity => { entity.HasKey(e => e.Id); - - // Unique index on hash — enables fast lookup and prevents duplicate tokens entity.HasIndex(e => e.TokenHash).IsUnique(); - entity.Property(e => e.TokenHash).HasMaxLength(64).IsRequired(); entity.Property(e => e.UserId).HasMaxLength(450).IsRequired(); entity.Property(e => e.DeviceInfo).HasMaxLength(200); entity.Property(e => e.IpAddress).HasMaxLength(45); entity.Property(e => e.ReplacedByHash).HasMaxLength(64); - - entity.HasOne(e => e.User) - .WithMany(u => u.RefreshTokens) - .HasForeignKey(e => e.UserId) - .OnDelete(DeleteBehavior.Cascade); - - // Computed properties are not DB columns + entity.HasOne(e => e.User).WithMany(u => u.RefreshTokens) + .HasForeignKey(e => e.UserId).OnDelete(DeleteBehavior.Cascade); entity.Ignore(e => e.IsExpired); entity.Ignore(e => e.IsRevoked); entity.Ignore(e => e.IsActive); }); + // ── AppUser (unchanged + new unique index on MemberId) ────────────── builder.Entity(entity => { entity.Property(e => e.LanguagePreference).HasMaxLength(10).HasDefaultValue("en"); + // Nullable unique: one member ↔ one user account, but member can have no account + entity.HasIndex(e => e.MemberId).IsUnique() + .HasFilter("\"MemberId\" IS NOT NULL"); }); + // ── AppRole (unchanged) ───────────────────────────────────────────── builder.Entity(entity => { entity.Property(e => e.Description).HasMaxLength(500); }); + + // ── FamilyUnit ────────────────────────────────────────────────────── + builder.Entity(entity => + { + entity.Property(e => e.FamilyName_en).HasMaxLength(200); + entity.Property(e => e.FamilyName_zh).HasMaxLength(200); + }); + + // ── Member ────────────────────────────────────────────────────────── + builder.Entity(entity => + { + entity.HasQueryFilter(m => !m.IsDeleted); + + entity.Property(e => e.FirstName_en).HasMaxLength(100).IsRequired(); + entity.Property(e => e.LastName_en).HasMaxLength(100).IsRequired(); + entity.Property(e => e.NickName).HasMaxLength(100); + entity.Property(e => e.FirstName_zh).HasMaxLength(100); + entity.Property(e => e.LastName_zh).HasMaxLength(100); + entity.Property(e => e.Gender).HasMaxLength(10); + entity.Property(e => e.BaptismChurch).HasMaxLength(200); + entity.Property(e => e.Email).HasMaxLength(200); + entity.Property(e => e.PhoneCell).HasMaxLength(30); + entity.Property(e => e.PhoneHome).HasMaxLength(30); + entity.Property(e => e.Address).HasMaxLength(500); + entity.Property(e => e.City).HasMaxLength(100); + entity.Property(e => e.State).HasMaxLength(50); + entity.Property(e => e.ZipCode).HasMaxLength(20); + entity.Property(e => e.Country).HasMaxLength(100).HasDefaultValue("USA"); + entity.Property(e => e.PhotoBlobPath).HasMaxLength(500); + entity.Property(e => e.Status).HasMaxLength(20).HasDefaultValue("Member"); + entity.Property(e => e.LanguagePreference).HasMaxLength(10).HasDefaultValue("en"); + entity.Property(e => e.CreatedBy).HasMaxLength(450); + entity.Property(e => e.UpdatedBy).HasMaxLength(450); + entity.Property(e => e.DeletedBy).HasMaxLength(450); + + entity.HasIndex(e => e.Status).HasFilter("\"IsDeleted\" = false"); + entity.HasIndex(e => e.Email).HasFilter("\"Email\" IS NOT NULL"); + entity.HasIndex(e => e.FamilyUnitId); + + entity.HasOne(e => e.FamilyUnit).WithMany() + .HasForeignKey(e => e.FamilyUnitId).OnDelete(DeleteBehavior.SetNull); + }); } } diff --git a/API/ROLAC.API/Migrations/20260527205155_AddMemberAndFamilyUnit.Designer.cs b/API/ROLAC.API/Migrations/20260527205155_AddMemberAndFamilyUnit.Designer.cs new file mode 100644 index 0000000..c209932 --- /dev/null +++ b/API/ROLAC.API/Migrations/20260527205155_AddMemberAndFamilyUnit.Designer.cs @@ -0,0 +1,562 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using ROLAC.API.Data; + +#nullable disable + +namespace ROLAC.API.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260527205155_AddMemberAndFamilyUnit")] + partial class AddMemberAndFamilyUnit + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("ROLAC.API.Entities.AppRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("ROLAC.API.Entities.AppUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LanguagePreference") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasDefaultValue("en"); + + b.Property("LastLoginAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("MemberId") + .HasColumnType("integer"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("MemberId") + .IsUnique() + .HasFilter("\"MemberId\" IS NOT NULL"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("ROLAC.API.Entities.FamilyUnit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("FamilyName_en") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("FamilyName_zh") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("FamilyUnits"); + }); + + modelBuilder.Entity("ROLAC.API.Entities.Member", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("BaptismChurch") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("BaptismDate") + .HasColumnType("date"); + + b.Property("City") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Country") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasDefaultValue("USA"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(450) + .HasColumnType("character varying(450)"); + + b.Property("DateOfBirth") + .HasColumnType("date"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasMaxLength(450) + .HasColumnType("character varying(450)"); + + b.Property("Email") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("FamilyUnitId") + .HasColumnType("integer"); + + b.Property("FirstName_en") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("FirstName_zh") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Gender") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("JoinDate") + .HasColumnType("date"); + + b.Property("LanguagePreference") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasDefaultValue("en"); + + b.Property("LastName_en") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LastName_zh") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("NickName") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("PhoneCell") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("PhoneHome") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("PhotoBlobPath") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("State") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Member"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .IsRequired() + .HasMaxLength(450) + .HasColumnType("character varying(450)"); + + b.Property("ZipCode") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .HasFilter("\"Email\" IS NOT NULL"); + + b.HasIndex("FamilyUnitId"); + + b.HasIndex("Status") + .HasFilter("\"IsDeleted\" = false"); + + b.ToTable("Members"); + }); + + modelBuilder.Entity("ROLAC.API.Entities.RefreshToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeviceInfo") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IpAddress") + .HasMaxLength(45) + .HasColumnType("character varying(45)"); + + b.Property("ReplacedByHash") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("RevokedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("TokenHash") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(450) + .HasColumnType("character varying(450)"); + + b.HasKey("Id"); + + b.HasIndex("TokenHash") + .IsUnique(); + + b.HasIndex("UserId"); + + b.ToTable("RefreshTokens"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("ROLAC.API.Entities.AppRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("ROLAC.API.Entities.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("ROLAC.API.Entities.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("ROLAC.API.Entities.AppRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ROLAC.API.Entities.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("ROLAC.API.Entities.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ROLAC.API.Entities.Member", b => + { + b.HasOne("ROLAC.API.Entities.FamilyUnit", "FamilyUnit") + .WithMany() + .HasForeignKey("FamilyUnitId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("FamilyUnit"); + }); + + modelBuilder.Entity("ROLAC.API.Entities.RefreshToken", b => + { + b.HasOne("ROLAC.API.Entities.AppUser", "User") + .WithMany("RefreshTokens") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("ROLAC.API.Entities.AppUser", b => + { + b.Navigation("RefreshTokens"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/API/ROLAC.API/Migrations/20260527205155_AddMemberAndFamilyUnit.cs b/API/ROLAC.API/Migrations/20260527205155_AddMemberAndFamilyUnit.cs new file mode 100644 index 0000000..e0e7fd2 --- /dev/null +++ b/API/ROLAC.API/Migrations/20260527205155_AddMemberAndFamilyUnit.cs @@ -0,0 +1,121 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace ROLAC.API.Migrations +{ + /// + public partial class AddMemberAndFamilyUnit : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "FamilyUnits", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + FamilyName_en = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + FamilyName_zh = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + Notes = table.Column(type: "text", nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + CreatedBy = table.Column(type: "text", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedBy = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_FamilyUnits", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Members", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + FirstName_en = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + LastName_en = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + NickName = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + FirstName_zh = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + LastName_zh = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + Gender = table.Column(type: "character varying(10)", maxLength: 10, nullable: true), + DateOfBirth = table.Column(type: "date", nullable: true), + BaptismDate = table.Column(type: "date", nullable: true), + BaptismChurch = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + Email = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + PhoneCell = table.Column(type: "character varying(30)", maxLength: 30, nullable: true), + PhoneHome = table.Column(type: "character varying(30)", maxLength: 30, nullable: true), + Address = table.Column(type: "character varying(500)", maxLength: 500, nullable: true), + City = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + State = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + ZipCode = table.Column(type: "character varying(20)", maxLength: 20, nullable: true), + Country = table.Column(type: "character varying(100)", maxLength: 100, nullable: false, defaultValue: "USA"), + PhotoBlobPath = table.Column(type: "character varying(500)", maxLength: 500, nullable: true), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false, defaultValue: "Member"), + LanguagePreference = table.Column(type: "character varying(10)", maxLength: 10, nullable: false, defaultValue: "en"), + JoinDate = table.Column(type: "date", nullable: true), + Notes = table.Column(type: "text", nullable: true), + FamilyUnitId = table.Column(type: "integer", nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + CreatedBy = table.Column(type: "character varying(450)", maxLength: 450, nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedBy = table.Column(type: "character varying(450)", maxLength: 450, nullable: false), + IsDeleted = table.Column(type: "boolean", nullable: false), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true), + DeletedBy = table.Column(type: "character varying(450)", maxLength: 450, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Members", x => x.Id); + table.ForeignKey( + name: "FK_Members_FamilyUnits_FamilyUnitId", + column: x => x.FamilyUnitId, + principalTable: "FamilyUnits", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUsers_MemberId", + table: "AspNetUsers", + column: "MemberId", + unique: true, + filter: "\"MemberId\" IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_Members_Email", + table: "Members", + column: "Email", + filter: "\"Email\" IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_Members_FamilyUnitId", + table: "Members", + column: "FamilyUnitId"); + + migrationBuilder.CreateIndex( + name: "IX_Members_Status", + table: "Members", + column: "Status", + filter: "\"IsDeleted\" = false"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Members"); + + migrationBuilder.DropTable( + name: "FamilyUnits"); + + migrationBuilder.DropIndex( + name: "IX_AspNetUsers_MemberId", + table: "AspNetUsers"); + } + } +} diff --git a/API/ROLAC.API/Migrations/AppDbContextModelSnapshot.cs b/API/ROLAC.API/Migrations/AppDbContextModelSnapshot.cs index a0cc0ae..62a4b23 100644 --- a/API/ROLAC.API/Migrations/AppDbContextModelSnapshot.cs +++ b/API/ROLAC.API/Migrations/AppDbContextModelSnapshot.cs @@ -231,6 +231,10 @@ namespace ROLAC.API.Migrations b.HasKey("Id"); + b.HasIndex("MemberId") + .IsUnique() + .HasFilter("\"MemberId\" IS NOT NULL"); + b.HasIndex("NormalizedEmail") .HasDatabaseName("EmailIndex"); @@ -241,6 +245,189 @@ namespace ROLAC.API.Migrations b.ToTable("AspNetUsers", (string)null); }); + modelBuilder.Entity("ROLAC.API.Entities.FamilyUnit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("FamilyName_en") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("FamilyName_zh") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("FamilyUnits"); + }); + + modelBuilder.Entity("ROLAC.API.Entities.Member", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("BaptismChurch") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("BaptismDate") + .HasColumnType("date"); + + b.Property("City") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Country") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasDefaultValue("USA"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(450) + .HasColumnType("character varying(450)"); + + b.Property("DateOfBirth") + .HasColumnType("date"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasMaxLength(450) + .HasColumnType("character varying(450)"); + + b.Property("Email") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("FamilyUnitId") + .HasColumnType("integer"); + + b.Property("FirstName_en") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("FirstName_zh") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Gender") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("JoinDate") + .HasColumnType("date"); + + b.Property("LanguagePreference") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasDefaultValue("en"); + + b.Property("LastName_en") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LastName_zh") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("NickName") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("PhoneCell") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("PhoneHome") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("PhotoBlobPath") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("State") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("Member"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .IsRequired() + .HasMaxLength(450) + .HasColumnType("character varying(450)"); + + b.Property("ZipCode") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .HasFilter("\"Email\" IS NOT NULL"); + + b.HasIndex("FamilyUnitId"); + + b.HasIndex("Status") + .HasFilter("\"IsDeleted\" = false"); + + b.ToTable("Members"); + }); + modelBuilder.Entity("ROLAC.API.Entities.RefreshToken", b => { b.Property("Id") @@ -341,6 +528,16 @@ namespace ROLAC.API.Migrations .IsRequired(); }); + modelBuilder.Entity("ROLAC.API.Entities.Member", b => + { + b.HasOne("ROLAC.API.Entities.FamilyUnit", "FamilyUnit") + .WithMany() + .HasForeignKey("FamilyUnitId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("FamilyUnit"); + }); + modelBuilder.Entity("ROLAC.API.Entities.RefreshToken", b => { b.HasOne("ROLAC.API.Entities.AppUser", "User") diff --git a/API/ROLAC.API/Program.cs b/API/ROLAC.API/Program.cs index e24be6b..019b708 100644 --- a/API/ROLAC.API/Program.cs +++ b/API/ROLAC.API/Program.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; using ROLAC.API.Data; +using ROLAC.API.Data.Interceptors; using ROLAC.API.Entities; using ROLAC.API.Services; @@ -13,8 +14,11 @@ var config = builder.Configuration; // --------------------------------------------------------------------------- // Database // --------------------------------------------------------------------------- -builder.Services.AddDbContext(opt => - opt.UseNpgsql(config.GetConnectionString("DefaultConnection"))); +builder.Services.AddHttpContextAccessor(); +builder.Services.AddScoped(); +builder.Services.AddDbContext((sp, opt) => + opt.UseNpgsql(config.GetConnectionString("DefaultConnection")) + .AddInterceptors(sp.GetRequiredService())); // --------------------------------------------------------------------------- // Identity