diff --git a/SpeakingInBitsWeb/Data/ApplicationDbContext.cs b/SpeakingInBitsWeb/Data/ApplicationDbContext.cs index 5cdd9b5..c29af8e 100644 --- a/SpeakingInBitsWeb/Data/ApplicationDbContext.cs +++ b/SpeakingInBitsWeb/Data/ApplicationDbContext.cs @@ -20,6 +20,11 @@ protected override void OnModelCreating(ModelBuilder builder) .HasMaxLength(50) .IsRequired(); }); + + builder.Entity() + .HasMany(i => i.Courses) + .WithOne(c => c.CourseInstructor) + .OnDelete(DeleteBehavior.Restrict); // Prevent accidental cascade deletes } public DbSet Courses { get; set; } diff --git a/SpeakingInBitsWeb/Data/Migrations/20251010170338_AddCourseInstructor.Designer.cs b/SpeakingInBitsWeb/Data/Migrations/20251010170338_AddCourseInstructor.Designer.cs new file mode 100644 index 0000000..29994f5 --- /dev/null +++ b/SpeakingInBitsWeb/Data/Migrations/20251010170338_AddCourseInstructor.Designer.cs @@ -0,0 +1,357 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using SpeakingInBitsWeb.Data; + +#nullable disable + +namespace SpeakingInBitsWeb.Data.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20251010170338_AddCourseInstructor")] + partial class AddCourseInstructor + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.0-rc.1.25451.107") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ProviderKey") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Name") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("SpeakingInBitsWeb.Models.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(21) + .HasColumnType("nvarchar(21)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + + b.HasDiscriminator().HasValue("ApplicationUser"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("SpeakingInBitsWeb.Models.Course", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CourseCode") + .HasMaxLength(8) + .HasColumnType("nvarchar(8)"); + + b.Property("CourseInstructorId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Description") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("CourseInstructorId"); + + b.ToTable("Courses"); + }); + + modelBuilder.Entity("SpeakingInBitsWeb.Models.Instructor", b => + { + b.HasBaseType("SpeakingInBitsWeb.Models.ApplicationUser"); + + b.HasDiscriminator().HasValue("Instructor"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("SpeakingInBitsWeb.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("SpeakingInBitsWeb.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SpeakingInBitsWeb.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("SpeakingInBitsWeb.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SpeakingInBitsWeb.Models.Course", b => + { + b.HasOne("SpeakingInBitsWeb.Models.Instructor", "CourseInstructor") + .WithMany("Courses") + .HasForeignKey("CourseInstructorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CourseInstructor"); + }); + + modelBuilder.Entity("SpeakingInBitsWeb.Models.Instructor", b => + { + b.Navigation("Courses"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/SpeakingInBitsWeb/Data/Migrations/20251010170338_AddCourseInstructor.cs b/SpeakingInBitsWeb/Data/Migrations/20251010170338_AddCourseInstructor.cs new file mode 100644 index 0000000..9df08c0 --- /dev/null +++ b/SpeakingInBitsWeb/Data/Migrations/20251010170338_AddCourseInstructor.cs @@ -0,0 +1,62 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace SpeakingInBitsWeb.Data.Migrations +{ + /// + public partial class AddCourseInstructor : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "CourseInstructorId", + table: "Courses", + type: "nvarchar(450)", + nullable: false, + defaultValue: ""); + + migrationBuilder.AddColumn( + name: "Discriminator", + table: "AspNetUsers", + type: "nvarchar(21)", + maxLength: 21, + nullable: false, + defaultValue: ""); + + migrationBuilder.CreateIndex( + name: "IX_Courses_CourseInstructorId", + table: "Courses", + column: "CourseInstructorId"); + + migrationBuilder.AddForeignKey( + name: "FK_Courses_AspNetUsers_CourseInstructorId", + table: "Courses", + column: "CourseInstructorId", + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Courses_AspNetUsers_CourseInstructorId", + table: "Courses"); + + migrationBuilder.DropIndex( + name: "IX_Courses_CourseInstructorId", + table: "Courses"); + + migrationBuilder.DropColumn( + name: "CourseInstructorId", + table: "Courses"); + + migrationBuilder.DropColumn( + name: "Discriminator", + table: "AspNetUsers"); + } + } +} diff --git a/SpeakingInBitsWeb/Data/Migrations/20251010170842_InstructorOfficeHours.Designer.cs b/SpeakingInBitsWeb/Data/Migrations/20251010170842_InstructorOfficeHours.Designer.cs new file mode 100644 index 0000000..e5a9518 --- /dev/null +++ b/SpeakingInBitsWeb/Data/Migrations/20251010170842_InstructorOfficeHours.Designer.cs @@ -0,0 +1,360 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using SpeakingInBitsWeb.Data; + +#nullable disable + +namespace SpeakingInBitsWeb.Data.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20251010170842_InstructorOfficeHours")] + partial class InstructorOfficeHours + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.0-rc.1.25451.107") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ProviderKey") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Name") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("SpeakingInBitsWeb.Models.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(21) + .HasColumnType("nvarchar(21)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + + b.HasDiscriminator().HasValue("ApplicationUser"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("SpeakingInBitsWeb.Models.Course", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CourseCode") + .HasMaxLength(8) + .HasColumnType("nvarchar(8)"); + + b.Property("CourseInstructorId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Description") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("CourseInstructorId"); + + b.ToTable("Courses"); + }); + + modelBuilder.Entity("SpeakingInBitsWeb.Models.Instructor", b => + { + b.HasBaseType("SpeakingInBitsWeb.Models.ApplicationUser"); + + b.Property("OfficeHours") + .HasColumnType("nvarchar(max)"); + + b.HasDiscriminator().HasValue("Instructor"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("SpeakingInBitsWeb.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("SpeakingInBitsWeb.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SpeakingInBitsWeb.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("SpeakingInBitsWeb.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SpeakingInBitsWeb.Models.Course", b => + { + b.HasOne("SpeakingInBitsWeb.Models.Instructor", "CourseInstructor") + .WithMany("Courses") + .HasForeignKey("CourseInstructorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CourseInstructor"); + }); + + modelBuilder.Entity("SpeakingInBitsWeb.Models.Instructor", b => + { + b.Navigation("Courses"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/SpeakingInBitsWeb/Data/Migrations/20251010170842_InstructorOfficeHours.cs b/SpeakingInBitsWeb/Data/Migrations/20251010170842_InstructorOfficeHours.cs new file mode 100644 index 0000000..fa38736 --- /dev/null +++ b/SpeakingInBitsWeb/Data/Migrations/20251010170842_InstructorOfficeHours.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace SpeakingInBitsWeb.Data.Migrations +{ + /// + public partial class InstructorOfficeHours : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "OfficeHours", + table: "AspNetUsers", + type: "nvarchar(max)", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "OfficeHours", + table: "AspNetUsers"); + } + } +} diff --git a/SpeakingInBitsWeb/Data/Migrations/20251010171129_OfficeHoursLength.Designer.cs b/SpeakingInBitsWeb/Data/Migrations/20251010171129_OfficeHoursLength.Designer.cs new file mode 100644 index 0000000..615dab9 --- /dev/null +++ b/SpeakingInBitsWeb/Data/Migrations/20251010171129_OfficeHoursLength.Designer.cs @@ -0,0 +1,361 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using SpeakingInBitsWeb.Data; + +#nullable disable + +namespace SpeakingInBitsWeb.Data.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20251010171129_OfficeHoursLength")] + partial class OfficeHoursLength + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.0-rc.1.25451.107") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ProviderKey") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Name") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("SpeakingInBitsWeb.Models.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(21) + .HasColumnType("nvarchar(21)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + + b.HasDiscriminator().HasValue("ApplicationUser"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("SpeakingInBitsWeb.Models.Course", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CourseCode") + .HasMaxLength(8) + .HasColumnType("nvarchar(8)"); + + b.Property("CourseInstructorId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Description") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("CourseInstructorId"); + + b.ToTable("Courses"); + }); + + modelBuilder.Entity("SpeakingInBitsWeb.Models.Instructor", b => + { + b.HasBaseType("SpeakingInBitsWeb.Models.ApplicationUser"); + + b.Property("OfficeHours") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasDiscriminator().HasValue("Instructor"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("SpeakingInBitsWeb.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("SpeakingInBitsWeb.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SpeakingInBitsWeb.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("SpeakingInBitsWeb.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SpeakingInBitsWeb.Models.Course", b => + { + b.HasOne("SpeakingInBitsWeb.Models.Instructor", "CourseInstructor") + .WithMany("Courses") + .HasForeignKey("CourseInstructorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CourseInstructor"); + }); + + modelBuilder.Entity("SpeakingInBitsWeb.Models.Instructor", b => + { + b.Navigation("Courses"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/SpeakingInBitsWeb/Data/Migrations/20251010171129_OfficeHoursLength.cs b/SpeakingInBitsWeb/Data/Migrations/20251010171129_OfficeHoursLength.cs new file mode 100644 index 0000000..5990955 --- /dev/null +++ b/SpeakingInBitsWeb/Data/Migrations/20251010171129_OfficeHoursLength.cs @@ -0,0 +1,38 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace SpeakingInBitsWeb.Data.Migrations +{ + /// + public partial class OfficeHoursLength : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "OfficeHours", + table: "AspNetUsers", + type: "nvarchar(200)", + maxLength: 200, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "OfficeHours", + table: "AspNetUsers", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(200)", + oldMaxLength: 200, + oldNullable: true); + } + } +} diff --git a/SpeakingInBitsWeb/Data/Migrations/ApplicationDbContextModelSnapshot.cs b/SpeakingInBitsWeb/Data/Migrations/ApplicationDbContextModelSnapshot.cs index bc6aca2..1b06fbb 100644 --- a/SpeakingInBitsWeb/Data/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/SpeakingInBitsWeb/Data/Migrations/ApplicationDbContextModelSnapshot.cs @@ -171,6 +171,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsConcurrencyToken() .HasColumnType("nvarchar(max)"); + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(21) + .HasColumnType("nvarchar(21)"); + b.Property("Email") .HasMaxLength(256) .HasColumnType("nvarchar(256)"); @@ -232,6 +237,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasFilter("[NormalizedUserName] IS NOT NULL"); b.ToTable("AspNetUsers", (string)null); + + b.HasDiscriminator().HasValue("ApplicationUser"); + + b.UseTphMappingStrategy(); }); modelBuilder.Entity("SpeakingInBitsWeb.Models.Course", b => @@ -246,6 +255,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(8) .HasColumnType("nvarchar(8)"); + b.Property("CourseInstructorId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + b.Property("Description") .HasMaxLength(300) .HasColumnType("nvarchar(300)"); @@ -257,9 +270,22 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); + b.HasIndex("CourseInstructorId"); + b.ToTable("Courses"); }); + modelBuilder.Entity("SpeakingInBitsWeb.Models.Instructor", b => + { + b.HasBaseType("SpeakingInBitsWeb.Models.ApplicationUser"); + + b.Property("OfficeHours") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasDiscriminator().HasValue("Instructor"); + }); + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => { b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) @@ -310,6 +336,22 @@ protected override void BuildModel(ModelBuilder modelBuilder) .OnDelete(DeleteBehavior.Cascade) .IsRequired(); }); + + modelBuilder.Entity("SpeakingInBitsWeb.Models.Course", b => + { + b.HasOne("SpeakingInBitsWeb.Models.Instructor", "CourseInstructor") + .WithMany("Courses") + .HasForeignKey("CourseInstructorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CourseInstructor"); + }); + + modelBuilder.Entity("SpeakingInBitsWeb.Models.Instructor", b => + { + b.Navigation("Courses"); + }); #pragma warning restore 612, 618 } } diff --git a/SpeakingInBitsWeb/Models/ApplicationUser.cs b/SpeakingInBitsWeb/Models/ApplicationUser.cs deleted file mode 100644 index 7d60de3..0000000 --- a/SpeakingInBitsWeb/Models/ApplicationUser.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.AspNetCore.Identity; - -namespace SpeakingInBitsWeb.Models -{ - /// - /// Represents a custom application user with additional profile information. - /// - /// Use this class to store and manage user-specific data beyond the default identity - /// fields. - public class ApplicationUser : IdentityUser - { - /// - /// The user's legal first name - /// - public required string FirstName { get; set; } - - /// - /// The user's legal last name - /// - public required string LastName { get; set; } - } -} diff --git a/SpeakingInBitsWeb/Models/Course.cs b/SpeakingInBitsWeb/Models/Course.cs index a6dd453..39306f1 100644 --- a/SpeakingInBitsWeb/Models/Course.cs +++ b/SpeakingInBitsWeb/Models/Course.cs @@ -36,4 +36,9 @@ public class Course /// [StringLength(300)] public string? Description { get; set; } + + /// + /// The instructor for the course + /// + public required Instructor CourseInstructor { get; set; } } diff --git a/SpeakingInBitsWeb/Models/CustomUsers.cs b/SpeakingInBitsWeb/Models/CustomUsers.cs new file mode 100644 index 0000000..5391815 --- /dev/null +++ b/SpeakingInBitsWeb/Models/CustomUsers.cs @@ -0,0 +1,36 @@ +using Microsoft.AspNetCore.Identity; +using System.ComponentModel.DataAnnotations; + +namespace SpeakingInBitsWeb.Models; + +/// +/// Represents a custom application user with additional profile information. +/// +/// Use this class to store and manage user-specific data beyond the default identity +/// fields. +public class ApplicationUser : IdentityUser +{ + /// + /// The user's legal first name + /// + public required string FirstName { get; set; } + + /// + /// The user's legal last name + /// + public required string LastName { get; set; } +} + +public class Instructor : ApplicationUser +{ + /// + /// Navigation property for courses taught by this instructor. + /// + public ICollection Courses { get; set; } = []; + + /// + /// The days/hours the instructor is available + /// + [StringLength(200)] + public string? OfficeHours { get; set; } +} \ No newline at end of file