Add notification entities, DbContext config, and migration

Creates MemberChannelBinding, LineBindingCode, MessagingGroup, and NotificationLog
entities under ROLAC.API.Entities.Notifications; wires DbSets and fluent config into
AppDbContext; generates EF migration AddNotifications creating the four tables.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Chris Chen
2026-06-23 19:03:35 -07:00
parent f9c4d7edb2
commit 0e90f19377
8 changed files with 2399 additions and 0 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,176 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace ROLAC.API.Migrations
{
/// <inheritdoc />
public partial class AddNotifications : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "LineBindingCodes",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
Code = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
MemberId = table.Column<int>(type: "integer", nullable: false),
ExpiresAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
ConsumedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_LineBindingCodes", x => x.Id);
table.ForeignKey(
name: "FK_LineBindingCodes_Members_MemberId",
column: x => x.MemberId,
principalTable: "Members",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "MemberChannelBindings",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
MemberId = table.Column<int>(type: "integer", nullable: false),
Channel = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
ExternalId = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
BoundAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_MemberChannelBindings", x => x.Id);
table.ForeignKey(
name: "FK_MemberChannelBindings_Members_MemberId",
column: x => x.MemberId,
principalTable: "Members",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "MessagingGroups",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
Channel = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
ExternalId = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
Name = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true),
IsActive = table.Column<bool>(type: "boolean", nullable: false),
RegisteredAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_MessagingGroups", x => x.Id);
});
migrationBuilder.CreateTable(
name: "NotificationLogs",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
Channel = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
TargetType = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
TargetExternalId = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
Subject = table.Column<string>(type: "character varying(300)", maxLength: 300, nullable: true),
MemberId = table.Column<int>(type: "integer", nullable: true),
MessagingGroupId = table.Column<int>(type: "integer", nullable: true),
Body = table.Column<string>(type: "text", nullable: false),
Status = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
Error = table.Column<string>(type: "text", nullable: true),
SentByUserId = table.Column<string>(type: "character varying(450)", maxLength: 450, nullable: false),
SentAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_NotificationLogs", x => x.Id);
table.ForeignKey(
name: "FK_NotificationLogs_Members_MemberId",
column: x => x.MemberId,
principalTable: "Members",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
table.ForeignKey(
name: "FK_NotificationLogs_MessagingGroups_MessagingGroupId",
column: x => x.MessagingGroupId,
principalTable: "MessagingGroups",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
});
migrationBuilder.CreateIndex(
name: "IX_LineBindingCodes_Code",
table: "LineBindingCodes",
column: "Code");
migrationBuilder.CreateIndex(
name: "IX_LineBindingCodes_MemberId",
table: "LineBindingCodes",
column: "MemberId");
migrationBuilder.CreateIndex(
name: "IX_MemberChannelBindings_Channel_ExternalId",
table: "MemberChannelBindings",
columns: new[] { "Channel", "ExternalId" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_MemberChannelBindings_MemberId_Channel",
table: "MemberChannelBindings",
columns: new[] { "MemberId", "Channel" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_MessagingGroups_Channel_ExternalId",
table: "MessagingGroups",
columns: new[] { "Channel", "ExternalId" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_NotificationLogs_Channel",
table: "NotificationLogs",
column: "Channel");
migrationBuilder.CreateIndex(
name: "IX_NotificationLogs_MemberId",
table: "NotificationLogs",
column: "MemberId");
migrationBuilder.CreateIndex(
name: "IX_NotificationLogs_MessagingGroupId",
table: "NotificationLogs",
column: "MessagingGroupId");
migrationBuilder.CreateIndex(
name: "IX_NotificationLogs_SentAt",
table: "NotificationLogs",
column: "SentAt");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "LineBindingCodes");
migrationBuilder.DropTable(
name: "MemberChannelBindings");
migrationBuilder.DropTable(
name: "NotificationLogs");
migrationBuilder.DropTable(
name: "MessagingGroups");
}
}
}
@@ -1323,6 +1323,174 @@ namespace ROLAC.API.Migrations
b.ToTable("MonthlyStatements");
});
modelBuilder.Entity("ROLAC.API.Entities.Notifications.LineBindingCode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)");
b.Property<DateTime?>("ConsumedAt")
.HasColumnType("timestamp with time zone");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("timestamp with time zone");
b.Property<int>("MemberId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("Code");
b.HasIndex("MemberId");
b.ToTable("LineBindingCodes");
});
modelBuilder.Entity("ROLAC.API.Entities.Notifications.MemberChannelBinding", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("BoundAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Channel")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)");
b.Property<string>("ExternalId")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<int>("MemberId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("Channel", "ExternalId")
.IsUnique();
b.HasIndex("MemberId", "Channel")
.IsUnique();
b.ToTable("MemberChannelBindings");
});
modelBuilder.Entity("ROLAC.API.Entities.Notifications.MessagingGroup", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("Channel")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)");
b.Property<string>("ExternalId")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<bool>("IsActive")
.HasColumnType("boolean");
b.Property<string>("Name")
.HasMaxLength(200)
.HasColumnType("character varying(200)");
b.Property<DateTime>("RegisteredAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("Channel", "ExternalId")
.IsUnique();
b.ToTable("MessagingGroups");
});
modelBuilder.Entity("ROLAC.API.Entities.Notifications.NotificationLog", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<string>("Body")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Channel")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)");
b.Property<string>("Error")
.HasColumnType("text");
b.Property<int?>("MemberId")
.HasColumnType("integer");
b.Property<int?>("MessagingGroupId")
.HasColumnType("integer");
b.Property<DateTime>("SentAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("SentByUserId")
.IsRequired()
.HasMaxLength(450)
.HasColumnType("character varying(450)");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)");
b.Property<string>("Subject")
.HasMaxLength(300)
.HasColumnType("character varying(300)");
b.Property<string>("TargetExternalId")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)");
b.Property<string>("TargetType")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)");
b.HasKey("Id");
b.HasIndex("Channel");
b.HasIndex("MemberId");
b.HasIndex("MessagingGroupId");
b.HasIndex("SentAt");
b.ToTable("NotificationLogs");
});
modelBuilder.Entity("ROLAC.API.Entities.OfferingSession", b =>
{
b.Property<int>("Id")
@@ -1645,6 +1813,45 @@ namespace ROLAC.API.Migrations
b.Navigation("FamilyUnit");
});
modelBuilder.Entity("ROLAC.API.Entities.Notifications.LineBindingCode", b =>
{
b.HasOne("ROLAC.API.Entities.Member", "Member")
.WithMany()
.HasForeignKey("MemberId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Member");
});
modelBuilder.Entity("ROLAC.API.Entities.Notifications.MemberChannelBinding", b =>
{
b.HasOne("ROLAC.API.Entities.Member", "Member")
.WithMany()
.HasForeignKey("MemberId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Member");
});
modelBuilder.Entity("ROLAC.API.Entities.Notifications.NotificationLog", b =>
{
b.HasOne("ROLAC.API.Entities.Member", "Member")
.WithMany()
.HasForeignKey("MemberId")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("ROLAC.API.Entities.Notifications.MessagingGroup", "MessagingGroup")
.WithMany()
.HasForeignKey("MessagingGroupId")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("Member");
b.Navigation("MessagingGroup");
});
modelBuilder.Entity("ROLAC.API.Entities.RefreshToken", b =>
{
b.HasOne("ROLAC.API.Entities.AppUser", "User")