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
+49
View File
@@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using ROLAC.API.Data.Logging;
using ROLAC.API.Entities;
using ROLAC.API.Entities.Notifications;
namespace ROLAC.API.Data;
@@ -26,6 +27,11 @@ public class AppDbContext : IdentityDbContext<AppUser, AppRole, string>
public DbSet<MealAttendance> MealAttendances => Set<MealAttendance>();
public DbSet<RolePermission> RolePermissions => Set<RolePermission>();
public DbSet<MemberChannelBinding> MemberChannelBindings => Set<MemberChannelBinding>();
public DbSet<LineBindingCode> LineBindingCodes => Set<LineBindingCode>();
public DbSet<MessagingGroup> MessagingGroups => Set<MessagingGroup>();
public DbSet<NotificationLog> NotificationLogs => Set<NotificationLog>();
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
@@ -326,6 +332,49 @@ public class AppDbContext : IdentityDbContext<AppUser, AppRole, string>
entity.HasIndex(e => new { e.Year, e.Month }).IsUnique();
});
// ── Notifications (email + Line) ─────────────────────────────────────
builder.Entity<MemberChannelBinding>(entity =>
{
entity.Property(e => e.Channel).HasMaxLength(20).IsRequired();
entity.Property(e => e.ExternalId).HasMaxLength(100).IsRequired();
entity.HasIndex(e => new { e.MemberId, e.Channel }).IsUnique();
entity.HasIndex(e => new { e.Channel, e.ExternalId }).IsUnique();
entity.HasOne(e => e.Member).WithMany()
.HasForeignKey(e => e.MemberId).OnDelete(DeleteBehavior.Cascade);
});
builder.Entity<LineBindingCode>(entity =>
{
entity.Property(e => e.Code).HasMaxLength(20).IsRequired();
entity.HasIndex(e => e.Code);
entity.HasOne(e => e.Member).WithMany()
.HasForeignKey(e => e.MemberId).OnDelete(DeleteBehavior.Cascade);
});
builder.Entity<MessagingGroup>(entity =>
{
entity.Property(e => e.Channel).HasMaxLength(20).IsRequired();
entity.Property(e => e.ExternalId).HasMaxLength(100).IsRequired();
entity.Property(e => e.Name).HasMaxLength(200);
entity.HasIndex(e => new { e.Channel, e.ExternalId }).IsUnique();
});
builder.Entity<NotificationLog>(entity =>
{
entity.Property(e => e.Channel).HasMaxLength(20).IsRequired();
entity.Property(e => e.TargetType).HasMaxLength(20).IsRequired();
entity.Property(e => e.TargetExternalId).HasMaxLength(200).IsRequired();
entity.Property(e => e.Subject).HasMaxLength(300);
entity.Property(e => e.Status).HasMaxLength(20).IsRequired();
entity.Property(e => e.SentByUserId).HasMaxLength(450).IsRequired();
entity.HasIndex(e => e.SentAt);
entity.HasIndex(e => e.Channel);
entity.HasOne(e => e.Member).WithMany()
.HasForeignKey(e => e.MemberId).OnDelete(DeleteBehavior.SetNull);
entity.HasOne(e => e.MessagingGroup).WithMany()
.HasForeignKey(e => e.MessagingGroupId).OnDelete(DeleteBehavior.SetNull);
});
// ── SystemLog / AuditLog (append-only) ───────────────────────────────
// Mapped here for SCHEMA only — there are deliberately no DbSets on this
// context, so business code can't write logs through the audited context.