diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/DataStore/Postgres/Migrations/20231214153052_RenameEventSourceUserId.Designer.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/DataStore/Postgres/Migrations/20231214153052_RenameEventSourceUserId.Designer.cs new file mode 100644 index 000000000..8e8737182 --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/DataStore/Postgres/Migrations/20231214153052_RenameEventSourceUserId.Designer.cs @@ -0,0 +1,805 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using TeachingRecordSystem.Core.DataStore.Postgres; + +#nullable disable + +namespace TeachingRecordSystem.Core.DataStore.Postgres.Migrations +{ + [DbContext(typeof(TrsDbContext))] + [Migration("20231214153052_RenameEventSourceUserId")] + partial class RenameEventSourceUserId + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("TeachingRecordSystem.Core.DataStore.Postgres.Models.EntityChangesJournal", b => + { + b.Property("Key") + .HasColumnType("text") + .HasColumnName("key"); + + b.Property("EntityLogicalName") + .HasColumnType("text") + .HasColumnName("entity_logical_name"); + + b.Property("DataToken") + .HasColumnType("text") + .HasColumnName("data_token"); + + b.Property("LastUpdated") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_updated"); + + b.Property("LastUpdatedBy") + .HasColumnType("text") + .HasColumnName("last_updated_by"); + + b.Property("NextQueryPageNumber") + .HasColumnType("integer") + .HasColumnName("next_query_page_number"); + + b.Property("NextQueryPageSize") + .HasColumnType("integer") + .HasColumnName("next_query_page_size"); + + b.Property("NextQueryPagingCookie") + .HasColumnType("text") + .HasColumnName("next_query_paging_cookie"); + + b.HasKey("Key", "EntityLogicalName") + .HasName("pk_entity_changes_journals"); + + b.ToTable("entity_changes_journals", (string)null); + }); + + modelBuilder.Entity("TeachingRecordSystem.Core.DataStore.Postgres.Models.Event", b => + { + b.Property("EventId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("event_id"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("created"); + + b.Property("EventName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("event_name"); + + b.Property("Payload") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("payload"); + + b.Property("Published") + .HasColumnType("boolean") + .HasColumnName("published"); + + b.HasKey("EventId") + .HasName("pk_events"); + + b.HasIndex("Payload") + .HasDatabaseName("ix_events_payload"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Payload"), "gin"); + + b.ToTable("events", (string)null); + }); + + modelBuilder.Entity("TeachingRecordSystem.Core.DataStore.Postgres.Models.EytsAwardedEmailsJob", b => + { + b.Property("EytsAwardedEmailsJobId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("eyts_awarded_emails_job_id"); + + b.Property("AwardedToUtc") + .HasColumnType("timestamp with time zone") + .HasColumnName("awarded_to_utc"); + + b.Property("ExecutedUtc") + .HasColumnType("timestamp with time zone") + .HasColumnName("executed_utc"); + + b.HasKey("EytsAwardedEmailsJobId") + .HasName("pk_eyts_awarded_emails_jobs"); + + b.HasIndex("ExecutedUtc") + .HasDatabaseName("ix_eyts_awarded_emails_jobs_executed_utc"); + + b.ToTable("eyts_awarded_emails_jobs", (string)null); + }); + + modelBuilder.Entity("TeachingRecordSystem.Core.DataStore.Postgres.Models.EytsAwardedEmailsJobItem", b => + { + b.Property("EytsAwardedEmailsJobId") + .HasColumnType("uuid") + .HasColumnName("eyts_awarded_emails_job_id"); + + b.Property("PersonId") + .HasColumnType("uuid") + .HasColumnName("person_id"); + + b.Property("EmailAddress") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("email_address"); + + b.Property("EmailSent") + .HasColumnType("boolean") + .HasColumnName("email_sent"); + + b.Property("Personalization") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("personalization"); + + b.Property("Trn") + .IsRequired() + .HasMaxLength(7) + .HasColumnType("character(7)") + .HasColumnName("trn") + .IsFixedLength(); + + b.HasKey("EytsAwardedEmailsJobId", "PersonId") + .HasName("pk_eyts_awarded_emails_job_items"); + + b.HasIndex("Personalization") + .HasDatabaseName("ix_eyts_awarded_emails_job_items_personalization"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Personalization"), "gin"); + + b.ToTable("eyts_awarded_emails_job_items", (string)null); + }); + + modelBuilder.Entity("TeachingRecordSystem.Core.DataStore.Postgres.Models.InductionCompletedEmailsJob", b => + { + b.Property("InductionCompletedEmailsJobId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("induction_completed_emails_job_id"); + + b.Property("AwardedToUtc") + .HasColumnType("timestamp with time zone") + .HasColumnName("awarded_to_utc"); + + b.Property("ExecutedUtc") + .HasColumnType("timestamp with time zone") + .HasColumnName("executed_utc"); + + b.HasKey("InductionCompletedEmailsJobId") + .HasName("pk_induction_completed_emails_jobs"); + + b.HasIndex("ExecutedUtc") + .HasDatabaseName("ix_induction_completed_emails_jobs_executed_utc"); + + b.ToTable("induction_completed_emails_jobs", (string)null); + }); + + modelBuilder.Entity("TeachingRecordSystem.Core.DataStore.Postgres.Models.InductionCompletedEmailsJobItem", b => + { + b.Property("InductionCompletedEmailsJobId") + .HasColumnType("uuid") + .HasColumnName("induction_completed_emails_job_id"); + + b.Property("PersonId") + .HasColumnType("uuid") + .HasColumnName("person_id"); + + b.Property("EmailAddress") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("email_address"); + + b.Property("EmailSent") + .HasColumnType("boolean") + .HasColumnName("email_sent"); + + b.Property("Personalization") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("personalization"); + + b.Property("Trn") + .IsRequired() + .HasMaxLength(7) + .HasColumnType("character(7)") + .HasColumnName("trn") + .IsFixedLength(); + + b.HasKey("InductionCompletedEmailsJobId", "PersonId") + .HasName("pk_induction_completed_emails_job_items"); + + b.HasIndex("Personalization") + .HasDatabaseName("ix_induction_completed_emails_job_items_personalization"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Personalization"), "gin"); + + b.ToTable("induction_completed_emails_job_items", (string)null); + }); + + modelBuilder.Entity("TeachingRecordSystem.Core.DataStore.Postgres.Models.InternationalQtsAwardedEmailsJob", b => + { + b.Property("InternationalQtsAwardedEmailsJobId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("international_qts_awarded_emails_job_id"); + + b.Property("AwardedToUtc") + .HasColumnType("timestamp with time zone") + .HasColumnName("awarded_to_utc"); + + b.Property("ExecutedUtc") + .HasColumnType("timestamp with time zone") + .HasColumnName("executed_utc"); + + b.HasKey("InternationalQtsAwardedEmailsJobId") + .HasName("pk_international_qts_awarded_emails_jobs"); + + b.HasIndex("ExecutedUtc") + .HasDatabaseName("ix_international_qts_awarded_emails_jobs_executed_utc"); + + b.ToTable("international_qts_awarded_emails_jobs", (string)null); + }); + + modelBuilder.Entity("TeachingRecordSystem.Core.DataStore.Postgres.Models.InternationalQtsAwardedEmailsJobItem", b => + { + b.Property("InternationalQtsAwardedEmailsJobId") + .HasColumnType("uuid") + .HasColumnName("international_qts_awarded_emails_job_id"); + + b.Property("PersonId") + .HasColumnType("uuid") + .HasColumnName("person_id"); + + b.Property("EmailAddress") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("email_address"); + + b.Property("EmailSent") + .HasColumnType("boolean") + .HasColumnName("email_sent"); + + b.Property("Personalization") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("personalization"); + + b.Property("Trn") + .IsRequired() + .HasMaxLength(7) + .HasColumnType("character(7)") + .HasColumnName("trn") + .IsFixedLength(); + + b.HasKey("InternationalQtsAwardedEmailsJobId", "PersonId") + .HasName("pk_international_qts_awarded_emails_job_items"); + + b.HasIndex("Personalization") + .HasDatabaseName("ix_international_qts_awarded_emails_job_items_personalization"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Personalization"), "gin"); + + b.ToTable("international_qts_awarded_emails_job_items", (string)null); + }); + + modelBuilder.Entity("TeachingRecordSystem.Core.DataStore.Postgres.Models.JourneyState", b => + { + b.Property("InstanceId") + .HasMaxLength(300) + .HasColumnType("character varying(300)") + .HasColumnName("instance_id"); + + b.Property("Completed") + .HasColumnType("timestamp with time zone") + .HasColumnName("completed"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("created"); + + b.Property("State") + .IsRequired() + .HasColumnType("text") + .HasColumnName("state"); + + b.Property("Updated") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("InstanceId") + .HasName("pk_journey_states"); + + b.ToTable("journey_states", (string)null); + }); + + modelBuilder.Entity("TeachingRecordSystem.Core.DataStore.Postgres.Models.Person", b => + { + b.Property("PersonId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("person_id"); + + b.Property("DateOfBirth") + .HasColumnType("date") + .HasColumnName("date_of_birth"); + + b.Property("DqtContactId") + .HasColumnType("uuid") + .HasColumnName("dqt_contact_id"); + + b.Property("DqtCreatedOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("dqt_created_on"); + + b.Property("DqtFirstName") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("dqt_first_name") + .UseCollation("case_insensitive"); + + b.Property("DqtFirstSync") + .HasColumnType("timestamp with time zone") + .HasColumnName("dqt_first_sync"); + + b.Property("DqtLastName") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("dqt_last_name") + .UseCollation("case_insensitive"); + + b.Property("DqtLastSync") + .HasColumnType("timestamp with time zone") + .HasColumnName("dqt_last_sync"); + + b.Property("DqtMiddleName") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("dqt_middle_name") + .UseCollation("case_insensitive"); + + b.Property("DqtModifiedOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("dqt_modified_on"); + + b.Property("DqtState") + .HasColumnType("integer") + .HasColumnName("dqt_state"); + + b.Property("EmailAddress") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("email_address") + .UseCollation("case_insensitive"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("first_name") + .UseCollation("case_insensitive"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("last_name") + .UseCollation("case_insensitive"); + + b.Property("MiddleName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("middle_name") + .UseCollation("case_insensitive"); + + b.Property("NationalInsuranceNumber") + .HasMaxLength(9) + .HasColumnType("character(9)") + .HasColumnName("national_insurance_number") + .IsFixedLength(); + + b.Property("Trn") + .IsRequired() + .HasMaxLength(7) + .HasColumnType("character(7)") + .HasColumnName("trn") + .IsFixedLength(); + + b.HasKey("PersonId") + .HasName("pk_persons"); + + b.HasIndex("DqtContactId") + .IsUnique() + .HasDatabaseName("ix_persons_dqt_contact_id") + .HasFilter("dqt_contact_id is not null"); + + b.HasIndex("Trn") + .IsUnique() + .HasDatabaseName("ix_persons_trn") + .HasFilter("trn is not null"); + + b.ToTable("persons", (string)null); + }); + + modelBuilder.Entity("TeachingRecordSystem.Core.DataStore.Postgres.Models.QtsAwardedEmailsJob", b => + { + b.Property("QtsAwardedEmailsJobId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("qts_awarded_emails_job_id"); + + b.Property("AwardedToUtc") + .HasColumnType("timestamp with time zone") + .HasColumnName("awarded_to_utc"); + + b.Property("ExecutedUtc") + .HasColumnType("timestamp with time zone") + .HasColumnName("executed_utc"); + + b.HasKey("QtsAwardedEmailsJobId") + .HasName("pk_qts_awarded_emails_jobs"); + + b.HasIndex("ExecutedUtc") + .HasDatabaseName("ix_qts_awarded_emails_jobs_executed_utc"); + + b.ToTable("qts_awarded_emails_jobs", (string)null); + }); + + modelBuilder.Entity("TeachingRecordSystem.Core.DataStore.Postgres.Models.QtsAwardedEmailsJobItem", b => + { + b.Property("QtsAwardedEmailsJobId") + .HasColumnType("uuid") + .HasColumnName("qts_awarded_emails_job_id"); + + b.Property("PersonId") + .HasColumnType("uuid") + .HasColumnName("person_id"); + + b.Property("EmailAddress") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("email_address"); + + b.Property("EmailSent") + .HasColumnType("boolean") + .HasColumnName("email_sent"); + + b.Property("Personalization") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("personalization"); + + b.Property("Trn") + .IsRequired() + .HasMaxLength(7) + .HasColumnType("character(7)") + .HasColumnName("trn") + .IsFixedLength(); + + b.HasKey("QtsAwardedEmailsJobId", "PersonId") + .HasName("pk_qts_awarded_emails_job_items"); + + b.HasIndex("Personalization") + .HasDatabaseName("ix_qts_awarded_emails_job_items_personalization"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Personalization"), "gin"); + + b.ToTable("qts_awarded_emails_job_items", (string)null); + }); + + modelBuilder.Entity("TeachingRecordSystem.Core.DataStore.Postgres.Models.Qualification", b => + { + b.Property("QualificationId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("qualification_id"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_on"); + + b.Property("DeletedOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_on"); + + b.Property("DqtCreatedOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("dqt_created_on"); + + b.Property("DqtFirstSync") + .HasColumnType("timestamp with time zone") + .HasColumnName("dqt_first_sync"); + + b.Property("DqtLastSync") + .HasColumnType("timestamp with time zone") + .HasColumnName("dqt_last_sync"); + + b.Property("DqtModifiedOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("dqt_modified_on"); + + b.Property("DqtQualificationId") + .HasColumnType("uuid") + .HasColumnName("dqt_qualification_id"); + + b.Property("DqtState") + .HasColumnType("integer") + .HasColumnName("dqt_state"); + + b.Property("PersonId") + .HasColumnType("uuid") + .HasColumnName("person_id"); + + b.Property("QualificationType") + .HasColumnType("integer") + .HasColumnName("qualification_type"); + + b.Property("UpdatedOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_on"); + + b.HasKey("QualificationId") + .HasName("pk_qualifications"); + + b.HasIndex("DqtQualificationId") + .IsUnique() + .HasDatabaseName("ix_qualifications_dqt_qualification_id") + .HasFilter("dqt_qualification_id is not null"); + + b.HasIndex("PersonId") + .HasDatabaseName("ix_qualifications_person_id"); + + b.ToTable("qualifications", (string)null); + + b.HasDiscriminator("QualificationType"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("TeachingRecordSystem.Core.DataStore.Postgres.Models.TrnRequest", b => + { + b.Property("TrnRequestId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("trn_request_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TrnRequestId")); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("client_id"); + + b.Property("IdentityUserId") + .HasColumnType("uuid") + .HasColumnName("identity_user_id"); + + b.Property("LinkedToIdentity") + .HasColumnType("boolean") + .HasColumnName("linked_to_identity"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("request_id"); + + b.Property("TeacherId") + .HasColumnType("uuid") + .HasColumnName("teacher_id"); + + b.Property("TrnToken") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("trn_token"); + + b.HasKey("TrnRequestId") + .HasName("pk_trn_requests"); + + b.HasIndex("ClientId", "RequestId") + .IsUnique() + .HasDatabaseName("ix_trn_requests_client_id_request_id"); + + b.ToTable("trn_requests", (string)null); + }); + + modelBuilder.Entity("TeachingRecordSystem.Core.DataStore.Postgres.Models.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Active") + .HasColumnType("boolean") + .HasColumnName("active"); + + b.Property("AzureAdUserId") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("azure_ad_user_id"); + + b.Property("DqtUserId") + .HasColumnType("uuid") + .HasColumnName("dqt_user_id"); + + b.Property("Email") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("email") + .UseCollation("case_insensitive"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("name"); + + b.Property("Roles") + .IsRequired() + .HasColumnType("varchar[]") + .HasColumnName("roles"); + + b.Property("UserType") + .HasColumnType("integer") + .HasColumnName("user_type"); + + b.HasKey("UserId") + .HasName("pk_users"); + + b.HasIndex("AzureAdUserId") + .IsUnique() + .HasDatabaseName("ix_users_azure_ad_user_id"); + + b.ToTable("users", (string)null); + + b.HasData( + new + { + UserId = new Guid("a81394d1-a498-46d8-af3e-e077596ab303"), + Active = true, + Name = "System", + Roles = new[] { "Administrator" }, + UserType = 2 + }); + }); + + modelBuilder.Entity("TeachingRecordSystem.Core.DataStore.Postgres.Models.MandatoryQualification", b => + { + b.HasBaseType("TeachingRecordSystem.Core.DataStore.Postgres.Models.Qualification"); + + b.Property("DqtMqEstablishmentId") + .HasColumnType("uuid") + .HasColumnName("dqt_mq_establishment_id"); + + b.Property("DqtSpecialismId") + .HasColumnType("uuid") + .HasColumnName("dqt_specialism_id"); + + b.Property("EndDate") + .HasColumnType("date") + .HasColumnName("end_date"); + + b.Property("Specialism") + .HasColumnType("integer") + .HasColumnName("mq_specialism"); + + b.Property("StartDate") + .HasColumnType("date") + .HasColumnName("start_date"); + + b.Property("Status") + .HasColumnType("integer") + .HasColumnName("mq_status"); + + b.HasDiscriminator().HasValue(0); + }); + + modelBuilder.Entity("TeachingRecordSystem.Core.DataStore.Postgres.Models.EytsAwardedEmailsJobItem", b => + { + b.HasOne("TeachingRecordSystem.Core.DataStore.Postgres.Models.EytsAwardedEmailsJob", "EytsAwardedEmailsJob") + .WithMany("JobItems") + .HasForeignKey("EytsAwardedEmailsJobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_eyts_awarded_emails_job_items_eyts_awarded_emails_jobs_eyts"); + + b.Navigation("EytsAwardedEmailsJob"); + }); + + modelBuilder.Entity("TeachingRecordSystem.Core.DataStore.Postgres.Models.InductionCompletedEmailsJobItem", b => + { + b.HasOne("TeachingRecordSystem.Core.DataStore.Postgres.Models.InductionCompletedEmailsJob", "InductionCompletedEmailsJob") + .WithMany("JobItems") + .HasForeignKey("InductionCompletedEmailsJobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_induction_completed_emails_job_items_induction_completed_em"); + + b.Navigation("InductionCompletedEmailsJob"); + }); + + modelBuilder.Entity("TeachingRecordSystem.Core.DataStore.Postgres.Models.InternationalQtsAwardedEmailsJobItem", b => + { + b.HasOne("TeachingRecordSystem.Core.DataStore.Postgres.Models.InternationalQtsAwardedEmailsJob", "InternationalQtsAwardedEmailsJob") + .WithMany("JobItems") + .HasForeignKey("InternationalQtsAwardedEmailsJobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_international_qts_awarded_emails_job_items_international_qt"); + + b.Navigation("InternationalQtsAwardedEmailsJob"); + }); + + modelBuilder.Entity("TeachingRecordSystem.Core.DataStore.Postgres.Models.QtsAwardedEmailsJobItem", b => + { + b.HasOne("TeachingRecordSystem.Core.DataStore.Postgres.Models.QtsAwardedEmailsJob", "QtsAwardedEmailsJob") + .WithMany("JobItems") + .HasForeignKey("QtsAwardedEmailsJobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_qts_awarded_emails_job_items_qts_awarded_emails_jobs_qts_aw"); + + b.Navigation("QtsAwardedEmailsJob"); + }); + + modelBuilder.Entity("TeachingRecordSystem.Core.DataStore.Postgres.Models.Qualification", b => + { + b.HasOne("TeachingRecordSystem.Core.DataStore.Postgres.Models.Person", null) + .WithMany() + .HasForeignKey("PersonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_qualifications_person"); + }); + + modelBuilder.Entity("TeachingRecordSystem.Core.DataStore.Postgres.Models.EytsAwardedEmailsJob", b => + { + b.Navigation("JobItems"); + }); + + modelBuilder.Entity("TeachingRecordSystem.Core.DataStore.Postgres.Models.InductionCompletedEmailsJob", b => + { + b.Navigation("JobItems"); + }); + + modelBuilder.Entity("TeachingRecordSystem.Core.DataStore.Postgres.Models.InternationalQtsAwardedEmailsJob", b => + { + b.Navigation("JobItems"); + }); + + modelBuilder.Entity("TeachingRecordSystem.Core.DataStore.Postgres.Models.QtsAwardedEmailsJob", b => + { + b.Navigation("JobItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/DataStore/Postgres/Migrations/20231214153052_RenameEventSourceUserId.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/DataStore/Postgres/Migrations/20231214153052_RenameEventSourceUserId.cs new file mode 100644 index 000000000..28f031675 --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/DataStore/Postgres/Migrations/20231214153052_RenameEventSourceUserId.cs @@ -0,0 +1,32 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace TeachingRecordSystem.Core.DataStore.Postgres.Migrations +{ + /// + public partial class RenameEventSourceUserId : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql( + """ + update events set + payload = jsonb_set(payload, array['RaisedBy'], to_jsonb(payload->>'SourceUserId'::text), true) - 'SourceUserId' + where payload->>'RaisedBy' is null; + """); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql( + """ + update events set + payload = jsonb_set(payload, array['SourceUserId'], to_jsonb(payload->>'RaisedBy'::text), true) - 'RaisedBy' + where payload->>'SourceUserId' is null; + """); + } + } +} diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Events/EventBase.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Events/EventBase.cs index 7713962db..a1a1926b0 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Events/EventBase.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Events/EventBase.cs @@ -1,4 +1,5 @@ using System.Text.Json; +using TeachingRecordSystem.Core.Events.Models; namespace TeachingRecordSystem.Core.Events; @@ -8,5 +9,5 @@ public abstract record EventBase public required Guid EventId { get; init; } public required DateTime CreatedUtc { get; init; } - public required Guid SourceUserId { get; init; } + public required RaisedByUserInfo RaisedBy { get; init; } } diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Events/Models/RaisedByUserInfo.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Events/Models/RaisedByUserInfo.cs new file mode 100644 index 000000000..21e064b12 --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Events/Models/RaisedByUserInfo.cs @@ -0,0 +1,77 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace TeachingRecordSystem.Core.Events.Models; + +/// +/// Represents the user who raised the event. +/// Contains either a TRS user ID or the DQT user ID and name. +/// +[JsonConverter(typeof(RaisedByUserInfoJsonConverter))] +public sealed class RaisedByUserInfo +{ + private RaisedByUserInfo() { } + + public Guid? UserId { get; private set; } + + public Guid? DqtUserId { get; private set; } + public string? DqtUserName { get; private set; } + + [MemberNotNullWhen(true, nameof(DqtUserId), nameof(DqtUserName))] + [MemberNotNullWhen(false, nameof(UserId))] + public bool IsDqtUser => DqtUserId.HasValue; + + public static implicit operator RaisedByUserInfo(Guid userId) => FromUserId(userId); + + public static RaisedByUserInfo FromUserId(Guid userId) => new() + { + UserId = userId + }; + + public static RaisedByUserInfo FromDqtUser(Guid dqtUserId, string dqtUserName) => new() + { + DqtUserId = dqtUserId, + DqtUserName = dqtUserName + }; +} + +public class RaisedByUserInfoJsonConverter : JsonConverter +{ + public override RaisedByUserInfo? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.StartObject) + { + var dqtUserInfo = JsonSerializer.Deserialize(ref reader, options)!; + return RaisedByUserInfo.FromDqtUser(dqtUserInfo.DqtUserId, dqtUserInfo.DqtUserName); + } + else + { + var userId = reader.GetGuid(); + return RaisedByUserInfo.FromUserId(userId); + } + } + + public override void Write(Utf8JsonWriter writer, RaisedByUserInfo value, JsonSerializerOptions options) + { + if (value.IsDqtUser) + { + var dqtUserInfo = new DqtUserInfo() + { + DqtUserId = value.DqtUserId.Value, + DqtUserName = value.DqtUserName + }; + JsonSerializer.Serialize(writer, dqtUserInfo, options); + } + else + { + writer.WriteStringValue(value.UserId.ToString()); + } + } + + private sealed class DqtUserInfo + { + public required Guid DqtUserId { get; init; } + public required string DqtUserName { get; init; } + } +} diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Events/User.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Events/Models/User.cs similarity index 92% rename from TeachingRecordSystem/src/TeachingRecordSystem.Core/Events/User.cs rename to TeachingRecordSystem/src/TeachingRecordSystem.Core/Events/Models/User.cs index bacbd204f..c87287eaa 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Events/User.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Events/Models/User.cs @@ -1,4 +1,4 @@ -namespace TeachingRecordSystem.Core.Events; +namespace TeachingRecordSystem.Core.Events.Models; public record User { diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Events/UserActivatedEvent.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Events/UserActivatedEvent.cs index 9b225e8fd..8ab97e252 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Events/UserActivatedEvent.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Events/UserActivatedEvent.cs @@ -1,3 +1,5 @@ +using TeachingRecordSystem.Core.Events.Models; + namespace TeachingRecordSystem.Core.Events; public record UserActivatedEvent : EventBase diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Events/UserAddedEvent.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Events/UserAddedEvent.cs index 136ae1fe2..6e344db34 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Events/UserAddedEvent.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Events/UserAddedEvent.cs @@ -1,3 +1,5 @@ +using TeachingRecordSystem.Core.Events.Models; + namespace TeachingRecordSystem.Core.Events; public record UserAddedEvent : EventBase diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Events/UserDeactivatedEvent.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Events/UserDeactivatedEvent.cs index dc7fc4e2d..945e47d01 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Events/UserDeactivatedEvent.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Events/UserDeactivatedEvent.cs @@ -1,3 +1,5 @@ +using TeachingRecordSystem.Core.Events.Models; + namespace TeachingRecordSystem.Core.Events; public record UserDeactivatedEvent : EventBase diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Events/UserUpdatedEvent.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Events/UserUpdatedEvent.cs index 06b9cd292..5329d1f62 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Events/UserUpdatedEvent.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Events/UserUpdatedEvent.cs @@ -1,3 +1,5 @@ +using TeachingRecordSystem.Core.Events.Models; + namespace TeachingRecordSystem.Core.Events; public record UserUpdatedEvent : EventBase diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Jobs/SendEytsAwardedEmailJob.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Jobs/SendEytsAwardedEmailJob.cs index 74961c9db..88395587e 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Jobs/SendEytsAwardedEmailJob.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Jobs/SendEytsAwardedEmailJob.cs @@ -59,7 +59,7 @@ public async Task Execute(Guid eytsAwardedEmailsJobId, Guid personId) PersonId = personId, EmailAddress = item.EmailAddress, CreatedUtc = _clock.UtcNow, - SourceUserId = DataStore.Postgres.Models.User.SystemUserId + RaisedBy = DataStore.Postgres.Models.User.SystemUserId }); await _dbContext.SaveChangesAsync(); diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Jobs/SendInductionCompletedEmailJob.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Jobs/SendInductionCompletedEmailJob.cs index 78a673df3..80a8ce647 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Jobs/SendInductionCompletedEmailJob.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Jobs/SendInductionCompletedEmailJob.cs @@ -59,7 +59,7 @@ public async Task Execute(Guid inductionCompletedEmailsJobId, Guid personId) PersonId = personId, EmailAddress = item.EmailAddress, CreatedUtc = _clock.UtcNow, - SourceUserId = DataStore.Postgres.Models.User.SystemUserId + RaisedBy = DataStore.Postgres.Models.User.SystemUserId }); await _dbContext.SaveChangesAsync(); diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Jobs/SendInternationalQtsAwardedEmailJob.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Jobs/SendInternationalQtsAwardedEmailJob.cs index ff51411d1..177be0d9f 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Jobs/SendInternationalQtsAwardedEmailJob.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Jobs/SendInternationalQtsAwardedEmailJob.cs @@ -59,7 +59,7 @@ public async Task Execute(Guid internationalQtsAwardedEmailsJobId, Guid personId PersonId = personId, EmailAddress = item.EmailAddress, CreatedUtc = _clock.UtcNow, - SourceUserId = DataStore.Postgres.Models.User.SystemUserId + RaisedBy = DataStore.Postgres.Models.User.SystemUserId }); await _dbContext.SaveChangesAsync(); diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Jobs/SendQtsAwardedEmailJob.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Jobs/SendQtsAwardedEmailJob.cs index 02c204857..e6a1f262f 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Jobs/SendQtsAwardedEmailJob.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Jobs/SendQtsAwardedEmailJob.cs @@ -59,7 +59,7 @@ public async Task Execute(Guid qtsAwardedEmailsJobId, Guid personId) PersonId = personId, EmailAddress = item.EmailAddress, CreatedUtc = _clock.UtcNow, - SourceUserId = DataStore.Postgres.Models.User.SystemUserId + RaisedBy = DataStore.Postgres.Models.User.SystemUserId }); await _dbContext.SaveChangesAsync(); diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Users/AddUser/Confirm.cshtml.cs b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Users/AddUser/Confirm.cshtml.cs index 7f0de3216..cb2826f36 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Users/AddUser/Confirm.cshtml.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Users/AddUser/Confirm.cshtml.cs @@ -88,8 +88,8 @@ public async Task OnPost() _dbContext.AddEvent(new UserAddedEvent() { EventId = Guid.NewGuid(), - User = Core.Events.User.FromModel(newUser), - SourceUserId = User.GetUserId(), + User = Core.Events.Models.User.FromModel(newUser), + RaisedBy = User.GetUserId(), CreatedUtc = _clock.UtcNow }); diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Users/EditUser.cshtml.cs b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Users/EditUser.cshtml.cs index 1f5eb0e5a..67a5ef62b 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Users/EditUser.cshtml.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Users/EditUser.cshtml.cs @@ -103,8 +103,8 @@ public async Task OnPost() _dbContext.AddEvent(new UserUpdatedEvent { EventId = Guid.NewGuid(), - User = Core.Events.User.FromModel(user), - SourceUserId = User.GetUserId(), + User = Core.Events.Models.User.FromModel(user), + RaisedBy = User.GetUserId(), CreatedUtc = _clock.UtcNow, Changes = changes }); @@ -130,8 +130,8 @@ public async Task OnPostDeactivate() _dbContext.AddEvent(new UserDeactivatedEvent { EventId = Guid.NewGuid(), - User = Core.Events.User.FromModel(user), - SourceUserId = User.GetUserId(), + User = Core.Events.Models.User.FromModel(user), + RaisedBy = User.GetUserId(), CreatedUtc = _clock.UtcNow }); @@ -155,8 +155,8 @@ public async Task OnPostActivate() _dbContext.AddEvent(new UserActivatedEvent { EventId = Guid.NewGuid(), - User = Core.Events.User.FromModel(user), - SourceUserId = User.GetUserId(), + User = Core.Events.Models.User.FromModel(user), + RaisedBy = User.GetUserId(), CreatedUtc = _clock.UtcNow }); diff --git a/TeachingRecordSystem/tests/TeachingRecordSystem.Core.Tests/EventInfoTests.cs b/TeachingRecordSystem/tests/TeachingRecordSystem.Core.Tests/EventInfoTests.cs index da8916bda..45878b233 100644 --- a/TeachingRecordSystem/tests/TeachingRecordSystem.Core.Tests/EventInfoTests.cs +++ b/TeachingRecordSystem/tests/TeachingRecordSystem.Core.Tests/EventInfoTests.cs @@ -1,4 +1,5 @@ using TeachingRecordSystem.Core.Events; +using TeachingRecordSystem.Core.Events.Models; using TeachingRecordSystem.Core.Models; namespace TeachingRecordSystem.Core.Tests; @@ -13,7 +14,7 @@ public void EventSerializesCorrectly() { EventId = Guid.NewGuid(), CreatedUtc = DateTime.UtcNow, - SourceUserId = DataStore.Postgres.Models.User.SystemUserId, + RaisedBy = DataStore.Postgres.Models.User.SystemUserId, User = new() { AzureAdUserId = "ad-user-id", @@ -34,7 +35,7 @@ public void EventSerializesCorrectly() // Assert var roundTripped = Assert.IsType>(deserialized); Assert.Equal(e.CreatedUtc, roundTripped.Event.CreatedUtc); - Assert.Equal(e.SourceUserId, roundTripped.Event.SourceUserId); + Assert.Equal(e.RaisedBy.UserId, roundTripped.Event.RaisedBy.UserId); Assert.Equal(e.User.AzureAdUserId, roundTripped.Event.User.AzureAdUserId); Assert.Equal(e.User.Email, roundTripped.Event.User.Email); Assert.Equal(e.User.Name, roundTripped.Event.User.Name); @@ -42,4 +43,36 @@ public void EventSerializesCorrectly() Assert.Equal(e.User.UserId, roundTripped.Event.User.UserId); Assert.Equal(e.User.UserType, roundTripped.Event.User.UserType); } + + [Fact] + public void EventWithDqtUserIdSerializesRaisedByCorrectly() + { + // Arrange + var @e = new UserActivatedEvent() + { + EventId = Guid.NewGuid(), + CreatedUtc = DateTime.UtcNow, + RaisedBy = RaisedByUserInfo.FromDqtUser(Guid.NewGuid(), "A DQT User"), + User = new() + { + AzureAdUserId = "ad-user-id", + Email = "test.user@place.com", + Name = "Test User", + Roles = ["Administrator"], + UserId = Guid.NewGuid(), + UserType = UserType.Person + } + }; + + var eventInfo = EventInfo.Create(@e); + + // Act + var serialized = eventInfo.Serialize(); + var deserialized = EventInfo.Deserialize(serialized); + + // Assert + var roundTripped = Assert.IsType>(deserialized); + Assert.Equal(e.RaisedBy.DqtUserId, roundTripped.Event.RaisedBy.DqtUserId); + Assert.Equal(e.RaisedBy.DqtUserName, roundTripped.Event.RaisedBy.DqtUserName); + } } diff --git a/TeachingRecordSystem/tests/TeachingRecordSystem.Core.Tests/Events/Processing/PublishEventsBackgroundServiceTests.cs b/TeachingRecordSystem/tests/TeachingRecordSystem.Core.Tests/Events/Processing/PublishEventsBackgroundServiceTests.cs index ce847e88d..ea384a864 100644 --- a/TeachingRecordSystem/tests/TeachingRecordSystem.Core.Tests/Events/Processing/PublishEventsBackgroundServiceTests.cs +++ b/TeachingRecordSystem/tests/TeachingRecordSystem.Core.Tests/Events/Processing/PublishEventsBackgroundServiceTests.cs @@ -101,7 +101,7 @@ public async Task PublishEvents_EventObserverThrows_DoesNotThrow() EventId = Guid.NewGuid(), DummyProperty = Faker.Name.FullName(), CreatedUtc = TestableClock.Initial.ToUniversalTime(), - SourceUserId = DataStore.Postgres.Models.User.SystemUserId + RaisedBy = User.SystemUserId }; private class TestableEventObserver : IEventObserver diff --git a/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Users/AddUser/ConfirmTests.cs b/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Users/AddUser/ConfirmTests.cs index ecd26418c..93875ec4f 100644 --- a/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Users/AddUser/ConfirmTests.cs +++ b/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Users/AddUser/ConfirmTests.cs @@ -246,7 +246,7 @@ public async Task Post_ValidRequest_CreatesUserEmitsEventAndRedirectsWithFlashMe { var userCreatedEvent = Assert.IsType(e); Assert.Equal(Clock.UtcNow, userCreatedEvent.CreatedUtc); - Assert.Equal(userCreatedEvent.SourceUserId, GetCurrentUserId()); + Assert.Equal(userCreatedEvent.RaisedBy.UserId, GetCurrentUserId()); Assert.Equal(UserType.Person, userCreatedEvent.User.UserType); Assert.Equal(newName, userCreatedEvent.User.Name); Assert.Equal(email, userCreatedEvent.User.Email); diff --git a/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Users/EditUserTests.cs b/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Users/EditUserTests.cs index fd1be26f4..38c41ee56 100644 --- a/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Users/EditUserTests.cs +++ b/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Users/EditUserTests.cs @@ -194,7 +194,7 @@ public async Task Post_ValidRequest_CreatesUserEmitsEventAndRedirectsWithFlashMe { var userCreatedEvent = Assert.IsType(e); Assert.Equal(Clock.UtcNow, userCreatedEvent.CreatedUtc); - Assert.Equal(userCreatedEvent.SourceUserId, GetCurrentUserId()); + Assert.Equal(userCreatedEvent.RaisedBy.UserId, GetCurrentUserId()); Assert.Equal(UserType.Person, userCreatedEvent.User.UserType); Assert.Equal(newName, userCreatedEvent.User.Name); Assert.Equal(updatedUser.Email, userCreatedEvent.User.Email); @@ -247,7 +247,7 @@ public async Task Post_ValidRequest_DeactivatesUsersEmitsEventAndRedirectsWithFl { var userCreatedEvent = Assert.IsType(e); Assert.Equal(Clock.UtcNow, userCreatedEvent.CreatedUtc); - Assert.Equal(userCreatedEvent.SourceUserId, GetCurrentUserId()); + Assert.Equal(userCreatedEvent.RaisedBy.UserId, GetCurrentUserId()); Assert.Equal(UserType.Person, userCreatedEvent.User.UserType); }); @@ -294,7 +294,7 @@ public async Task Post_ValidRequest_ActivatesUsersEmitsEventAndRedirectsWithFlas { var userCreatedEvent = Assert.IsType(e); Assert.Equal(Clock.UtcNow, userCreatedEvent.CreatedUtc); - Assert.Equal(userCreatedEvent.SourceUserId, GetCurrentUserId()); + Assert.Equal(userCreatedEvent.RaisedBy.UserId, GetCurrentUserId()); Assert.Equal(UserType.Person, userCreatedEvent.User.UserType); });