diff --git a/EmailCollector.Api/Migrations/20241212054047_AddCustomEmailTemplates.Designer.cs b/EmailCollector.Api/Migrations/20241212054047_AddCustomEmailTemplates.Designer.cs new file mode 100644 index 0000000..85604b4 --- /dev/null +++ b/EmailCollector.Api/Migrations/20241212054047_AddCustomEmailTemplates.Designer.cs @@ -0,0 +1,535 @@ +// +using System; +using EmailCollector.Api.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EmailCollector.Api.Migrations +{ + [DbContext(typeof(EmailCollectorApiContext))] + [Migration("20241212054047_AddCustomEmailTemplates")] + partial class AddCustomEmailTemplates + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.8"); + + modelBuilder.Entity("EmailCollector.Domain.Entities.ApiKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Expiration") + .HasColumnType("TEXT"); + + b.Property("IsRevoked") + .HasColumnType("INTEGER"); + + b.Property("KeyHash") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("ApiKeys"); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.CustomEmailTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Event") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FormId") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("TemplateBodyUri") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TemplateSubject") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("FormId"); + + b.ToTable("CustomEmailTemplates"); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.EmailCollectorApiUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.EmailSignup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("EmailAddress") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SignupDate") + .HasColumnType("TEXT"); + + b.Property("SignupFormId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("EmailSignups"); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.FormCorsSettings", b => + { + b.Property("FormId") + .HasColumnType("TEXT"); + + b.Property("AllowedOrigins") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("FormId"); + + b.ToTable("FormCorsSettings"); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.FormEmailSettings", b => + { + b.Property("FormId") + .HasColumnType("TEXT"); + + b.Property("EmailFrom") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("EmailMethod") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("FormId"); + + b.ToTable("FormEmailSettings"); + + b.UseTptMappingStrategy(); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.RecaptchaFormSettings", b => + { + b.Property("FormId") + .HasColumnType("TEXT"); + + b.Property("SecretKey") + .HasColumnType("TEXT"); + + b.Property("SiteKey") + .HasColumnType("TEXT"); + + b.HasKey("FormId"); + + b.ToTable("RecaptchaFormSettings"); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.SignupForm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("FormName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("SignupForms"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + 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"); + + 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("EmailCollector.Domain.Entities.SmtpEmailSettings", b => + { + b.HasBaseType("EmailCollector.Domain.Entities.FormEmailSettings"); + + b.Property("SmtpPassword") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SmtpPort") + .HasColumnType("INTEGER"); + + b.Property("SmtpServer") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SmtpUsername") + .IsRequired() + .HasColumnType("TEXT"); + + b.ToTable("SmtpEmailSettings", (string)null); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.ApiKey", b => + { + b.HasOne("EmailCollector.Domain.Entities.EmailCollectorApiUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.CustomEmailTemplates", b => + { + b.HasOne("EmailCollector.Domain.Entities.SignupForm", "Form") + .WithMany("CustomEmailTemplates") + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Form"); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.FormCorsSettings", b => + { + b.HasOne("EmailCollector.Domain.Entities.SignupForm", "Form") + .WithOne("FormCorsSettings") + .HasForeignKey("EmailCollector.Domain.Entities.FormCorsSettings", "FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Form"); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.FormEmailSettings", b => + { + b.HasOne("EmailCollector.Domain.Entities.SignupForm", "Form") + .WithOne("FormEmailSettings") + .HasForeignKey("EmailCollector.Domain.Entities.FormEmailSettings", "FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Form"); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.RecaptchaFormSettings", b => + { + b.HasOne("EmailCollector.Domain.Entities.SignupForm", "Form") + .WithOne("RecaptchaSettings") + .HasForeignKey("EmailCollector.Domain.Entities.RecaptchaFormSettings", "FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Form"); + }); + + 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("EmailCollector.Domain.Entities.EmailCollectorApiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EmailCollector.Domain.Entities.EmailCollectorApiUser", 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("EmailCollector.Domain.Entities.EmailCollectorApiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EmailCollector.Domain.Entities.EmailCollectorApiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.SmtpEmailSettings", b => + { + b.HasOne("EmailCollector.Domain.Entities.FormEmailSettings", null) + .WithOne() + .HasForeignKey("EmailCollector.Domain.Entities.SmtpEmailSettings", "FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.SignupForm", b => + { + b.Navigation("CustomEmailTemplates"); + + b.Navigation("FormCorsSettings") + .IsRequired(); + + b.Navigation("FormEmailSettings") + .IsRequired(); + + b.Navigation("RecaptchaSettings") + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/EmailCollector.Api/Migrations/20241212054047_AddCustomEmailTemplates.cs b/EmailCollector.Api/Migrations/20241212054047_AddCustomEmailTemplates.cs new file mode 100644 index 0000000..3e88e83 --- /dev/null +++ b/EmailCollector.Api/Migrations/20241212054047_AddCustomEmailTemplates.cs @@ -0,0 +1,51 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EmailCollector.Api.Migrations +{ + /// + public partial class AddCustomEmailTemplates : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "CustomEmailTemplates", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + FormId = table.Column(type: "TEXT", nullable: false), + Event = table.Column(type: "TEXT", nullable: false), + TemplateSubject = table.Column(type: "TEXT", nullable: false), + TemplateBodyUri = table.Column(type: "TEXT", nullable: false), + IsActive = table.Column(type: "INTEGER", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CustomEmailTemplates", x => x.Id); + table.ForeignKey( + name: "FK_CustomEmailTemplates_SignupForms_FormId", + column: x => x.FormId, + principalTable: "SignupForms", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_CustomEmailTemplates_FormId", + table: "CustomEmailTemplates", + column: "FormId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "CustomEmailTemplates"); + } + } +} diff --git a/EmailCollector.Api/Migrations/20241213073002_fixCustomEmailTemplateName.Designer.cs b/EmailCollector.Api/Migrations/20241213073002_fixCustomEmailTemplateName.Designer.cs new file mode 100644 index 0000000..a97e701 --- /dev/null +++ b/EmailCollector.Api/Migrations/20241213073002_fixCustomEmailTemplateName.Designer.cs @@ -0,0 +1,535 @@ +// +using System; +using EmailCollector.Api.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EmailCollector.Api.Migrations +{ + [DbContext(typeof(EmailCollectorApiContext))] + [Migration("20241213073002_fixCustomEmailTemplateName")] + partial class fixCustomEmailTemplateName + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.8"); + + modelBuilder.Entity("EmailCollector.Domain.Entities.ApiKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Expiration") + .HasColumnType("TEXT"); + + b.Property("IsRevoked") + .HasColumnType("INTEGER"); + + b.Property("KeyHash") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("ApiKeys"); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.CustomEmailTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Event") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FormId") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("TemplateBodyUri") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TemplateSubject") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("FormId"); + + b.ToTable("CustomEmailTemplates"); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.EmailCollectorApiUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.EmailSignup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("EmailAddress") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SignupDate") + .HasColumnType("TEXT"); + + b.Property("SignupFormId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("EmailSignups"); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.FormCorsSettings", b => + { + b.Property("FormId") + .HasColumnType("TEXT"); + + b.Property("AllowedOrigins") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("FormId"); + + b.ToTable("FormCorsSettings"); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.FormEmailSettings", b => + { + b.Property("FormId") + .HasColumnType("TEXT"); + + b.Property("EmailFrom") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("EmailMethod") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("FormId"); + + b.ToTable("FormEmailSettings"); + + b.UseTptMappingStrategy(); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.RecaptchaFormSettings", b => + { + b.Property("FormId") + .HasColumnType("TEXT"); + + b.Property("SecretKey") + .HasColumnType("TEXT"); + + b.Property("SiteKey") + .HasColumnType("TEXT"); + + b.HasKey("FormId"); + + b.ToTable("RecaptchaFormSettings"); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.SignupForm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("FormName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("SignupForms"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + 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"); + + 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("EmailCollector.Domain.Entities.SmtpEmailSettings", b => + { + b.HasBaseType("EmailCollector.Domain.Entities.FormEmailSettings"); + + b.Property("SmtpPassword") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SmtpPort") + .HasColumnType("INTEGER"); + + b.Property("SmtpServer") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SmtpUsername") + .IsRequired() + .HasColumnType("TEXT"); + + b.ToTable("SmtpEmailSettings", (string)null); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.ApiKey", b => + { + b.HasOne("EmailCollector.Domain.Entities.EmailCollectorApiUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.CustomEmailTemplate", b => + { + b.HasOne("EmailCollector.Domain.Entities.SignupForm", "Form") + .WithMany("CustomEmailTemplates") + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Form"); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.FormCorsSettings", b => + { + b.HasOne("EmailCollector.Domain.Entities.SignupForm", "Form") + .WithOne("FormCorsSettings") + .HasForeignKey("EmailCollector.Domain.Entities.FormCorsSettings", "FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Form"); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.FormEmailSettings", b => + { + b.HasOne("EmailCollector.Domain.Entities.SignupForm", "Form") + .WithOne("FormEmailSettings") + .HasForeignKey("EmailCollector.Domain.Entities.FormEmailSettings", "FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Form"); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.RecaptchaFormSettings", b => + { + b.HasOne("EmailCollector.Domain.Entities.SignupForm", "Form") + .WithOne("RecaptchaSettings") + .HasForeignKey("EmailCollector.Domain.Entities.RecaptchaFormSettings", "FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Form"); + }); + + 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("EmailCollector.Domain.Entities.EmailCollectorApiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EmailCollector.Domain.Entities.EmailCollectorApiUser", 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("EmailCollector.Domain.Entities.EmailCollectorApiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EmailCollector.Domain.Entities.EmailCollectorApiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.SmtpEmailSettings", b => + { + b.HasOne("EmailCollector.Domain.Entities.FormEmailSettings", null) + .WithOne() + .HasForeignKey("EmailCollector.Domain.Entities.SmtpEmailSettings", "FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.SignupForm", b => + { + b.Navigation("CustomEmailTemplates"); + + b.Navigation("FormCorsSettings") + .IsRequired(); + + b.Navigation("FormEmailSettings") + .IsRequired(); + + b.Navigation("RecaptchaSettings") + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/EmailCollector.Api/Migrations/20241213073002_fixCustomEmailTemplateName.cs b/EmailCollector.Api/Migrations/20241213073002_fixCustomEmailTemplateName.cs new file mode 100644 index 0000000..ff9ec45 --- /dev/null +++ b/EmailCollector.Api/Migrations/20241213073002_fixCustomEmailTemplateName.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EmailCollector.Api.Migrations +{ + /// + public partial class fixCustomEmailTemplateName : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/EmailCollector.Api/Migrations/20241213073748_AddUniqueConstraint_FormId_Event_On_CustomEmailTemplates.Designer.cs b/EmailCollector.Api/Migrations/20241213073748_AddUniqueConstraint_FormId_Event_On_CustomEmailTemplates.Designer.cs new file mode 100644 index 0000000..3c07394 --- /dev/null +++ b/EmailCollector.Api/Migrations/20241213073748_AddUniqueConstraint_FormId_Event_On_CustomEmailTemplates.Designer.cs @@ -0,0 +1,536 @@ +// +using System; +using EmailCollector.Api.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EmailCollector.Api.Migrations +{ + [DbContext(typeof(EmailCollectorApiContext))] + [Migration("20241213073748_AddUniqueConstraint_FormId_Event_On_CustomEmailTemplates")] + partial class AddUniqueConstraint_FormId_Event_On_CustomEmailTemplates + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.8"); + + modelBuilder.Entity("EmailCollector.Domain.Entities.ApiKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Expiration") + .HasColumnType("TEXT"); + + b.Property("IsRevoked") + .HasColumnType("INTEGER"); + + b.Property("KeyHash") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("ApiKeys"); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.CustomEmailTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Event") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FormId") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("TemplateBodyUri") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TemplateSubject") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex(new[] { "FormId", "Event" }, "IX_CustomEmailTemplate_FormId_Event_Unique") + .IsUnique(); + + b.ToTable("CustomEmailTemplates"); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.EmailCollectorApiUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.EmailSignup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("EmailAddress") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SignupDate") + .HasColumnType("TEXT"); + + b.Property("SignupFormId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("EmailSignups"); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.FormCorsSettings", b => + { + b.Property("FormId") + .HasColumnType("TEXT"); + + b.Property("AllowedOrigins") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("FormId"); + + b.ToTable("FormCorsSettings"); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.FormEmailSettings", b => + { + b.Property("FormId") + .HasColumnType("TEXT"); + + b.Property("EmailFrom") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("EmailMethod") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("FormId"); + + b.ToTable("FormEmailSettings"); + + b.UseTptMappingStrategy(); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.RecaptchaFormSettings", b => + { + b.Property("FormId") + .HasColumnType("TEXT"); + + b.Property("SecretKey") + .HasColumnType("TEXT"); + + b.Property("SiteKey") + .HasColumnType("TEXT"); + + b.HasKey("FormId"); + + b.ToTable("RecaptchaFormSettings"); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.SignupForm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("FormName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("SignupForms"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + 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"); + + 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("EmailCollector.Domain.Entities.SmtpEmailSettings", b => + { + b.HasBaseType("EmailCollector.Domain.Entities.FormEmailSettings"); + + b.Property("SmtpPassword") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SmtpPort") + .HasColumnType("INTEGER"); + + b.Property("SmtpServer") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SmtpUsername") + .IsRequired() + .HasColumnType("TEXT"); + + b.ToTable("SmtpEmailSettings", (string)null); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.ApiKey", b => + { + b.HasOne("EmailCollector.Domain.Entities.EmailCollectorApiUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.CustomEmailTemplate", b => + { + b.HasOne("EmailCollector.Domain.Entities.SignupForm", "Form") + .WithMany("CustomEmailTemplates") + .HasForeignKey("FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Form"); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.FormCorsSettings", b => + { + b.HasOne("EmailCollector.Domain.Entities.SignupForm", "Form") + .WithOne("FormCorsSettings") + .HasForeignKey("EmailCollector.Domain.Entities.FormCorsSettings", "FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Form"); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.FormEmailSettings", b => + { + b.HasOne("EmailCollector.Domain.Entities.SignupForm", "Form") + .WithOne("FormEmailSettings") + .HasForeignKey("EmailCollector.Domain.Entities.FormEmailSettings", "FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Form"); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.RecaptchaFormSettings", b => + { + b.HasOne("EmailCollector.Domain.Entities.SignupForm", "Form") + .WithOne("RecaptchaSettings") + .HasForeignKey("EmailCollector.Domain.Entities.RecaptchaFormSettings", "FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Form"); + }); + + 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("EmailCollector.Domain.Entities.EmailCollectorApiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("EmailCollector.Domain.Entities.EmailCollectorApiUser", 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("EmailCollector.Domain.Entities.EmailCollectorApiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("EmailCollector.Domain.Entities.EmailCollectorApiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.SmtpEmailSettings", b => + { + b.HasOne("EmailCollector.Domain.Entities.FormEmailSettings", null) + .WithOne() + .HasForeignKey("EmailCollector.Domain.Entities.SmtpEmailSettings", "FormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EmailCollector.Domain.Entities.SignupForm", b => + { + b.Navigation("CustomEmailTemplates"); + + b.Navigation("FormCorsSettings") + .IsRequired(); + + b.Navigation("FormEmailSettings") + .IsRequired(); + + b.Navigation("RecaptchaSettings") + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/EmailCollector.Api/Migrations/20241213073748_AddUniqueConstraint_FormId_Event_On_CustomEmailTemplates.cs b/EmailCollector.Api/Migrations/20241213073748_AddUniqueConstraint_FormId_Event_On_CustomEmailTemplates.cs new file mode 100644 index 0000000..e1f7dfa --- /dev/null +++ b/EmailCollector.Api/Migrations/20241213073748_AddUniqueConstraint_FormId_Event_On_CustomEmailTemplates.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EmailCollector.Api.Migrations +{ + /// + public partial class AddUniqueConstraint_FormId_Event_On_CustomEmailTemplates : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_CustomEmailTemplates_FormId", + table: "CustomEmailTemplates"); + + migrationBuilder.CreateIndex( + name: "IX_CustomEmailTemplate_FormId_Event_Unique", + table: "CustomEmailTemplates", + columns: new[] { "FormId", "Event" }, + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_CustomEmailTemplate_FormId_Event_Unique", + table: "CustomEmailTemplates"); + + migrationBuilder.CreateIndex( + name: "IX_CustomEmailTemplates_FormId", + table: "CustomEmailTemplates", + column: "FormId"); + } + } +} diff --git a/EmailCollector.Api/Services/CustomEmailTemplates/CustomEmailTemplatesService.cs b/EmailCollector.Api/Services/CustomEmailTemplates/CustomEmailTemplatesService.cs index 8e7d62d..d4e4b8d 100644 --- a/EmailCollector.Api/Services/CustomEmailTemplates/CustomEmailTemplatesService.cs +++ b/EmailCollector.Api/Services/CustomEmailTemplates/CustomEmailTemplatesService.cs @@ -6,12 +6,19 @@ namespace EmailCollector.Api.Services.CustomEmailTemplates; +/// +/// Service for managing custom email templates, including CRUD operations and template storage. +/// public class CustomEmailTemplatesService : ICustomEmailTemplatesService { private IFormRelatedRepository _customEmailTemplatesRepository; private readonly ITemplateStorageProvider _templateStorageProvider; - + /// + /// Initializes a new instance of the CustomEmailTemplatesService class. + /// + /// Repository for managing custom email templates + /// Provider for template storage operations public CustomEmailTemplatesService(IFormRelatedRepository customEmailTemplatesRepository, ITemplateStorageProvider templateStorageProvider) { @@ -19,6 +26,11 @@ public CustomEmailTemplatesService(IFormRelatedRepository c _templateStorageProvider = templateStorageProvider; } + /// + /// Retrieves all custom email templates associated with a specific form. + /// + /// The ID of the form to get templates for + /// A collection of custom email templates public async Task> GetCustomEmailTemplatesByFormId(Guid formId) { var customEmailTemplates = await _customEmailTemplatesRepository.GetByFormId(formId); @@ -45,6 +57,11 @@ public async Task> GetCustomEmailTemplatesBy return templates; } + /// + /// Retrieves custom email templates for multiple forms, organized by form ID. + /// + /// Collection of form IDs to get templates for + /// Dictionary mapping form IDs to their associated templates public async Task>> GetCustomEmailTemplatesByFormIds(IEnumerable formIds) { if (formIds == null) @@ -94,6 +111,12 @@ public async Task>> GetCust return dtoDictionary; } + /// + /// Retrieves a specific email template for a form and event type. + /// + /// The ID of the form + /// The event type of the template + /// The matching email template, or null if not found public async Task GetCustomEmailTemplateByFormIdAndEvent(Guid formId, TemplateEvent templateEvent) { var templatesForForm = await GetCustomEmailTemplatesByFormId(formId); @@ -101,6 +124,11 @@ public async Task>> GetCust .FirstOrDefault(t => t.Event == templateEvent); } + /// + /// Creates or updates a custom email template. + /// + /// The template data to save + /// Thrown when customEmailTemplateDto is null public async Task SaveCustomEmailTemplate(CustomEmailTemplateDto customEmailTemplateDto) { if (customEmailTemplateDto == null) @@ -144,15 +172,28 @@ public async Task SaveCustomEmailTemplate(CustomEmailTemplateDto customEmailTemp } } + /// + /// Deletes a custom email template and its associated template body. + /// + /// The ID of the template to delete public async Task DeleteCustomEmailTemplate(Guid templateId) { await _customEmailTemplatesRepository.RemoveById(templateId); _templateStorageProvider.DeleteTemplateBodyAsync(templateId.ToString()); } + /// + /// Retrieves a custom email template by its ID. + /// + /// The ID of the template to retrieve + /// The email template with the specified ID + /// Thrown when no template exists with the specified ID public async Task GetCustomEmailTemplateById(Guid id) { var entity = await _customEmailTemplatesRepository.GetByIdAsync(id); + if (entity == null) + throw new KeyNotFoundException($"Template with ID {id} not found"); + var templateBody = await _templateStorageProvider.GetTemplateBodyAsync(entity.TemplateBodyUri); return new CustomEmailTemplateDto diff --git a/EmailCollector.Api/Services/CustomEmailTemplates/StorageProviders/FileSystemTemplateStorageProvider.cs b/EmailCollector.Api/Services/CustomEmailTemplates/StorageProviders/FileSystemTemplateStorageProvider.cs index d8239fc..f650d66 100644 --- a/EmailCollector.Api/Services/CustomEmailTemplates/StorageProviders/FileSystemTemplateStorageProvider.cs +++ b/EmailCollector.Api/Services/CustomEmailTemplates/StorageProviders/FileSystemTemplateStorageProvider.cs @@ -1,9 +1,17 @@ namespace EmailCollector.Api.Services.CustomEmailTemplates.StorageProviders; +/// +/// Provides file system storage operations for email templates, managing template files on disk. +/// public class FileSystemTemplateStorageProvider : ITemplateStorageProvider { private readonly string _baseDirectory; + /// + /// Initializes a new instance of the FileSystemTemplateStorageProvider class. + /// + /// The base directory path where template files will be stored + /// Thrown when baseDirectory is null or empty public FileSystemTemplateStorageProvider(string baseDirectory) { if (string.IsNullOrWhiteSpace(baseDirectory)) @@ -17,6 +25,12 @@ public FileSystemTemplateStorageProvider(string baseDirectory) } } + /// + /// Retrieves the template body content from the file system. + /// + /// The URI (filename) of the template to retrieve + /// The template content, or empty string if file not found + /// Thrown when uri is null or empty public async Task GetTemplateBodyAsync(string uri) { if (string.IsNullOrWhiteSpace(uri)) @@ -34,7 +48,14 @@ public async Task GetTemplateBodyAsync(string uri) return await File.ReadAllTextAsync(filePath); } - public async Task SaveTemplateBodyAsync(string templateBody, string currentUri = null) + /// + /// Saves a template body to the file system. + /// + /// The content to save + /// Optional existing URI to update. If null, creates new file + /// The URI (filename) where the template was saved + /// Thrown when templateBody is null + public async Task SaveTemplateBodyAsync(string templateBody, string? currentUri = null) { if (templateBody == null) throw new ArgumentNullException(nameof(templateBody)); @@ -57,6 +78,10 @@ public async Task SaveTemplateBodyAsync(string templateBody, string curr return fileName; } + /// + /// Deletes a template file from the file system. + /// + /// The URI (filename) of the template to delete public void DeleteTemplateBodyAsync(string uri) { var filePath = Path.Combine(_baseDirectory, uri); diff --git a/EmailCollector.Api/Services/CustomEmailTemplates/StorageProviders/ITemplateStorageProvider.cs b/EmailCollector.Api/Services/CustomEmailTemplates/StorageProviders/ITemplateStorageProvider.cs index 33eba88..e071007 100644 --- a/EmailCollector.Api/Services/CustomEmailTemplates/StorageProviders/ITemplateStorageProvider.cs +++ b/EmailCollector.Api/Services/CustomEmailTemplates/StorageProviders/ITemplateStorageProvider.cs @@ -1,8 +1,28 @@ namespace EmailCollector.Api.Services.CustomEmailTemplates.StorageProviders; +/// +/// Defines operations for storing and retrieving email template bodies. +/// public interface ITemplateStorageProvider { + /// + /// Retrieves the template body content from storage. + /// + /// The URI of the template to retrieve + /// The template content Task GetTemplateBodyAsync(string uri); - Task SaveTemplateBodyAsync(string templateBody, string currentUri = null); + + /// + /// Saves a template body to storage. + /// + /// The content to save + /// Optional existing URI to update + /// The URI where the template was saved + Task SaveTemplateBodyAsync(string templateBody, string? currentUri = null); + + /// + /// Deletes a template from storage. + /// + /// The URI of the template to delete void DeleteTemplateBodyAsync(string uri); } \ No newline at end of file diff --git a/EmailCollector.Tests/Services/EmailSignupServiceTests.cs b/EmailCollector.Tests/Services/EmailSignupServiceTests.cs index 892f39c..25ee6e1 100644 --- a/EmailCollector.Tests/Services/EmailSignupServiceTests.cs +++ b/EmailCollector.Tests/Services/EmailSignupServiceTests.cs @@ -11,300 +11,298 @@ using System.Text; using MediatR; -namespace EmailCollector.Api.Tests.Services +namespace EmailCollector.Api.Tests.Services; + +[TestFixture] +public class EmailSignupServiceTests { - [TestFixture] - public class EmailSignupServiceTests + private Mock _emailSignupRepositoryMock; + private Mock _signupFormRepositoryMock; + private Mock> _loggerMock; + private IEmailSignupService _emailSignupService; + private Mock _signupCandidatesCacheMock; + private Mock _emailSenderMock; + private Mock _featureTogglesServiceMock; + private Mock> _smtpEmailSettingsRepositoryMock; + private Mock _mediatorMock; + + [SetUp] + public void Setup() { - private Mock _emailSignupRepositoryMock; - private Mock _signupFormRepositoryMock; - private Mock> _loggerMock; - private IEmailSignupService _emailSignupService; - private Mock _signupCandidatesCacheMock; - private Mock _emailSenderMock; - private Mock _featureTogglesServiceMock; - private Mock> _smtpEmailSettingsRepositoryMock; - private Mock _mediatorMock; - - [SetUp] - public void Setup() - { - _emailSignupRepositoryMock = new Mock(); - _signupFormRepositoryMock = new Mock(); - _loggerMock = new Mock>(); - _signupCandidatesCacheMock = new Mock(); - _emailSenderMock = new Mock(); - _featureTogglesServiceMock = new Mock(); - _smtpEmailSettingsRepositoryMock = new Mock>(); - _mediatorMock = new Mock(); - - _emailSignupService = new EmailSignupService( - _emailSignupRepositoryMock.Object, - _signupFormRepositoryMock.Object, - _loggerMock.Object, - _signupCandidatesCacheMock.Object, - _emailSenderMock.Object, - _featureTogglesServiceMock.Object, - _smtpEmailSettingsRepositoryMock.Object, - _mediatorMock.Object); - } + _emailSignupRepositoryMock = new Mock(); + _signupFormRepositoryMock = new Mock(); + _loggerMock = new Mock>(); + _signupCandidatesCacheMock = new Mock(); + _emailSenderMock = new Mock(); + _featureTogglesServiceMock = new Mock(); + _smtpEmailSettingsRepositoryMock = new Mock>(); + _mediatorMock = new Mock(); + + _emailSignupService = new EmailSignupService( + _emailSignupRepositoryMock.Object, + _signupFormRepositoryMock.Object, + _loggerMock.Object, + _signupCandidatesCacheMock.Object, + _emailSenderMock.Object, + _featureTogglesServiceMock.Object, + _smtpEmailSettingsRepositoryMock.Object, + _mediatorMock.Object); + } - [Test] - public async Task GetSignupsByFormIdAsync_FormNotFound_ReturnsNull() - { - // Arrange - Guid formId = Guid.NewGuid(); - _signupFormRepositoryMock.Setup(repo => repo.GetByIdAsync(formId)) - .ReturnsAsync((SignupForm)null); + [Test] + public async Task GetSignupsByFormIdAsync_FormNotFound_ReturnsNull() + { + // Arrange + Guid formId = Guid.NewGuid(); + _signupFormRepositoryMock.Setup(repo => repo.GetByIdAsync(formId)) + .ReturnsAsync((SignupForm)null!); - // Act - var result = await _emailSignupService.GetSignupsByFormIdAsync(formId); + // Act + var result = await _emailSignupService.GetSignupsByFormIdAsync(formId); - // Assert - Assert.That(result, Is.Null); - } + // Assert + Assert.That(result, Is.Null); + } - [Test] - public async Task GetSignupsByFormIdAsync_FormFound_ReturnsSignups() + [Test] + public async Task GetSignupsByFormIdAsync_FormFound_ReturnsSignups() + { + // Arrange + Guid formId = Guid.NewGuid(); + Guid userId = Guid.NewGuid(); + var form = new SignupForm { FormName = "test", CreatedBy = userId }; + var signups = new List { - // Arrange - Guid formId = Guid.NewGuid(); - Guid userId = Guid.NewGuid(); - var form = new SignupForm { FormName = "test", CreatedBy = userId }; - var signups = new List - { - new() { EmailAddress = "test1@example.com", SignupFormId = formId, SignupDate = DateTime.Now }, - new() { EmailAddress = "test2@example.com", SignupFormId = formId, SignupDate = DateTime.Now }, - }; - _signupFormRepositoryMock.Setup(repo => repo.GetByIdAsync(formId)) - .ReturnsAsync(form); - _emailSignupRepositoryMock.Setup(repo => repo.GetByFormIdAsync(formId)) - .ReturnsAsync(signups); - - // Act - var result = await _emailSignupService.GetSignupsByFormIdAsync(formId); - - // Assert - Assert.That(result, Is.Not.Null); - Assert.That(result.Count(), Is.EqualTo(signups.Count)); - foreach (var signup in signups) - { - Assert.That(result.Any(dto => - dto.Email == signup.EmailAddress && - dto.FormId == signup.SignupFormId && - dto.SignupDate == signup.SignupDate), Is.True); - } - } - - [Test] - public async Task SubmitEmailAsync_InvalidEmail_ReturnsInvalidEmailResult() + new() { EmailAddress = "test1@example.com", SignupFormId = formId, SignupDate = DateTime.Now }, + new() { EmailAddress = "test2@example.com", SignupFormId = formId, SignupDate = DateTime.Now }, + }; + _signupFormRepositoryMock.Setup(repo => repo.GetByIdAsync(formId)) + .ReturnsAsync(form); + _emailSignupRepositoryMock.Setup(repo => repo.GetByFormIdAsync(formId)) + .ReturnsAsync(signups); + + // Act + var result = await _emailSignupService.GetSignupsByFormIdAsync(formId); + + // Assert + Assert.That(result, Is.Not.Null); + Assert.That(result.Count(), Is.EqualTo(signups.Count)); + foreach (var signup in signups) { - // Arrange - var emailSignupDto = new EmailSignupDto { Email = "invalidemail", FormId = Guid.NewGuid() }; - var expectedResponse = new SignupResultDto - { - Success = false, - Message = "Invalid email address.", - ErrorCode = EmailSignupErrorCode.InvalidEmail, - }; - - // Act - var result = await _emailSignupService.SubmitEmailAsync(emailSignupDto); - - // Assert - Assert.That(result.Success, Is.False); - Assert.That(result.ErrorCode, Is.EqualTo(expectedResponse.ErrorCode)); + Assert.That(result.Any(dto => + dto.Email == signup.EmailAddress && + dto.FormId == signup.SignupFormId && + dto.SignupDate == signup.SignupDate), Is.True); } + } - [Test] - public async Task SubmitEmailAsync_FormNotFound_ReturnsFormNotFoundResult() + [Test] + public async Task SubmitEmailAsync_InvalidEmail_ReturnsInvalidEmailResult() + { + // Arrange + var emailSignupDto = new EmailSignupDto { Email = "invalidemail", FormId = Guid.NewGuid() }; + var expectedResponse = new SignupResultDto { - // Arrange - var emailSignupDto = new EmailSignupDto { Email = "test@gmail.com", FormId = Guid.NewGuid() }; - _signupFormRepositoryMock.Setup(repo => repo.GetByIdAsync(emailSignupDto.FormId)) - .ReturnsAsync((SignupForm)null); - var expectedResponse = new SignupResultDto - { - Success = false, - Message = "Form not found.", - ErrorCode = EmailSignupErrorCode.FormNotFound, - }; - - // Act - var result = await _emailSignupService.SubmitEmailAsync(emailSignupDto); - - // Assert - Assert.That(result.Success, Is.False); - Assert.That(result.ErrorCode, Is.EqualTo(expectedResponse.ErrorCode)); - } + Success = false, + Message = "Invalid email address.", + ErrorCode = EmailSignupErrorCode.InvalidEmail, + }; - [Test] - public async Task SubmitEmailAsync_FormNotActive_ReturnsFormNotActiveResult() + // Act + var result = await _emailSignupService.SubmitEmailAsync(emailSignupDto); + + // Assert + Assert.That(result.Success, Is.False); + Assert.That(result.ErrorCode, Is.EqualTo(expectedResponse.ErrorCode)); + } + + [Test] + public async Task SubmitEmailAsync_FormNotFound_ReturnsFormNotFoundResult() + { + // Arrange + var emailSignupDto = new EmailSignupDto { Email = "test@gmail.com", FormId = Guid.NewGuid() }; + _signupFormRepositoryMock.Setup(repo => repo.GetByIdAsync(emailSignupDto.FormId)) + .ReturnsAsync((SignupForm)null!); + var expectedResponse = new SignupResultDto { - // Arrange - var emailSignupDto = new EmailSignupDto { Email = "test@gmail.com", FormId = Guid.NewGuid() }; - var form = new SignupForm { FormName = "test", Status = FormStatus.Inactive, CreatedBy = Guid.NewGuid() }; - _signupFormRepositoryMock.Setup(repo => repo.GetByIdAsync(emailSignupDto.FormId)) - .ReturnsAsync(form); - var expectedResponse = new SignupResultDto - { - Success = false, - Message = "Form is not active.", - ErrorCode = EmailSignupErrorCode.FormNotActive, - }; - - // Act - var result = await _emailSignupService.SubmitEmailAsync(emailSignupDto); - - // Assert - Assert.That(result.Success, Is.False); - Assert.That(result.ErrorCode, Is.EqualTo(expectedResponse.ErrorCode)); - } + Success = false, + Message = "Form not found.", + ErrorCode = EmailSignupErrorCode.FormNotFound, + }; + + // Act + var result = await _emailSignupService.SubmitEmailAsync(emailSignupDto); + + // Assert + Assert.That(result.Success, Is.False); + Assert.That(result.ErrorCode, Is.EqualTo(expectedResponse.ErrorCode)); + } - [Test] - public async Task SubmitEmailAsync_ValidEmailAndForm_EmailConfirmation_Disabled_ReturnsSuccessResult() + [Test] + public async Task SubmitEmailAsync_FormNotActive_ReturnsFormNotActiveResult() + { + // Arrange + var emailSignupDto = new EmailSignupDto { Email = "test@gmail.com", FormId = Guid.NewGuid() }; + var form = new SignupForm { FormName = "test", Status = FormStatus.Inactive, CreatedBy = Guid.NewGuid() }; + _signupFormRepositoryMock.Setup(repo => repo.GetByIdAsync(emailSignupDto.FormId)) + .ReturnsAsync(form); + var expectedResponse = new SignupResultDto { - // Arrange - var emailSignupDto = new EmailSignupDto { Email = "test@gmail.com", FormId = Guid.NewGuid() }; - var form = new SignupForm { FormName = "test", Status = FormStatus.Active, CreatedBy = Guid.NewGuid() }; - _signupFormRepositoryMock.Setup(repo => repo.GetByIdAsync(emailSignupDto.FormId)) - .ReturnsAsync(form); - _featureTogglesServiceMock.Setup(service => service.IsEmailConfirmationEnabled()).Returns(false); + Success = false, + Message = "Form is not active.", + ErrorCode = EmailSignupErrorCode.FormNotActive, + }; - // Act - var result = await _emailSignupService.SubmitEmailAsync(emailSignupDto); + // Act + var result = await _emailSignupService.SubmitEmailAsync(emailSignupDto); - // Assert - Assert.That(result.Success, Is.True); + // Assert + Assert.That(result.Success, Is.False); + Assert.That(result.ErrorCode, Is.EqualTo(expectedResponse.ErrorCode)); + } - // assert distributed cache was not called - _signupCandidatesCacheMock.Verify(cache => cache.SetAsync(It.IsAny(), It.IsAny(), It.IsAny(), default), Times.Never); + [Test] + public async Task SubmitEmailAsync_ValidEmailAndForm_EmailConfirmation_Disabled_ReturnsSuccessResult() + { + // Arrange + var emailSignupDto = new EmailSignupDto { Email = "test@gmail.com", FormId = Guid.NewGuid() }; + var form = new SignupForm { FormName = "test", Status = FormStatus.Active, CreatedBy = Guid.NewGuid() }; + _signupFormRepositoryMock.Setup(repo => repo.GetByIdAsync(emailSignupDto.FormId)) + .ReturnsAsync(form); + _featureTogglesServiceMock.Setup(service => service.IsEmailConfirmationEnabled()).Returns(false); - // assert email sender was not called - _emailSenderMock.Verify(sender => sender.SendEmail(It.IsAny(), It.IsAny()), Times.Never); - } + // Act + var result = await _emailSignupService.SubmitEmailAsync(emailSignupDto); - [Test] - public async Task SubmitEmailAsync_ValidEmailAndForm_EmailConfirmation_Enabled_ReturnsSuccessResult() - { - // Arrange - var emailSignupDto = new EmailSignupDto { Email = "test@gmail.com", FormId = Guid.NewGuid() }; - var form = new SignupForm { FormName = "test", Status = FormStatus.Active, CreatedBy = Guid.NewGuid() }; - _signupFormRepositoryMock.Setup(repo => repo.GetByIdAsync(emailSignupDto.FormId)) - .ReturnsAsync(form); - _featureTogglesServiceMock.Setup(service => service.IsEmailConfirmationEnabled()).Returns(true); - - var expectedResponse = new SignupResultDto - { - Success = true, - Message = "Email address submitted successfully.", - }; - - // Act - var result = await _emailSignupService.SubmitEmailAsync(emailSignupDto); - - // Assert - Assert.That(result.Success, Is.True); - - // assert distributed cache was not called - _signupCandidatesCacheMock.Verify(cache => cache.SetAsync(It.IsAny(), It.IsAny(), It.IsAny(), CancellationToken.None), Times.Once); - - // assert email sender was not called - _emailSenderMock.Verify(sender => sender.SendEmail(It.IsAny(), It.IsAny()), Times.Once); - } + // Assert + Assert.That(result.Success, Is.True); - [Test] - public async Task ConfirmEmailSignupAsync_ExpiredToken_ReturnsExpiredTokenResult() + // assert distributed cache was not called + _signupCandidatesCacheMock.Verify(cache => cache.SetAsync(It.IsAny(), It.IsAny(), It.IsAny(), default), Times.Never); + + // assert email sender was not called + _emailSenderMock.Verify(sender => sender.SendEmail(It.IsAny(), It.IsAny()), Times.Never); + } + + [Test] + public async Task SubmitEmailAsync_ValidEmailAndForm_EmailConfirmation_Enabled_ReturnsSuccessResult() + { + // Arrange + var emailSignupDto = new EmailSignupDto { Email = "test@gmail.com", FormId = Guid.NewGuid() }; + var form = new SignupForm { FormName = "test", Status = FormStatus.Active, CreatedBy = Guid.NewGuid() }; + _signupFormRepositoryMock.Setup(repo => repo.GetByIdAsync(emailSignupDto.FormId)) + .ReturnsAsync(form); + _featureTogglesServiceMock.Setup(service => service.IsEmailConfirmationEnabled()).Returns(true); + + var expectedResponse = new SignupResultDto { - // Arrange - var confirmationToken = "expiredToken"; - _signupCandidatesCacheMock.Setup(cache => cache.GetAsync(confirmationToken, CancellationToken.None)).ReturnsAsync((byte[])null); + Success = true, + Message = "Email address submitted successfully.", + }; - // Act - var result = await _emailSignupService.ConfirmEmailSignupAsync(confirmationToken); + // Act + var result = await _emailSignupService.SubmitEmailAsync(emailSignupDto); - // Assert - Assert.That(result.Success, Is.False); - Assert.That(result.Message, Is.EqualTo("Confirmation token expired.")); - Assert.That(result.ErrorCode, Is.EqualTo(EmailConfirmationErrorCode.ExpiredToken)); - } + // Assert + Assert.That(result.Success, Is.True); - [Test] - public async Task ConfirmEmailSignupAsync_InvalidToken_ReturnsInvalidTokenResult() - { - // Arrange - var confirmationToken = "invalidToken"; - var encodedSignupCandidate = Encoding.UTF8.GetBytes("formId:1#signup:"); // signup candidate without email - _signupCandidatesCacheMock.Setup(cache => cache.GetAsync(confirmationToken, CancellationToken.None)).ReturnsAsync(encodedSignupCandidate); - - // Act - var result = await _emailSignupService.ConfirmEmailSignupAsync(confirmationToken); - - // Assert - Assert.That(result.Success, Is.False); - Assert.That(result.Message, Is.EqualTo("Invalid confirmation token.")); - Assert.That(result.ErrorCode, Is.EqualTo(EmailConfirmationErrorCode.InvalidToken)); - } + // assert distributed cache was not called + _signupCandidatesCacheMock.Verify(cache => cache.SetAsync(It.IsAny(), It.IsAny(), It.IsAny(), CancellationToken.None), Times.Once); - [Test] - public async Task ConfirmEmailSignupAsync_EmailAlreadyConfirmed_ReturnsEmailAlreadyConfirmedResult() - { - // Arrange - var formId = Guid.NewGuid(); - var confirmationToken = "validToken"; - var encodedSignupCandidate = Encoding.UTF8.GetBytes($"formId:{formId}#signup:test@example.com"); - var email = "test@example.com"; - var existingSignups = new List - { - new EmailSignup { EmailAddress = email } - }; - _signupCandidatesCacheMock.Setup(cache => cache.GetAsync(confirmationToken, CancellationToken.None)).ReturnsAsync(encodedSignupCandidate); - _emailSignupRepositoryMock.Setup(repo => repo.GetByFormIdAsync(formId)).ReturnsAsync(existingSignups); - - // Act - var result = await _emailSignupService.ConfirmEmailSignupAsync(confirmationToken); - - // Assert - Assert.That(result.Success, Is.False); - Assert.That(result.Message, Is.EqualTo("Email already confirmed.")); - Assert.That(result.ErrorCode, Is.EqualTo(EmailConfirmationErrorCode.EmailAlreadyConfirmed)); - } + // assert email sender was not called + _emailSenderMock.Verify(sender => sender.SendEmail(It.IsAny(), It.IsAny()), Times.Once); + } - [Test] - public async Task ConfirmEmailSignupAsync_ValidToken_AddsEmailSignupAndRemovesTokenAndReturnsSuccessResult() - { - // Arrange - var formId = Guid.NewGuid(); - var confirmationToken = "validToken"; - var encodedSignupCandidate = Encoding.UTF8.GetBytes($"formId:{formId}#signup:test@example.com"); - var email = "test@example.com"; - var form = new SignupForm { Id = formId, FormName = "test", CreatedBy = Guid.NewGuid() }; - _signupCandidatesCacheMock.Setup(cache => cache.GetAsync(confirmationToken, CancellationToken.None)).ReturnsAsync(encodedSignupCandidate); - _signupFormRepositoryMock.Setup(repo => repo.GetByIdAsync(formId)).ReturnsAsync(form); - //_emailSignupRepositoryMock.Setup(repo => repo.AddAsync(It.IsAny())).Returns(Task.CompletedTask); - //_signupCandidatesCacheMock.Setup(cache => cache.RemoveAsync(confirmationToken, CancellationToken.None)).Returns(Task.CompletedTask); - - // Act - var result = await _emailSignupService.ConfirmEmailSignupAsync(confirmationToken); - - // Assert - Assert.That(result.Success, Is.True); - Assert.That(result.Message, Is.EqualTo("Email confirmed.")); - _emailSignupRepositoryMock.Verify(repo => repo.AddAsync(It.IsAny()), Times.Once); - _signupCandidatesCacheMock.Verify(cache => cache.RemoveAsync(confirmationToken, CancellationToken.None), Times.Once); - } + [Test] + public async Task ConfirmEmailSignupAsync_ExpiredToken_ReturnsExpiredTokenResult() + { + // Arrange + var confirmationToken = "expiredToken"; + _signupCandidatesCacheMock.Setup(cache => cache.GetAsync(confirmationToken, CancellationToken.None)).ReturnsAsync((byte[])null!); + + // Act + var result = await _emailSignupService.ConfirmEmailSignupAsync(confirmationToken); - [Test] - public async Task GetSignupsPerDayAsync_FormNotFound_ThrowsArgumentException() + // Assert + Assert.That(result.Success, Is.False); + Assert.That(result.Message, Is.EqualTo("Confirmation token expired.")); + Assert.That(result.ErrorCode, Is.EqualTo(EmailConfirmationErrorCode.ExpiredToken)); + } + + [Test] + public async Task ConfirmEmailSignupAsync_InvalidToken_ReturnsInvalidTokenResult() + { + // Arrange + var confirmationToken = "invalidToken"; + var encodedSignupCandidate = Encoding.UTF8.GetBytes("formId:1#signup:"); // signup candidate without email + _signupCandidatesCacheMock.Setup(cache => cache.GetAsync(confirmationToken, CancellationToken.None)).ReturnsAsync(encodedSignupCandidate); + + // Act + var result = await _emailSignupService.ConfirmEmailSignupAsync(confirmationToken); + + // Assert + Assert.That(result.Success, Is.False); + Assert.That(result.Message, Is.EqualTo("Invalid confirmation token.")); + Assert.That(result.ErrorCode, Is.EqualTo(EmailConfirmationErrorCode.InvalidToken)); + } + + [Test] + public async Task ConfirmEmailSignupAsync_EmailAlreadyConfirmed_ReturnsEmailAlreadyConfirmedResult() + { + // Arrange + var formId = Guid.NewGuid(); + var confirmationToken = "validToken"; + var encodedSignupCandidate = Encoding.UTF8.GetBytes($"formId:{formId}#signup:test@example.com"); + var email = "test@example.com"; + var existingSignups = new List { - // Arrange - var formId = Guid.NewGuid(); - var startDate = DateTime.Now.Date; - var endDate = DateTime.Now.Date; - _signupFormRepositoryMock.Setup(repo => repo.GetByIdAsync(formId)).ReturnsAsync((SignupForm)null); - - // Act & Assert - Assert.ThrowsAsync(() => _emailSignupService.GetSignupsPerDayAsync(formId, startDate, endDate)); - } + new EmailSignup { EmailAddress = email } + }; + _signupCandidatesCacheMock.Setup(cache => cache.GetAsync(confirmationToken, CancellationToken.None)).ReturnsAsync(encodedSignupCandidate); + _emailSignupRepositoryMock.Setup(repo => repo.GetByFormIdAsync(formId)).ReturnsAsync(existingSignups); + + // Act + var result = await _emailSignupService.ConfirmEmailSignupAsync(confirmationToken); + + // Assert + Assert.That(result.Success, Is.False); + Assert.That(result.Message, Is.EqualTo("Email already confirmed.")); + Assert.That(result.ErrorCode, Is.EqualTo(EmailConfirmationErrorCode.EmailAlreadyConfirmed)); + } + + [Test] + public async Task ConfirmEmailSignupAsync_ValidToken_AddsEmailSignupAndRemovesTokenAndReturnsSuccessResult() + { + // Arrange + var formId = Guid.NewGuid(); + var confirmationToken = "validToken"; + var encodedSignupCandidate = Encoding.UTF8.GetBytes($"formId:{formId}#signup:test@example.com"); + var form = new SignupForm { Id = formId, FormName = "test", CreatedBy = Guid.NewGuid() }; + _signupCandidatesCacheMock.Setup(cache => cache.GetAsync(confirmationToken, CancellationToken.None)).ReturnsAsync(encodedSignupCandidate); + _signupFormRepositoryMock.Setup(repo => repo.GetByIdAsync(formId)).ReturnsAsync(form); + _emailSignupRepositoryMock.Setup(repo => repo.AddAsync(It.IsAny())).Returns(Task.CompletedTask); + _signupCandidatesCacheMock.Setup(cache => cache.RemoveAsync(confirmationToken, CancellationToken.None)).Returns(Task.CompletedTask); + + // Act + var result = await _emailSignupService.ConfirmEmailSignupAsync(confirmationToken); + + // Assert + Assert.That(result.Success, Is.True); + Assert.That(result.Message, Is.EqualTo("Email confirmed.")); + _emailSignupRepositoryMock.Verify(repo => repo.AddAsync(It.IsAny()), Times.Once); + _signupCandidatesCacheMock.Verify(cache => cache.RemoveAsync(confirmationToken, CancellationToken.None), Times.Once); + } + + [Test] + public async Task GetSignupsPerDayAsync_FormNotFound_ThrowsArgumentException() + { + // Arrange + var formId = Guid.NewGuid(); + var startDate = DateTime.Now.Date; + var endDate = DateTime.Now.Date; + _signupFormRepositoryMock.Setup(repo => repo.GetByIdAsync(formId)).ReturnsAsync((SignupForm)null!); + + // Act & Assert + Assert.ThrowsAsync(() => _emailSignupService.GetSignupsPerDayAsync(formId, startDate, endDate)); } }