From 2bdb1a8de619740bafafa323f61f25a379fa13eb Mon Sep 17 00:00:00 2001 From: David Headrick Date: Mon, 3 Mar 2025 19:15:16 -0600 Subject: [PATCH] Implement RESTful APIs for email management features Created controllers for managing bounced emails, email domains, templates, test email lists, and unsubscribe URLs. Modified the ServersController for improved routing. Added DTOs and repository interfaces for new functionalities. Updated SetupDataContext for state management and adjusted frontend components for user interaction. Updated package dependencies for new features. --- .../Controllers/BouncedEmailsController.cs | 74 ++ .../Controllers/EmailDomainsController.cs | 65 ++ .../Controllers/ServersController.cs | 2 +- .../Controllers/TemplatesController.cs | 65 ++ .../Controllers/TestEmailListsController.cs | 65 ++ .../Controllers/UnsubscribeUrlController.cs | 66 ++ Surge365.MassEmailReact.API/Program.cs | 11 + .../DTOs/BouncedEmailUpdateDto.cs | 10 + .../DTOs/EmailDomainUpdateDto.cs | 15 + .../DTOs/TemplateUpdateDto.cs | 20 + .../DTOs/TestEmailListUpdateDto.cs | 9 + .../DTOs/UnsubscribeUrlUpdateDto.cs | 11 + .../Interfaces/IBouncedEmailRepository.cs | 16 + .../Interfaces/IBouncedEmailService.cs | 13 + .../Interfaces/IEmailDomainRepository.cs | 14 + .../Interfaces/IEmailDomainService.cs | 14 + .../Interfaces/ITemplateRepository.cs | 14 + .../Interfaces/ITemplateService.cs | 14 + .../Interfaces/ITestEmailListRepository.cs | 14 + .../Interfaces/ITestEmailListService.cs | 14 + .../Interfaces/IUnsubscribeUrlRepository.cs | 15 + .../Interfaces/IUnsubscribeUrlService.cs | 16 + .../Entities/BouncedEmail.cs | 23 + .../Entities/EmailDomain.cs | 33 + .../Entities/Template.cs | 53 ++ .../Entities/TestEmailList.cs | 48 ++ .../Entities/UnsubscribeUrl.cs | 23 + .../DapperMaps/BouncedEmailMap.cs | 21 + .../DapperMaps/DapperConfiguration.cs | 6 + .../DapperMaps/EmailDomainMap.cs | 19 + .../DapperMaps/TemplateMap.cs | 26 + .../DapperMaps/TestEmailListMap.cs | 20 + .../DapperMaps/UnsubscribeUrlMap.cs | 15 + .../Repositories/BouncedEmailRepository.cs | 121 +++ .../Repositories/EmailDomainRepository.cs | 101 +++ .../Repositories/TargetRepository.cs | 3 +- .../Repositories/TemplateRepository.cs | 114 +++ .../Repositories/TestEmailListRepository.cs | 85 +++ .../Repositories/UnsubscribeUrlRepository.cs | 96 +++ .../Services/BouncedEmailService.cs | 72 ++ .../Services/EmailDomainService.cs | 68 ++ .../Services/TemplateService.cs | 80 ++ .../Services/TestEmailListService.cs | 57 ++ .../Services/UnsubscribeUrlService.cs | 58 ++ Surge365.MassEmailReact.Web/package-lock.json | 711 +++++++++++++++++- Surge365.MassEmailReact.Web/package.json | 3 + .../src/components/layouts/Layout.tsx | 11 +- .../components/modals/BouncedEmailEdit.tsx | 149 ++++ .../src/components/modals/EmailDomainEdit.tsx | 168 +++++ .../src/components/modals/TargetEdit.tsx | 2 +- .../src/components/modals/TemplateEdit.tsx | 283 +++++++ .../components/modals/TestEmailListEdit.tsx | 156 ++++ .../components/modals/UnsubscribeUrlEdit.tsx | 105 +++ .../src/components/pages/App.tsx | 55 ++ .../src/components/pages/BouncedEmails.tsx | 166 ++++ .../src/components/pages/EmailDomains.tsx | 143 ++++ .../src/components/pages/Servers.tsx | 31 +- .../src/components/pages/Targets.tsx | 30 +- .../src/components/pages/Templates.tsx | 192 +++-- .../src/components/pages/TestEmailLists.tsx | 141 ++++ .../src/components/pages/UnsubscribeUrls.tsx | 134 ++++ .../src/context/SetupDataContext.tsx | 355 ++++++++- .../src/types/bouncedEmail.ts | 8 + .../src/types/emailDomain.ts | 11 + .../src/types/template.ts | 18 + .../src/types/testEmailList.ts | 5 + .../src/types/unsubscribeUrl.ts | 7 + 67 files changed, 4462 insertions(+), 121 deletions(-) create mode 100644 Surge365.MassEmailReact.API/Controllers/BouncedEmailsController.cs create mode 100644 Surge365.MassEmailReact.API/Controllers/EmailDomainsController.cs create mode 100644 Surge365.MassEmailReact.API/Controllers/TemplatesController.cs create mode 100644 Surge365.MassEmailReact.API/Controllers/TestEmailListsController.cs create mode 100644 Surge365.MassEmailReact.API/Controllers/UnsubscribeUrlController.cs create mode 100644 Surge365.MassEmailReact.Application/DTOs/BouncedEmailUpdateDto.cs create mode 100644 Surge365.MassEmailReact.Application/DTOs/EmailDomainUpdateDto.cs create mode 100644 Surge365.MassEmailReact.Application/DTOs/TemplateUpdateDto.cs create mode 100644 Surge365.MassEmailReact.Application/DTOs/TestEmailListUpdateDto.cs create mode 100644 Surge365.MassEmailReact.Application/DTOs/UnsubscribeUrlUpdateDto.cs create mode 100644 Surge365.MassEmailReact.Application/Interfaces/IBouncedEmailRepository.cs create mode 100644 Surge365.MassEmailReact.Application/Interfaces/IBouncedEmailService.cs create mode 100644 Surge365.MassEmailReact.Application/Interfaces/IEmailDomainRepository.cs create mode 100644 Surge365.MassEmailReact.Application/Interfaces/IEmailDomainService.cs create mode 100644 Surge365.MassEmailReact.Application/Interfaces/ITemplateRepository.cs create mode 100644 Surge365.MassEmailReact.Application/Interfaces/ITemplateService.cs create mode 100644 Surge365.MassEmailReact.Application/Interfaces/ITestEmailListRepository.cs create mode 100644 Surge365.MassEmailReact.Application/Interfaces/ITestEmailListService.cs create mode 100644 Surge365.MassEmailReact.Application/Interfaces/IUnsubscribeUrlRepository.cs create mode 100644 Surge365.MassEmailReact.Application/Interfaces/IUnsubscribeUrlService.cs create mode 100644 Surge365.MassEmailReact.Domain/Entities/BouncedEmail.cs create mode 100644 Surge365.MassEmailReact.Domain/Entities/EmailDomain.cs create mode 100644 Surge365.MassEmailReact.Domain/Entities/Template.cs create mode 100644 Surge365.MassEmailReact.Domain/Entities/TestEmailList.cs create mode 100644 Surge365.MassEmailReact.Domain/Entities/UnsubscribeUrl.cs create mode 100644 Surge365.MassEmailReact.Infrastructure/DapperMaps/BouncedEmailMap.cs create mode 100644 Surge365.MassEmailReact.Infrastructure/DapperMaps/EmailDomainMap.cs create mode 100644 Surge365.MassEmailReact.Infrastructure/DapperMaps/TemplateMap.cs create mode 100644 Surge365.MassEmailReact.Infrastructure/DapperMaps/TestEmailListMap.cs create mode 100644 Surge365.MassEmailReact.Infrastructure/DapperMaps/UnsubscribeUrlMap.cs create mode 100644 Surge365.MassEmailReact.Infrastructure/Repositories/BouncedEmailRepository.cs create mode 100644 Surge365.MassEmailReact.Infrastructure/Repositories/EmailDomainRepository.cs create mode 100644 Surge365.MassEmailReact.Infrastructure/Repositories/TemplateRepository.cs create mode 100644 Surge365.MassEmailReact.Infrastructure/Repositories/TestEmailListRepository.cs create mode 100644 Surge365.MassEmailReact.Infrastructure/Repositories/UnsubscribeUrlRepository.cs create mode 100644 Surge365.MassEmailReact.Infrastructure/Services/BouncedEmailService.cs create mode 100644 Surge365.MassEmailReact.Infrastructure/Services/EmailDomainService.cs create mode 100644 Surge365.MassEmailReact.Infrastructure/Services/TemplateService.cs create mode 100644 Surge365.MassEmailReact.Infrastructure/Services/TestEmailListService.cs create mode 100644 Surge365.MassEmailReact.Infrastructure/Services/UnsubscribeUrlService.cs create mode 100644 Surge365.MassEmailReact.Web/src/components/modals/BouncedEmailEdit.tsx create mode 100644 Surge365.MassEmailReact.Web/src/components/modals/EmailDomainEdit.tsx create mode 100644 Surge365.MassEmailReact.Web/src/components/modals/TemplateEdit.tsx create mode 100644 Surge365.MassEmailReact.Web/src/components/modals/TestEmailListEdit.tsx create mode 100644 Surge365.MassEmailReact.Web/src/components/modals/UnsubscribeUrlEdit.tsx create mode 100644 Surge365.MassEmailReact.Web/src/components/pages/BouncedEmails.tsx create mode 100644 Surge365.MassEmailReact.Web/src/components/pages/EmailDomains.tsx create mode 100644 Surge365.MassEmailReact.Web/src/components/pages/TestEmailLists.tsx create mode 100644 Surge365.MassEmailReact.Web/src/components/pages/UnsubscribeUrls.tsx create mode 100644 Surge365.MassEmailReact.Web/src/types/bouncedEmail.ts create mode 100644 Surge365.MassEmailReact.Web/src/types/emailDomain.ts create mode 100644 Surge365.MassEmailReact.Web/src/types/template.ts create mode 100644 Surge365.MassEmailReact.Web/src/types/testEmailList.ts create mode 100644 Surge365.MassEmailReact.Web/src/types/unsubscribeUrl.ts diff --git a/Surge365.MassEmailReact.API/Controllers/BouncedEmailsController.cs b/Surge365.MassEmailReact.API/Controllers/BouncedEmailsController.cs new file mode 100644 index 0000000..02a0975 --- /dev/null +++ b/Surge365.MassEmailReact.API/Controllers/BouncedEmailsController.cs @@ -0,0 +1,74 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Surge365.MassEmailReact.Application.Interfaces; +using Surge365.MassEmailReact.Domain.Entities; +using System.Net.Mail; + +namespace Surge365.MassEmailReact.Server.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class BouncedEmailsController : ControllerBase + { + private readonly IBouncedEmailService _bouncedEmailService; + + public BouncedEmailsController(IBouncedEmailService bouncedEmailService) + { + _bouncedEmailService = bouncedEmailService; + } + + [HttpGet("GetAll")] + public async Task GetAll() + { + var bouncedEmails = await _bouncedEmailService.GetAllAsync(); + return Ok(bouncedEmails); + } + + [HttpGet("{emailAddress}")] + public async Task GetByEmail(string emailAddress) + { + var bouncedEmail = await _bouncedEmailService.GetByEmailAsync(emailAddress); + return bouncedEmail is not null ? Ok(bouncedEmail) : NotFound($"Bounced email with emailAddress '{emailAddress}' not found."); + } + + [HttpPost()] + public async Task CreateBouncedEmail([FromBody] BouncedEmailUpdateDto bouncedEmailUpdateDto) + { + var success = await _bouncedEmailService.CreateAsync(bouncedEmailUpdateDto); + if (!success) + return StatusCode(StatusCodes.Status500InternalServerError, "Failed to create bounced email."); + + var updatedBouncedEmail = await _bouncedEmailService.GetByEmailAsync(bouncedEmailUpdateDto.EmailAddress); + + return Ok(updatedBouncedEmail); + } + + [HttpPut("{emailAddress}")] + public async Task UpdateBouncedEmail(string emailAddress, [FromBody] BouncedEmailUpdateDto bouncedEmailUpdateDto) + { + var existingBouncedEmail = await _bouncedEmailService.GetByEmailAsync(emailAddress); + if (existingBouncedEmail == null) + return NotFound($"Bounced email with emailAddress {emailAddress} not found"); + + var success = await _bouncedEmailService.UpdateAsync(emailAddress, bouncedEmailUpdateDto); + if (!success) + return StatusCode(StatusCodes.Status500InternalServerError, "Failed to update bounced email."); + + var updatedBouncedEmail = await _bouncedEmailService.GetByEmailAsync(bouncedEmailUpdateDto.EmailAddress); + + return Ok(updatedBouncedEmail); + } + + [HttpDelete("{emailAddress}")] + public async Task DeleteBouncedEmail(string emailAddress) + { + ArgumentNullException.ThrowIfNullOrWhiteSpace(emailAddress); + + var success = await _bouncedEmailService.DeleteAsync(emailAddress); + if (!success) + return StatusCode(StatusCodes.Status500InternalServerError, "Failed to delete bounced email."); + + return Ok(); + } + } +} \ No newline at end of file diff --git a/Surge365.MassEmailReact.API/Controllers/EmailDomainsController.cs b/Surge365.MassEmailReact.API/Controllers/EmailDomainsController.cs new file mode 100644 index 0000000..13951ae --- /dev/null +++ b/Surge365.MassEmailReact.API/Controllers/EmailDomainsController.cs @@ -0,0 +1,65 @@ +using Microsoft.AspNetCore.Mvc; +using Surge365.MassEmailReact.Application.Interfaces; +using Surge365.MassEmailReact.Domain.Entities; +using System.Threading.Tasks; + +namespace Surge365.MassEmailReact.Server.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class EmailDomainsController : ControllerBase + { + private readonly IEmailDomainService _emailDomainService; + + public EmailDomainsController(IEmailDomainService emailDomainService) + { + _emailDomainService = emailDomainService; + } + + [HttpGet("GetAll")] + public async Task GetAll([FromQuery] bool? activeOnly) + { + var emailDomains = await _emailDomainService.GetAllAsync(activeOnly == null || activeOnly.Value ? true : false); + return Ok(emailDomains); + } + + [HttpGet("{id}")] + public async Task GetByKey(int id) + { + var emailDomain = await _emailDomainService.GetByIdAsync(id); + return emailDomain is not null ? Ok(emailDomain) : NotFound($"EmailDomain with key '{id}' not found."); + } + + [HttpPost()] + public async Task CreateTarget(int id, [FromBody] EmailDomainUpdateDto emailDomainUpdateDto) + { + if (emailDomainUpdateDto.Id != null && emailDomainUpdateDto.Id > 0) + return BadRequest("Id must be null or 0"); + + var emailDomainId = await _emailDomainService.CreateAsync(emailDomainUpdateDto); + if (emailDomainId == null) + return StatusCode(StatusCodes.Status500InternalServerError, "Failed to create email domain."); + + var createdEmailDomain = await _emailDomainService.GetByIdAsync(emailDomainId.Value); + return Ok(createdEmailDomain); + } + + [HttpPut("{id}")] + public async Task UpdateTarget(int id, [FromBody] EmailDomainUpdateDto emailDomainUpdateDto) + { + if (id != emailDomainUpdateDto.Id) + return BadRequest("Id in URL does not match Id in request body"); + + var existingEmailDomain = await _emailDomainService.GetByIdAsync(id); + if (existingEmailDomain == null) + return NotFound($"EmailDomain with Id {id} not found"); + + var success = await _emailDomainService.UpdateAsync(emailDomainUpdateDto); + if (!success) + return StatusCode(StatusCodes.Status500InternalServerError, "Failed to update email domain."); + + var updatedEmailDomain = await _emailDomainService.GetByIdAsync(id); + return Ok(updatedEmailDomain); + } + } +} \ No newline at end of file diff --git a/Surge365.MassEmailReact.API/Controllers/ServersController.cs b/Surge365.MassEmailReact.API/Controllers/ServersController.cs index b03440a..318bd14 100644 --- a/Surge365.MassEmailReact.API/Controllers/ServersController.cs +++ b/Surge365.MassEmailReact.API/Controllers/ServersController.cs @@ -28,7 +28,7 @@ namespace Surge365.MassEmailReact.Server.Controllers return Ok(servers); } - [HttpGet("{key}")] + [HttpGet("{id}")] public async Task GetByKey(int id, bool? returnPassword = null) { bool returnPasswordValue = returnPassword == null || returnPassword.Value ? true : false; diff --git a/Surge365.MassEmailReact.API/Controllers/TemplatesController.cs b/Surge365.MassEmailReact.API/Controllers/TemplatesController.cs new file mode 100644 index 0000000..4956017 --- /dev/null +++ b/Surge365.MassEmailReact.API/Controllers/TemplatesController.cs @@ -0,0 +1,65 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Surge365.MassEmailReact.Application.Interfaces; +using Surge365.MassEmailReact.Domain.Entities; + +namespace Surge365.MassEmailReact.Server.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class TemplatesController : ControllerBase + { + private readonly ITemplateService _templateService; + + public TemplatesController(ITemplateService templateService) + { + _templateService = templateService; + } + + [HttpGet("GetAll")] + public async Task GetAll([FromQuery] bool? activeOnly) + { + var templates = await _templateService.GetAllAsync(activeOnly == null || activeOnly.Value ? true : false); + return Ok(templates); + } + + [HttpGet("{id}")] + public async Task GetById(int id) + { + var template = await _templateService.GetByIdAsync(id); + return template is not null ? Ok(template) : NotFound($"Template with id '{id}' not found."); + } + + [HttpPost] + public async Task CreateTemplate([FromBody] TemplateUpdateDto templateUpdateDto) + { + if (templateUpdateDto.Id != null && templateUpdateDto.Id > 0) + return BadRequest("Id must be null or 0"); + + var templateId = await _templateService.CreateAsync(templateUpdateDto); + if (templateId == null) + return StatusCode(StatusCodes.Status500InternalServerError, "Failed to create template."); + + var createdTemplate = await _templateService.GetByIdAsync(templateId.Value); + return Ok(createdTemplate); + } + + [HttpPut("{id}")] + public async Task UpdateTemplate(int id, [FromBody] TemplateUpdateDto templateUpdateDto) + { + if (id != templateUpdateDto.Id) + return BadRequest("Id in URL does not match Id in request body"); + + var existingTemplate = await _templateService.GetByIdAsync(id); + if (existingTemplate == null) + return NotFound($"Template with Id {id} not found"); + + var success = await _templateService.UpdateAsync(templateUpdateDto); + if (!success) + return StatusCode(StatusCodes.Status500InternalServerError, "Failed to update template."); + + var updatedTemplate = await _templateService.GetByIdAsync(id); + return Ok(updatedTemplate); + } + } +} \ No newline at end of file diff --git a/Surge365.MassEmailReact.API/Controllers/TestEmailListsController.cs b/Surge365.MassEmailReact.API/Controllers/TestEmailListsController.cs new file mode 100644 index 0000000..45a97e0 --- /dev/null +++ b/Surge365.MassEmailReact.API/Controllers/TestEmailListsController.cs @@ -0,0 +1,65 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Surge365.MassEmailReact.Application.Interfaces; +using Surge365.MassEmailReact.Domain.Entities; + +namespace Surge365.MassEmailReact.Server.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class TestEmailListsController : ControllerBase + { + private readonly ITestEmailListService _testEmailListService; + + public TestEmailListsController(ITestEmailListService testEmailListService) + { + _testEmailListService = testEmailListService; + } + + [HttpGet("GetAll")] + public async Task GetAll() + { + var testEmailLists = await _testEmailListService.GetAllAsync(); + return Ok(testEmailLists); + } + + [HttpGet("{id}")] + public async Task GetById(int id) + { + var testEmailList = await _testEmailListService.GetByIdAsync(id); + return testEmailList is not null ? Ok(testEmailList) : NotFound($"Test email list with id '{id}' not found."); + } + + [HttpPost] + public async Task Create([FromBody] TestEmailListUpdateDto testEmailListDto) + { + if (testEmailListDto.Id != null && testEmailListDto.Id > 0) + return BadRequest("Id must be null or 0"); + + var testEmailListId = await _testEmailListService.CreateAsync(testEmailListDto); + if (testEmailListId == null) + return StatusCode(StatusCodes.Status500InternalServerError, "Failed to create test email list."); + + var createdTestEmailList = await _testEmailListService.GetByIdAsync(testEmailListId.Value); + return Ok(createdTestEmailList); + } + + [HttpPut("{id}")] + public async Task Update(int id, [FromBody] TestEmailListUpdateDto testEmailListDto) + { + if (id != testEmailListDto.Id) + return BadRequest("Id in URL does not match Id in request body"); + + var existingTestEmailList = await _testEmailListService.GetByIdAsync(id); + if (existingTestEmailList == null) + return NotFound($"Test email list with Id {id} not found"); + + var success = await _testEmailListService.UpdateAsync(testEmailListDto); + if (!success) + return StatusCode(StatusCodes.Status500InternalServerError, "Failed to update test email list."); + + var updatedTestEmailList = await _testEmailListService.GetByIdAsync(id); + return Ok(updatedTestEmailList); + } + } +} \ No newline at end of file diff --git a/Surge365.MassEmailReact.API/Controllers/UnsubscribeUrlController.cs b/Surge365.MassEmailReact.API/Controllers/UnsubscribeUrlController.cs new file mode 100644 index 0000000..3366617 --- /dev/null +++ b/Surge365.MassEmailReact.API/Controllers/UnsubscribeUrlController.cs @@ -0,0 +1,66 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Surge365.MassEmailReact.Application.DTOs; +using Surge365.MassEmailReact.Application.Interfaces; +using Surge365.MassEmailReact.Domain.Entities; + +namespace Surge365.MassEmailReact.Server.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class UnsubscribeUrlsController : ControllerBase + { + private readonly IUnsubscribeUrlService _unsubscribeUrlService; + + public UnsubscribeUrlsController(IUnsubscribeUrlService unsubscribeUrlService) + { + _unsubscribeUrlService = unsubscribeUrlService; + } + + [HttpGet("GetAll")] + public async Task GetAll([FromQuery] bool? activeOnly) + { + var unsubscribeUrls = await _unsubscribeUrlService.GetAllAsync(activeOnly == null || activeOnly.Value ? true : false); + return Ok(unsubscribeUrls); + } + + [HttpGet("{id}")] + public async Task GetByKey(int id) + { + var unsubscribeUrl = await _unsubscribeUrlService.GetByIdAsync(id); + return unsubscribeUrl is not null ? Ok(unsubscribeUrl) : NotFound($"UnsubscribeUrl with key '{id}' not found."); + } + [HttpPost()] + public async Task Create(int id, [FromBody] UnsubscribeUrlUpdateDto unsubscribeUrlUpdateDto) + { + if (unsubscribeUrlUpdateDto.Id != null && unsubscribeUrlUpdateDto.Id > 0) + return BadRequest("Id must be null or 0"); + + var unsubscribeUrlId = await _unsubscribeUrlService.CreateAsync(unsubscribeUrlUpdateDto); + if (unsubscribeUrlId == null) + return StatusCode(StatusCodes.Status500InternalServerError, "Failed to create unsubscribe url."); + + var createdUnsubscribeUrl = await _unsubscribeUrlService.GetByIdAsync(unsubscribeUrlId.Value); + + return Ok(createdUnsubscribeUrl); + } + [HttpPut("{id}")] + public async Task Update(int id, [FromBody] UnsubscribeUrlUpdateDto unsubscribeUrlUpdateDto) + { + if (id != unsubscribeUrlUpdateDto.Id) + return BadRequest("Id in URL does not match Id in request body"); + + var existingUnsubscribeUrl = await _unsubscribeUrlService.GetByIdAsync(id); + if (existingUnsubscribeUrl == null) + return NotFound($"UnsubscribeUrl with Id {id} not found"); + + var success = await _unsubscribeUrlService.UpdateAsync(unsubscribeUrlUpdateDto); + if (!success) + return StatusCode(StatusCodes.Status500InternalServerError, "Failed to update unsubscribe url."); + + var updatedUnsubscribeUrl = await _unsubscribeUrlService.GetByIdAsync(id); + + return Ok(updatedUnsubscribeUrl); + } + } +} \ No newline at end of file diff --git a/Surge365.MassEmailReact.API/Program.cs b/Surge365.MassEmailReact.API/Program.cs index b6298ac..cec99e9 100644 --- a/Surge365.MassEmailReact.API/Program.cs +++ b/Surge365.MassEmailReact.API/Program.cs @@ -18,6 +18,17 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); + var app = builder.Build(); app.UseDefaultFiles(); diff --git a/Surge365.MassEmailReact.Application/DTOs/BouncedEmailUpdateDto.cs b/Surge365.MassEmailReact.Application/DTOs/BouncedEmailUpdateDto.cs new file mode 100644 index 0000000..8b26ba6 --- /dev/null +++ b/Surge365.MassEmailReact.Application/DTOs/BouncedEmailUpdateDto.cs @@ -0,0 +1,10 @@ +namespace Surge365.MassEmailReact.Domain.Entities +{ + public class BouncedEmailUpdateDto + { + public int? Id { get; set; } + public string EmailAddress { get; set; } = ""; + public bool Spam { get; set; } = false; + public bool Unsubscribe { get; set; } = false; + } +} \ No newline at end of file diff --git a/Surge365.MassEmailReact.Application/DTOs/EmailDomainUpdateDto.cs b/Surge365.MassEmailReact.Application/DTOs/EmailDomainUpdateDto.cs new file mode 100644 index 0000000..98c2aa3 --- /dev/null +++ b/Surge365.MassEmailReact.Application/DTOs/EmailDomainUpdateDto.cs @@ -0,0 +1,15 @@ +using System; + +namespace Surge365.MassEmailReact.Domain.Entities +{ + public class EmailDomainUpdateDto + { + public int? Id { get; set; } + public string Name { get; set; } = ""; + public string EmailAddress { get; set; } = ""; + public string Username { get; set; } = ""; + public string Password { get; set; } = ""; + public bool IsActive { get; set; } = true; + public int DisplayOrder { get; set; } = 0; + } +} \ No newline at end of file diff --git a/Surge365.MassEmailReact.Application/DTOs/TemplateUpdateDto.cs b/Surge365.MassEmailReact.Application/DTOs/TemplateUpdateDto.cs new file mode 100644 index 0000000..2b3710d --- /dev/null +++ b/Surge365.MassEmailReact.Application/DTOs/TemplateUpdateDto.cs @@ -0,0 +1,20 @@ +namespace Surge365.MassEmailReact.Domain.Entities +{ + public class TemplateUpdateDto + { + public int? Id { get; set; } + public string Name { get; set; } = ""; + public int DomainId { get; set; } + public string Description { get; set; } = ""; + public string HtmlBody { get; set; } = ""; + public string Subject { get; set; } = ""; + public string ToName { get; set; } = ""; + public string FromName { get; set; } = ""; + public string FromEmail { get; set; } = ""; + public string ReplyToEmail { get; set; } = ""; + public bool ClickTracking { get; set; } = false; + public bool OpenTracking { get; set; } = false; + public string CategoryXml { get; set; } = ""; + public bool IsActive { get; set; } = true; + } +} \ No newline at end of file diff --git a/Surge365.MassEmailReact.Application/DTOs/TestEmailListUpdateDto.cs b/Surge365.MassEmailReact.Application/DTOs/TestEmailListUpdateDto.cs new file mode 100644 index 0000000..4672fea --- /dev/null +++ b/Surge365.MassEmailReact.Application/DTOs/TestEmailListUpdateDto.cs @@ -0,0 +1,9 @@ +namespace Surge365.MassEmailReact.Domain.Entities +{ + public class TestEmailListUpdateDto + { + public int? Id { get; set; } + public string Name { get; set; } = ""; + public List Emails { get; set; } = new List(); + } +} \ No newline at end of file diff --git a/Surge365.MassEmailReact.Application/DTOs/UnsubscribeUrlUpdateDto.cs b/Surge365.MassEmailReact.Application/DTOs/UnsubscribeUrlUpdateDto.cs new file mode 100644 index 0000000..d6918a2 --- /dev/null +++ b/Surge365.MassEmailReact.Application/DTOs/UnsubscribeUrlUpdateDto.cs @@ -0,0 +1,11 @@ +using System; + +namespace Surge365.MassEmailReact.Application.DTOs +{ + public class UnsubscribeUrlUpdateDto + { + public int? Id { get; set; } + public string Name { get; set; } = ""; + public string Url { get; set; } = ""; + } +} \ No newline at end of file diff --git a/Surge365.MassEmailReact.Application/Interfaces/IBouncedEmailRepository.cs b/Surge365.MassEmailReact.Application/Interfaces/IBouncedEmailRepository.cs new file mode 100644 index 0000000..ae4a0d4 --- /dev/null +++ b/Surge365.MassEmailReact.Application/Interfaces/IBouncedEmailRepository.cs @@ -0,0 +1,16 @@ +using Surge365.MassEmailReact.Domain.Entities; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Surge365.MassEmailReact.Application.Interfaces +{ + public interface IBouncedEmailRepository + { + Task GetByEmailAsync(string emailAddress); + Task> GetAllAsync(); + Task CreateAsync(BouncedEmail bouncedEmail); + Task UpdateAsync(string originalEmailAddress, BouncedEmail bouncedEmail); + Task DeleteAsync(string emailAddress); + } +} \ No newline at end of file diff --git a/Surge365.MassEmailReact.Application/Interfaces/IBouncedEmailService.cs b/Surge365.MassEmailReact.Application/Interfaces/IBouncedEmailService.cs new file mode 100644 index 0000000..ff3c4a3 --- /dev/null +++ b/Surge365.MassEmailReact.Application/Interfaces/IBouncedEmailService.cs @@ -0,0 +1,13 @@ +using Surge365.MassEmailReact.Domain.Entities; + +namespace Surge365.MassEmailReact.Application.Interfaces +{ + public interface IBouncedEmailService + { + Task GetByEmailAsync(string emailAddress); + Task> GetAllAsync(); + Task CreateAsync(BouncedEmailUpdateDto bouncedEmailDto); + Task UpdateAsync(string originalEmailAddress, BouncedEmailUpdateDto bouncedEmailDto); + Task DeleteAsync(string emailAddress); + } +} \ No newline at end of file diff --git a/Surge365.MassEmailReact.Application/Interfaces/IEmailDomainRepository.cs b/Surge365.MassEmailReact.Application/Interfaces/IEmailDomainRepository.cs new file mode 100644 index 0000000..987bf39 --- /dev/null +++ b/Surge365.MassEmailReact.Application/Interfaces/IEmailDomainRepository.cs @@ -0,0 +1,14 @@ +using Surge365.MassEmailReact.Domain.Entities; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Surge365.MassEmailReact.Application.Interfaces +{ + public interface IEmailDomainRepository + { + Task GetByIdAsync(int id); + Task> GetAllAsync(bool activeOnly = true); + Task CreateAsync(EmailDomain emailDomain); + Task UpdateAsync(EmailDomain emailDomain); + } +} \ No newline at end of file diff --git a/Surge365.MassEmailReact.Application/Interfaces/IEmailDomainService.cs b/Surge365.MassEmailReact.Application/Interfaces/IEmailDomainService.cs new file mode 100644 index 0000000..6bffa51 --- /dev/null +++ b/Surge365.MassEmailReact.Application/Interfaces/IEmailDomainService.cs @@ -0,0 +1,14 @@ +using Surge365.MassEmailReact.Domain.Entities; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Surge365.MassEmailReact.Application.Interfaces +{ + public interface IEmailDomainService + { + Task GetByIdAsync(int id); + Task> GetAllAsync(bool activeOnly = true); + Task CreateAsync(EmailDomainUpdateDto emailDomainDto); + Task UpdateAsync(EmailDomainUpdateDto emailDomainDto); + } +} \ No newline at end of file diff --git a/Surge365.MassEmailReact.Application/Interfaces/ITemplateRepository.cs b/Surge365.MassEmailReact.Application/Interfaces/ITemplateRepository.cs new file mode 100644 index 0000000..f2f4ee2 --- /dev/null +++ b/Surge365.MassEmailReact.Application/Interfaces/ITemplateRepository.cs @@ -0,0 +1,14 @@ +using Surge365.MassEmailReact.Domain.Entities; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Surge365.MassEmailReact.Application.Interfaces +{ + public interface ITemplateRepository + { + Task GetByIdAsync(int id); + Task> GetAllAsync(bool activeOnly = true); + Task CreateAsync(Template template); + Task UpdateAsync(Template template); + } +} \ No newline at end of file diff --git a/Surge365.MassEmailReact.Application/Interfaces/ITemplateService.cs b/Surge365.MassEmailReact.Application/Interfaces/ITemplateService.cs new file mode 100644 index 0000000..6d8d034 --- /dev/null +++ b/Surge365.MassEmailReact.Application/Interfaces/ITemplateService.cs @@ -0,0 +1,14 @@ +using Surge365.MassEmailReact.Domain.Entities; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Surge365.MassEmailReact.Application.Interfaces +{ + public interface ITemplateService + { + Task GetByIdAsync(int id); + Task> GetAllAsync(bool activeOnly = true); + Task CreateAsync(TemplateUpdateDto templateDto); + Task UpdateAsync(TemplateUpdateDto templateDto); + } +} \ No newline at end of file diff --git a/Surge365.MassEmailReact.Application/Interfaces/ITestEmailListRepository.cs b/Surge365.MassEmailReact.Application/Interfaces/ITestEmailListRepository.cs new file mode 100644 index 0000000..05bfbab --- /dev/null +++ b/Surge365.MassEmailReact.Application/Interfaces/ITestEmailListRepository.cs @@ -0,0 +1,14 @@ +using Surge365.MassEmailReact.Domain.Entities; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Surge365.MassEmailReact.Application.Interfaces +{ + public interface ITestEmailListRepository + { + Task GetByIdAsync(int id); + Task> GetAllAsync(); + Task CreateAsync(TestEmailList testEmailList); + Task UpdateAsync(TestEmailList testEmailList); + } +} \ No newline at end of file diff --git a/Surge365.MassEmailReact.Application/Interfaces/ITestEmailListService.cs b/Surge365.MassEmailReact.Application/Interfaces/ITestEmailListService.cs new file mode 100644 index 0000000..449e169 --- /dev/null +++ b/Surge365.MassEmailReact.Application/Interfaces/ITestEmailListService.cs @@ -0,0 +1,14 @@ +using Surge365.MassEmailReact.Domain.Entities; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Surge365.MassEmailReact.Application.Interfaces +{ + public interface ITestEmailListService + { + Task GetByIdAsync(int id); + Task> GetAllAsync(); + Task CreateAsync(TestEmailListUpdateDto testEmailListDto); + Task UpdateAsync(TestEmailListUpdateDto testEmailListDto); + } +} \ No newline at end of file diff --git a/Surge365.MassEmailReact.Application/Interfaces/IUnsubscribeUrlRepository.cs b/Surge365.MassEmailReact.Application/Interfaces/IUnsubscribeUrlRepository.cs new file mode 100644 index 0000000..5d7152c --- /dev/null +++ b/Surge365.MassEmailReact.Application/Interfaces/IUnsubscribeUrlRepository.cs @@ -0,0 +1,15 @@ +using Surge365.MassEmailReact.Domain.Entities; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Surge365.MassEmailReact.Application.Interfaces +{ + public interface IUnsubscribeUrlRepository + { + Task GetByIdAsync(int id); + Task> GetAllAsync(bool activeOnly = true); + Task CreateAsync(UnsubscribeUrl unsubscribeUrl); + Task UpdateAsync(UnsubscribeUrl unsubscribeUrl); + } +} \ No newline at end of file diff --git a/Surge365.MassEmailReact.Application/Interfaces/IUnsubscribeUrlService.cs b/Surge365.MassEmailReact.Application/Interfaces/IUnsubscribeUrlService.cs new file mode 100644 index 0000000..17ebd36 --- /dev/null +++ b/Surge365.MassEmailReact.Application/Interfaces/IUnsubscribeUrlService.cs @@ -0,0 +1,16 @@ +using Surge365.MassEmailReact.Application.DTOs; +using Surge365.MassEmailReact.Domain.Entities; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Surge365.MassEmailReact.Application.Interfaces +{ + public interface IUnsubscribeUrlService + { + Task GetByIdAsync(int id); + Task> GetAllAsync(bool activeOnly = true); + Task CreateAsync(UnsubscribeUrlUpdateDto unsubscribeUrlDto); + Task UpdateAsync(UnsubscribeUrlUpdateDto unsubscribeUrlDto); + } +} \ No newline at end of file diff --git a/Surge365.MassEmailReact.Domain/Entities/BouncedEmail.cs b/Surge365.MassEmailReact.Domain/Entities/BouncedEmail.cs new file mode 100644 index 0000000..ae5d4b4 --- /dev/null +++ b/Surge365.MassEmailReact.Domain/Entities/BouncedEmail.cs @@ -0,0 +1,23 @@ +namespace Surge365.MassEmailReact.Domain.Entities +{ + public class BouncedEmail + { + public string EmailAddress { get; set; } = ""; + public bool Spam { get; set; } + public bool Unsubscribe { get; set; } + public bool EnteredByAdmin { get; set; } + + public BouncedEmail() { } + private BouncedEmail(string emailAddress, bool spam, bool unsubscribe, bool enteredByAdmin) + { + EmailAddress = emailAddress; + Spam = spam; + Unsubscribe = unsubscribe; + EnteredByAdmin = enteredByAdmin; + } + public static BouncedEmail Create(string emailAddress, bool spam, bool unsubscribe, bool enteredByAdmin) + { + return new BouncedEmail(emailAddress, spam, unsubscribe, enteredByAdmin); + } + } +} \ No newline at end of file diff --git a/Surge365.MassEmailReact.Domain/Entities/EmailDomain.cs b/Surge365.MassEmailReact.Domain/Entities/EmailDomain.cs new file mode 100644 index 0000000..f6cf520 --- /dev/null +++ b/Surge365.MassEmailReact.Domain/Entities/EmailDomain.cs @@ -0,0 +1,33 @@ +using System; + +namespace Surge365.MassEmailReact.Domain.Entities +{ + public class EmailDomain + { + public int? Id { get; private set; } + public string Name { get; set; } = ""; + public string EmailAddress { get; set; } = ""; + public string Username { get; set; } = ""; + public string Password { get; set; } = ""; + public bool IsActive { get; set; } + public int DisplayOrder { get; set; } + + public EmailDomain() { } + + private EmailDomain(int id, string name, string emailAddress, string username, string password, bool isActive, int displayOrder) + { + Id = id; + Name = name; + EmailAddress = emailAddress; + Username = username; + Password = password; + IsActive = isActive; + DisplayOrder = displayOrder; + } + + public static EmailDomain Create(int id, string name, string emailAddress, string username, string password, bool isActive, int displayOrder) + { + return new EmailDomain(id, name, emailAddress, username, password, isActive, displayOrder); + } + } +} \ No newline at end of file diff --git a/Surge365.MassEmailReact.Domain/Entities/Template.cs b/Surge365.MassEmailReact.Domain/Entities/Template.cs new file mode 100644 index 0000000..f61f3aa --- /dev/null +++ b/Surge365.MassEmailReact.Domain/Entities/Template.cs @@ -0,0 +1,53 @@ +using System; + +namespace Surge365.MassEmailReact.Domain.Entities +{ + public class Template + { + public int? Id { get; private set; } + public string Name { get; set; } = ""; + public int DomainId { get; set; } + public string Description { get; set; } = ""; + public string HtmlBody { get; set; } = ""; + public string Subject { get; set; } = ""; + public string ToName { get; set; } = ""; + public string FromName { get; set; } = ""; + public string FromEmail { get; set; } = ""; + public string ReplyToEmail { get; set; } = ""; + public bool ClickTracking { get; set; } + public bool OpenTracking { get; set; } + public string CategoryXml { get; set; } = ""; + public bool IsActive { get; set; } + + public Template() { } + + private Template(int id, string name, int domainId, string description, string htmlBody, string subject, + string toName, string fromName, string fromEmail, string replyToEmail, + bool clickTracking, bool openTracking, string categoryXml, bool isActive) + { + Id = id; + Name = name; + DomainId = domainId; + Description = description; + HtmlBody = htmlBody; + Subject = subject; + ToName = toName; + FromName = fromName; + FromEmail = fromEmail; + ReplyToEmail = replyToEmail; + ClickTracking = clickTracking; + OpenTracking = openTracking; + CategoryXml = categoryXml; + IsActive = isActive; + } + + public static Template Create(int id, string name, int domainId, string description, string htmlBody, + string subject, string toName, string fromName, string fromEmail, + string replyToEmail, bool clickTracking, bool openTracking, + string categoryXml, bool isActive) + { + return new Template(id, name, domainId, description, htmlBody, subject, toName, fromName, + fromEmail, replyToEmail, clickTracking, openTracking, categoryXml, isActive); + } + } +} \ No newline at end of file diff --git a/Surge365.MassEmailReact.Domain/Entities/TestEmailList.cs b/Surge365.MassEmailReact.Domain/Entities/TestEmailList.cs new file mode 100644 index 0000000..de185d3 --- /dev/null +++ b/Surge365.MassEmailReact.Domain/Entities/TestEmailList.cs @@ -0,0 +1,48 @@ +using System.Data.SqlTypes; +using System.Xml.Linq; + +namespace Surge365.MassEmailReact.Domain.Entities +{ + public class TestEmailList + { + public int? Id { get; private set; } + public string Name { get; set; } = ""; + public string List { get; set; } = ""; + public List Emails + { + get + { + return ParseListXml(List); + } + } + public TestEmailList() { } + + public static List ParseListXml(string xml) + { + return XDocument.Parse(xml).Descendants("Email").Select(e => e.Value).ToList(); + } + public string GetListXml() + { + return GetListXml(Emails); + } + public static string GetListXml(List emails) + { + if (emails.Count == 0) + return ""; + + string xmlString = new XDocument(new XElement("Emails",emails.Select(email => new XElement("Email", email)))).ToString(); + return xmlString; + } + private TestEmailList(int id, string name, string list) + { + Id = id; + Name = name; + List = list; + } + + public static TestEmailList Create(int id, string name, string list) + { + return new TestEmailList(id, name, list); + } + } +} \ No newline at end of file diff --git a/Surge365.MassEmailReact.Domain/Entities/UnsubscribeUrl.cs b/Surge365.MassEmailReact.Domain/Entities/UnsubscribeUrl.cs new file mode 100644 index 0000000..a28fde8 --- /dev/null +++ b/Surge365.MassEmailReact.Domain/Entities/UnsubscribeUrl.cs @@ -0,0 +1,23 @@ +using System; + +namespace Surge365.MassEmailReact.Domain.Entities +{ + public class UnsubscribeUrl + { + public int? Id { get; private set; } + public string Name { get; set; } = ""; + public string Url { get; set; } = ""; + + public UnsubscribeUrl() { } + private UnsubscribeUrl(int id, string name, string url) + { + Id = id; + Name = name; + Url = url; + } + public static UnsubscribeUrl Create(int id, string name, string url) + { + return new UnsubscribeUrl(id, name, url); + } + } +} \ No newline at end of file diff --git a/Surge365.MassEmailReact.Infrastructure/DapperMaps/BouncedEmailMap.cs b/Surge365.MassEmailReact.Infrastructure/DapperMaps/BouncedEmailMap.cs new file mode 100644 index 0000000..88b904a --- /dev/null +++ b/Surge365.MassEmailReact.Infrastructure/DapperMaps/BouncedEmailMap.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Dapper.FluentMap.Mapping; +using Surge365.MassEmailReact.Domain.Entities; + +namespace Surge365.MassEmailReact.Infrastructure.DapperMaps +{ + public class BouncedEmailMap : EntityMap + { + public BouncedEmailMap() + { + Map(p => p.EmailAddress).ToColumn("email_address"); + Map(p => p.Spam).ToColumn("spam"); + Map(p => p.Unsubscribe).ToColumn("unsubscribe"); + Map(p => p.EnteredByAdmin).ToColumn("entered_by_admin"); + } + } +} diff --git a/Surge365.MassEmailReact.Infrastructure/DapperMaps/DapperConfiguration.cs b/Surge365.MassEmailReact.Infrastructure/DapperMaps/DapperConfiguration.cs index 53d32a7..36e1a21 100644 --- a/Surge365.MassEmailReact.Infrastructure/DapperMaps/DapperConfiguration.cs +++ b/Surge365.MassEmailReact.Infrastructure/DapperMaps/DapperConfiguration.cs @@ -1,4 +1,5 @@ using Dapper.FluentMap; +using Surge365.MassEmailReact.Domain.Entities; using System; using System.Collections.Generic; using System.Linq; @@ -15,6 +16,11 @@ namespace Surge365.MassEmailReact.Infrastructure.DapperMaps { config.AddMap(new TargetMap()); config.AddMap(new ServerMap()); + config.AddMap(new TestEmailListMap()); + config.AddMap(new BouncedEmailMap()); + config.AddMap(new UnsubscribeUrlMap()); + config.AddMap(new TemplateMap()); + config.AddMap(new EmailDomainMap()); }); } } diff --git a/Surge365.MassEmailReact.Infrastructure/DapperMaps/EmailDomainMap.cs b/Surge365.MassEmailReact.Infrastructure/DapperMaps/EmailDomainMap.cs new file mode 100644 index 0000000..369ab10 --- /dev/null +++ b/Surge365.MassEmailReact.Infrastructure/DapperMaps/EmailDomainMap.cs @@ -0,0 +1,19 @@ +using Dapper.FluentMap.Mapping; +using Surge365.MassEmailReact.Domain.Entities; + +namespace Surge365.MassEmailReact.Infrastructure.DapperMaps +{ + public class EmailDomainMap : EntityMap + { + public EmailDomainMap() + { + Map(p => p.Id).ToColumn("domain_key"); + Map(p => p.Name).ToColumn("name"); + Map(p => p.EmailAddress).ToColumn("email_address"); + Map(p => p.Username).ToColumn("username"); + Map(p => p.Password).ToColumn("password"); + Map(p => p.IsActive).ToColumn("is_active"); + Map(p => p.DisplayOrder).ToColumn("display_order"); + } + } +} \ No newline at end of file diff --git a/Surge365.MassEmailReact.Infrastructure/DapperMaps/TemplateMap.cs b/Surge365.MassEmailReact.Infrastructure/DapperMaps/TemplateMap.cs new file mode 100644 index 0000000..56d4663 --- /dev/null +++ b/Surge365.MassEmailReact.Infrastructure/DapperMaps/TemplateMap.cs @@ -0,0 +1,26 @@ +using Dapper.FluentMap.Mapping; +using Surge365.MassEmailReact.Domain.Entities; + +namespace Surge365.MassEmailReact.Infrastructure.DapperMaps +{ + public class TemplateMap : EntityMap