From 9703517974d4880efb60d5ba3546cf253969ff3a Mon Sep 17 00:00:00 2001 From: David Headrick Date: Wed, 9 Apr 2025 11:57:32 -0500 Subject: [PATCH] Update configuration and mailing service functionality - Added HTTP client configuration for SendGrid in Program.cs. - Cleaned up appsettings files for development and production environments. - Modified MailingService to use IHttpClientFactory for SendGrid. - Enhanced NewMailings component with cancel functionality and confirmation dialog. - Updated project dependencies in .csproj and created package-lock.json. --- Surge365.MassEmailReact.API/Program.cs | 12 +++- .../appsettings.Development.json | 10 +--- .../appsettings.Uat.json | 10 ++-- Surge365.MassEmailReact.API/appsettings.json | 6 +- Surge365.MassEmailReact.API/package-lock.json | 6 ++ .../Services/MailingService.cs | 21 ++----- ...ge365.MassEmailReact.Infrastructure.csproj | 13 +++-- .../src/components/pages/NewMailings.tsx | 57 +++++++++++++++++-- .../components/pages/ScheduledMailings.tsx | 2 +- 9 files changed, 91 insertions(+), 46 deletions(-) create mode 100644 Surge365.MassEmailReact.API/package-lock.json diff --git a/Surge365.MassEmailReact.API/Program.cs b/Surge365.MassEmailReact.API/Program.cs index fac2119..6371212 100644 --- a/Surge365.MassEmailReact.API/Program.cs +++ b/Surge365.MassEmailReact.API/Program.cs @@ -4,9 +4,19 @@ using Surge365.MassEmailReact.Domain.Entities; using Surge365.MassEmailReact.Infrastructure.DapperMaps; using Surge365.MassEmailReact.Infrastructure.Repositories; using Surge365.MassEmailReact.Infrastructure.Services; +using System.Net; +using System.Security.Authentication; + + var builder = WebApplication.CreateBuilder(args); - +builder.Services.AddHttpClient("SendGridClient", client => +{ + client.BaseAddress = new Uri("https://api.sendgrid.com/"); // Optional, for clarity +}).ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler +{ + SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13 +}); // Add services to the container. builder.Services.AddControllers(); diff --git a/Surge365.MassEmailReact.API/appsettings.Development.json b/Surge365.MassEmailReact.API/appsettings.Development.json index a5465e6..128b0e3 100644 --- a/Surge365.MassEmailReact.API/appsettings.Development.json +++ b/Surge365.MassEmailReact.API/appsettings.Development.json @@ -9,18 +9,10 @@ "Jwt": { "Secret": "Z9R5aFml+eRMeb7tyf8N9wCq3tZpS/EM6nGqOxlXPtOw4cJ3zS1AByczrIlD5F9d" }, - "AppCode": "MassEmailReactApi", - "AuthAppCode": "MassEmailWeb", "EnvironmentCode": "UAT", "ConnectionStrings": { "Marketing.ConnectionString": "data source=uat.surge365.com;initial catalog=Marketing;User ID=ytb;Password=YTB()nl!n3;Encrypt=yes;TrustServerCertificate=yes;Connection Timeout=3;Application Name=##application_name##;", //TODO: Move this to development.json, on server should go somewhere secure. GET IT OUT OF GIT "MassEmail.ConnectionString": "data source=uat.surge365.com;initial catalog=MassEmail;User ID=ytb;Password=YTB()nl!n3;Encrypt=yes;TrustServerCertificate=yes;Connection Timeout=3;Application Name=##application_name##;" //TODO: Move this to development.json, on server should go somewhere secure. GET IT OUT OF GIT }, - "TestTargetSql": "CREATE TABLE #columns\r\n(\r\n primary_key INT NOT NULL IDENTITY(1,1) PRIMARY KEY,\r\n name VARCHAR(255),\r\n data_type CHAR(1)\r\n)\r\nSELECT TOP 10 *\r\nINTO #list\r\nFROM ##database_name##..##view_name##\r\n##filter##\r\n\r\nDECLARE @row_count INT\r\nSELECT @row_count = COUNT(*)\r\nFROM ##database_name##..##view_name##\r\n##filter##\r\n\r\nDECLARE c_curs CURSOR FOR \r\nSELECT c.name AS column_name, t.name AS data_type\r\nFROM tempdb.sys.columns c\r\nINNER JOIN tempdb.sys.types t ON c.user_type_id = t.user_type_id\r\n AND t.name NOT IN ('text','ntext','image','binary','varbinary','image','cursor','timestamp','hierarchyid','sql_variant','xml','table')\r\nWHERE object_id = object_id('tempdb..#list') \r\n AND ((t.name IN ('char','varchar') AND c.max_length <= 255)\r\n OR (t.name IN ('nchar','nvarchar') AND c.max_length <= 510)\r\n OR (t.name NOT IN ('char','varchar','nchar','nvarchar')))\r\n \r\nOPEN c_curs\r\nDECLARE @column_name VARCHAR(255), @column_type VARCHAR(255)\r\n\r\nFETCH NEXT FROM c_curs INTO @column_name, @column_type\r\nWHILE(@@FETCH_STATUS = 0)\r\nBEGIN \r\n DECLARE @data_type CHAR(1) = 'S'\r\n IF(@column_type IN ('date','datetime','datetime2','datetimeoffset','smalldatetime','time'))\r\n BEGIN\r\n SET @data_type = 'D'\r\n END\r\n ELSE IF(@column_type IN ('bit'))\r\n BEGIN\r\n SET @data_type = 'B'\r\n END\r\n ELSE IF(@column_type IN ('bigint','numeric','smallint','decimal','smallmoney','int','tinyint','money','float','real'))\r\n BEGIN\r\n SET @data_type = 'N'\r\n END\r\n INSERT INTO #columns(name, data_type) VALUES(@column_name, @data_type)\r\n FETCH NEXT FROM c_curs INTO @column_name, @column_type\r\nEND\r\nCLOSE c_curs\r\nDEALLOCATE c_curs\r\nSELECT * FROM #columns ORDER BY primary_key\r\nSELECT * FROM #list\r\nSELECT @row_count AS row_count\r\nDROP TABLE #columns\r\nDROP TABLE #list", - "ConnectionStringTemplate": "data source=##server_name##,##port##;initial catalog=##database_name##;User ID=##username##;Password=##password##;persist security info=False;packet size=4096;TrustServerCertificate=True;", - "DefaultUnsubscribeUrl": "https://uat.emailopentracking.surge365.com/unsubscribe.htm", - "SendGrid_TestMode": false, - "RegularExpression_Email": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", - "SendGrid_Url": "smtp.sendgrid.net", - "SendGrid_Port": "587" + "DefaultUnsubscribeUrl": "https://uat.emailopentracking.surge365.com/unsubscribe.htm" } \ No newline at end of file diff --git a/Surge365.MassEmailReact.API/appsettings.Uat.json b/Surge365.MassEmailReact.API/appsettings.Uat.json index fe842cd..5c857ec 100644 --- a/Surge365.MassEmailReact.API/appsettings.Uat.json +++ b/Surge365.MassEmailReact.API/appsettings.Uat.json @@ -10,9 +10,9 @@ "Secret": "1bXgXk7v/W9XksGoNiqWvM7+9/BERZonShxqoCVvdi8Ew47M1VFzJGA9sPMgkmn/HRmuZ83iytNsHXI6GkAb8g==" }, "EnvironmentCode": "UAT", - "DefaultUnsubscribeUrl": "https://uat.emailopentracking.surge365.com/unsubscribe.htm", - "SendGrid_TestMode": true, - "RegularExpression_Email": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", - "SendGrid_Url": "smtp.sendgrid.net", - "SendGrid_Port": "587" + "ConnectionStrings": { + "Marketing.ConnectionString": "data source=localhost;initial catalog=Marketing;User ID=ytb;Password=YTB()nl!n3;Encrypt=yes;TrustServerCertificate=yes;Connection Timeout=3;Application Name=##application_name##;", //TODO: Move this to development.json, on server should go somewhere secure. GET IT OUT OF GIT + "MassEmail.ConnectionString": "data source=localhost;initial catalog=MassEmail;User ID=ytb;Password=YTB()nl!n3;Encrypt=yes;TrustServerCertificate=yes;Connection Timeout=3;Application Name=##application_name##;" //TODO: Move this to development.json, on server should go somewhere secure. GET IT OUT OF GIT + }, + "DefaultUnsubscribeUrl": "https://uat.emailopentracking.surge365.com/unsubscribe.htm" } \ No newline at end of file diff --git a/Surge365.MassEmailReact.API/appsettings.json b/Surge365.MassEmailReact.API/appsettings.json index 29bbe73..9d04617 100644 --- a/Surge365.MassEmailReact.API/appsettings.json +++ b/Surge365.MassEmailReact.API/appsettings.json @@ -11,7 +11,7 @@ }, "AppCode": "MassEmailReactApi", "AuthAppCode": "MassEmailWeb", - "EnvironmentCode": "UAT", + "EnvironmentCode": "PRODUCTION", "ConnectionStrings": { "Marketing.ConnectionString": "data source=localhost;initial catalog=Marketing;User ID=ytb;Password=YTB()nl!n3;Encrypt=yes;TrustServerCertificate=yes;Connection Timeout=3;Application Name=##application_name##;", //TODO: Move this to development.json, on server should go somewhere secure. GET IT OUT OF GIT "MassEmail.ConnectionString": "data source=localhost;initial catalog=MassEmail;User ID=ytb;Password=YTB()nl!n3;Encrypt=yes;TrustServerCertificate=yes;Connection Timeout=3;Application Name=##application_name##;" //TODO: Move this to development.json, on server should go somewhere secure. GET IT OUT OF GIT @@ -20,7 +20,5 @@ "ConnectionStringTemplate": "data source=##server_name##,##port##;initial catalog=##database_name##;User ID=##username##;Password=##password##;persist security info=False;packet size=4096;TrustServerCertificate=True;", "DefaultUnsubscribeUrl": "https://emailopentracking.surge365.com/unsubscribe.htm", "SendGrid_TestMode": false, - "RegularExpression_Email": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", - "SendGrid_Url": "smtp.sendgrid.net", - "SendGrid_Port": "587" + "RegularExpression_Email": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$" } \ No newline at end of file diff --git a/Surge365.MassEmailReact.API/package-lock.json b/Surge365.MassEmailReact.API/package-lock.json new file mode 100644 index 0000000..8e770ed --- /dev/null +++ b/Surge365.MassEmailReact.API/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "Surge365.MassEmailReact.API", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/Surge365.MassEmailReact.Infrastructure/Services/MailingService.cs b/Surge365.MassEmailReact.Infrastructure/Services/MailingService.cs index 2841b8d..b6ecf14 100644 --- a/Surge365.MassEmailReact.Infrastructure/Services/MailingService.cs +++ b/Surge365.MassEmailReact.Infrastructure/Services/MailingService.cs @@ -15,6 +15,7 @@ namespace Surge365.MassEmailReact.Infrastructure.Services { public class MailingService : IMailingService { + private readonly IHttpClientFactory _httpClientFactory; private readonly ITargetService _targetService; private readonly ITemplateService _templateService; private readonly IEmailDomainService _emailDomainService; @@ -41,22 +42,9 @@ namespace Surge365.MassEmailReact.Infrastructure.Services return _config["RegularExpression_Email"] ?? ""; } } - private string SendGridUrl - { - get - { - return _config["SendGrid_Url"] ?? ""; - } - } - private int SendGridPort - { - get - { - return _config.GetValue("SendGrid_Port"); - } - } - public MailingService(IMailingRepository mailingRepository, ITargetService targetService, ITemplateService templateService, IEmailDomainService emailDomainService, IConfiguration config) + public MailingService(IHttpClientFactory httpClientFactory, IMailingRepository mailingRepository, ITargetService targetService, ITemplateService templateService, IEmailDomainService emailDomainService, IConfiguration config) { + _httpClientFactory = httpClientFactory; _mailingRepository = mailingRepository; _targetService = targetService; _templateService = templateService; @@ -269,7 +257,8 @@ namespace Surge365.MassEmailReact.Infrastructure.Services //string url = SendGridUrl; //int port = SendGridPort; - var client = new SendGridClient(password); + var httpClient = _httpClientFactory.CreateClient("SendGridClient"); + var client = new SendGridClient(httpClient, password); var message = new SendGridMessage() { From = new EmailAddress(msg.From.Address, msg.From.DisplayName), Subject = msg.Subject, diff --git a/Surge365.MassEmailReact.Infrastructure/Surge365.MassEmailReact.Infrastructure.csproj b/Surge365.MassEmailReact.Infrastructure/Surge365.MassEmailReact.Infrastructure.csproj index cb943d0..9fec5ce 100644 --- a/Surge365.MassEmailReact.Infrastructure/Surge365.MassEmailReact.Infrastructure.csproj +++ b/Surge365.MassEmailReact.Infrastructure/Surge365.MassEmailReact.Infrastructure.csproj @@ -14,13 +14,14 @@ - - - - - + + + + + + - + diff --git a/Surge365.MassEmailReact.Web/src/components/pages/NewMailings.tsx b/Surge365.MassEmailReact.Web/src/components/pages/NewMailings.tsx index 7eb2b3d..6f55c01 100644 --- a/Surge365.MassEmailReact.Web/src/components/pages/NewMailings.tsx +++ b/Surge365.MassEmailReact.Web/src/components/pages/NewMailings.tsx @@ -3,11 +3,13 @@ import { useSetupData, SetupData } from "@/context/SetupDataContext"; import EditIcon from '@mui/icons-material/Edit'; import AddIcon from '@mui/icons-material/Add'; import RefreshIcon from '@mui/icons-material/Refresh'; +import CancelIcon from '@mui/icons-material/Cancel'; import { List, Card, CardContent, Typography, Box, useTheme, useMediaQuery, CircularProgress, IconButton } from '@mui/material'; import { DataGrid, GridColDef, GridRenderCellParams, GridRowModel, GridToolbarContainer, GridToolbarQuickFilter, GridToolbarExport, GridToolbarDensitySelector, GridToolbarColumnsButton } from '@mui/x-data-grid'; import Mailing from '@/types/mailing'; //import Template from '@/types/template'; import MailingEdit from "@/components/modals/MailingEdit"; +import ConfirmationDialog from "@/components/modals/ConfirmationDialog"; function NewMailings() { const theme = useTheme(); @@ -19,17 +21,24 @@ function NewMailings() { const [mailings, setMailings] = useState([]); const [selectedRow, setSelectedRow] = useState(null); const [open, setOpen] = useState(false); + const [confirmDialogOpen, setConfirmDialogOpen] = useState(false); + const [mailingToCancel, setMailingToCancel] = useState(null); const columns: GridColDef[] = [ { field: "actions", headerName: "", sortable: false, - width: 60, + width: 100, renderCell: (params: GridRenderCellParams) => ( - { e.stopPropagation(); handleEdit(params.row); }}> - - + <> + { e.stopPropagation(); handleEdit(params.row); }}> + + + { e.stopPropagation(); handleCancelClick(params.row); }}> + + + ), }, { field: "id", headerName: "ID", width: 80 }, @@ -76,6 +85,34 @@ function NewMailings() { updateMailings(updatedRow); }; + const handleCancelClick = (row: Mailing) => { + setMailingToCancel(row); + setConfirmDialogOpen(true); + }; + + const handleCancelConfirm = async () => { + if (!mailingToCancel) return; + + try { + const response = await fetch(`/api/mailings/${mailingToCancel.id}/cancel`, { method: 'POST' }); + if (response.ok) { + setMailings((prev) => prev.filter(m => m.id !== mailingToCancel.id)); + } else { + console.error("Failed to cancel mailing"); + } + } catch (error) { + console.error("Error cancelling mailing:", error); + } finally { + setConfirmDialogOpen(false); + setMailingToCancel(null); + } + }; + + const handleCancelDialogClose = () => { + setConfirmDialogOpen(false); + setMailingToCancel(null); + }; + useEffect(() => { reloadMailings(); }, []); @@ -117,6 +154,9 @@ function NewMailings() { { e.stopPropagation(); handleEdit(row); }}> + handleCancelClick(row)}> + + ))} @@ -171,6 +211,15 @@ function NewMailings() { onSave={handleUpdateRow} /> )} + {confirmDialogOpen && ( + + )} ); } diff --git a/Surge365.MassEmailReact.Web/src/components/pages/ScheduledMailings.tsx b/Surge365.MassEmailReact.Web/src/components/pages/ScheduledMailings.tsx index 884f58a..6b98b6f 100644 --- a/Surge365.MassEmailReact.Web/src/components/pages/ScheduledMailings.tsx +++ b/Surge365.MassEmailReact.Web/src/components/pages/ScheduledMailings.tsx @@ -7,7 +7,7 @@ import { Box, useTheme, useMediaQuery, CircularProgress, IconButton, List, Card, import { DataGrid, GridColDef, GridRenderCellParams, GridToolbarContainer, GridToolbarQuickFilter, GridToolbarExport, GridToolbarDensitySelector, GridToolbarColumnsButton } from '@mui/x-data-grid'; import Mailing from '@/types/mailing'; import MailingEdit from "@/components/modals/MailingEdit"; -import MailingView from "@/components/modals/MailingView"; // Assume this is a new read-only view component +import MailingView from "@/components/modals/MailingView"; import ConfirmationDialog from "@/components/modals/ConfirmationDialog"; function ScheduleMailings() {