Refactor authentication and restructure project architecture
This commit removes the `AuthenticationController.cs` and related DTOs, indicating a shift in authentication handling. The `BaseController.cs` has been updated to remove the authorization attribute, affecting access control. Multiple controllers have been restructured to reference `Surge365.Core.Controllers`. Significant changes in `Program.cs` enhance security and service management with new middleware and JWT configurations. The project file now includes references to `Surge365.Core`, and the `IAuthService` interface has been updated accordingly. React components have been modified to support the new authentication flow, including token refresh handling. The `customFetch.ts` utility has been improved for better session management. Mapping classes have been introduced or updated for improved entity mapping. Overall, these changes enhance the application's architecture, security, and data handling processes.
This commit is contained in:
parent
7faac8b448
commit
ba01cfcaf7
@ -1,97 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Surge365.MassEmailReact.API.Controllers;
|
|
||||||
using Surge365.MassEmailReact.Application.DTOs;
|
|
||||||
using Surge365.MassEmailReact.Application.Interfaces;
|
|
||||||
using Surge365.MassEmailReact.Domain.Entities;
|
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.API.Controllers
|
|
||||||
{
|
|
||||||
[AllowAnonymous]
|
|
||||||
public class AuthenticationController : BaseController
|
|
||||||
{
|
|
||||||
private readonly IAuthService _authService;
|
|
||||||
|
|
||||||
public AuthenticationController(IAuthService authService)
|
|
||||||
{
|
|
||||||
_authService = authService;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("logout")]
|
|
||||||
public IActionResult Logout()
|
|
||||||
{
|
|
||||||
Response.Cookies.Append("refreshToken", "", new CookieOptions
|
|
||||||
{
|
|
||||||
HttpOnly = true,
|
|
||||||
Secure = true,
|
|
||||||
SameSite = SameSiteMode.Strict,
|
|
||||||
Expires = DateTimeOffset.UtcNow.AddDays(-1) // Expire immediately
|
|
||||||
});
|
|
||||||
Response.Cookies.Append("accessToken", "", new CookieOptions
|
|
||||||
{
|
|
||||||
HttpOnly = true,
|
|
||||||
Secure = true,
|
|
||||||
SameSite = SameSiteMode.Strict,
|
|
||||||
Expires = DateTimeOffset.UtcNow.AddDays(-1) // Expire immediately
|
|
||||||
});
|
|
||||||
|
|
||||||
return Ok(new { message = "Logged out successfully" });
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("authenticate")]
|
|
||||||
public async Task<IActionResult> Authenticate([FromBody] LoginRequest request)
|
|
||||||
{
|
|
||||||
var authResponse = await _authService.Authenticate(request.Username, request.Password);
|
|
||||||
if (!authResponse.authenticated)
|
|
||||||
return Unauthorized(new { message = authResponse.errorMessage });
|
|
||||||
else if(authResponse.data == null)
|
|
||||||
return Unauthorized(new { message = "Invalid credentials" });
|
|
||||||
|
|
||||||
//TODO: Store refresh token in DB
|
|
||||||
var cookieOptions = new CookieOptions
|
|
||||||
{
|
|
||||||
HttpOnly = true, // Prevents JavaScript access (mitigates XSS)
|
|
||||||
Secure = true, // Ensures cookie is only sent over HTTPS
|
|
||||||
SameSite = SameSiteMode.Strict, // Mitigates CSRF by restricting cross-site usage
|
|
||||||
Expires = DateTimeOffset.UtcNow.AddDays(7)
|
|
||||||
};
|
|
||||||
Response.Cookies.Append("refreshToken", authResponse.data.Value.refreshToken, cookieOptions);
|
|
||||||
Response.Cookies.Append("accessToken", authResponse.data.Value.accessToken, cookieOptions);
|
|
||||||
|
|
||||||
//TODO: Store user in session
|
|
||||||
return Ok(new { success = true, authResponse.data.Value.accessToken, authResponse.data.Value.user });
|
|
||||||
}
|
|
||||||
[HttpPost("refreshtoken")]
|
|
||||||
public async Task<IActionResult> RefreshToken()
|
|
||||||
{
|
|
||||||
var refreshToken = Request.Cookies["refreshToken"];
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(refreshToken))
|
|
||||||
return Unauthorized("Invalid refresh token");
|
|
||||||
|
|
||||||
var authResponse = await _authService.Authenticate(refreshToken);
|
|
||||||
if (!authResponse.authenticated)
|
|
||||||
return Unauthorized(new { message = authResponse.errorMessage });
|
|
||||||
else if (authResponse.data == null)
|
|
||||||
return Unauthorized(new { message = "Invalid credentials" });
|
|
||||||
|
|
||||||
var cookieOptions = new CookieOptions
|
|
||||||
{
|
|
||||||
HttpOnly = true,
|
|
||||||
Secure = true,
|
|
||||||
SameSite = SameSiteMode.Strict,
|
|
||||||
Expires = DateTimeOffset.UtcNow.AddDays(7)
|
|
||||||
};
|
|
||||||
Response.Cookies.Append("refreshToken", authResponse.data.Value.refreshToken, cookieOptions);
|
|
||||||
Response.Cookies.Append("accessToken", authResponse.data.Value.accessToken, cookieOptions);
|
|
||||||
|
|
||||||
return Ok(new { accessToken = authResponse.data.Value.accessToken });
|
|
||||||
}
|
|
||||||
[HttpPost("generatepasswordrecovery")]
|
|
||||||
public IActionResult GeneratePasswordRecovery([FromBody] GeneratePasswordRecoveryRequest request)
|
|
||||||
{
|
|
||||||
return Ok(new { });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.API.Controllers
|
|
||||||
{
|
|
||||||
[Route("[controller]")]
|
|
||||||
[Route("api/[controller]")]
|
|
||||||
[ApiController]
|
|
||||||
[Authorize]
|
|
||||||
public class BaseController : ControllerBase
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,5 +1,6 @@
|
|||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Surge365.Core.Controllers;
|
||||||
using Surge365.MassEmailReact.Application.Interfaces;
|
using Surge365.MassEmailReact.Application.Interfaces;
|
||||||
using Surge365.MassEmailReact.Domain.Entities;
|
using Surge365.MassEmailReact.Domain.Entities;
|
||||||
using System.Net.Mail;
|
using System.Net.Mail;
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Surge365.Core.Controllers;
|
||||||
using Surge365.MassEmailReact.Application.Interfaces;
|
using Surge365.MassEmailReact.Application.Interfaces;
|
||||||
using Surge365.MassEmailReact.Domain.Entities;
|
using Surge365.MassEmailReact.Domain.Entities;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Data.SqlClient;
|
using Microsoft.Data.SqlClient;
|
||||||
|
using Surge365.Core.Controllers;
|
||||||
using Surge365.MassEmailReact.Application.Interfaces;
|
using Surge365.MassEmailReact.Application.Interfaces;
|
||||||
using Surge365.MassEmailReact.Domain.Entities;
|
using Surge365.MassEmailReact.Domain.Entities;
|
||||||
using Surge365.MassEmailReact.Domain.Enums;
|
using Surge365.MassEmailReact.Domain.Enums;
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
using Microsoft.AspNetCore.DataProtection.KeyManagement;
|
using Microsoft.AspNetCore.DataProtection.KeyManagement;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Surge365.Core.Controllers;
|
||||||
using Surge365.MassEmailReact.Application.DTOs;
|
using Surge365.MassEmailReact.Application.DTOs;
|
||||||
using Surge365.MassEmailReact.Application.Interfaces;
|
using Surge365.MassEmailReact.Application.Interfaces;
|
||||||
using Surge365.MassEmailReact.Domain.Entities;
|
using Surge365.MassEmailReact.Domain.Entities;
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
using Microsoft.AspNetCore.DataProtection.KeyManagement;
|
using Microsoft.AspNetCore.DataProtection.KeyManagement;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Surge365.Core.Controllers;
|
||||||
using Surge365.MassEmailReact.Application.DTOs;
|
using Surge365.MassEmailReact.Application.DTOs;
|
||||||
using Surge365.MassEmailReact.Application.Interfaces;
|
using Surge365.MassEmailReact.Application.Interfaces;
|
||||||
using Surge365.MassEmailReact.Domain.Entities;
|
using Surge365.MassEmailReact.Domain.Entities;
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Surge365.Core.Controllers;
|
||||||
using Surge365.MassEmailReact.Application.Interfaces;
|
using Surge365.MassEmailReact.Application.Interfaces;
|
||||||
using Surge365.MassEmailReact.Domain.Entities;
|
using Surge365.MassEmailReact.Domain.Entities;
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Surge365.Core.Controllers;
|
||||||
using Surge365.MassEmailReact.Application.Interfaces;
|
using Surge365.MassEmailReact.Application.Interfaces;
|
||||||
using Surge365.MassEmailReact.Domain.Entities;
|
using Surge365.MassEmailReact.Domain.Entities;
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Surge365.Core.Controllers;
|
||||||
using Surge365.MassEmailReact.Application.DTOs;
|
using Surge365.MassEmailReact.Application.DTOs;
|
||||||
using Surge365.MassEmailReact.Application.Interfaces;
|
using Surge365.MassEmailReact.Application.Interfaces;
|
||||||
using Surge365.MassEmailReact.Domain.Entities;
|
using Surge365.MassEmailReact.Domain.Entities;
|
||||||
|
|||||||
@ -1,21 +1,24 @@
|
|||||||
using Azure.Identity;
|
using Azure.Identity;
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Surge365.MassEmailReact.Application.Interfaces;
|
|
||||||
using Surge365.MassEmailReact.Domain.Entities;
|
|
||||||
using Surge365.MassEmailReact.Infrastructure;
|
|
||||||
using Surge365.MassEmailReact.Infrastructure.DapperMaps;
|
|
||||||
using Surge365.MassEmailReact.Infrastructure.Repositories;
|
|
||||||
using Surge365.MassEmailReact.Infrastructure.Services;
|
|
||||||
using Surge365.MassEmailReact.Infrastructure.Middleware;
|
|
||||||
using System.Net;
|
|
||||||
using System.Security.Authentication;
|
|
||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using Surge365.Core.Authentication;
|
||||||
|
using Surge365.Core.Extensions;
|
||||||
|
using Surge365.Core.Services;
|
||||||
|
using Surge365.Core.Middleware;
|
||||||
|
using Surge365.MassEmailReact.Application.Interfaces;
|
||||||
|
using Surge365.MassEmailReact.Infrastructure.Repositories;
|
||||||
|
using Surge365.MassEmailReact.Infrastructure.Services;
|
||||||
|
using System.Security.Authentication;
|
||||||
|
using System.Security.Claims;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Surge365.Core.Interfaces;
|
||||||
|
using Surge365.MassEmailReact.Infrastructure.EntityMaps;
|
||||||
|
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
builder.AddCustomConfigurationSources();
|
||||||
|
|
||||||
WebApplication? app = null;
|
WebApplication? app = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -50,21 +53,41 @@ try
|
|||||||
IssuerSigningKey = new SymmetricSecurityKey(jwtKey),
|
IssuerSigningKey = new SymmetricSecurityKey(jwtKey),
|
||||||
ClockSkew = TimeSpan.Zero // Optional: no grace period for expiration
|
ClockSkew = TimeSpan.Zero // Optional: no grace period for expiration
|
||||||
};
|
};
|
||||||
// Read JWT from accessToken cookie
|
|
||||||
options.Events = new JwtBearerEvents
|
|
||||||
{
|
|
||||||
OnMessageReceived = context =>
|
|
||||||
{
|
|
||||||
var token = context.Request.Cookies["accessToken"];
|
|
||||||
if (!string.IsNullOrEmpty(token))
|
|
||||||
{
|
|
||||||
context.Token = token;
|
|
||||||
}
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
builder.Services.AddHttpContextAccessor();
|
||||||
|
|
||||||
|
|
||||||
|
Factory.RegisterDefaultServices(builder.Services,
|
||||||
|
adminContextProvider: provider =>
|
||||||
|
{
|
||||||
|
var httpContextAccessor = provider.GetRequiredService<IHttpContextAccessor>();
|
||||||
|
var httpContext = httpContextAccessor.HttpContext;
|
||||||
|
|
||||||
|
if (httpContext == null)
|
||||||
|
return new AdminContext();
|
||||||
|
|
||||||
|
string? adminId = null;
|
||||||
|
string? ipAddress = httpContext?.Connection?.RemoteIpAddress?.ToString();
|
||||||
|
string? appVersion = httpContext?.Request.Headers["app-version"].ToString();
|
||||||
|
string? deviceInfo = httpContext?.Request.Headers["User-Agent"].ToString();
|
||||||
|
|
||||||
|
if (httpContext?.User?.Identity?.IsAuthenticated == true)
|
||||||
|
{
|
||||||
|
adminId = httpContext.User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? httpContext.User.FindFirst("sub")?.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AdminContext
|
||||||
|
{
|
||||||
|
LoggedIn = !string.IsNullOrWhiteSpace(adminId),
|
||||||
|
AdminId = adminId,
|
||||||
|
IpAddress = ipAddress,
|
||||||
|
AppVersion = appVersion,
|
||||||
|
DeviceInfo = deviceInfo
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
builder.Services.AddHttpClient("SendGridClient", client =>
|
builder.Services.AddHttpClient("SendGridClient", client =>
|
||||||
{
|
{
|
||||||
client.BaseAddress = new Uri("https://api.sendgrid.com/"); // Optional, for clarity
|
client.BaseAddress = new Uri("https://api.sendgrid.com/"); // Optional, for clarity
|
||||||
@ -74,10 +97,10 @@ try
|
|||||||
});
|
});
|
||||||
// Add services to the container.
|
// Add services to the container.
|
||||||
|
|
||||||
builder.Services.AddControllers();
|
builder.Services.AddControllers().AddApplicationPart(typeof(Surge365.Core.Controllers.AuthenticationController).Assembly);
|
||||||
|
|
||||||
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
|
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
|
||||||
builder.Services.AddOpenApi();
|
builder.Services.AddOpenApi();
|
||||||
builder.Services.AddScoped<IAuthService, AuthService>();
|
|
||||||
builder.Services.AddScoped<ITargetService, TargetService>();
|
builder.Services.AddScoped<ITargetService, TargetService>();
|
||||||
builder.Services.AddScoped<ITargetRepository, TargetRepository>();
|
builder.Services.AddScoped<ITargetRepository, TargetRepository>();
|
||||||
builder.Services.AddScoped<IServerService, ServerService>();
|
builder.Services.AddScoped<IServerService, ServerService>();
|
||||||
@ -95,10 +118,12 @@ try
|
|||||||
builder.Services.AddScoped<IMailingService, MailingService>();
|
builder.Services.AddScoped<IMailingService, MailingService>();
|
||||||
builder.Services.AddScoped<IMailingRepository, MailingRepository>();
|
builder.Services.AddScoped<IMailingRepository, MailingRepository>();
|
||||||
|
|
||||||
|
EntityMapperConfiguration.ConfigureCustomMaps();
|
||||||
|
|
||||||
app = builder.Build();
|
app = builder.Build();
|
||||||
|
|
||||||
|
|
||||||
app.UseCustomExceptionHandler();
|
app.UseCustomExceptionHandler();
|
||||||
|
|
||||||
app.UseDefaultFiles();
|
app.UseDefaultFiles();
|
||||||
app.MapStaticAssets();
|
app.MapStaticAssets();
|
||||||
|
|
||||||
@ -109,17 +134,17 @@ try
|
|||||||
}
|
}
|
||||||
|
|
||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
|
app.UseAuthentication();
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
|
||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
|
|
||||||
|
|
||||||
DapperConfiguration.ConfigureMappings();
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LoggingService appLoggingService = new LoggingService(builder.Configuration);
|
Console.WriteLine($"Error during application startup: {ex.Message}");
|
||||||
|
LoggingService appLoggingService = new LoggingService(builder.Configuration, new DataAccessFactory(builder.Configuration));
|
||||||
appLoggingService.LogError(ex).Wait();
|
appLoggingService.LogError(ex).Wait();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,6 +18,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\..\Core\Surge365.Core\Surge365.Core.csproj" />
|
||||||
<ProjectReference Include="..\Surge365.MassEmailReact.Application\Surge365.MassEmailReact.Application.csproj" />
|
<ProjectReference Include="..\Surge365.MassEmailReact.Application\Surge365.MassEmailReact.Application.csproj" />
|
||||||
<ProjectReference Include="..\Surge365.MassEmailReact.Infrastructure\Surge365.MassEmailReact.Infrastructure.csproj" />
|
<ProjectReference Include="..\Surge365.MassEmailReact.Infrastructure\Surge365.MassEmailReact.Infrastructure.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@ -1,16 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Application.DTOs.AuthApi
|
|
||||||
{
|
|
||||||
public class AuthResponse
|
|
||||||
{
|
|
||||||
public string accessToken { get; set; } = "";
|
|
||||||
public string refreshToken { get; set; } = "";
|
|
||||||
public User? user { get; set; }
|
|
||||||
public string message { get; set; } = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Application.DTOs.AuthApi
|
|
||||||
{
|
|
||||||
public class AuthenticateApiRequest
|
|
||||||
{
|
|
||||||
public required string appCode { get; set; }
|
|
||||||
public required string username { get; set; }
|
|
||||||
public required string password { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Application.DTOs.AuthApi
|
|
||||||
{
|
|
||||||
public class RefreshTokenApiRequest
|
|
||||||
{
|
|
||||||
public string appCode { get; set; } = "";
|
|
||||||
public string refreshToken { get; set; } = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Application.DTOs.AuthApi
|
|
||||||
{
|
|
||||||
public class User
|
|
||||||
{
|
|
||||||
public int? UserKey { get; set; }
|
|
||||||
public Guid UserId { get; set; }
|
|
||||||
public string Username { get; set; } = "";
|
|
||||||
public string FirstName { get; set; } = "";
|
|
||||||
public string MiddleInitial { get; set; } = "";
|
|
||||||
public string LastName { get; set; } = "";
|
|
||||||
public bool IsActive { get; set; }
|
|
||||||
public List<string> Roles { get; set; } = new List<string>();
|
|
||||||
|
|
||||||
public User() { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Application.DTOs
|
|
||||||
{
|
|
||||||
public class GeneratePasswordRecoveryRequest
|
|
||||||
{
|
|
||||||
public required string Username { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Application.DTOs
|
|
||||||
{
|
|
||||||
public class LoginRequest
|
|
||||||
{
|
|
||||||
public required string Username { get; set; }
|
|
||||||
public required string Password { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Application.DTOs
|
|
||||||
{
|
|
||||||
public class RefreshTokenRequest
|
|
||||||
{
|
|
||||||
public required string RefreshToken { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
using Surge365.MassEmailReact.Application.DTOs.AuthApi;
|
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Application.Interfaces
|
|
||||||
{
|
|
||||||
public interface IAuthService
|
|
||||||
{
|
|
||||||
Task<(bool authenticated, (User user, string accessToken, string refreshToken)? data, string errorMessage)> Authenticate(string username, string password);
|
|
||||||
Task<(bool authenticated, (User user, string accessToken, string refreshToken)? data, string errorMessage)> Authenticate(string refreshToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
using Surge365.MassEmailReact.Application.DTOs.AuthApi;
|
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Application.Interfaces
|
|
||||||
{
|
|
||||||
public enum LogLevels //TODO: Move all this to Surge365.Core (new project)
|
|
||||||
{
|
|
||||||
Fatal = 1,
|
|
||||||
Error,
|
|
||||||
Warn,
|
|
||||||
Info,
|
|
||||||
Debug
|
|
||||||
}
|
|
||||||
public interface ILoggingService
|
|
||||||
{
|
|
||||||
Task<bool> InsertLog(LogLevels level, string logger, int? userKey, string task, string message,
|
|
||||||
string exceptionStack, string exceptionMessage, string exceptionInnerMessage,
|
|
||||||
string customMessage1, string customMessage2, string customMessage3,
|
|
||||||
string customMessage4, string customMessage5);
|
|
||||||
Task<bool> LogError(Exception ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Domain.Enums
|
|
||||||
{
|
|
||||||
public enum AuthResult
|
|
||||||
{
|
|
||||||
Success = 0,
|
|
||||||
LoginNotFound = 1,
|
|
||||||
LoginLocked = 2,
|
|
||||||
InvalidPassword = 3,
|
|
||||||
UnexpectedError = 4,
|
|
||||||
NotAllowed = 5
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Domain.Enums.Extensions
|
|
||||||
{
|
|
||||||
public static class AuthResultExtensions
|
|
||||||
{
|
|
||||||
public static string GetMessage(this AuthResult response)
|
|
||||||
{
|
|
||||||
return response switch
|
|
||||||
{
|
|
||||||
AuthResult.Success => "Login successful.",
|
|
||||||
AuthResult.InvalidPassword or AuthResult.LoginNotFound => "Invalid username and/or password.",
|
|
||||||
AuthResult.LoginLocked => "User has been locked out.",
|
|
||||||
AuthResult.NotAllowed => "User has not been granted access to this application.",
|
|
||||||
AuthResult.UnexpectedError or _ => "Unexpected error."
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -6,4 +6,8 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Enums\Extensions\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -1,36 +0,0 @@
|
|||||||
using Dapper;
|
|
||||||
using Dapper.FluentMap;
|
|
||||||
using Surge365.MassEmailReact.Domain.Entities;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Infrastructure.DapperMaps
|
|
||||||
{
|
|
||||||
public class DapperConfiguration
|
|
||||||
{
|
|
||||||
public static void ConfigureMappings()
|
|
||||||
{
|
|
||||||
SqlMapper.AddTypeHandler(new JsonListStringTypeHandler());
|
|
||||||
|
|
||||||
FluentMapper.Initialize(config =>
|
|
||||||
{
|
|
||||||
config.AddMap(new TargetMap());
|
|
||||||
config.AddMap(new TargetColumnMap());
|
|
||||||
config.AddMap(new ServerMap());
|
|
||||||
config.AddMap(new TestEmailListMap());
|
|
||||||
config.AddMap(new BouncedEmailMap());
|
|
||||||
config.AddMap(new UnsubscribeUrlMap());
|
|
||||||
config.AddMap(new TemplateMap());
|
|
||||||
config.AddMap(new EmailDomainMap());
|
|
||||||
config.AddMap(new MailingMap());
|
|
||||||
config.AddMap(new MailingEmailMap());
|
|
||||||
config.AddMap(new MailingTemplateMap());
|
|
||||||
config.AddMap(new MailingTargetMap());
|
|
||||||
config.AddMap(new MailingStatisticMap());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
using Dapper;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Data;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Infrastructure.DapperMaps
|
|
||||||
{
|
|
||||||
public class JsonListStringTypeHandler : SqlMapper.TypeHandler<List<string>>
|
|
||||||
{
|
|
||||||
public override List<string> Parse(object value)
|
|
||||||
{
|
|
||||||
if (value == null || value == DBNull.Value)
|
|
||||||
return new List<string>();
|
|
||||||
|
|
||||||
string json = value.ToString() ?? "";
|
|
||||||
return string.IsNullOrEmpty(json)
|
|
||||||
? new List<string>()
|
|
||||||
: JsonSerializer.Deserialize<List<string>>(json) ?? new List<string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetValue(IDbDataParameter parameter, List<string> value)
|
|
||||||
{
|
|
||||||
parameter.Value = value != null ? JsonSerializer.Serialize(value) : DBNull.Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,437 +0,0 @@
|
|||||||
using Microsoft.Data.SqlClient;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Data;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection.PortableExecutable;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Infrastructure
|
|
||||||
{
|
|
||||||
public class DataAccess
|
|
||||||
{
|
|
||||||
private string GetConnectionString(string connectionStringName)
|
|
||||||
{
|
|
||||||
if (_configuration == null)
|
|
||||||
return "";
|
|
||||||
return _configuration[$"ConnectionStrings:{connectionStringName}"] ?? "";
|
|
||||||
}
|
|
||||||
public DataAccess(IConfiguration configuration, string connectionStringName)
|
|
||||||
{
|
|
||||||
_configuration = configuration;
|
|
||||||
_connectionString = GetConnectionString(connectionStringName).Replace("##application_code##", Utilities.GetAppCode(configuration));
|
|
||||||
}
|
|
||||||
|
|
||||||
internal IConfiguration? _configuration;
|
|
||||||
internal Guid _sessionID = Guid.NewGuid();
|
|
||||||
internal string _connectionString;
|
|
||||||
internal int _timeout = 30;
|
|
||||||
internal SqlTransaction? _transaction;
|
|
||||||
internal SqlConnection? _connection;
|
|
||||||
internal bool _transactionStarted;
|
|
||||||
internal Object? _transactionStarter;
|
|
||||||
|
|
||||||
public string ConnectionString
|
|
||||||
{
|
|
||||||
get { return _connectionString; }
|
|
||||||
set { _connectionString = value; }
|
|
||||||
}
|
|
||||||
public int Timeout
|
|
||||||
{
|
|
||||||
get { return _timeout; }
|
|
||||||
set { _timeout = value; }
|
|
||||||
}
|
|
||||||
public bool TransactionStarted
|
|
||||||
{
|
|
||||||
get { return _transactionStarted; }
|
|
||||||
internal set { _transactionStarted = value; }
|
|
||||||
}
|
|
||||||
public Object? TransactionStarter
|
|
||||||
{
|
|
||||||
get { return _transactionStarter; }
|
|
||||||
internal set { _transactionStarter = value; }
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Non-Async
|
|
||||||
internal void OpenConnection()
|
|
||||||
{
|
|
||||||
_connection = new SqlConnection(_connectionString);
|
|
||||||
_connection.Open();
|
|
||||||
}
|
|
||||||
internal void CloseConnection()
|
|
||||||
{
|
|
||||||
if (_connection == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_connection.Close();
|
|
||||||
_connection.Dispose();
|
|
||||||
_connection = null;
|
|
||||||
}
|
|
||||||
public SqlTransaction BeginTransaction(Object sender)
|
|
||||||
{
|
|
||||||
if (_transaction != null)
|
|
||||||
return _transaction;
|
|
||||||
|
|
||||||
OpenConnection();
|
|
||||||
|
|
||||||
ArgumentNullException.ThrowIfNull(_connection);
|
|
||||||
|
|
||||||
_transaction = _connection.BeginTransaction();
|
|
||||||
_transactionStarted = true;
|
|
||||||
_transactionStarter = sender;
|
|
||||||
return _transaction;
|
|
||||||
}
|
|
||||||
public void RollbackTransaction(Object sender)
|
|
||||||
{
|
|
||||||
if (_transaction != null && sender == _transactionStarter)
|
|
||||||
{
|
|
||||||
_transaction.Rollback();
|
|
||||||
_transaction = null;
|
|
||||||
CloseConnection();
|
|
||||||
_transactionStarter = null;
|
|
||||||
_transactionStarted = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public void CommitTransaction(Object sender)
|
|
||||||
{
|
|
||||||
if (_transaction != null && sender == _transactionStarter)
|
|
||||||
{
|
|
||||||
_transaction.Commit();
|
|
||||||
_transaction = null;
|
|
||||||
CloseConnection();
|
|
||||||
_transactionStarter = null;
|
|
||||||
_transactionStarted = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public DataSet CallRetrievalProcedure(List<SqlParameter> parameters, string proc, int timeoutSeconds = 0)
|
|
||||||
{
|
|
||||||
DataSet ds = new DataSet();
|
|
||||||
bool createdConnection = false;
|
|
||||||
if (timeoutSeconds == 0)
|
|
||||||
timeoutSeconds = _timeout;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (_connection == null)
|
|
||||||
{
|
|
||||||
createdConnection = true;
|
|
||||||
OpenConnection();
|
|
||||||
}
|
|
||||||
|
|
||||||
using SqlCommand cmd = new SqlCommand(proc, _connection);
|
|
||||||
cmd.Transaction = _transaction;
|
|
||||||
if (parameters != null)
|
|
||||||
{
|
|
||||||
foreach (SqlParameter p in parameters)
|
|
||||||
{
|
|
||||||
if (p != null)
|
|
||||||
cmd.Parameters.Add(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cmd.CommandType = CommandType.StoredProcedure;
|
|
||||||
cmd.CommandTimeout = timeoutSeconds;
|
|
||||||
using SqlDataAdapter da = new SqlDataAdapter(cmd);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
da.Fill(ds);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
string message = String.Format("Unable to retrieve data from proc: {0}", proc);
|
|
||||||
LogError(ex, message, "CallRetrievalProcedure");
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
if (createdConnection)
|
|
||||||
{
|
|
||||||
CloseConnection();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
if (createdConnection)
|
|
||||||
{
|
|
||||||
CloseConnection();
|
|
||||||
}
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ds;
|
|
||||||
}
|
|
||||||
public int CallActionProcedure(List<SqlParameter> parameters, string proc, int timeoutSeconds = 0)
|
|
||||||
{
|
|
||||||
int iReturnVal = -1;
|
|
||||||
if (timeoutSeconds == 0)
|
|
||||||
timeoutSeconds = _timeout;
|
|
||||||
bool createdConnection = false;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (_connection == null)
|
|
||||||
{
|
|
||||||
createdConnection = true;
|
|
||||||
OpenConnection();
|
|
||||||
}
|
|
||||||
using SqlCommand cmd = new SqlCommand(proc, _connection);
|
|
||||||
cmd.Transaction = _transaction;
|
|
||||||
if (parameters != null)
|
|
||||||
{
|
|
||||||
foreach (SqlParameter p in parameters)
|
|
||||||
{
|
|
||||||
if (p != null)
|
|
||||||
cmd.Parameters.Add(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cmd.CommandType = CommandType.StoredProcedure;
|
|
||||||
cmd.CommandTimeout = timeoutSeconds;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
iReturnVal = cmd.ExecuteNonQuery();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
string message = String.Format("Unable to execute proc: {0}", proc);
|
|
||||||
LogError(ex, message, "CallActionProcedure");
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
if (createdConnection)
|
|
||||||
{
|
|
||||||
CloseConnection();
|
|
||||||
}
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (createdConnection)
|
|
||||||
{
|
|
||||||
CloseConnection();
|
|
||||||
}
|
|
||||||
return iReturnVal;
|
|
||||||
}
|
|
||||||
private static void LogError(Exception ex, string message, string task)
|
|
||||||
{
|
|
||||||
Exception ex1 = ex;
|
|
||||||
if (ex == null)
|
|
||||||
ex1 = new Exception(message);
|
|
||||||
else if (!string.IsNullOrEmpty(message))
|
|
||||||
ex1 = new Exception(message, ex);
|
|
||||||
|
|
||||||
//Log
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Async
|
|
||||||
internal async Task OpenConnectionAsync()
|
|
||||||
{
|
|
||||||
//_connection = new SqlConnection(_connectionString);
|
|
||||||
//await _connection.OpenAsync();
|
|
||||||
await Task.Run(() => OpenConnectionWithRetry());
|
|
||||||
}
|
|
||||||
internal void OpenConnectionWithRetry(short maxRetries = 3, int totalTimeoutMs = 1000)
|
|
||||||
{
|
|
||||||
int attempt = 0;
|
|
||||||
while (attempt < maxRetries)
|
|
||||||
{
|
|
||||||
using (var cts = new CancellationTokenSource(totalTimeoutMs)) // Total cap, e.g., 3 seconds
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Attempt {attempt + 1}...");
|
|
||||||
_connection = new SqlConnection(_connectionString);
|
|
||||||
_connection.OpenAsync(cts.Token).Wait(); // Use async with cancellation
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
attempt++;
|
|
||||||
if (attempt == maxRetries)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Failed after {attempt} attempts: {ex.Message}");
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
Console.WriteLine($"Retrying after failure: {ex.Message}");
|
|
||||||
Thread.Sleep(1000); // Delay between retries
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
internal async Task CloseConnectionAsync()
|
|
||||||
{
|
|
||||||
if (_connection == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
await _connection.CloseAsync();
|
|
||||||
_connection.Dispose();
|
|
||||||
_connection = null;
|
|
||||||
}
|
|
||||||
public async Task<SqlTransaction> BeginTransactionAsync(Object sender)
|
|
||||||
{
|
|
||||||
if (_transaction != null)
|
|
||||||
return _transaction;
|
|
||||||
|
|
||||||
await OpenConnectionAsync();
|
|
||||||
|
|
||||||
ArgumentNullException.ThrowIfNull(_connection);
|
|
||||||
|
|
||||||
_transaction = _connection.BeginTransaction();
|
|
||||||
_transactionStarted = true;
|
|
||||||
_transactionStarter = sender;
|
|
||||||
return _transaction;
|
|
||||||
}
|
|
||||||
public async Task RollbackTransactionAsync(Object sender)
|
|
||||||
{
|
|
||||||
if (_transaction != null && sender == _transactionStarter)
|
|
||||||
{
|
|
||||||
await _transaction.RollbackAsync();
|
|
||||||
_transaction = null;
|
|
||||||
await CloseConnectionAsync();
|
|
||||||
_transactionStarter = null;
|
|
||||||
_transactionStarted = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public async Task CommitTransactionAsync(Object sender)
|
|
||||||
{
|
|
||||||
if (_transaction != null && sender == _transactionStarter)
|
|
||||||
{
|
|
||||||
await _transaction.CommitAsync();
|
|
||||||
_transaction = null;
|
|
||||||
await CloseConnectionAsync();
|
|
||||||
_transactionStarter = null;
|
|
||||||
_transactionStarted = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public async Task<DataSet> CallRetrievalProcedureAsync(List<SqlParameter> parameters, string proc, int timeoutSeconds = 0)
|
|
||||||
{
|
|
||||||
DataSet ds = new DataSet();
|
|
||||||
bool createdConnection = false;
|
|
||||||
if (timeoutSeconds == 0)
|
|
||||||
timeoutSeconds = _timeout;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (_connection == null)
|
|
||||||
{
|
|
||||||
createdConnection = true;
|
|
||||||
await OpenConnectionAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
using SqlCommand cmd = new SqlCommand(proc, _connection);
|
|
||||||
|
|
||||||
cmd.Transaction = _transaction;
|
|
||||||
if (parameters != null)
|
|
||||||
{
|
|
||||||
foreach (SqlParameter p in parameters)
|
|
||||||
{
|
|
||||||
if (p != null)
|
|
||||||
cmd.Parameters.Add(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cmd.CommandType = CommandType.StoredProcedure;
|
|
||||||
cmd.CommandTimeout = timeoutSeconds;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
//using var reader = await cmd.ExecuteReaderAsync();
|
|
||||||
using var adapter = new SqlDataAdapter(cmd);
|
|
||||||
await Task.Run(() => adapter.Fill(ds));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
string message = String.Format("Unable to retrieve data from proc: {0}", proc);
|
|
||||||
await LogErrorAsync(ex, message, "CallRetrievalProcedure");
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
if (createdConnection)
|
|
||||||
{
|
|
||||||
await CloseConnectionAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
if (createdConnection)
|
|
||||||
{
|
|
||||||
await CloseConnectionAsync();
|
|
||||||
}
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ds;
|
|
||||||
}
|
|
||||||
public async Task<int> CallActionProcedureAsync(List<SqlParameter> parameters, string proc, int timeoutSeconds = 0)
|
|
||||||
{
|
|
||||||
int iReturnVal = -1;
|
|
||||||
if (timeoutSeconds == 0)
|
|
||||||
timeoutSeconds = _timeout;
|
|
||||||
bool createdConnection = false;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (_connection == null)
|
|
||||||
{
|
|
||||||
createdConnection = true;
|
|
||||||
await OpenConnectionAsync();
|
|
||||||
}
|
|
||||||
using SqlCommand cmd = new SqlCommand(proc, _connection);
|
|
||||||
|
|
||||||
cmd.Transaction = _transaction;
|
|
||||||
if (parameters != null)
|
|
||||||
{
|
|
||||||
foreach (SqlParameter p in parameters)
|
|
||||||
{
|
|
||||||
if (p != null)
|
|
||||||
cmd.Parameters.Add(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cmd.CommandType = CommandType.StoredProcedure;
|
|
||||||
cmd.CommandTimeout = timeoutSeconds;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
iReturnVal = await cmd.ExecuteNonQueryAsync();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
string message = String.Format("Unable to execute proc: {0}", proc);
|
|
||||||
await LogErrorAsync(ex, message, "CallActionProcedure");
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
if (createdConnection)
|
|
||||||
{
|
|
||||||
await CloseConnectionAsync();
|
|
||||||
}
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (createdConnection)
|
|
||||||
{
|
|
||||||
await CloseConnectionAsync();
|
|
||||||
}
|
|
||||||
return iReturnVal;
|
|
||||||
}
|
|
||||||
private static async Task LogErrorAsync(Exception ex, string message, string task)
|
|
||||||
{
|
|
||||||
Exception ex1 = ex;
|
|
||||||
if (ex == null)
|
|
||||||
ex1 = new Exception(message);
|
|
||||||
else if (!string.IsNullOrEmpty(message))
|
|
||||||
ex1 = new Exception(message, ex);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using SqlConnection c = new SqlConnection();
|
|
||||||
await c.OpenAsync();
|
|
||||||
using SqlCommand cmd = new SqlCommand("lg_log_error", c);
|
|
||||||
await cmd.ExecuteNonQueryAsync();
|
|
||||||
}
|
|
||||||
catch (Exception iex) //Trap all errors, don't do any
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Unhandled Exception occurred logging exception: exception={iex}; trying to log exception={ex}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -3,10 +3,10 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Dapper.FluentMap.Mapping;
|
using Surge365.Core.Mapping;
|
||||||
using Surge365.MassEmailReact.Domain.Entities;
|
using Surge365.MassEmailReact.Domain.Entities;
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Infrastructure.DapperMaps
|
namespace Surge365.MassEmailReact.Infrastructure.EntityMaps
|
||||||
{
|
{
|
||||||
public class BouncedEmailMap : EntityMap<BouncedEmail>
|
public class BouncedEmailMap : EntityMap<BouncedEmail>
|
||||||
{
|
{
|
||||||
@ -1,7 +1,7 @@
|
|||||||
using Dapper.FluentMap.Mapping;
|
using Surge365.Core.Mapping;
|
||||||
using Surge365.MassEmailReact.Domain.Entities;
|
using Surge365.MassEmailReact.Domain.Entities;
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Infrastructure.DapperMaps
|
namespace Surge365.MassEmailReact.Infrastructure.EntityMaps
|
||||||
{
|
{
|
||||||
public class EmailDomainMap : EntityMap<EmailDomain>
|
public class EmailDomainMap : EntityMap<EmailDomain>
|
||||||
{
|
{
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
using Surge365.Core.Mapping;
|
||||||
|
|
||||||
|
namespace Surge365.MassEmailReact.Infrastructure.EntityMaps
|
||||||
|
{
|
||||||
|
public static class EntityMapperConfiguration
|
||||||
|
{
|
||||||
|
public static void ConfigureCustomMaps()
|
||||||
|
{
|
||||||
|
QueryMapper.AddMap(new TargetMap());
|
||||||
|
QueryMapper.AddMap(new TargetColumnMap());
|
||||||
|
QueryMapper.AddMap(new ServerMap());
|
||||||
|
QueryMapper.AddMap(new TestEmailListMap());
|
||||||
|
QueryMapper.AddMap(new BouncedEmailMap());
|
||||||
|
QueryMapper .AddMap(new UnsubscribeUrlMap());
|
||||||
|
QueryMapper.AddMap(new TemplateMap());
|
||||||
|
QueryMapper.AddMap(new EmailDomainMap());
|
||||||
|
QueryMapper .AddMap(new MailingMap());
|
||||||
|
QueryMapper.AddMap(new MailingEmailMap());
|
||||||
|
QueryMapper.AddMap(new MailingTemplateMap());
|
||||||
|
QueryMapper.AddMap(new MailingTargetMap());
|
||||||
|
QueryMapper.AddMap(new MailingStatisticMap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
using Dapper.FluentMap.Mapping;
|
using Surge365.Core.Mapping;
|
||||||
using Surge365.MassEmailReact.Domain.Entities;
|
using Surge365.MassEmailReact.Domain.Entities;
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Infrastructure.DapperMaps
|
namespace Surge365.MassEmailReact.Infrastructure.EntityMaps
|
||||||
{
|
{
|
||||||
public class MailingEmailMap : EntityMap<MailingEmail>
|
public class MailingEmailMap : EntityMap<MailingEmail>
|
||||||
{
|
{
|
||||||
@ -1,7 +1,7 @@
|
|||||||
using Dapper.FluentMap.Mapping;
|
using Surge365.Core.Mapping;
|
||||||
using Surge365.MassEmailReact.Domain.Entities;
|
using Surge365.MassEmailReact.Domain.Entities;
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Infrastructure.DapperMaps
|
namespace Surge365.MassEmailReact.Infrastructure.EntityMaps
|
||||||
{
|
{
|
||||||
public class MailingMap : EntityMap<Mailing>
|
public class MailingMap : EntityMap<Mailing>
|
||||||
{
|
{
|
||||||
@ -1,7 +1,7 @@
|
|||||||
using Dapper.FluentMap.Mapping;
|
using Surge365.Core.Mapping;
|
||||||
using Surge365.MassEmailReact.Domain.Entities;
|
using Surge365.MassEmailReact.Domain.Entities;
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Infrastructure.DapperMaps
|
namespace Surge365.MassEmailReact.Infrastructure.EntityMaps
|
||||||
{
|
{
|
||||||
public class MailingStatisticMap : EntityMap<MailingStatistic>
|
public class MailingStatisticMap : EntityMap<MailingStatistic>
|
||||||
{
|
{
|
||||||
@ -3,10 +3,10 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Dapper.FluentMap.Mapping;
|
using Surge365.Core.Mapping;
|
||||||
using Surge365.MassEmailReact.Domain.Entities;
|
using Surge365.MassEmailReact.Domain.Entities;
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Infrastructure.DapperMaps
|
namespace Surge365.MassEmailReact.Infrastructure.EntityMaps
|
||||||
{
|
{
|
||||||
public class MailingTargetMap : EntityMap<MailingTarget>
|
public class MailingTargetMap : EntityMap<MailingTarget>
|
||||||
{
|
{
|
||||||
@ -1,7 +1,7 @@
|
|||||||
using Dapper.FluentMap.Mapping;
|
using Surge365.Core.Mapping;
|
||||||
using Surge365.MassEmailReact.Domain.Entities;
|
using Surge365.MassEmailReact.Domain.Entities;
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Infrastructure.DapperMaps
|
namespace Surge365.MassEmailReact.Infrastructure.EntityMaps
|
||||||
{
|
{
|
||||||
public class MailingTemplateMap : EntityMap<MailingTemplate>
|
public class MailingTemplateMap : EntityMap<MailingTemplate>
|
||||||
{
|
{
|
||||||
@ -3,10 +3,10 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Dapper.FluentMap.Mapping;
|
using Surge365.Core.Mapping;
|
||||||
using Surge365.MassEmailReact.Domain.Entities;
|
using Surge365.MassEmailReact.Domain.Entities;
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Infrastructure.DapperMaps
|
namespace Surge365.MassEmailReact.Infrastructure.EntityMaps
|
||||||
{
|
{
|
||||||
public class ServerMap : EntityMap<Server>
|
public class ServerMap : EntityMap<Server>
|
||||||
{
|
{
|
||||||
@ -3,10 +3,10 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Dapper.FluentMap.Mapping;
|
using Surge365.Core.Mapping;
|
||||||
using Surge365.MassEmailReact.Domain.Entities;
|
using Surge365.MassEmailReact.Domain.Entities;
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Infrastructure.DapperMaps
|
namespace Surge365.MassEmailReact.Infrastructure.EntityMaps
|
||||||
{
|
{
|
||||||
public class TargetColumnMap : EntityMap<TargetColumn>
|
public class TargetColumnMap : EntityMap<TargetColumn>
|
||||||
{
|
{
|
||||||
@ -3,10 +3,10 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Dapper.FluentMap.Mapping;
|
using Surge365.Core.Mapping;
|
||||||
using Surge365.MassEmailReact.Domain.Entities;
|
using Surge365.MassEmailReact.Domain.Entities;
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Infrastructure.DapperMaps
|
namespace Surge365.MassEmailReact.Infrastructure.EntityMaps
|
||||||
{
|
{
|
||||||
public class TargetMap : EntityMap<Target>
|
public class TargetMap : EntityMap<Target>
|
||||||
{
|
{
|
||||||
@ -1,7 +1,7 @@
|
|||||||
using Dapper.FluentMap.Mapping;
|
using Surge365.Core.Mapping;
|
||||||
using Surge365.MassEmailReact.Domain.Entities;
|
using Surge365.MassEmailReact.Domain.Entities;
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Infrastructure.DapperMaps
|
namespace Surge365.MassEmailReact.Infrastructure.EntityMaps
|
||||||
{
|
{
|
||||||
public class TemplateMap : EntityMap<Template>
|
public class TemplateMap : EntityMap<Template>
|
||||||
{
|
{
|
||||||
@ -1,4 +1,4 @@
|
|||||||
using Dapper.FluentMap.Mapping;
|
using Surge365.Core.Mapping;
|
||||||
using Surge365.MassEmailReact.Domain.Entities;
|
using Surge365.MassEmailReact.Domain.Entities;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@ -6,7 +6,7 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Infrastructure.DapperMaps
|
namespace Surge365.MassEmailReact.Infrastructure.EntityMaps
|
||||||
{
|
{
|
||||||
public class TestEmailListMap : EntityMap<TestEmailList>
|
public class TestEmailListMap : EntityMap<TestEmailList>
|
||||||
{
|
{
|
||||||
@ -1,7 +1,7 @@
|
|||||||
using Dapper.FluentMap.Mapping;
|
using Surge365.Core.Mapping;
|
||||||
using Surge365.MassEmailReact.Domain.Entities;
|
using Surge365.MassEmailReact.Domain.Entities;
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Infrastructure.DapperMaps
|
namespace Surge365.MassEmailReact.Infrastructure.EntityMaps
|
||||||
{
|
{
|
||||||
public class UnsubscribeUrlMap : EntityMap<UnsubscribeUrl>
|
public class UnsubscribeUrlMap : EntityMap<UnsubscribeUrl>
|
||||||
{
|
{
|
||||||
@ -1,36 +0,0 @@
|
|||||||
using Azure.Extensions.AspNetCore.Configuration.Secrets;
|
|
||||||
using Azure.Security.KeyVault.Secrets;
|
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Infrastructure
|
|
||||||
{
|
|
||||||
public class SurgeKeyVaultSecretManager : KeyVaultSecretManager
|
|
||||||
{
|
|
||||||
public override bool Load(SecretProperties secret)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
public override string GetKey(KeyVaultSecret secret)
|
|
||||||
{
|
|
||||||
// Transform secret name back to configuration key
|
|
||||||
// Replace double hyphens (--) with colons (:) and single hyphens (-) with periods (.)
|
|
||||||
/*
|
|
||||||
* Example:
|
|
||||||
* AppSettings.json =
|
|
||||||
{
|
|
||||||
"AdminAuth": {
|
|
||||||
"ApiKey": "abcde"
|
|
||||||
},
|
|
||||||
"AdminAuth.ConnectionString":"abc"
|
|
||||||
}
|
|
||||||
* _config path = "AdminAuth:ApiKey"
|
|
||||||
* Environment Variable = "AdminAuth__ApiKey"
|
|
||||||
* Azure Key Vault = "AdminAuth--ApiKey"
|
|
||||||
*
|
|
||||||
* _config path = "AdminAuth.ConnectionString"
|
|
||||||
* Environment Variable = "AdminAuth.ConnectionString"
|
|
||||||
* Azure Key Vault = "AdminAuth-ConnectionString"
|
|
||||||
*/
|
|
||||||
return secret.Name.Replace("--", ":").Replace("-", ".");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,66 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Surge365.MassEmailReact.Application.Interfaces;
|
|
||||||
using Surge365.MassEmailReact.Infrastructure;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Claims;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Infrastructure.Middleware
|
|
||||||
{
|
|
||||||
public class CustomException
|
|
||||||
{
|
|
||||||
private readonly RequestDelegate _next;
|
|
||||||
private readonly ILoggingService _loggingService;
|
|
||||||
|
|
||||||
public CustomException(RequestDelegate next, ILoggingService loggingService)
|
|
||||||
{
|
|
||||||
_next = next;
|
|
||||||
_loggingService = loggingService;
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Invokes the middleware peforming session start
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="context">The <see cref="HttpContext"/>.</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public async Task Invoke(HttpContext context)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await _next(context);
|
|
||||||
}
|
|
||||||
catch(Exception ex)
|
|
||||||
{
|
|
||||||
if (!(ex is ThreadAbortException))
|
|
||||||
{
|
|
||||||
/* Guid? loginId = null;
|
|
||||||
|
|
||||||
if (context.User.Identity?.IsAuthenticated == true)
|
|
||||||
{
|
|
||||||
// Adjust the claim type based on your JWT configuration (e.g., "sub", "nameid", or custom)
|
|
||||||
var loginIdClaim = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? context.User.FindFirst("sub")?.Value;
|
|
||||||
if (!string.IsNullOrEmpty(loginIdClaim) && Guid.TryParse(loginIdClaim, out var parsedUserId))
|
|
||||||
{
|
|
||||||
loginId = parsedUserId;
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
await _loggingService.LogError(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public static class CustomExceptionMiddlewareExtensions
|
|
||||||
{
|
|
||||||
public static IApplicationBuilder UseCustomExceptionHandler(this IApplicationBuilder builder)
|
|
||||||
{
|
|
||||||
return builder.UseMiddleware<CustomException>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,12 +1,14 @@
|
|||||||
using Dapper;
|
using Microsoft.Data.SqlClient;
|
||||||
using Dapper.FluentMap;
|
|
||||||
using Microsoft.Data.SqlClient;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Surge365.Core.Interfaces;
|
||||||
|
using Surge365.Core.Mapping;
|
||||||
|
using Surge365.Core.Services;
|
||||||
using Surge365.MassEmailReact.Application.Interfaces;
|
using Surge365.MassEmailReact.Application.Interfaces;
|
||||||
using Surge365.MassEmailReact.Domain.Entities;
|
using Surge365.MassEmailReact.Domain.Entities;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||||
@ -14,22 +16,19 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
public class BouncedEmailRepository : IBouncedEmailRepository
|
public class BouncedEmailRepository : IBouncedEmailRepository
|
||||||
{
|
{
|
||||||
private IConfiguration _config;
|
private IConfiguration _config;
|
||||||
private const string _connectionStringName = "MassEmail.ConnectionString";
|
private DataAccessFactory _dataAccessFactory;
|
||||||
private string? ConnectionString
|
private IQueryMapper _queryMapper;
|
||||||
{
|
public DataAccess GetDataAccess(string connectionStringName = "MassEmail") => _dataAccessFactory.Get(connectionStringName) ?? throw new ArgumentNullException(nameof(_dataAccessFactory), $"DataAccess context for '{connectionStringName}' not found.");
|
||||||
get
|
|
||||||
{
|
|
||||||
return _config.GetConnectionString(_connectionStringName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public BouncedEmailRepository(IConfiguration config)
|
public BouncedEmailRepository(IConfiguration config, DataAccessFactory dataAccessFactory, IQueryMapper queryMapper)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
|
_dataAccessFactory = dataAccessFactory ?? throw new ArgumentNullException(nameof(dataAccessFactory), "DataAccessFactory cannot be null.");
|
||||||
|
_queryMapper = queryMapper;
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
if (!FluentMapper.EntityMaps.ContainsKey(typeof(BouncedEmail)))
|
if (!_queryMapper.EntityMaps.ContainsKey(typeof(BouncedEmail)))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("BouncedEmail dapper mapping is missing. Make sure ConfigureMappings() is called inside program.cs (program startup).");
|
throw new InvalidOperationException("BouncedEmail query mapping is missing. Make sure ConfigureCustomMaps() is called inside program.cs (program startup).");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@ -37,21 +36,28 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
public async Task<BouncedEmail?> GetByEmailAsync(string emailAddress)
|
public async Task<BouncedEmail?> GetByEmailAsync(string emailAddress)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(_config);
|
ArgumentNullException.ThrowIfNull(_config);
|
||||||
ArgumentNullException.ThrowIfNull(_connectionStringName);
|
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
var parameters = new List<SqlParameter>
|
||||||
|
{
|
||||||
|
new SqlParameter("@email_address", emailAddress)
|
||||||
|
};
|
||||||
|
|
||||||
return (await conn.QueryAsync<BouncedEmail>("mem_get_bounced_email_by_email", new { email_address = emailAddress }, commandType: CommandType.StoredProcedure)).FirstOrDefault();
|
DataAccess dataAccess = GetDataAccess();
|
||||||
|
var dataSet = await dataAccess.CallRetrievalProcedureAsync(parameters, "mem_get_bounced_email_by_email");
|
||||||
|
|
||||||
|
var results = await _queryMapper.MapAsync<BouncedEmail>(dataSet);
|
||||||
|
return results.FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<BouncedEmail>> GetAllAsync()
|
public async Task<List<BouncedEmail>> GetAllAsync()
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(_config);
|
var parameters = new List<SqlParameter>();
|
||||||
ArgumentNullException.ThrowIfNull(_connectionStringName);
|
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
DataAccess dataAccess = GetDataAccess();
|
||||||
|
var dataSet = await dataAccess.CallRetrievalProcedureAsync(parameters, "mem_get_bounced_email_all");
|
||||||
|
|
||||||
return (await conn.QueryAsync<BouncedEmail>("mem_get_bounced_email_all", new { }, commandType: CommandType.StoredProcedure)).ToList();
|
var results = await _queryMapper.MapAsync<BouncedEmail>(dataSet);
|
||||||
|
return results.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<int?> CreateAsync(BouncedEmail bouncedEmail)
|
public async Task<int?> CreateAsync(BouncedEmail bouncedEmail)
|
||||||
@ -59,22 +65,30 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
ArgumentNullException.ThrowIfNull(bouncedEmail);
|
ArgumentNullException.ThrowIfNull(bouncedEmail);
|
||||||
ArgumentNullException.ThrowIfNullOrEmpty(bouncedEmail.EmailAddress);
|
ArgumentNullException.ThrowIfNullOrEmpty(bouncedEmail.EmailAddress);
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
SqlParameter pmBouncedEmailKey = new SqlParameter("@bounced_email_key", SqlDbType.Int)
|
||||||
|
{
|
||||||
|
Direction = ParameterDirection.Output
|
||||||
|
};
|
||||||
|
SqlParameter pmSuccess = new SqlParameter("@success", SqlDbType.Bit)
|
||||||
|
{
|
||||||
|
Direction = ParameterDirection.Output
|
||||||
|
};
|
||||||
|
|
||||||
var parameters = new DynamicParameters();
|
List<SqlParameter> parameters = new List<SqlParameter>
|
||||||
parameters.Add("@email_address", bouncedEmail.EmailAddress, DbType.String);
|
{
|
||||||
parameters.Add("@spam", bouncedEmail.Spam, DbType.Boolean);
|
pmBouncedEmailKey,
|
||||||
parameters.Add("@unsubscribe", bouncedEmail.Unsubscribe, DbType.Boolean);
|
new SqlParameter("@email_address", bouncedEmail.EmailAddress),
|
||||||
parameters.Add("@entered_by_admin", bouncedEmail.EnteredByAdmin, DbType.Boolean);
|
new SqlParameter("@spam", bouncedEmail.Spam),
|
||||||
parameters.Add("@success", dbType: DbType.Boolean, direction: ParameterDirection.Output);
|
new SqlParameter("@unsubscribe", bouncedEmail.Unsubscribe),
|
||||||
parameters.Add("@bounced_email_key", dbType: DbType.Int32, direction: ParameterDirection.Output);
|
new SqlParameter("@entered_by_admin", bouncedEmail.EnteredByAdmin),
|
||||||
|
pmSuccess
|
||||||
|
};
|
||||||
|
|
||||||
await conn.ExecuteAsync("mem_save_bounced_email", parameters, commandType: CommandType.StoredProcedure);
|
DataAccess dataAccess = GetDataAccess();
|
||||||
|
await dataAccess.CallActionProcedureAsync(parameters, "mem_save_bounced_email");
|
||||||
|
bool success = pmSuccess.Value != null && (bool)pmSuccess.Value;
|
||||||
|
|
||||||
bool success = parameters.Get<bool>("@success");
|
if (success) return (int?)pmBouncedEmailKey.Value;
|
||||||
|
|
||||||
if (success)
|
|
||||||
return parameters.Get<int>("@bounced_email_key");
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -85,19 +99,24 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
ArgumentNullException.ThrowIfNullOrEmpty(originalEmailAddress);
|
ArgumentNullException.ThrowIfNullOrEmpty(originalEmailAddress);
|
||||||
ArgumentNullException.ThrowIfNullOrEmpty(bouncedEmail.EmailAddress);
|
ArgumentNullException.ThrowIfNullOrEmpty(bouncedEmail.EmailAddress);
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
SqlParameter pmSuccess = new SqlParameter("@success", SqlDbType.Bit)
|
||||||
|
{
|
||||||
|
Direction = ParameterDirection.Output
|
||||||
|
};
|
||||||
|
|
||||||
var parameters = new DynamicParameters();
|
List<SqlParameter> parameters = new List<SqlParameter>
|
||||||
parameters.Add("@old_email_address", originalEmailAddress, DbType.String);
|
{
|
||||||
parameters.Add("@email_address", bouncedEmail.EmailAddress, DbType.String);
|
new SqlParameter("@old_email_address", originalEmailAddress),
|
||||||
parameters.Add("@spam", bouncedEmail.Spam, DbType.Boolean);
|
new SqlParameter("@email_address", bouncedEmail.EmailAddress),
|
||||||
parameters.Add("@unsubscribe", bouncedEmail.Unsubscribe, DbType.Boolean);
|
new SqlParameter("@spam", bouncedEmail.Spam),
|
||||||
parameters.Add("@entered_by_admin", bouncedEmail.EnteredByAdmin, DbType.Boolean);
|
new SqlParameter("@unsubscribe", bouncedEmail.Unsubscribe),
|
||||||
parameters.Add("@success", dbType: DbType.Boolean, direction: ParameterDirection.Output);
|
new SqlParameter("@entered_by_admin", bouncedEmail.EnteredByAdmin),
|
||||||
|
pmSuccess
|
||||||
|
};
|
||||||
|
|
||||||
await conn.ExecuteAsync("mem_save_bounced_email", parameters, commandType: CommandType.StoredProcedure);
|
DataAccess dataAccess = GetDataAccess();
|
||||||
|
await dataAccess.CallActionProcedureAsync(parameters, "mem_save_bounced_email");
|
||||||
bool success = parameters.Get<bool>("@success");
|
bool success = pmSuccess.Value != null && (bool)pmSuccess.Value;
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
@ -106,15 +125,20 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(emailAddress);
|
ArgumentNullException.ThrowIfNull(emailAddress);
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
SqlParameter pmSuccess = new SqlParameter("@success", SqlDbType.Bit)
|
||||||
|
{
|
||||||
|
Direction = ParameterDirection.Output
|
||||||
|
};
|
||||||
|
|
||||||
var parameters = new DynamicParameters();
|
List<SqlParameter> parameters = new List<SqlParameter>
|
||||||
parameters.Add("@email_address", emailAddress, DbType.String);
|
{
|
||||||
parameters.Add("@success", dbType: DbType.Boolean, direction: ParameterDirection.Output);
|
new SqlParameter("@email_address", emailAddress),
|
||||||
|
pmSuccess
|
||||||
|
};
|
||||||
|
|
||||||
await conn.ExecuteAsync("mem_delete_bounced_email", parameters, commandType: CommandType.StoredProcedure);
|
DataAccess dataAccess = GetDataAccess();
|
||||||
|
await dataAccess.CallActionProcedureAsync(parameters, "mem_delete_bounced_email");
|
||||||
bool success = parameters.Get<bool>("@success");
|
bool success = pmSuccess.Value != null && (bool)pmSuccess.Value;
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
using Dapper;
|
using Microsoft.Data.SqlClient;
|
||||||
using Dapper.FluentMap;
|
|
||||||
using Microsoft.Data.SqlClient;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Surge365.Core.Interfaces;
|
||||||
|
using Surge365.Core.Mapping;
|
||||||
|
using Surge365.Core.Services;
|
||||||
using Surge365.MassEmailReact.Application.Interfaces;
|
using Surge365.MassEmailReact.Application.Interfaces;
|
||||||
using Surge365.MassEmailReact.Domain.Entities;
|
using Surge365.MassEmailReact.Domain.Entities;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.Intrinsics.Arm;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||||
@ -16,22 +16,19 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
public class EmailDomainRepository : IEmailDomainRepository
|
public class EmailDomainRepository : IEmailDomainRepository
|
||||||
{
|
{
|
||||||
private readonly IConfiguration _config;
|
private readonly IConfiguration _config;
|
||||||
private const string _connectionStringName = "MassEmail.ConnectionString";
|
private DataAccessFactory _dataAccessFactory;
|
||||||
private string? ConnectionString
|
private IQueryMapper _queryMapper;
|
||||||
{
|
public DataAccess GetDataAccess(string connectionStringName = "MassEmail") => _dataAccessFactory.Get(connectionStringName) ?? throw new ArgumentNullException(nameof(_dataAccessFactory), $"DataAccess context for '{connectionStringName}' not found.");
|
||||||
get
|
|
||||||
{
|
|
||||||
return _config.GetConnectionString(_connectionStringName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public EmailDomainRepository(IConfiguration config)
|
public EmailDomainRepository(IConfiguration config, DataAccessFactory dataAccessFactory, IQueryMapper queryMapper)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
|
_dataAccessFactory = dataAccessFactory ?? throw new ArgumentNullException(nameof(dataAccessFactory), "DataAccessFactory cannot be null.");
|
||||||
|
_queryMapper = queryMapper;
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
if (!FluentMapper.EntityMaps.ContainsKey(typeof(EmailDomain)))
|
if (!_queryMapper.EntityMaps.ContainsKey(typeof(EmailDomain)))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("EmailDomain dapper mapping is missing. Make sure ConfigureMappings() is called inside program.cs (program startup).");
|
throw new InvalidOperationException("EmailDomain query mapping is missing. Make sure ConfigureCustomMaps() is called inside program.cs (program startup).");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@ -39,10 +36,18 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
public async Task<EmailDomain?> GetByIdAsync(int id, bool returnPassword = false)
|
public async Task<EmailDomain?> GetByIdAsync(int id, bool returnPassword = false)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(_config);
|
ArgumentNullException.ThrowIfNull(_config);
|
||||||
ArgumentNullException.ThrowIfNull(_connectionStringName);
|
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(ConnectionString);
|
var parameters = new List<SqlParameter>
|
||||||
EmailDomain? domain = (await conn.QueryAsync<EmailDomain>("mem_get_domain_by_id", new { domain_key = id }, commandType: CommandType.StoredProcedure)).FirstOrDefault();
|
{
|
||||||
|
new SqlParameter("@domain_key", id)
|
||||||
|
};
|
||||||
|
|
||||||
|
DataAccess dataAccess = GetDataAccess();
|
||||||
|
var dataSet = await dataAccess.CallRetrievalProcedureAsync(parameters, "mem_get_domain_by_id");
|
||||||
|
|
||||||
|
var results = await _queryMapper.MapAsync<EmailDomain>(dataSet);
|
||||||
|
EmailDomain? domain = results.FirstOrDefault();
|
||||||
|
|
||||||
if (domain != null && !returnPassword)
|
if (domain != null && !returnPassword)
|
||||||
{
|
{
|
||||||
domain.Password = "";
|
domain.Password = "";
|
||||||
@ -52,11 +57,17 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
|
|
||||||
public async Task<List<EmailDomain>> GetAllAsync(bool activeOnly = true, bool returnPassword = false)
|
public async Task<List<EmailDomain>> GetAllAsync(bool activeOnly = true, bool returnPassword = false)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(_config);
|
var parameters = new List<SqlParameter>
|
||||||
ArgumentNullException.ThrowIfNull(_connectionStringName);
|
{
|
||||||
|
new SqlParameter("@active_only", activeOnly)
|
||||||
|
};
|
||||||
|
|
||||||
|
DataAccess dataAccess = GetDataAccess();
|
||||||
|
var dataSet = await dataAccess.CallRetrievalProcedureAsync(parameters, "mem_get_domain_all");
|
||||||
|
|
||||||
|
var results = await _queryMapper.MapAsync<EmailDomain>(dataSet);
|
||||||
|
List<EmailDomain> domains = results.ToList();
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(ConnectionString);
|
|
||||||
List<EmailDomain> domains = (await conn.QueryAsync<EmailDomain>("mem_get_domain_all", new { active_only = activeOnly }, commandType: CommandType.StoredProcedure)).ToList();
|
|
||||||
if(!returnPassword)
|
if(!returnPassword)
|
||||||
{
|
{
|
||||||
foreach (var domain in domains)
|
foreach (var domain in domains)
|
||||||
@ -73,21 +84,33 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
if (emailDomain.Id != null && emailDomain.Id > 0)
|
if (emailDomain.Id != null && emailDomain.Id > 0)
|
||||||
throw new Exception("ID must be null");
|
throw new Exception("ID must be null");
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(ConnectionString);
|
SqlParameter pmDomainKey = new SqlParameter("@domain_key", SqlDbType.Int)
|
||||||
var parameters = new DynamicParameters();
|
{
|
||||||
parameters.Add("@domain_key", dbType: DbType.Int32, direction: ParameterDirection.Output);
|
Direction = ParameterDirection.Output
|
||||||
parameters.Add("@name", emailDomain.Name, DbType.String);
|
};
|
||||||
parameters.Add("@email_address", emailDomain.EmailAddress, DbType.String);
|
SqlParameter pmSuccess = new SqlParameter("@success", SqlDbType.Bit)
|
||||||
parameters.Add("@username", emailDomain.Username, DbType.String);
|
{
|
||||||
parameters.Add("@password", emailDomain.Password, DbType.String);
|
Direction = ParameterDirection.Output
|
||||||
parameters.Add("@is_active", emailDomain.IsActive, DbType.Boolean);
|
};
|
||||||
parameters.Add("@display_order", emailDomain.DisplayOrder, DbType.Int32);
|
|
||||||
parameters.Add("@success", dbType: DbType.Boolean, direction: ParameterDirection.Output);
|
List<SqlParameter> parameters = new List<SqlParameter>
|
||||||
|
{
|
||||||
|
pmDomainKey,
|
||||||
|
new SqlParameter("@name", emailDomain.Name),
|
||||||
|
new SqlParameter("@email_address", emailDomain.EmailAddress),
|
||||||
|
new SqlParameter("@username", emailDomain.Username),
|
||||||
|
new SqlParameter("@password", emailDomain.Password),
|
||||||
|
new SqlParameter("@is_active", emailDomain.IsActive),
|
||||||
|
new SqlParameter("@display_order", emailDomain.DisplayOrder),
|
||||||
|
pmSuccess
|
||||||
|
};
|
||||||
|
|
||||||
|
DataAccess dataAccess = GetDataAccess();
|
||||||
|
await dataAccess.CallActionProcedureAsync(parameters, "mem_save_domain");
|
||||||
|
bool success = pmSuccess.Value != null && (bool)pmSuccess.Value;
|
||||||
|
|
||||||
await conn.ExecuteAsync("mem_save_domain", parameters, commandType: CommandType.StoredProcedure);
|
|
||||||
bool success = parameters.Get<bool>("@success");
|
|
||||||
if (success)
|
if (success)
|
||||||
return parameters.Get<int>("@domain_key");
|
return (int?)pmDomainKey.Value;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,19 +119,26 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
ArgumentNullException.ThrowIfNull(emailDomain);
|
ArgumentNullException.ThrowIfNull(emailDomain);
|
||||||
ArgumentNullException.ThrowIfNull(emailDomain.Id);
|
ArgumentNullException.ThrowIfNull(emailDomain.Id);
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(ConnectionString);
|
SqlParameter pmSuccess = new SqlParameter("@success", SqlDbType.Bit)
|
||||||
var parameters = new DynamicParameters();
|
{
|
||||||
parameters.Add("@domain_key", emailDomain.Id, DbType.Int32);
|
Direction = ParameterDirection.Output
|
||||||
parameters.Add("@name", emailDomain.Name, DbType.String);
|
};
|
||||||
parameters.Add("@email_address", emailDomain.EmailAddress, DbType.String);
|
|
||||||
parameters.Add("@username", emailDomain.Username, DbType.String);
|
|
||||||
parameters.Add("@password", emailDomain.Password, DbType.String);
|
|
||||||
parameters.Add("@is_active", emailDomain.IsActive, DbType.Boolean);
|
|
||||||
parameters.Add("@display_order", emailDomain.DisplayOrder, DbType.Int32);
|
|
||||||
parameters.Add("@success", dbType: DbType.Boolean, direction: ParameterDirection.Output);
|
|
||||||
|
|
||||||
await conn.ExecuteAsync("mem_save_domain", parameters, commandType: CommandType.StoredProcedure);
|
List<SqlParameter> parameters = new List<SqlParameter>
|
||||||
bool success = parameters.Get<bool>("@success");
|
{
|
||||||
|
new SqlParameter("@domain_key", emailDomain.Id),
|
||||||
|
new SqlParameter("@name", emailDomain.Name),
|
||||||
|
new SqlParameter("@email_address", emailDomain.EmailAddress),
|
||||||
|
new SqlParameter("@username", emailDomain.Username),
|
||||||
|
new SqlParameter("@password", emailDomain.Password),
|
||||||
|
new SqlParameter("@is_active", emailDomain.IsActive),
|
||||||
|
new SqlParameter("@display_order", emailDomain.DisplayOrder),
|
||||||
|
pmSuccess
|
||||||
|
};
|
||||||
|
|
||||||
|
DataAccess dataAccess = GetDataAccess();
|
||||||
|
await dataAccess.CallActionProcedureAsync(parameters, "mem_save_domain");
|
||||||
|
bool success = pmSuccess.Value != null && (bool)pmSuccess.Value;
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
using Dapper;
|
using Microsoft.Data.SqlClient;
|
||||||
using Dapper.FluentMap;
|
|
||||||
using Microsoft.Data.SqlClient;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Surge365.Core.Interfaces;
|
||||||
|
using Surge365.Core.Mapping;
|
||||||
|
using Surge365.Core.Services;
|
||||||
using Surge365.MassEmailReact.Application.Interfaces;
|
using Surge365.MassEmailReact.Application.Interfaces;
|
||||||
using Surge365.MassEmailReact.Domain.Entities;
|
using Surge365.MassEmailReact.Domain.Entities;
|
||||||
using System;
|
using System;
|
||||||
@ -16,38 +17,43 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
public class MailingRepository : IMailingRepository
|
public class MailingRepository : IMailingRepository
|
||||||
{
|
{
|
||||||
private readonly IConfiguration _config;
|
private readonly IConfiguration _config;
|
||||||
private const string _connectionStringName = "MassEmail.ConnectionString";
|
private DataAccessFactory _dataAccessFactory;
|
||||||
private string? ConnectionString => _config.GetConnectionString(_connectionStringName);
|
private IQueryMapper _queryMapper;
|
||||||
|
public DataAccess GetDataAccess(string connectionStringName = "MassEmail") => _dataAccessFactory.Get(connectionStringName) ?? throw new ArgumentNullException(nameof(_dataAccessFactory), $"DataAccess context for '{connectionStringName}' not found.");
|
||||||
|
|
||||||
public MailingRepository(IConfiguration config)
|
public MailingRepository(IConfiguration config, DataAccessFactory dataAccessFactory, IQueryMapper queryMapper)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
|
_dataAccessFactory = dataAccessFactory ?? throw new ArgumentNullException(nameof(dataAccessFactory), "DataAccessFactory cannot be null.");
|
||||||
|
_queryMapper = queryMapper;
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
if (!FluentMapper.EntityMaps.ContainsKey(typeof(Mailing)))
|
if (!_queryMapper.EntityMaps.ContainsKey(typeof(Mailing)))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Mailing dapper mapping is missing. Make sure ConfigureMappings() is called inside program.cs (program startup).");
|
throw new InvalidOperationException("Mailing query mapping is missing. Make sure ConfigureCustomMaps() is called inside program.cs (program startup).");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Mailing?> GetByIdAsync(int id)
|
public async Task<Mailing?> GetByIdAsync(int id)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(ConnectionString);
|
ArgumentNullException.ThrowIfNull(_config);
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(ConnectionString);
|
var parameters = new List<SqlParameter>
|
||||||
|
{
|
||||||
|
new SqlParameter("@blast_key", id)
|
||||||
|
};
|
||||||
|
|
||||||
await conn.OpenAsync();
|
DataAccess dataAccess = GetDataAccess();
|
||||||
|
var dataSet = await dataAccess.CallRetrievalProcedureAsync(parameters, "mem_get_blast_by_id");
|
||||||
|
|
||||||
using var multi = await conn.QueryMultipleAsync(
|
// Handle multiple result sets
|
||||||
"mem_get_blast_by_id",
|
var mailings = await _queryMapper.MapAsync<Mailing>(dataSet);
|
||||||
new { blast_key = id },
|
var mailing = mailings.FirstOrDefault();
|
||||||
commandType: CommandType.StoredProcedure);
|
|
||||||
|
|
||||||
var mailing = await multi.ReadSingleOrDefaultAsync<Mailing>();
|
|
||||||
if (mailing == null) return null;
|
if (mailing == null) return null;
|
||||||
|
|
||||||
var template = await multi.ReadSingleOrDefaultAsync<MailingTemplate>();
|
var templates = await _queryMapper.MapAsync<MailingTemplate>(dataSet);
|
||||||
if (mailing != null)
|
var template = templates.FirstOrDefault();
|
||||||
|
if (mailing != null && template != null)
|
||||||
mailing.Template = template;
|
mailing.Template = template;
|
||||||
|
|
||||||
return mailing;
|
return mailing;
|
||||||
@ -55,24 +61,23 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
|
|
||||||
public async Task<List<Mailing>> GetAllAsync(bool activeOnly = true)
|
public async Task<List<Mailing>> GetAllAsync(bool activeOnly = true)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(ConnectionString);
|
var parameters = new List<SqlParameter>
|
||||||
|
{
|
||||||
|
new SqlParameter("@active_only", activeOnly)
|
||||||
|
};
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(ConnectionString);
|
DataAccess dataAccess = GetDataAccess();
|
||||||
|
var dataSet = await dataAccess.CallRetrievalProcedureAsync(parameters, "mem_get_blast_all");
|
||||||
|
|
||||||
await conn.OpenAsync();
|
var mailings = await _queryMapper.MapAsync<Mailing>(dataSet);
|
||||||
|
var mailingList = mailings.ToList();
|
||||||
|
if (!mailingList.Any()) return mailingList;
|
||||||
|
|
||||||
using var multi = await conn.QueryMultipleAsync(
|
var templates = await _queryMapper.MapAsync<MailingTemplate>(dataSet);
|
||||||
"mem_get_blast_all",
|
var templateList = templates.ToList();
|
||||||
new { active_only = activeOnly },
|
|
||||||
commandType: CommandType.StoredProcedure);
|
|
||||||
|
|
||||||
var mailings = (await multi.ReadAsync<Mailing>()).ToList();
|
var mailingDictionary = mailingList.ToDictionary(t => t.Id!.Value);
|
||||||
if (!mailings.Any()) return mailings;
|
foreach (var template in templateList)
|
||||||
|
|
||||||
var templates = (await multi.ReadAsync<MailingTemplate>()).ToList();
|
|
||||||
|
|
||||||
var mailingDictionary = mailings.ToDictionary(t => t.Id!.Value);
|
|
||||||
foreach (var template in templates)
|
|
||||||
{
|
{
|
||||||
if (mailingDictionary.TryGetValue(template.MailingId, out var mailing))
|
if (mailingDictionary.TryGetValue(template.MailingId, out var mailing))
|
||||||
{
|
{
|
||||||
@ -80,27 +85,30 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return mailings;
|
return mailingList;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<Mailing>> GetByStatusAsync(string codes, string? startDate, string? endDate)
|
public async Task<List<Mailing>> GetByStatusAsync(string codes, string? startDate, string? endDate)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(ConnectionString);
|
var parameters = new List<SqlParameter>
|
||||||
|
{
|
||||||
|
new SqlParameter("@blast_status_codes", codes),
|
||||||
|
new SqlParameter("@start_date", startDate ?? (object)DBNull.Value),
|
||||||
|
new SqlParameter("@end_date", endDate ?? (object)DBNull.Value)
|
||||||
|
};
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(ConnectionString);
|
DataAccess dataAccess = GetDataAccess();
|
||||||
await conn.OpenAsync();
|
var dataSet = await dataAccess.CallRetrievalProcedureAsync(parameters, "mem_get_blast_by_status");
|
||||||
|
|
||||||
using var multi = await conn.QueryMultipleAsync(
|
var mailings = await _queryMapper.MapAsync<Mailing>(dataSet);
|
||||||
"mem_get_blast_by_status",
|
var mailingList = mailings.ToList();
|
||||||
new { blast_status_codes = codes, start_date = startDate, end_date = endDate },
|
if (!mailingList.Any()) return mailingList;
|
||||||
commandType: CommandType.StoredProcedure);
|
|
||||||
|
|
||||||
var mailings = (await multi.ReadAsync<Mailing>()).ToList();
|
var templates = await _queryMapper.MapAsync<MailingTemplate>(dataSet);
|
||||||
if (!mailings.Any()) return mailings;
|
var templateList = templates.ToList();
|
||||||
|
|
||||||
var templates = (await multi.ReadAsync<MailingTemplate>()).ToList();
|
var mailingDictionary = mailingList.ToDictionary(t => t.Id!.Value);
|
||||||
|
foreach (var template in templateList)
|
||||||
var mailingDictionary = mailings.ToDictionary(t => t.Id!.Value);
|
|
||||||
foreach (var template in templates)
|
|
||||||
{
|
{
|
||||||
if (mailingDictionary.TryGetValue(template.MailingId, out var mailing))
|
if (mailingDictionary.TryGetValue(template.MailingId, out var mailing))
|
||||||
{
|
{
|
||||||
@ -108,138 +116,213 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return mailings;
|
return mailingList;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<MailingStatistic>> GetStatisticsByStatusAsync(string codes, string? startDate, string? endDate)
|
public async Task<List<MailingStatistic>> GetStatisticsByStatusAsync(string codes, string? startDate, string? endDate)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(ConnectionString);
|
var parameters = new List<SqlParameter>
|
||||||
|
{
|
||||||
|
new SqlParameter("@blast_status_codes", codes),
|
||||||
|
new SqlParameter("@start_date", startDate ?? (object)DBNull.Value),
|
||||||
|
new SqlParameter("@end_date", endDate ?? (object)DBNull.Value)
|
||||||
|
};
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(ConnectionString);
|
DataAccess dataAccess = GetDataAccess();
|
||||||
return (await conn.QueryAsync<MailingStatistic>("mem_get_blast_statistic_by_status", new { blast_status_codes = codes, start_date = startDate, end_date = endDate }, commandType: CommandType.StoredProcedure)).ToList();
|
var dataSet = await dataAccess.CallRetrievalProcedureAsync(parameters, "mem_get_blast_statistic_by_status");
|
||||||
|
|
||||||
|
var results = await _queryMapper.MapAsync<MailingStatistic>(dataSet);
|
||||||
|
return results.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<MailingStatistic?> GetStatisticByIdAsync(int id)
|
public async Task<MailingStatistic?> GetStatisticByIdAsync(int id)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(ConnectionString);
|
var parameters = new List<SqlParameter>
|
||||||
|
{
|
||||||
|
new SqlParameter("@blast_key", id)
|
||||||
|
};
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(ConnectionString);
|
DataAccess dataAccess = GetDataAccess();
|
||||||
return (await conn.QueryAsync<MailingStatistic>("mem_get_blast_statistic_by_blast", new { blast_key = id }, commandType: CommandType.StoredProcedure)).FirstOrDefault();
|
var dataSet = await dataAccess.CallRetrievalProcedureAsync(parameters, "mem_get_blast_statistic_by_blast");
|
||||||
|
|
||||||
|
var results = await _queryMapper.MapAsync<MailingStatistic>(dataSet);
|
||||||
|
return results.FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<MailingEmail>> GetEmailsByIdAsync(int id)
|
public async Task<List<MailingEmail>> GetEmailsByIdAsync(int id)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(ConnectionString);
|
var parameters = new List<SqlParameter>
|
||||||
|
{
|
||||||
|
new SqlParameter("@blast_key", id)
|
||||||
|
};
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(ConnectionString);
|
DataAccess dataAccess = GetDataAccess();
|
||||||
return (await conn.QueryAsync<MailingEmail>("mem_get_blast_email_by_blast_id", new { blast_key = id }, commandType: CommandType.StoredProcedure)).ToList();
|
var dataSet = await dataAccess.CallRetrievalProcedureAsync(parameters, "mem_get_blast_email_by_blast_id");
|
||||||
|
|
||||||
|
var results = await _queryMapper.MapAsync<MailingEmail>(dataSet);
|
||||||
|
return results.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<MailingTemplate?> GetTemplateByIdAsync(int id)
|
public async Task<MailingTemplate?> GetTemplateByIdAsync(int id)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(ConnectionString);
|
var parameters = new List<SqlParameter>
|
||||||
|
{
|
||||||
|
new SqlParameter("@blast_key", id)
|
||||||
|
};
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(ConnectionString);
|
DataAccess dataAccess = GetDataAccess();
|
||||||
return (await conn.QueryAsync<MailingTemplate>("mem_get_blast_template_by_blast_id", new { blast_key = id }, commandType: CommandType.StoredProcedure)).FirstOrDefault();
|
var dataSet = await dataAccess.CallRetrievalProcedureAsync(parameters, "mem_get_blast_template_by_blast_id");
|
||||||
|
|
||||||
|
var results = await _queryMapper.MapAsync<MailingTemplate>(dataSet);
|
||||||
|
return results.FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<MailingTarget?> GetTargetByIdAsync(int id)
|
public async Task<MailingTarget?> GetTargetByIdAsync(int id)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(ConnectionString);
|
var parameters = new List<SqlParameter>
|
||||||
|
{
|
||||||
|
new SqlParameter("@blast_key", id)
|
||||||
|
};
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(ConnectionString);
|
DataAccess dataAccess = GetDataAccess();
|
||||||
return (await conn.QueryAsync<MailingTarget>("mem_get_blast_target_by_blast_id", new { blast_key = id }, commandType: CommandType.StoredProcedure)).FirstOrDefault();
|
var dataSet = await dataAccess.CallRetrievalProcedureAsync(parameters, "mem_get_blast_target_by_blast_id");
|
||||||
|
|
||||||
|
var results = await _queryMapper.MapAsync<MailingTarget>(dataSet);
|
||||||
|
return results.FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> NameIsAvailableAsync(int? id, string name)
|
public async Task<bool> NameIsAvailableAsync(int? id, string name)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(ConnectionString);
|
SqlParameter pmAvailable = new SqlParameter("@available", SqlDbType.Bit)
|
||||||
using var conn = new SqlConnection(ConnectionString);
|
{
|
||||||
|
Direction = ParameterDirection.Output
|
||||||
|
};
|
||||||
|
|
||||||
var parameters = new DynamicParameters();
|
var parameters = new List<SqlParameter>
|
||||||
parameters.Add("@blast_key", id, DbType.Int32);
|
{
|
||||||
parameters.Add("@blast_name", name, DbType.String);
|
new SqlParameter("@blast_key", id ?? (object)DBNull.Value),
|
||||||
parameters.Add("@available", dbType: DbType.Boolean, direction: ParameterDirection.Output);
|
new SqlParameter("@blast_name", name),
|
||||||
|
pmAvailable
|
||||||
|
};
|
||||||
|
|
||||||
await conn.ExecuteAsync("mem_is_blast_name_available", parameters, commandType: CommandType.StoredProcedure);
|
DataAccess dataAccess = GetDataAccess();
|
||||||
|
await dataAccess.CallActionProcedureAsync(parameters, "mem_is_blast_name_available");
|
||||||
|
|
||||||
return parameters.Get<bool>("@available");
|
bool available = pmAvailable.Value != null && (bool)pmAvailable.Value;
|
||||||
|
return available;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> GetNextAvailableNameAsync(int? id, string name)
|
public async Task<string> GetNextAvailableNameAsync(int? id, string name)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(ConnectionString);
|
SqlParameter pmNextBlastName = new SqlParameter("@next_blast_name", SqlDbType.NVarChar, -1)
|
||||||
using var conn = new SqlConnection(ConnectionString);
|
{
|
||||||
|
Direction = ParameterDirection.Output
|
||||||
|
};
|
||||||
|
|
||||||
var parameters = new DynamicParameters();
|
var parameters = new List<SqlParameter>
|
||||||
parameters.Add("@blast_key", id, DbType.Int32);
|
{
|
||||||
parameters.Add("@blast_name", name, DbType.String);
|
new SqlParameter("@blast_key", id ?? (object)DBNull.Value),
|
||||||
parameters.Add("@next_blast_name", dbType: DbType.String, size:-1, direction: ParameterDirection.Output);
|
new SqlParameter("@blast_name", name),
|
||||||
|
pmNextBlastName
|
||||||
|
};
|
||||||
|
|
||||||
await conn.ExecuteAsync("mem_get_next_available_blast_name", parameters, commandType: CommandType.StoredProcedure);
|
DataAccess dataAccess = GetDataAccess();
|
||||||
|
await dataAccess.CallActionProcedureAsync(parameters, "mem_get_next_available_blast_name");
|
||||||
|
|
||||||
return parameters.Get<string>("@next_blast_name");
|
return pmNextBlastName.Value?.ToString() ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<int?> CreateAsync(Mailing mailing)
|
public async Task<int?> CreateAsync(Mailing mailing)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(ConnectionString);
|
|
||||||
ArgumentNullException.ThrowIfNull(mailing);
|
ArgumentNullException.ThrowIfNull(mailing);
|
||||||
if (mailing.Id != null && mailing.Id > 0)
|
if (mailing.Id != null && mailing.Id > 0)
|
||||||
throw new Exception("ID must be null");
|
throw new Exception("ID must be null");
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(ConnectionString);
|
SqlParameter pmBlastKey = new SqlParameter("@blast_key", SqlDbType.Int)
|
||||||
var parameters = new DynamicParameters();
|
{
|
||||||
parameters.Add("@blast_key", dbType: DbType.Int32, direction: ParameterDirection.Output);
|
Direction = ParameterDirection.Output
|
||||||
parameters.Add("@name", mailing.Name, DbType.String);
|
};
|
||||||
parameters.Add("@description", mailing.Description, DbType.String);
|
SqlParameter pmSuccess = new SqlParameter("@success", SqlDbType.Bit)
|
||||||
parameters.Add("@template_key", mailing.TemplateId, DbType.Int32);
|
{
|
||||||
parameters.Add("@target_key", mailing.TargetId, DbType.Int32);
|
Direction = ParameterDirection.Output
|
||||||
parameters.Add("@blast_status_code", mailing.StatusCode, DbType.String);
|
};
|
||||||
parameters.Add("@schedule_date", mailing.ScheduleDate, DbType.DateTime);
|
|
||||||
parameters.Add("@sent_date", mailing.SentDate, DbType.DateTime);
|
|
||||||
parameters.Add("@blast_recurring_type_code", mailing.RecurringTypeCode, DbType.String);
|
|
||||||
parameters.Add("@recurring_start_date", mailing.RecurringStartDate, DbType.DateTime);
|
|
||||||
parameters.Add("@template_json", JsonSerializer.Serialize(mailing.Template, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower }), DbType.String);
|
|
||||||
parameters.Add("@success", dbType: DbType.Boolean, direction: ParameterDirection.Output);
|
|
||||||
|
|
||||||
await conn.ExecuteAsync("mem_save_blast", parameters, commandType: CommandType.StoredProcedure);
|
List<SqlParameter> parameters = new List<SqlParameter>
|
||||||
|
{
|
||||||
|
pmBlastKey,
|
||||||
|
new SqlParameter("@name", mailing.Name),
|
||||||
|
new SqlParameter("@description", mailing.Description),
|
||||||
|
new SqlParameter("@template_key", mailing.TemplateId),
|
||||||
|
new SqlParameter("@target_key", mailing.TargetId),
|
||||||
|
new SqlParameter("@blast_status_code", mailing.StatusCode),
|
||||||
|
new SqlParameter("@schedule_date", mailing.ScheduleDate ?? (object)DBNull.Value),
|
||||||
|
new SqlParameter("@sent_date", mailing.SentDate ?? (object)DBNull.Value),
|
||||||
|
new SqlParameter("@blast_recurring_type_code", mailing.RecurringTypeCode ?? (object)DBNull.Value),
|
||||||
|
new SqlParameter("@recurring_start_date", mailing.RecurringStartDate ?? (object)DBNull.Value),
|
||||||
|
new SqlParameter("@template_json", JsonSerializer.Serialize(mailing.Template, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower })),
|
||||||
|
pmSuccess
|
||||||
|
};
|
||||||
|
|
||||||
bool success = parameters.Get<bool>("@success");
|
DataAccess dataAccess = GetDataAccess();
|
||||||
|
await dataAccess.CallActionProcedureAsync(parameters, "mem_save_blast");
|
||||||
|
|
||||||
|
bool success = pmSuccess.Value != null && (bool)pmSuccess.Value;
|
||||||
if (success)
|
if (success)
|
||||||
return parameters.Get<int>("@blast_key");
|
return (int?)pmBlastKey.Value;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> UpdateAsync(Mailing mailing)
|
public async Task<bool> UpdateAsync(Mailing mailing)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(ConnectionString);
|
|
||||||
ArgumentNullException.ThrowIfNull(mailing);
|
ArgumentNullException.ThrowIfNull(mailing);
|
||||||
ArgumentNullException.ThrowIfNull(mailing.Id);
|
ArgumentNullException.ThrowIfNull(mailing.Id);
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(ConnectionString);
|
SqlParameter pmSuccess = new SqlParameter("@success", SqlDbType.Bit)
|
||||||
var parameters = new DynamicParameters();
|
{
|
||||||
parameters.Add("@blast_key", mailing.Id, DbType.Int32);
|
Direction = ParameterDirection.Output
|
||||||
parameters.Add("@name", mailing.Name, DbType.String);
|
};
|
||||||
parameters.Add("@description", mailing.Description, DbType.String);
|
|
||||||
parameters.Add("@template_key", mailing.TemplateId, DbType.Int32);
|
|
||||||
parameters.Add("@target_key", mailing.TargetId, DbType.Int32);
|
|
||||||
parameters.Add("@blast_status_code", mailing.StatusCode, DbType.String);
|
|
||||||
parameters.Add("@schedule_date", mailing.ScheduleDate, DbType.DateTime);
|
|
||||||
parameters.Add("@sent_date", mailing.SentDate, DbType.DateTime);
|
|
||||||
parameters.Add("@blast_recurring_type_code", mailing.RecurringTypeCode, DbType.String);
|
|
||||||
parameters.Add("@recurring_start_date", mailing.RecurringStartDate, DbType.DateTime);
|
|
||||||
parameters.Add("@template_json", JsonSerializer.Serialize(mailing.Template, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower }), DbType.String);
|
|
||||||
parameters.Add("@success", dbType: DbType.Boolean, direction: ParameterDirection.Output);
|
|
||||||
|
|
||||||
await conn.ExecuteAsync("mem_save_blast", parameters, commandType: CommandType.StoredProcedure);
|
List<SqlParameter> parameters = new List<SqlParameter>
|
||||||
|
{
|
||||||
|
new SqlParameter("@blast_key", mailing.Id),
|
||||||
|
new SqlParameter("@name", mailing.Name),
|
||||||
|
new SqlParameter("@description", mailing.Description),
|
||||||
|
new SqlParameter("@template_key", mailing.TemplateId),
|
||||||
|
new SqlParameter("@target_key", mailing.TargetId),
|
||||||
|
new SqlParameter("@blast_status_code", mailing.StatusCode),
|
||||||
|
new SqlParameter("@schedule_date", mailing.ScheduleDate ?? (object)DBNull.Value),
|
||||||
|
new SqlParameter("@sent_date", mailing.SentDate ?? (object)DBNull.Value),
|
||||||
|
new SqlParameter("@blast_recurring_type_code", mailing.RecurringTypeCode ?? (object)DBNull.Value),
|
||||||
|
new SqlParameter("@recurring_start_date", mailing.RecurringStartDate ?? (object)DBNull.Value),
|
||||||
|
new SqlParameter("@template_json", JsonSerializer.Serialize(mailing.Template, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower })),
|
||||||
|
pmSuccess
|
||||||
|
};
|
||||||
|
|
||||||
return parameters.Get<bool>("@success");
|
DataAccess dataAccess = GetDataAccess();
|
||||||
|
await dataAccess.CallActionProcedureAsync(parameters, "mem_save_blast");
|
||||||
|
|
||||||
|
bool success = pmSuccess.Value != null && (bool)pmSuccess.Value;
|
||||||
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> CancelMailingAsync(int id)
|
public async Task<bool> CancelMailingAsync(int id)
|
||||||
{
|
{
|
||||||
|
SqlParameter pmSuccess = new SqlParameter("@success", SqlDbType.Bit)
|
||||||
|
{
|
||||||
|
Direction = ParameterDirection.Output
|
||||||
|
};
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(ConnectionString);
|
var parameters = new List<SqlParameter>
|
||||||
var parameters = new DynamicParameters();
|
{
|
||||||
parameters.Add("@blast_key", id, DbType.Int32);
|
new SqlParameter("@blast_key", id),
|
||||||
parameters.Add("@success", dbType: DbType.Boolean, direction: ParameterDirection.Output);
|
pmSuccess
|
||||||
|
};
|
||||||
|
|
||||||
await conn.ExecuteAsync("mem_cancel_blast", parameters, commandType: CommandType.StoredProcedure);
|
DataAccess dataAccess = GetDataAccess();
|
||||||
|
await dataAccess.CallActionProcedureAsync(parameters, "mem_cancel_blast");
|
||||||
|
|
||||||
return parameters.Get<bool>("@success");
|
bool success = pmSuccess.Value != null && (bool)pmSuccess.Value;
|
||||||
|
return success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,11 +1,10 @@
|
|||||||
using Dapper;
|
using Microsoft.Data.SqlClient;
|
||||||
using Dapper.FluentMap;
|
|
||||||
using Microsoft.Data.SqlClient;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Surge365.Core.Interfaces;
|
||||||
|
using Surge365.Core.Mapping;
|
||||||
|
using Surge365.Core.Services;
|
||||||
using Surge365.MassEmailReact.Application.Interfaces;
|
using Surge365.MassEmailReact.Application.Interfaces;
|
||||||
using Surge365.MassEmailReact.Domain.Entities;
|
using Surge365.MassEmailReact.Domain.Entities;
|
||||||
using Surge365.MassEmailReact.Domain.Enums;
|
|
||||||
using Surge365.MassEmailReact.Domain.Enums.Extensions;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
@ -18,32 +17,37 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
public class ServerRepository : IServerRepository
|
public class ServerRepository : IServerRepository
|
||||||
{
|
{
|
||||||
private IConfiguration _config;
|
private IConfiguration _config;
|
||||||
private const string _connectionStringName = "MassEmail.ConnectionString";
|
private DataAccessFactory _dataAccessFactory;
|
||||||
private string? ConnectionString
|
private IQueryMapper _queryMapper;
|
||||||
{
|
public DataAccess GetDataAccess(string connectionStringName = "MassEmail") => _dataAccessFactory.Get(connectionStringName) ?? throw new ArgumentNullException(nameof(_dataAccessFactory), $"DataAccess context for '{connectionStringName}' not found.");
|
||||||
get
|
public ServerRepository(IConfiguration config, DataAccessFactory dataAccessFactory, IQueryMapper queryMapper)
|
||||||
{
|
|
||||||
return _config.GetConnectionString(_connectionStringName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public ServerRepository(IConfiguration config)
|
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
|
_dataAccessFactory = dataAccessFactory ?? throw new ArgumentNullException(nameof(dataAccessFactory), "DataAccessFactory cannot be null.");
|
||||||
|
_queryMapper = queryMapper;
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
if (!FluentMapper.EntityMaps.ContainsKey(typeof(Server)))
|
if (!_queryMapper.EntityMaps.ContainsKey(typeof(Server)))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Server dapper mapping is missing. Make sure ConfigureMappings() is called inside program.cs (program startup).");
|
throw new InvalidOperationException("Server query mapping is missing. Make sure ConfigureCustomMaps() is called inside program.cs (program startup).");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
public async Task<Server?> GetByIdAsync(int serverKey, bool returnPassword = false)
|
public async Task<Server?> GetByIdAsync(int serverKey, bool returnPassword = false)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(_config);
|
ArgumentNullException.ThrowIfNull(_config);
|
||||||
ArgumentNullException.ThrowIfNull(_connectionStringName);
|
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
var parameters = new List<SqlParameter>
|
||||||
|
{
|
||||||
|
new SqlParameter("@server_key", serverKey)
|
||||||
|
};
|
||||||
|
|
||||||
|
DataAccess dataAccess = GetDataAccess();
|
||||||
|
|
||||||
|
var dataSet = await dataAccess.CallRetrievalProcedureAsync(parameters, "mem_get_server_by_id");
|
||||||
|
|
||||||
|
var results = await _queryMapper.MapAsync<Server>(dataSet);
|
||||||
|
Server? server = results.FirstOrDefault();
|
||||||
|
|
||||||
Server? server = (await conn.QueryAsync<Server>("mem_get_server_by_id", new { server_key = serverKey }, commandType: CommandType.StoredProcedure)).FirstOrDefault();
|
|
||||||
if (server != null && !returnPassword)
|
if (server != null && !returnPassword)
|
||||||
{
|
{
|
||||||
server.Password = "";
|
server.Password = "";
|
||||||
@ -52,18 +56,15 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
}
|
}
|
||||||
public async Task<List<Server>> GetAllAsync(bool activeOnly = true, bool returnPassword = false)
|
public async Task<List<Server>> GetAllAsync(bool activeOnly = true, bool returnPassword = false)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(_config);
|
var parameters = new List<SqlParameter>
|
||||||
ArgumentNullException.ThrowIfNull(_connectionStringName);
|
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
|
||||||
|
|
||||||
List<Server> servers = (await conn.QueryAsync<Server>("mem_get_server_all", new { active_only = activeOnly }, commandType: CommandType.StoredProcedure)).ToList();
|
|
||||||
if (!returnPassword)
|
|
||||||
{
|
{
|
||||||
foreach (Server server in servers)
|
new SqlParameter("@active_only", activeOnly)
|
||||||
server.Password = "";
|
};
|
||||||
}
|
DataAccess dataAccess = GetDataAccess();
|
||||||
return servers;
|
var dataSet = await dataAccess.CallRetrievalProcedureAsync(parameters, "mem_get_server_all");
|
||||||
|
|
||||||
|
var results = await _queryMapper.MapAsync<Server>(dataSet);
|
||||||
|
return results.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<int?> CreateAsync(Server server)
|
public async Task<int?> CreateAsync(Server server)
|
||||||
@ -72,26 +73,30 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
if (server.Id != null && server.Id > 0)
|
if (server.Id != null && server.Id > 0)
|
||||||
throw new Exception("ID must be null");
|
throw new Exception("ID must be null");
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
SqlParameter pmServerKey = new SqlParameter("@server_key", SqlDbType.Int)
|
||||||
|
{
|
||||||
|
Direction = ParameterDirection.Output
|
||||||
|
};
|
||||||
|
SqlParameter pmSuccess = new SqlParameter("@success", SqlDbType.Bit)
|
||||||
|
{
|
||||||
|
Direction = ParameterDirection.Output
|
||||||
|
};
|
||||||
|
List<SqlParameter> parameters = new List<SqlParameter>
|
||||||
|
{
|
||||||
|
pmServerKey,
|
||||||
|
new SqlParameter("@name", server.Name),
|
||||||
|
new SqlParameter("@server_name", server.ServerName),
|
||||||
|
new SqlParameter("@port", server.Port),
|
||||||
|
new SqlParameter("@username", server.Username),
|
||||||
|
new SqlParameter("@password", server.Password),
|
||||||
|
pmSuccess
|
||||||
|
};
|
||||||
|
|
||||||
var parameters = new DynamicParameters();
|
DataAccess dataAccess = GetDataAccess();
|
||||||
parameters.Add("@server_key", dbType: DbType.Int32, direction: ParameterDirection.Output);
|
await dataAccess.CallActionProcedureAsync(parameters, "mem_save_server");
|
||||||
parameters.Add("@name", server.Name, DbType.String);
|
bool success = pmSuccess.Value != null && (bool)pmSuccess.Value;
|
||||||
parameters.Add("@server_name", server.ServerName, DbType.String);
|
|
||||||
parameters.Add("@port", server.Port, DbType.Int16);
|
|
||||||
parameters.Add("@username", server.Username, DbType.String);
|
|
||||||
parameters.Add("@password", server.Password, DbType.String);
|
|
||||||
|
|
||||||
// Output parameter
|
if (success) return (int?)pmServerKey.Value;
|
||||||
parameters.Add("@success", dbType: DbType.Boolean, direction: ParameterDirection.Output);
|
|
||||||
|
|
||||||
await conn.ExecuteAsync("mem_save_server", parameters, commandType: CommandType.StoredProcedure);
|
|
||||||
|
|
||||||
// Retrieve the output parameter value
|
|
||||||
bool success = parameters.Get<bool>("@success");
|
|
||||||
|
|
||||||
if (success)
|
|
||||||
return parameters.Get<int>("@server_key");
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -99,31 +104,27 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(server);
|
ArgumentNullException.ThrowIfNull(server);
|
||||||
ArgumentNullException.ThrowIfNull(server.Id);
|
ArgumentNullException.ThrowIfNull(server.Id);
|
||||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
|
||||||
|
|
||||||
var parameters = new DynamicParameters();
|
SqlParameter pmSuccess = new SqlParameter("@success", SqlDbType.Bit)
|
||||||
parameters.Add("@server_key", server.Id, DbType.Int32);
|
{
|
||||||
parameters.Add("@name", server.Name, DbType.String);
|
Direction = ParameterDirection.Output
|
||||||
parameters.Add("@server_name", server.ServerName, DbType.String);
|
};
|
||||||
parameters.Add("@port", server.Port, DbType.Int32);
|
|
||||||
parameters.Add("@username", server.Username, DbType.String);
|
|
||||||
parameters.Add("@password", server.Password, DbType.String);
|
|
||||||
|
|
||||||
// Output parameter
|
List<SqlParameter> parameters = new List<SqlParameter>
|
||||||
parameters.Add("@success", dbType: DbType.Boolean, direction: ParameterDirection.Output);
|
{
|
||||||
|
new SqlParameter("@server_key", server.Id),
|
||||||
await conn.ExecuteAsync("mem_save_server", parameters, commandType: CommandType.StoredProcedure);
|
new SqlParameter("@name", server.Name),
|
||||||
|
new SqlParameter("@server_name", server.ServerName),
|
||||||
// Retrieve the output parameter value
|
new SqlParameter("@port", server.Port),
|
||||||
bool success = parameters.Get<bool>("@success");
|
new SqlParameter("@username", server.Username),
|
||||||
|
new SqlParameter("@password", server.Password),
|
||||||
|
pmSuccess
|
||||||
|
};
|
||||||
|
|
||||||
|
DataAccess dataAccess = GetDataAccess();
|
||||||
|
await dataAccess.CallActionProcedureAsync(parameters, "mem_save_server");
|
||||||
|
bool success = pmSuccess.Value != null && (bool)pmSuccess.Value;
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
//public void Add(Server server)
|
|
||||||
//{
|
|
||||||
// Servers.Add(server);
|
|
||||||
//}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
using Dapper;
|
using Microsoft.Data.SqlClient;
|
||||||
using Dapper.FluentMap;
|
|
||||||
using Microsoft.Data.SqlClient;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Surge365.Core.Interfaces;
|
||||||
|
using Surge365.Core.Mapping;
|
||||||
|
using Surge365.Core.Services;
|
||||||
using Surge365.MassEmailReact.Application.DTOs;
|
using Surge365.MassEmailReact.Application.DTOs;
|
||||||
using Surge365.MassEmailReact.Application.Interfaces;
|
using Surge365.MassEmailReact.Application.Interfaces;
|
||||||
using Surge365.MassEmailReact.Domain.Entities;
|
using Surge365.MassEmailReact.Domain.Entities;
|
||||||
using Surge365.MassEmailReact.Domain.Enums;
|
|
||||||
using Surge365.MassEmailReact.Domain.Enums.Extensions;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
@ -21,70 +20,69 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
public class TargetRepository : ITargetRepository
|
public class TargetRepository : ITargetRepository
|
||||||
{
|
{
|
||||||
private IConfiguration _config;
|
private IConfiguration _config;
|
||||||
private const string _connectionStringName = "MassEmail.ConnectionString";
|
private DataAccessFactory _dataAccessFactory;
|
||||||
private string? ConnectionString
|
private IQueryMapper _queryMapper;
|
||||||
{
|
public DataAccess GetDataAccess(string connectionStringName = "MassEmail") => _dataAccessFactory.Get(connectionStringName) ?? throw new ArgumentNullException(nameof(_dataAccessFactory), $"DataAccess context for '{connectionStringName}' not found.");
|
||||||
get
|
|
||||||
{
|
public TargetRepository(IConfiguration config, DataAccessFactory dataAccessFactory, IQueryMapper queryMapper)
|
||||||
return _config.GetConnectionString(_connectionStringName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public TargetRepository(IConfiguration config)
|
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
|
_dataAccessFactory = dataAccessFactory ?? throw new ArgumentNullException(nameof(dataAccessFactory), "DataAccessFactory cannot be null.");
|
||||||
|
_queryMapper = queryMapper;
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
if (!FluentMapper.EntityMaps.ContainsKey(typeof(Target)))
|
if (!_queryMapper.EntityMaps.ContainsKey(typeof(Target)))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Target dapper mapping is missing. Make sure ConfigureMappings() is called inside program.cs (program startup).");
|
throw new InvalidOperationException("Target query mapping is missing. Make sure ConfigureCustomMaps() is called inside program.cs (program startup).");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Target?> GetByIdAsync(int targetKey)
|
public async Task<Target?> GetByIdAsync(int targetKey)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(_config);
|
ArgumentNullException.ThrowIfNull(_config);
|
||||||
ArgumentNullException.ThrowIfNull(_connectionStringName);
|
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
var parameters = new List<SqlParameter>
|
||||||
await conn.OpenAsync();
|
{
|
||||||
|
new SqlParameter("@target_key", targetKey)
|
||||||
|
};
|
||||||
|
|
||||||
using var multi = await conn.QueryMultipleAsync(
|
DataAccess dataAccess = GetDataAccess();
|
||||||
"mem_get_target_by_id",
|
var dataSet = await dataAccess.CallRetrievalProcedureAsync(parameters, "mem_get_target_by_id");
|
||||||
new { target_key = targetKey },
|
|
||||||
commandType: CommandType.StoredProcedure);
|
|
||||||
|
|
||||||
// Read the first result set (Target)
|
// Read the first result set (Target)
|
||||||
var target = await multi.ReadSingleOrDefaultAsync<Target>();
|
var targets = await _queryMapper.MapAsync<Target>(dataSet);
|
||||||
|
var target = targets.FirstOrDefault();
|
||||||
if (target == null) return null;
|
if (target == null) return null;
|
||||||
|
|
||||||
// Read the second result set (TargetColumns)
|
// Read the second result set (TargetColumns)
|
||||||
var columns = await multi.ReadAsync<TargetColumn>();
|
var columns = await _queryMapper.MapAsync<TargetColumn>(dataSet);
|
||||||
target.Columns = columns.ToList();
|
target.Columns = columns.ToList();
|
||||||
|
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<Target>> GetAllAsync(bool activeOnly = true)
|
public async Task<List<Target>> GetAllAsync(bool activeOnly = true)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(_config);
|
var parameters = new List<SqlParameter>
|
||||||
ArgumentNullException.ThrowIfNull(_connectionStringName);
|
{
|
||||||
|
new SqlParameter("@active_only", activeOnly)
|
||||||
|
};
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
DataAccess dataAccess = GetDataAccess();
|
||||||
await conn.OpenAsync();
|
var dataSet = await dataAccess.CallRetrievalProcedureAsync(parameters, "mem_get_target_all");
|
||||||
|
|
||||||
using var multi = await conn.QueryMultipleAsync(
|
|
||||||
"mem_get_target_all",
|
|
||||||
new { active_only = activeOnly },
|
|
||||||
commandType: CommandType.StoredProcedure);
|
|
||||||
|
|
||||||
// Read the first result set (Targets)
|
// Read the first result set (Targets)
|
||||||
var targets = (await multi.ReadAsync<Target>()).ToList();
|
var targets = await _queryMapper.MapAsync<Target>(dataSet);
|
||||||
if (!targets.Any()) return targets;
|
var targetList = targets.ToList();
|
||||||
|
if (!targetList.Any()) return targetList;
|
||||||
|
|
||||||
// Read the second result set (TargetColumns)
|
// Read the second result set (TargetColumns)
|
||||||
var columns = (await multi.ReadAsync<TargetColumn>()).ToList();
|
var columns = await _queryMapper.MapAsync<TargetColumn>(dataSet);
|
||||||
|
var columnList = columns.ToList();
|
||||||
|
|
||||||
// Map columns to their respective targets
|
// Map columns to their respective targets
|
||||||
var targetDictionary = targets.ToDictionary(t => t.Id!.Value);
|
var targetDictionary = targetList.ToDictionary(t => t.Id!.Value);
|
||||||
foreach (var column in columns)
|
foreach (var column in columnList)
|
||||||
{
|
{
|
||||||
if (targetDictionary.TryGetValue(column.TargetId, out var target))
|
if (targetDictionary.TryGetValue(column.TargetId, out var target))
|
||||||
{
|
{
|
||||||
@ -92,7 +90,7 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return targets;
|
return targetList;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<int?> CreateAsync(Target target)
|
public async Task<int?> CreateAsync(Target target)
|
||||||
@ -101,61 +99,70 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
if (target.Id != null && target.Id > 0)
|
if (target.Id != null && target.Id > 0)
|
||||||
throw new Exception("ID must be null");
|
throw new Exception("ID must be null");
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
SqlParameter pmTargetKey = new SqlParameter("@target_key", SqlDbType.Int)
|
||||||
|
{
|
||||||
|
Direction = ParameterDirection.Output
|
||||||
|
};
|
||||||
|
SqlParameter pmSuccess = new SqlParameter("@success", SqlDbType.Bit)
|
||||||
|
{
|
||||||
|
Direction = ParameterDirection.Output
|
||||||
|
};
|
||||||
|
|
||||||
var parameters = new DynamicParameters();
|
List<SqlParameter> parameters = new List<SqlParameter>
|
||||||
parameters.Add("@target_key", dbType: DbType.Int32, direction: ParameterDirection.Output);
|
{
|
||||||
parameters.Add("@server_key", target.ServerId, DbType.Int32);
|
pmTargetKey,
|
||||||
parameters.Add("@name", target.Name, DbType.String);
|
new SqlParameter("@server_key", target.ServerId),
|
||||||
parameters.Add("@database_name", target.DatabaseName, DbType.String);
|
new SqlParameter("@name", target.Name),
|
||||||
parameters.Add("@view_name", target.ViewName, DbType.String);
|
new SqlParameter("@database_name", target.DatabaseName),
|
||||||
parameters.Add("@filter_query", target.FilterQuery, DbType.String);
|
new SqlParameter("@view_name", target.ViewName),
|
||||||
parameters.Add("@allow_write_back", target.AllowWriteBack, DbType.Boolean);
|
new SqlParameter("@filter_query", target.FilterQuery),
|
||||||
parameters.Add("@is_active", target.IsActive, DbType.Boolean);
|
new SqlParameter("@allow_write_back", target.AllowWriteBack),
|
||||||
if(target.Columns != null)
|
new SqlParameter("@is_active", target.IsActive),
|
||||||
parameters.Add("@column_json", JsonSerializer.Serialize(target.Columns), DbType.String);
|
new SqlParameter("@column_json", target.Columns != null ? JsonSerializer.Serialize(target.Columns) : (object)DBNull.Value),
|
||||||
|
pmSuccess
|
||||||
|
};
|
||||||
|
|
||||||
// Output parameter
|
DataAccess dataAccess = GetDataAccess();
|
||||||
parameters.Add("@success", dbType: DbType.Boolean, direction: ParameterDirection.Output);
|
await dataAccess.CallActionProcedureAsync(parameters, "mem_save_target");
|
||||||
|
|
||||||
await conn.ExecuteAsync("mem_save_target", parameters, commandType: CommandType.StoredProcedure);
|
|
||||||
|
|
||||||
// Retrieve the output parameter value
|
|
||||||
bool success = parameters.Get<bool>("@success");
|
|
||||||
|
|
||||||
|
bool success = pmSuccess.Value != null && (bool)pmSuccess.Value;
|
||||||
if (success)
|
if (success)
|
||||||
return parameters.Get<int>("@target_key");
|
return (int?)pmTargetKey.Value;
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> UpdateAsync(Target target)
|
public async Task<bool> UpdateAsync(Target target)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(target);
|
ArgumentNullException.ThrowIfNull(target);
|
||||||
ArgumentNullException.ThrowIfNull(target.Id);
|
ArgumentNullException.ThrowIfNull(target.Id);
|
||||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
|
||||||
|
|
||||||
var parameters = new DynamicParameters();
|
SqlParameter pmSuccess = new SqlParameter("@success", SqlDbType.Bit)
|
||||||
parameters.Add("@target_key", target.Id, DbType.Int32);
|
{
|
||||||
parameters.Add("@server_key", target.ServerId, DbType.Int32);
|
Direction = ParameterDirection.Output
|
||||||
parameters.Add("@name", target.Name, DbType.String);
|
};
|
||||||
parameters.Add("@database_name", target.DatabaseName, DbType.String);
|
|
||||||
parameters.Add("@view_name", target.ViewName, DbType.String);
|
|
||||||
parameters.Add("@filter_query", target.FilterQuery, DbType.String);
|
|
||||||
parameters.Add("@allow_write_back", target.AllowWriteBack, DbType.Boolean);
|
|
||||||
parameters.Add("@is_active", target.IsActive, DbType.Boolean);
|
|
||||||
if (target.Columns != null)
|
|
||||||
parameters.Add("@column_json", JsonSerializer.Serialize(target.Columns), DbType.String);
|
|
||||||
|
|
||||||
// Output parameter
|
List<SqlParameter> parameters = new List<SqlParameter>
|
||||||
parameters.Add("@success", dbType: DbType.Boolean, direction: ParameterDirection.Output);
|
{
|
||||||
|
new SqlParameter("@target_key", target.Id),
|
||||||
|
new SqlParameter("@server_key", target.ServerId),
|
||||||
|
new SqlParameter("@name", target.Name),
|
||||||
|
new SqlParameter("@database_name", target.DatabaseName),
|
||||||
|
new SqlParameter("@view_name", target.ViewName),
|
||||||
|
new SqlParameter("@filter_query", target.FilterQuery),
|
||||||
|
new SqlParameter("@allow_write_back", target.AllowWriteBack),
|
||||||
|
new SqlParameter("@is_active", target.IsActive),
|
||||||
|
new SqlParameter("@column_json", target.Columns != null ? JsonSerializer.Serialize(target.Columns) : (object)DBNull.Value),
|
||||||
|
pmSuccess
|
||||||
|
};
|
||||||
|
|
||||||
await conn.ExecuteAsync("mem_save_target", parameters, commandType: CommandType.StoredProcedure);
|
DataAccess dataAccess = GetDataAccess();
|
||||||
|
await dataAccess.CallActionProcedureAsync(parameters, "mem_save_target");
|
||||||
// Retrieve the output parameter value
|
|
||||||
bool success = parameters.Get<bool>("@success");
|
|
||||||
|
|
||||||
|
bool success = pmSuccess.Value != null && (bool)pmSuccess.Value;
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<TargetSample?> TestTargetAsync(
|
public async Task<TargetSample?> TestTargetAsync(
|
||||||
string serverName,
|
string serverName,
|
||||||
short port,
|
short port,
|
||||||
@ -179,6 +186,7 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
|
|
||||||
// Clean up server name
|
// Clean up server name
|
||||||
serverName = serverName.Replace("110494-db", "www.surge365.com");
|
serverName = serverName.Replace("110494-db", "www.surge365.com");
|
||||||
|
|
||||||
// Get configuration
|
// Get configuration
|
||||||
string sql = _config["TestTargetSql"] ?? "";
|
string sql = _config["TestTargetSql"] ?? "";
|
||||||
string connectionStringTemplate = _config["ConnectionStringTemplate"] ?? "";
|
string connectionStringTemplate = _config["ConnectionStringTemplate"] ?? "";
|
||||||
@ -201,20 +209,42 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
using var connection = new SqlConnection(connectionString);
|
using var connection = new SqlConnection(connectionString);
|
||||||
await connection.OpenAsync();
|
await connection.OpenAsync();
|
||||||
|
|
||||||
// Assuming the SQL returns three result sets: columns, sample data, and row count
|
// Execute the SQL and get multiple result sets
|
||||||
using var multi = await connection.QueryMultipleAsync(sql, commandTimeout: 300);
|
using var command = new SqlCommand(sql, connection);
|
||||||
|
command.CommandTimeout = 300;
|
||||||
|
|
||||||
// Read column definitions
|
using var adapter = new SqlDataAdapter(command);
|
||||||
var columnEntities = await multi.ReadAsync<dynamic>();
|
var dataSet = new DataSet();
|
||||||
if (!columnEntities.Any())
|
adapter.Fill(dataSet);
|
||||||
|
|
||||||
|
if (dataSet.Tables.Count < 3)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
// Read sample data
|
// Read column definitions (first table)
|
||||||
var sampleRows = (await multi.ReadAsync<dynamic>()).ToList();
|
var columnTable = dataSet.Tables[0];
|
||||||
|
if (columnTable.Rows.Count == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
// Read row count
|
// Read sample data (second table)
|
||||||
var rowCountResult = await multi.ReadSingleAsync<dynamic>();
|
var sampleTable = dataSet.Tables[1];
|
||||||
int rowCount = Convert.ToInt32(rowCountResult.row_count);
|
var sampleRows = new List<Dictionary<string, string>>();
|
||||||
|
foreach (DataRow row in sampleTable.Rows)
|
||||||
|
{
|
||||||
|
var dict = new Dictionary<string, string>();
|
||||||
|
foreach (DataColumn col in sampleTable.Columns)
|
||||||
|
{
|
||||||
|
dict[col.ColumnName] = row[col]?.ToString() ?? "";
|
||||||
|
}
|
||||||
|
sampleRows.Add(dict);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read row count (third table)
|
||||||
|
var rowCountTable = dataSet.Tables[2];
|
||||||
|
int rowCount = 0;
|
||||||
|
if (rowCountTable.Rows.Count > 0)
|
||||||
|
{
|
||||||
|
rowCount = Convert.ToInt32(rowCountTable.Rows[0]["row_count"]);
|
||||||
|
}
|
||||||
|
|
||||||
// Build TargetSample
|
// Build TargetSample
|
||||||
var targetSample = new TargetSample();
|
var targetSample = new TargetSample();
|
||||||
@ -224,10 +254,10 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
bool bounceFound = false;
|
bool bounceFound = false;
|
||||||
bool unsubFound = false;
|
bool unsubFound = false;
|
||||||
|
|
||||||
foreach (var col in columnEntities)
|
foreach (DataRow col in columnTable.Rows)
|
||||||
{
|
{
|
||||||
string name = col.name;
|
string name = col["name"].ToString();
|
||||||
string dataType = col.data_type;
|
string dataType = col["data_type"].ToString();
|
||||||
|
|
||||||
string typeCode = TargetColumnType.General;
|
string typeCode = TargetColumnType.General;
|
||||||
if (!emailFound && name.ToUpper().Contains("EMAIL"))
|
if (!emailFound && name.ToUpper().Contains("EMAIL"))
|
||||||
@ -259,13 +289,8 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert sample rows to dictionary format
|
// Add sample rows
|
||||||
foreach (var row in sampleRows)
|
targetSample.Rows.AddRange(sampleRows);
|
||||||
{
|
|
||||||
var dict = ((IDictionary<string, object>)row)
|
|
||||||
.ToDictionary(k => k.Key, v => v.Value?.ToString() ?? "");
|
|
||||||
targetSample.Rows.Add(dict);
|
|
||||||
}
|
|
||||||
|
|
||||||
return targetSample;
|
return targetSample;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
using Dapper;
|
using Microsoft.Data.SqlClient;
|
||||||
using Dapper.FluentMap;
|
|
||||||
using Microsoft.Data.SqlClient;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Surge365.Core.Interfaces;
|
||||||
|
using Surge365.Core.Mapping;
|
||||||
|
using Surge365.Core.Services;
|
||||||
using Surge365.MassEmailReact.Application.Interfaces;
|
using Surge365.MassEmailReact.Application.Interfaces;
|
||||||
using Surge365.MassEmailReact.Domain.Entities;
|
using Surge365.MassEmailReact.Domain.Entities;
|
||||||
using System;
|
using System;
|
||||||
@ -15,22 +16,19 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
public class TemplateRepository : ITemplateRepository
|
public class TemplateRepository : ITemplateRepository
|
||||||
{
|
{
|
||||||
private IConfiguration _config;
|
private IConfiguration _config;
|
||||||
private const string _connectionStringName = "MassEmail.ConnectionString";
|
private DataAccessFactory _dataAccessFactory;
|
||||||
private string? ConnectionString
|
private IQueryMapper _queryMapper;
|
||||||
{
|
public DataAccess GetDataAccess(string connectionStringName = "MassEmail") => _dataAccessFactory.Get(connectionStringName) ?? throw new ArgumentNullException(nameof(_dataAccessFactory), $"DataAccess context for '{connectionStringName}' not found.");
|
||||||
get
|
|
||||||
{
|
|
||||||
return _config.GetConnectionString(_connectionStringName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public TemplateRepository(IConfiguration config)
|
public TemplateRepository(IConfiguration config, DataAccessFactory dataAccessFactory, IQueryMapper queryMapper)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
|
_dataAccessFactory = dataAccessFactory ?? throw new ArgumentNullException(nameof(dataAccessFactory), "DataAccessFactory cannot be null.");
|
||||||
|
_queryMapper = queryMapper;
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
if (!FluentMapper.EntityMaps.ContainsKey(typeof(Template)))
|
if (!_queryMapper.EntityMaps.ContainsKey(typeof(Template)))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Template dapper mapping is missing. Make sure ConfigureMappings() is called inside program.cs (program startup).");
|
throw new InvalidOperationException("Template query mapping is missing. Make sure ConfigureCustomMaps() is called inside program.cs (program startup).");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@ -38,21 +36,28 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
public async Task<Template?> GetByIdAsync(int id)
|
public async Task<Template?> GetByIdAsync(int id)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(_config);
|
ArgumentNullException.ThrowIfNull(_config);
|
||||||
ArgumentNullException.ThrowIfNull(_connectionStringName);
|
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
var parameters = new List<SqlParameter>
|
||||||
return (await conn.QueryAsync<Template>("mem_get_template_by_id", new { template_key = id },
|
{
|
||||||
commandType: CommandType.StoredProcedure)).FirstOrDefault();
|
new SqlParameter("@template_key", id)
|
||||||
|
};
|
||||||
|
|
||||||
|
DataAccess dataAccess = GetDataAccess();
|
||||||
|
var dataSet = await dataAccess.CallRetrievalProcedureAsync(parameters, "mem_get_template_by_id");
|
||||||
|
|
||||||
|
var results = await _queryMapper.MapAsync<Template>(dataSet);
|
||||||
|
return results.FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<Template>> GetAllAsync(bool activeOnly = true)
|
public async Task<List<Template>> GetAllAsync(bool activeOnly = true)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(_config);
|
var parameters = new List<SqlParameter>();
|
||||||
ArgumentNullException.ThrowIfNull(_connectionStringName);
|
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
DataAccess dataAccess = GetDataAccess();
|
||||||
return (await conn.QueryAsync<Template>("mem_get_template_all", new { },
|
var dataSet = await dataAccess.CallRetrievalProcedureAsync(parameters, "mem_get_template_all");
|
||||||
commandType: CommandType.StoredProcedure)).ToList();
|
|
||||||
|
var results = await _queryMapper.MapAsync<Template>(dataSet);
|
||||||
|
return results.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<int?> CreateAsync(Template template)
|
public async Task<int?> CreateAsync(Template template)
|
||||||
@ -61,27 +66,38 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
if (template.Id != null && template.Id > 0)
|
if (template.Id != null && template.Id > 0)
|
||||||
throw new Exception("ID must be null");
|
throw new Exception("ID must be null");
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
SqlParameter pmTemplateKey = new SqlParameter("@template_key", SqlDbType.Int)
|
||||||
var parameters = new DynamicParameters();
|
{
|
||||||
parameters.Add("@template_key", dbType: DbType.Int32, direction: ParameterDirection.Output);
|
Direction = ParameterDirection.Output
|
||||||
parameters.Add("@name", template.Name, DbType.String);
|
};
|
||||||
parameters.Add("@domain_key", template.DomainId, DbType.Int32);
|
SqlParameter pmSuccess = new SqlParameter("@success", SqlDbType.Bit)
|
||||||
parameters.Add("@description", template.Description, DbType.String);
|
{
|
||||||
parameters.Add("@html_body", template.HtmlBody, DbType.String);
|
Direction = ParameterDirection.Output
|
||||||
parameters.Add("@subject", template.Subject, DbType.String);
|
};
|
||||||
parameters.Add("@to_name", template.ToName, DbType.String);
|
|
||||||
parameters.Add("@from_name", template.FromName, DbType.String);
|
|
||||||
parameters.Add("@from_email", template.FromEmail, DbType.String);
|
|
||||||
parameters.Add("@reply_to_email", template.ReplyToEmail, DbType.String);
|
|
||||||
parameters.Add("@click_tracking", template.ClickTracking, DbType.Boolean);
|
|
||||||
parameters.Add("@open_tracking", template.OpenTracking, DbType.Boolean);
|
|
||||||
parameters.Add("@category_xml", template.CategoryXml, DbType.Xml);
|
|
||||||
parameters.Add("@is_active", template.IsActive, DbType.Boolean);
|
|
||||||
parameters.Add("@success", dbType: DbType.Boolean, direction: ParameterDirection.Output);
|
|
||||||
|
|
||||||
await conn.ExecuteAsync("mem_save_template", parameters, commandType: CommandType.StoredProcedure);
|
List<SqlParameter> parameters = new List<SqlParameter>
|
||||||
bool success = parameters.Get<bool>("@success");
|
{
|
||||||
return success ? parameters.Get<int>("@template_key") : null;
|
pmTemplateKey,
|
||||||
|
new SqlParameter("@name", template.Name),
|
||||||
|
new SqlParameter("@domain_key", template.DomainId),
|
||||||
|
new SqlParameter("@description", template.Description),
|
||||||
|
new SqlParameter("@html_body", template.HtmlBody),
|
||||||
|
new SqlParameter("@subject", template.Subject),
|
||||||
|
new SqlParameter("@to_name", template.ToName),
|
||||||
|
new SqlParameter("@from_name", template.FromName),
|
||||||
|
new SqlParameter("@from_email", template.FromEmail),
|
||||||
|
new SqlParameter("@reply_to_email", template.ReplyToEmail),
|
||||||
|
new SqlParameter("@click_tracking", template.ClickTracking),
|
||||||
|
new SqlParameter("@open_tracking", template.OpenTracking),
|
||||||
|
new SqlParameter("@category_xml", template.CategoryXml),
|
||||||
|
new SqlParameter("@is_active", template.IsActive),
|
||||||
|
pmSuccess
|
||||||
|
};
|
||||||
|
|
||||||
|
DataAccess dataAccess = GetDataAccess();
|
||||||
|
await dataAccess.CallActionProcedureAsync(parameters, "mem_save_template");
|
||||||
|
bool success = pmSuccess.Value != null && (bool)pmSuccess.Value;
|
||||||
|
return success ? (int?)pmTemplateKey.Value : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> UpdateAsync(Template template)
|
public async Task<bool> UpdateAsync(Template template)
|
||||||
@ -89,26 +105,34 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
ArgumentNullException.ThrowIfNull(template);
|
ArgumentNullException.ThrowIfNull(template);
|
||||||
ArgumentNullException.ThrowIfNull(template.Id);
|
ArgumentNullException.ThrowIfNull(template.Id);
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
SqlParameter pmSuccess = new SqlParameter("@success", SqlDbType.Bit)
|
||||||
var parameters = new DynamicParameters();
|
{
|
||||||
parameters.Add("@template_key", template.Id, DbType.Int32);
|
Direction = ParameterDirection.Output
|
||||||
parameters.Add("@name", template.Name, DbType.String);
|
};
|
||||||
parameters.Add("@domain_key", template.DomainId, DbType.Int32);
|
|
||||||
parameters.Add("@description", template.Description, DbType.String);
|
|
||||||
parameters.Add("@html_body", template.HtmlBody, DbType.String);
|
|
||||||
parameters.Add("@subject", template.Subject, DbType.String);
|
|
||||||
parameters.Add("@to_name", template.ToName, DbType.String);
|
|
||||||
parameters.Add("@from_name", template.FromName, DbType.String);
|
|
||||||
parameters.Add("@from_email", template.FromEmail, DbType.String);
|
|
||||||
parameters.Add("@reply_to_email", template.ReplyToEmail, DbType.String);
|
|
||||||
parameters.Add("@click_tracking", template.ClickTracking, DbType.Boolean);
|
|
||||||
parameters.Add("@open_tracking", template.OpenTracking, DbType.Boolean);
|
|
||||||
parameters.Add("@category_xml", template.CategoryXml, DbType.Xml);
|
|
||||||
parameters.Add("@is_active", template.IsActive, DbType.Boolean);
|
|
||||||
parameters.Add("@success", dbType: DbType.Boolean, direction: ParameterDirection.Output);
|
|
||||||
|
|
||||||
await conn.ExecuteAsync("mem_save_template", parameters, commandType: CommandType.StoredProcedure);
|
List<SqlParameter> parameters = new List<SqlParameter>
|
||||||
return parameters.Get<bool>("@success");
|
{
|
||||||
|
new SqlParameter("@template_key", template.Id),
|
||||||
|
new SqlParameter("@name", template.Name),
|
||||||
|
new SqlParameter("@domain_key", template.DomainId),
|
||||||
|
new SqlParameter("@description", template.Description),
|
||||||
|
new SqlParameter("@html_body", template.HtmlBody),
|
||||||
|
new SqlParameter("@subject", template.Subject),
|
||||||
|
new SqlParameter("@to_name", template.ToName),
|
||||||
|
new SqlParameter("@from_name", template.FromName),
|
||||||
|
new SqlParameter("@from_email", template.FromEmail),
|
||||||
|
new SqlParameter("@reply_to_email", template.ReplyToEmail),
|
||||||
|
new SqlParameter("@click_tracking", template.ClickTracking),
|
||||||
|
new SqlParameter("@open_tracking", template.OpenTracking),
|
||||||
|
new SqlParameter("@category_xml", template.CategoryXml),
|
||||||
|
new SqlParameter("@is_active", template.IsActive),
|
||||||
|
pmSuccess
|
||||||
|
};
|
||||||
|
|
||||||
|
DataAccess dataAccess = GetDataAccess();
|
||||||
|
await dataAccess.CallActionProcedureAsync(parameters, "mem_save_template");
|
||||||
|
bool success = pmSuccess.Value != null && (bool)pmSuccess.Value;
|
||||||
|
return success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,6 +1,8 @@
|
|||||||
using Dapper;
|
using Microsoft.Data.SqlClient;
|
||||||
using Microsoft.Data.SqlClient;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Surge365.Core.Interfaces;
|
||||||
|
using Surge365.Core.Mapping;
|
||||||
|
using Surge365.Core.Services;
|
||||||
using Surge365.MassEmailReact.Application.Interfaces;
|
using Surge365.MassEmailReact.Application.Interfaces;
|
||||||
using Surge365.MassEmailReact.Domain.Entities;
|
using Surge365.MassEmailReact.Domain.Entities;
|
||||||
using System;
|
using System;
|
||||||
@ -14,36 +16,48 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
public class TestEmailListRepository : ITestEmailListRepository
|
public class TestEmailListRepository : ITestEmailListRepository
|
||||||
{
|
{
|
||||||
private readonly IConfiguration _config;
|
private readonly IConfiguration _config;
|
||||||
private const string _connectionStringName = "MassEmail.ConnectionString";
|
private DataAccessFactory _dataAccessFactory;
|
||||||
|
private IQueryMapper _queryMapper;
|
||||||
|
public DataAccess GetDataAccess(string connectionStringName = "MassEmail") => _dataAccessFactory.Get(connectionStringName) ?? throw new ArgumentNullException(nameof(_dataAccessFactory), $"DataAccess context for '{connectionStringName}' not found.");
|
||||||
|
|
||||||
public TestEmailListRepository(IConfiguration config)
|
public TestEmailListRepository(IConfiguration config, DataAccessFactory dataAccessFactory, IQueryMapper queryMapper)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
|
_dataAccessFactory = dataAccessFactory ?? throw new ArgumentNullException(nameof(dataAccessFactory), "DataAccessFactory cannot be null.");
|
||||||
|
_queryMapper = queryMapper;
|
||||||
|
#if DEBUG
|
||||||
|
if (!_queryMapper.EntityMaps.ContainsKey(typeof(TestEmailList)))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("TestEmailList query mapping is missing. Make sure ConfigureCustomMaps() is called inside program.cs (program startup).");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<TestEmailList?> GetByIdAsync(int id)
|
public async Task<TestEmailList?> GetByIdAsync(int id)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(_config);
|
ArgumentNullException.ThrowIfNull(_config);
|
||||||
ArgumentNullException.ThrowIfNull(_connectionStringName);
|
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
var parameters = new List<SqlParameter>
|
||||||
return (await conn.QueryAsync<TestEmailList>(
|
{
|
||||||
"mem_get_test_email_list_by_id",
|
new SqlParameter("@test_email_list_key", id)
|
||||||
new { test_email_list_key = id },
|
};
|
||||||
commandType: CommandType.StoredProcedure
|
|
||||||
)).FirstOrDefault();
|
DataAccess dataAccess = GetDataAccess();
|
||||||
|
var dataSet = await dataAccess.CallRetrievalProcedureAsync(parameters, "mem_get_test_email_list_by_id");
|
||||||
|
|
||||||
|
var results = await _queryMapper.MapAsync<TestEmailList>(dataSet);
|
||||||
|
return results.FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<TestEmailList>> GetAllAsync()
|
public async Task<List<TestEmailList>> GetAllAsync()
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(_config);
|
var parameters = new List<SqlParameter>();
|
||||||
ArgumentNullException.ThrowIfNull(_connectionStringName);
|
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
DataAccess dataAccess = GetDataAccess();
|
||||||
return (await conn.QueryAsync<TestEmailList>(
|
var dataSet = await dataAccess.CallRetrievalProcedureAsync(parameters, "mem_get_test_email_list_all");
|
||||||
"mem_get_test_email_list_all",
|
|
||||||
commandType: CommandType.StoredProcedure
|
var results = await _queryMapper.MapAsync<TestEmailList>(dataSet);
|
||||||
)).ToList();
|
return results.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<int?> CreateAsync(TestEmailList testEmailList)
|
public async Task<int?> CreateAsync(TestEmailList testEmailList)
|
||||||
@ -52,17 +66,28 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
if (testEmailList.Id != null && testEmailList.Id > 0)
|
if (testEmailList.Id != null && testEmailList.Id > 0)
|
||||||
throw new Exception("ID must be null");
|
throw new Exception("ID must be null");
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
SqlParameter pmTestEmailListKey = new SqlParameter("@test_email_list_key", SqlDbType.Int)
|
||||||
var parameters = new DynamicParameters();
|
{
|
||||||
parameters.Add("@test_email_list_key", dbType: DbType.Int32, direction: ParameterDirection.Output);
|
Direction = ParameterDirection.Output
|
||||||
parameters.Add("@name", testEmailList.Name, DbType.String);
|
};
|
||||||
parameters.Add("@list", testEmailList.GetListXml(), DbType.String);
|
SqlParameter pmSuccess = new SqlParameter("@success", SqlDbType.Bit)
|
||||||
parameters.Add("@success", dbType: DbType.Boolean, direction: ParameterDirection.Output);
|
{
|
||||||
|
Direction = ParameterDirection.Output
|
||||||
|
};
|
||||||
|
|
||||||
await conn.ExecuteAsync("mem_save_test_email_list", parameters, commandType: CommandType.StoredProcedure);
|
List<SqlParameter> parameters = new List<SqlParameter>
|
||||||
bool success = parameters.Get<bool>("@success");
|
{
|
||||||
|
pmTestEmailListKey,
|
||||||
|
new SqlParameter("@name", testEmailList.Name),
|
||||||
|
new SqlParameter("@list", testEmailList.GetListXml()),
|
||||||
|
pmSuccess
|
||||||
|
};
|
||||||
|
|
||||||
|
DataAccess dataAccess = GetDataAccess();
|
||||||
|
await dataAccess.CallActionProcedureAsync(parameters, "mem_save_test_email_list");
|
||||||
|
bool success = pmSuccess.Value != null && (bool)pmSuccess.Value;
|
||||||
if (success)
|
if (success)
|
||||||
return parameters.Get<int>("@test_email_list_key");
|
return (int?)pmTestEmailListKey.Value;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,15 +96,23 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
ArgumentNullException.ThrowIfNull(testEmailList);
|
ArgumentNullException.ThrowIfNull(testEmailList);
|
||||||
ArgumentNullException.ThrowIfNull(testEmailList.Id);
|
ArgumentNullException.ThrowIfNull(testEmailList.Id);
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
SqlParameter pmSuccess = new SqlParameter("@success", SqlDbType.Bit)
|
||||||
var parameters = new DynamicParameters();
|
{
|
||||||
parameters.Add("@test_email_list_key", testEmailList.Id, DbType.Int32);
|
Direction = ParameterDirection.Output
|
||||||
parameters.Add("@name", testEmailList.Name, DbType.String);
|
};
|
||||||
parameters.Add("@list", testEmailList.GetListXml(), DbType.String);
|
|
||||||
parameters.Add("@success", dbType: DbType.Boolean, direction: ParameterDirection.Output);
|
|
||||||
|
|
||||||
await conn.ExecuteAsync("mem_save_test_email_list", parameters, commandType: CommandType.StoredProcedure);
|
List<SqlParameter> parameters = new List<SqlParameter>
|
||||||
return parameters.Get<bool>("@success");
|
{
|
||||||
|
new SqlParameter("@test_email_list_key", testEmailList.Id),
|
||||||
|
new SqlParameter("@name", testEmailList.Name),
|
||||||
|
new SqlParameter("@list", testEmailList.GetListXml()),
|
||||||
|
pmSuccess
|
||||||
|
};
|
||||||
|
|
||||||
|
DataAccess dataAccess = GetDataAccess();
|
||||||
|
await dataAccess.CallActionProcedureAsync(parameters, "mem_save_test_email_list");
|
||||||
|
bool success = pmSuccess.Value != null && (bool)pmSuccess.Value;
|
||||||
|
return success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,12 +1,14 @@
|
|||||||
using Dapper;
|
using Microsoft.Data.SqlClient;
|
||||||
using Dapper.FluentMap;
|
|
||||||
using Microsoft.Data.SqlClient;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Surge365.Core.Interfaces;
|
||||||
|
using Surge365.Core.Mapping;
|
||||||
|
using Surge365.Core.Services;
|
||||||
using Surge365.MassEmailReact.Application.Interfaces;
|
using Surge365.MassEmailReact.Application.Interfaces;
|
||||||
using Surge365.MassEmailReact.Domain.Entities;
|
using Surge365.MassEmailReact.Domain.Entities;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||||
@ -14,81 +16,104 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
public class UnsubscribeUrlRepository : IUnsubscribeUrlRepository
|
public class UnsubscribeUrlRepository : IUnsubscribeUrlRepository
|
||||||
{
|
{
|
||||||
private IConfiguration _config;
|
private IConfiguration _config;
|
||||||
private const string _connectionStringName = "MassEmail.ConnectionString";
|
private DataAccessFactory _dataAccessFactory;
|
||||||
private string? ConnectionString
|
private IQueryMapper _queryMapper;
|
||||||
{
|
public DataAccess GetDataAccess(string connectionStringName = "MassEmail") => _dataAccessFactory.Get(connectionStringName) ?? throw new ArgumentNullException(nameof(_dataAccessFactory), $"DataAccess context for '{connectionStringName}' not found.");
|
||||||
get
|
|
||||||
{
|
public UnsubscribeUrlRepository(IConfiguration config, DataAccessFactory dataAccessFactory, IQueryMapper queryMapper)
|
||||||
return _config.GetConnectionString(_connectionStringName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public UnsubscribeUrlRepository(IConfiguration config)
|
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
|
_dataAccessFactory = dataAccessFactory ?? throw new ArgumentNullException(nameof(dataAccessFactory), "DataAccessFactory cannot be null.");
|
||||||
|
_queryMapper = queryMapper;
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
if (!FluentMapper.EntityMaps.ContainsKey(typeof(UnsubscribeUrl)))
|
if (!_queryMapper.EntityMaps.ContainsKey(typeof(UnsubscribeUrl)))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("UnsubscribeUrl dapper mapping is missing. Make sure ConfigureMappings() is called inside program.cs (program startup).");
|
throw new InvalidOperationException("UnsubscribeUrl query mapping is missing. Make sure ConfigureCustomMaps() is called inside program.cs (program startup).");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<UnsubscribeUrl?> GetByIdAsync(int unsubscribeUrlKey)
|
public async Task<UnsubscribeUrl?> GetByIdAsync(int unsubscribeUrlKey)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(_config);
|
ArgumentNullException.ThrowIfNull(_config);
|
||||||
ArgumentNullException.ThrowIfNull(_connectionStringName);
|
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
var parameters = new List<SqlParameter>
|
||||||
|
{
|
||||||
|
new SqlParameter("@unsubscribe_url_key", unsubscribeUrlKey)
|
||||||
|
};
|
||||||
|
|
||||||
return (await conn.QueryAsync<UnsubscribeUrl>("mem_get_unsubscribe_url_by_id", new { unsubscribe_url_key = unsubscribeUrlKey }, commandType: CommandType.StoredProcedure)).FirstOrDefault();
|
DataAccess dataAccess = GetDataAccess();
|
||||||
|
var dataSet = await dataAccess.CallRetrievalProcedureAsync(parameters, "mem_get_unsubscribe_url_by_id");
|
||||||
|
|
||||||
|
var results = await _queryMapper.MapAsync<UnsubscribeUrl>(dataSet);
|
||||||
|
return results.FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<UnsubscribeUrl>> GetAllAsync(bool activeOnly = true)
|
public async Task<List<UnsubscribeUrl>> GetAllAsync(bool activeOnly = true)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(_config);
|
var parameters = new List<SqlParameter>();
|
||||||
ArgumentNullException.ThrowIfNull(_connectionStringName);
|
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
DataAccess dataAccess = GetDataAccess();
|
||||||
|
var dataSet = await dataAccess.CallRetrievalProcedureAsync(parameters, "mem_get_unsubscribe_url_all");
|
||||||
|
|
||||||
return (await conn.QueryAsync<UnsubscribeUrl>("mem_get_unsubscribe_url_all", new { }, commandType: CommandType.StoredProcedure)).ToList();
|
var results = await _queryMapper.MapAsync<UnsubscribeUrl>(dataSet);
|
||||||
|
return results.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<int?> CreateAsync(UnsubscribeUrl unsubscribeUrl)
|
public async Task<int?> CreateAsync(UnsubscribeUrl unsubscribeUrl)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(unsubscribeUrl);
|
ArgumentNullException.ThrowIfNull(unsubscribeUrl);
|
||||||
if (unsubscribeUrl.Id != null && unsubscribeUrl.Id > 0)
|
if (unsubscribeUrl.Id != null && unsubscribeUrl.Id > 0)
|
||||||
throw new Exception("ID must be null");
|
throw new Exception("ID must be null");
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
SqlParameter pmUnsubscribeUrlKey = new SqlParameter("@unsubscribe_url_key", SqlDbType.Int)
|
||||||
|
{
|
||||||
|
Direction = ParameterDirection.Output
|
||||||
|
};
|
||||||
|
SqlParameter pmSuccess = new SqlParameter("@success", SqlDbType.Bit)
|
||||||
|
{
|
||||||
|
Direction = ParameterDirection.Output
|
||||||
|
};
|
||||||
|
|
||||||
var parameters = new DynamicParameters();
|
List<SqlParameter> parameters = new List<SqlParameter>
|
||||||
parameters.Add("@unsubscribe_url_key", dbType: DbType.Int32, direction: ParameterDirection.Output);
|
{
|
||||||
parameters.Add("@name", unsubscribeUrl.Name, DbType.String);
|
pmUnsubscribeUrlKey,
|
||||||
parameters.Add("@url", unsubscribeUrl.Url, DbType.String);
|
new SqlParameter("@name", unsubscribeUrl.Name),
|
||||||
parameters.Add("@success", dbType: DbType.Boolean, direction: ParameterDirection.Output);
|
new SqlParameter("@url", unsubscribeUrl.Url),
|
||||||
|
pmSuccess
|
||||||
|
};
|
||||||
|
|
||||||
await conn.ExecuteAsync("mem_save_unsubscribe_url", parameters, commandType: CommandType.StoredProcedure);
|
DataAccess dataAccess = GetDataAccess();
|
||||||
|
await dataAccess.CallActionProcedureAsync(parameters, "mem_save_unsubscribe_url");
|
||||||
bool success = parameters.Get<bool>("@success");
|
bool success = pmSuccess.Value != null && (bool)pmSuccess.Value;
|
||||||
|
|
||||||
if (success)
|
if (success)
|
||||||
return parameters.Get<int>("@unsubscribe_url_key");
|
return (int?)pmUnsubscribeUrlKey.Value;
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> UpdateAsync(UnsubscribeUrl unsubscribeUrl)
|
public async Task<bool> UpdateAsync(UnsubscribeUrl unsubscribeUrl)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(unsubscribeUrl);
|
ArgumentNullException.ThrowIfNull(unsubscribeUrl);
|
||||||
ArgumentNullException.ThrowIfNull(unsubscribeUrl.Id);
|
ArgumentNullException.ThrowIfNull(unsubscribeUrl.Id);
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
SqlParameter pmSuccess = new SqlParameter("@success", SqlDbType.Bit)
|
||||||
|
{
|
||||||
|
Direction = ParameterDirection.Output
|
||||||
|
};
|
||||||
|
|
||||||
var parameters = new DynamicParameters();
|
List<SqlParameter> parameters = new List<SqlParameter>
|
||||||
parameters.Add("@unsubscribe_url_key", unsubscribeUrl.Id, DbType.Int32);
|
{
|
||||||
parameters.Add("@name", unsubscribeUrl.Name, DbType.String);
|
new SqlParameter("@unsubscribe_url_key", unsubscribeUrl.Id),
|
||||||
parameters.Add("@url", unsubscribeUrl.Url, DbType.String);
|
new SqlParameter("@name", unsubscribeUrl.Name),
|
||||||
parameters.Add("@success", dbType: DbType.Boolean, direction: ParameterDirection.Output);
|
new SqlParameter("@url", unsubscribeUrl.Url),
|
||||||
|
pmSuccess
|
||||||
|
};
|
||||||
|
|
||||||
await conn.ExecuteAsync("mem_save_unsubscribe_url", parameters, commandType: CommandType.StoredProcedure);
|
DataAccess dataAccess = GetDataAccess();
|
||||||
|
await dataAccess.CallActionProcedureAsync(parameters, "mem_save_unsubscribe_url");
|
||||||
bool success = parameters.Get<bool>("@success");
|
bool success = pmSuccess.Value != null && (bool)pmSuccess.Value;
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,121 +0,0 @@
|
|||||||
using Surge365.MassEmailReact.Application.Interfaces;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Claims;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.IdentityModel.Tokens.Jwt;
|
|
||||||
using Microsoft.IdentityModel.Tokens;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using System.Net.Http.Json;
|
|
||||||
using Surge365.MassEmailReact.Application.DTOs.AuthApi;
|
|
||||||
|
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Infrastructure.Services
|
|
||||||
{
|
|
||||||
public class AuthService : IAuthService
|
|
||||||
{
|
|
||||||
private readonly IConfiguration _config;
|
|
||||||
string ApiUrl
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var apiUrl = _config["AdminAuth:Url"];
|
|
||||||
if (string.IsNullOrEmpty(apiUrl))
|
|
||||||
throw new Exception("Auth URL is not configured");
|
|
||||||
return apiUrl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
string ApiKey
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var apiKey = _config["AdminAuth:ApiKey"];
|
|
||||||
if (string.IsNullOrEmpty(apiKey))
|
|
||||||
throw new Exception("Api Key is not configured");
|
|
||||||
return apiKey;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
string AuthAppCode
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var appCode = _config["AuthAppCode"];
|
|
||||||
if (string.IsNullOrEmpty(appCode))
|
|
||||||
throw new Exception("Auth App Code is not configured");
|
|
||||||
return appCode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public AuthService(IConfiguration config)
|
|
||||||
{
|
|
||||||
_config = config;
|
|
||||||
}
|
|
||||||
public async Task<(bool authenticated, (User user, string accessToken, string refreshToken)? data, string errorMessage)> Authenticate(string refreshToken)
|
|
||||||
{
|
|
||||||
var authResponse = await AuthenticateAtApi(refreshToken);
|
|
||||||
if (!authResponse.success) return (false, null, authResponse.authResponse.message ?? "Authentication failed");
|
|
||||||
if (string.IsNullOrWhiteSpace(authResponse.authResponse.accessToken) || string.IsNullOrWhiteSpace(authResponse.authResponse.refreshToken) || authResponse.authResponse.user == null)
|
|
||||||
return (false, null, authResponse.authResponse.message ?? "Authentication failed");
|
|
||||||
|
|
||||||
return (true, (authResponse.authResponse.user, authResponse.authResponse.accessToken, authResponse.authResponse.refreshToken), "");
|
|
||||||
|
|
||||||
}
|
|
||||||
public async Task<(bool authenticated, (User user, string accessToken, string refreshToken)? data, string errorMessage)> Authenticate(string username, string password)
|
|
||||||
{
|
|
||||||
var authResponse = await AuthenticateAtApi(username, password);
|
|
||||||
if (!authResponse.success) return (false, null, "Authentication failed");
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(authResponse.authResponse.accessToken) || string.IsNullOrWhiteSpace(authResponse.authResponse.refreshToken) || authResponse.authResponse.user == null)
|
|
||||||
return (false, null, authResponse.authResponse.message ?? "Authentication failed");
|
|
||||||
|
|
||||||
return (true, (authResponse.authResponse.user, authResponse.authResponse.accessToken, authResponse.authResponse.refreshToken), "");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async Task<(bool success, AuthResponse authResponse)> AuthenticateAtApi(string refreshToken)
|
|
||||||
{
|
|
||||||
var httpClient = new HttpClient();
|
|
||||||
httpClient.BaseAddress = new Uri(ApiUrl);
|
|
||||||
httpClient.DefaultRequestHeaders.Add("X-Api-Key", ApiKey);
|
|
||||||
RefreshTokenApiRequest request = new RefreshTokenApiRequest()
|
|
||||||
{
|
|
||||||
appCode = AuthAppCode,
|
|
||||||
refreshToken = refreshToken
|
|
||||||
};
|
|
||||||
|
|
||||||
var response = await httpClient.PostAsJsonAsync("authentication/refreshtoken", request);
|
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode) return (false, new AuthResponse() { message = "Authentication failed" });
|
|
||||||
|
|
||||||
var authResponse = await response.Content.ReadFromJsonAsync<AuthResponse>();
|
|
||||||
|
|
||||||
if (authResponse?.user == null) return (false, new AuthResponse() { message = "Authentication failed" });
|
|
||||||
|
|
||||||
return (true, authResponse);
|
|
||||||
}
|
|
||||||
async Task<(bool success, AuthResponse authResponse)> AuthenticateAtApi(string username, string password)
|
|
||||||
{
|
|
||||||
var httpClient = new HttpClient();
|
|
||||||
httpClient.BaseAddress = new Uri(ApiUrl);
|
|
||||||
httpClient.DefaultRequestHeaders.Add("X-Api-Key", ApiKey);
|
|
||||||
AuthenticateApiRequest request = new AuthenticateApiRequest()
|
|
||||||
{
|
|
||||||
appCode = AuthAppCode,
|
|
||||||
username = username,
|
|
||||||
password = password
|
|
||||||
};
|
|
||||||
|
|
||||||
var response = await httpClient.PostAsJsonAsync("authentication/authenticate", request);
|
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode) return (false, new AuthResponse() { message = "Authentication failed" });
|
|
||||||
|
|
||||||
var authResponse = await response.Content.ReadFromJsonAsync<AuthResponse>();
|
|
||||||
|
|
||||||
if (authResponse?.user == null) return (false, new AuthResponse() { message = "Authentication failed" });
|
|
||||||
|
|
||||||
return (true, authResponse);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,66 +0,0 @@
|
|||||||
using Microsoft.Data.SqlClient;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Surge365.MassEmailReact.Application.Interfaces;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection.Metadata.Ecma335;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Infrastructure.Services
|
|
||||||
{
|
|
||||||
public class LoggingService(IConfiguration configuration) : ILoggingService
|
|
||||||
{
|
|
||||||
private IConfiguration _configuration = configuration;
|
|
||||||
public async Task<bool> InsertLog(LogLevels level, string logger, int? userId, string task, string message,
|
|
||||||
string exceptionStack, string exceptionMessage, string exceptionInnerMessage = "",
|
|
||||||
string customMessage1 = "", string customMessage2 = "", string customMessage3 = "",
|
|
||||||
string customMessage4 = "", string customMessage5 = "")
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
string applicationCode = _configuration["AppCode"] ?? "";
|
|
||||||
List<SqlParameter> pms = new List<SqlParameter> {
|
|
||||||
new SqlParameter("@ApplicationCode", applicationCode),
|
|
||||||
new SqlParameter("@level", level),
|
|
||||||
new SqlParameter("@logger", (string.IsNullOrWhiteSpace(logger) ? this.GetType().Name : logger)),
|
|
||||||
new SqlParameter("@UserKey", userId),
|
|
||||||
new SqlParameter("@Task", task),
|
|
||||||
new SqlParameter("@Message", message),
|
|
||||||
new SqlParameter("@ExceptionStack", exceptionStack),
|
|
||||||
new SqlParameter("@ExceptionMessage", exceptionMessage),
|
|
||||||
new SqlParameter("@ExceptionInnerMessage", exceptionInnerMessage),
|
|
||||||
new SqlParameter("@CustomMessage1", customMessage1),
|
|
||||||
new SqlParameter("@CustomMessage2", customMessage2),
|
|
||||||
new SqlParameter("@CustomMessage3", customMessage3),
|
|
||||||
new SqlParameter("@CustomMessage4", customMessage4),
|
|
||||||
new SqlParameter("@CustomMessage5", customMessage5)
|
|
||||||
};
|
|
||||||
DataAccess da = new DataAccess(_configuration, "YTBLog.ConnectionString");
|
|
||||||
await da.CallActionProcedureAsync(pms, "usp_InsertLog");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
public Task<bool> LogError(Exception ex)
|
|
||||||
{
|
|
||||||
string exceptionMessage = "";
|
|
||||||
string exceptionInnerMessage = "";
|
|
||||||
string exceptionStack = "";
|
|
||||||
|
|
||||||
exceptionMessage = ex.Message;
|
|
||||||
exceptionStack = ex.StackTrace ?? "";
|
|
||||||
if (ex.InnerException != null)
|
|
||||||
{
|
|
||||||
exceptionInnerMessage = ex.InnerException.Message;
|
|
||||||
}
|
|
||||||
|
|
||||||
return InsertLog(LogLevels.Error, "", null, "LogError", ex.Message,
|
|
||||||
exceptionStack, exceptionMessage, exceptionInnerMessage, "", "", "", "", "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -10,6 +10,7 @@ using System.Text.RegularExpressions;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using SendGrid;
|
using SendGrid;
|
||||||
using SendGrid.Helpers.Mail;
|
using SendGrid.Helpers.Mail;
|
||||||
|
using System.Net.Http;
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Infrastructure.Services
|
namespace Surge365.MassEmailReact.Infrastructure.Services
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
using Dapper;
|
using Surge365.MassEmailReact.Application.Interfaces;
|
||||||
using Surge365.MassEmailReact.Application.Interfaces;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|||||||
@ -7,23 +7,14 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\..\Core\Surge365.Core\Surge365.Core.csproj" />
|
||||||
<ProjectReference Include="..\Surge365.MassEmailReact.Application\Surge365.MassEmailReact.Application.csproj" />
|
<ProjectReference Include="..\Surge365.MassEmailReact.Application\Surge365.MassEmailReact.Application.csproj" />
|
||||||
<ProjectReference Include="..\Surge365.MassEmailReact.Domain\Surge365.MassEmailReact.Domain.csproj" />
|
<ProjectReference Include="..\Surge365.MassEmailReact.Domain\Surge365.MassEmailReact.Domain.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.4.0" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.6" />
|
||||||
<PackageReference Include="Dapper.FluentMap" Version="2.0.0" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.3.0" />
|
|
||||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.0.1" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.4" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.4" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.4" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.4" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.4" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.4" />
|
|
||||||
<PackageReference Include="SendGrid" Version="9.29.3" />
|
<PackageReference Include="SendGrid" Version="9.29.3" />
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.8.0" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -1,19 +0,0 @@
|
|||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Surge365.MassEmailReact.Infrastructure
|
|
||||||
{
|
|
||||||
public static class Utilities
|
|
||||||
{
|
|
||||||
public static string GetAppCode(IConfiguration? configuration)
|
|
||||||
{
|
|
||||||
if (configuration == null)
|
|
||||||
return "";
|
|
||||||
return configuration["AppCode"] ?? "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -6,7 +6,7 @@ import { useAuth } from '@/components/auth/AuthContext';
|
|||||||
const AuthCheck: React.FC = () => {
|
const AuthCheck: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { accessToken, setAuth, isLoading } = useAuth();
|
const { accessToken, refreshToken, isLoading, refreshAuthToken } = useAuth();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isLoading) return; // Wait for AuthProvider to finish
|
if (isLoading) return; // Wait for AuthProvider to finish
|
||||||
@ -14,28 +14,16 @@ const AuthCheck: React.FC = () => {
|
|||||||
if (currentPath.toLowerCase() === "/login") return;
|
if (currentPath.toLowerCase() === "/login") return;
|
||||||
|
|
||||||
const tryRefreshToken = async () => {
|
const tryRefreshToken = async () => {
|
||||||
try {
|
const success = await refreshAuthToken();
|
||||||
const response = await fetch('/api/authentication/refreshtoken', {
|
if (!success) {
|
||||||
method: 'POST',
|
|
||||||
credentials: 'include',
|
|
||||||
});
|
|
||||||
if (response.ok) {
|
|
||||||
const data = await response.json();
|
|
||||||
setAuth(data.accessToken);
|
|
||||||
} else {
|
|
||||||
setAuth(null);
|
|
||||||
navigate('/login');
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
setAuth(null);
|
|
||||||
navigate('/login');
|
navigate('/login');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!accessToken || utils.isTokenExpired(accessToken)) {
|
if (refreshToken && (!accessToken || utils.isTokenExpired(accessToken))) {
|
||||||
tryRefreshToken();
|
tryRefreshToken();
|
||||||
}
|
}
|
||||||
}, [navigate, location.pathname, accessToken, setAuth, isLoading]);
|
}, [navigate, accessToken, refreshToken, location.pathname, isLoading, refreshAuthToken]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,46 +4,71 @@ import { Box, CircularProgress, Typography } from '@mui/material';
|
|||||||
|
|
||||||
interface AuthContextType {
|
interface AuthContextType {
|
||||||
accessToken: string | null;
|
accessToken: string | null;
|
||||||
|
refreshToken: string | null;
|
||||||
userRoles: string[];
|
userRoles: string[];
|
||||||
setAuth: (token: string | null) => void;
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
setAuth: (accessToken: string | null, refreshToken?: string | null) => void;
|
||||||
isLoading: boolean; // Add loading state
|
isLoading: boolean; // Add loading state
|
||||||
refreshToken: () => Promise<boolean>;
|
refreshAuthToken: () => Promise<boolean>;
|
||||||
|
syncAccessToken: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AuthContext = createContext<AuthContextType>({
|
const AuthContext = createContext<AuthContextType>({
|
||||||
accessToken: null,
|
accessToken: null,
|
||||||
|
refreshToken: null,
|
||||||
userRoles: [],
|
userRoles: [],
|
||||||
setAuth: () => { },
|
setAuth: () => { },
|
||||||
isLoading: true, // Default to loading
|
isLoading: true, // Default to loading
|
||||||
refreshToken: async () => false,
|
refreshAuthToken: async () => false,
|
||||||
|
syncAccessToken: async () => { },
|
||||||
});
|
});
|
||||||
|
|
||||||
export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
||||||
const [accessToken, setAccessToken] = useState<string | null>(null); // Start as null
|
const [accessToken, setAccessToken] = useState<string | null>(null); // Start as null
|
||||||
|
const [refreshToken, setRefreshToken] = useState<string | null>(null); // Start as null
|
||||||
const [userRoles, setUserRoles] = useState<string[]>([]);
|
const [userRoles, setUserRoles] = useState<string[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState(true); // Track loading
|
const [isLoading, setIsLoading] = useState(true); // Track loading
|
||||||
|
|
||||||
const setAuth = (token: string | null) => {
|
const setAuth = (accessToken: string | null, refreshToken?: string | null) => {
|
||||||
if (token) {
|
if (accessToken) {
|
||||||
localStorage.setItem('accessToken', token);
|
localStorage.setItem('accessToken', accessToken);
|
||||||
setAccessToken(token);
|
setAccessToken(accessToken);
|
||||||
setUserRoles(utils.getUserRoles(token));
|
setUserRoles(utils.getUserRoles(accessToken));
|
||||||
|
|
||||||
|
// Handle refreshToken if provided
|
||||||
|
if (refreshToken !== undefined && refreshToken !== null) {
|
||||||
|
localStorage.setItem('refreshToken', refreshToken);
|
||||||
|
setRefreshToken(refreshToken);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
localStorage.removeItem('accessToken');
|
localStorage.removeItem('accessToken');
|
||||||
|
localStorage.removeItem('refreshToken');
|
||||||
setAccessToken(null);
|
setAccessToken(null);
|
||||||
|
setRefreshToken(null);
|
||||||
setUserRoles([]);
|
setUserRoles([]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const refreshToken = async (): Promise<boolean> => {
|
const refreshAuthToken = async (): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
|
const storedRefreshToken = localStorage.getItem('refreshToken');
|
||||||
|
if (!storedRefreshToken) {
|
||||||
|
setAuth(null);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const response = await fetch('/api/authentication/refreshtoken', {
|
const response = await fetch('/api/authentication/refreshtoken', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
|
body: JSON.stringify({ refreshToken: storedRefreshToken }),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
setAuth(data.accessToken);
|
setAuth(data.accessToken, data.refreshToken);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
setAuth(null);
|
setAuth(null);
|
||||||
@ -55,23 +80,47 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const syncAccessToken = async (): Promise<void> => {
|
||||||
|
if (!accessToken) {
|
||||||
|
const storedAccessToken = localStorage.getItem('accessToken');
|
||||||
|
if (storedAccessToken && !utils.isTokenExpired(storedAccessToken)) {
|
||||||
|
setAccessToken(storedAccessToken);
|
||||||
|
setUserRoles(utils.getUserRoles(storedAccessToken));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
localStorage.setItem('accessToken', accessToken);
|
||||||
|
}
|
||||||
|
};
|
||||||
// Check auth on mount
|
// Check auth on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initializeAuth = async () => {
|
const initializeAuth = async () => {
|
||||||
const storedToken = localStorage.getItem('accessToken');
|
const storedAccessToken = localStorage.getItem('accessToken');
|
||||||
if (storedToken && !utils.isTokenExpired(storedToken)) {
|
const storedRefreshToken = localStorage.getItem('refreshToken');
|
||||||
setAccessToken(storedToken);
|
|
||||||
setUserRoles(utils.getUserRoles(storedToken));
|
if (storedAccessToken && !utils.isTokenExpired(storedAccessToken)) {
|
||||||
|
setAccessToken(storedAccessToken);
|
||||||
|
setRefreshToken(storedRefreshToken);
|
||||||
|
setUserRoles(utils.getUserRoles(storedAccessToken));
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/authentication/refreshtoken', {
|
if (storedRefreshToken) {
|
||||||
method: 'POST',
|
const response = await fetch('/api/authentication/refreshtoken', {
|
||||||
credentials: 'include',
|
method: 'POST',
|
||||||
});
|
headers: {
|
||||||
if (response.ok) {
|
'Content-Type': 'application/json',
|
||||||
const data = await response.json();
|
},
|
||||||
setAuth(data.accessToken);
|
credentials: 'include',
|
||||||
|
body: JSON.stringify({ refreshToken: storedRefreshToken }),
|
||||||
|
});
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
setAuth(data.accessToken, data.refreshToken);
|
||||||
|
} else {
|
||||||
|
setAuth(null);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
setAuth(null);
|
setAuth(null);
|
||||||
}
|
}
|
||||||
@ -86,7 +135,7 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthContext.Provider value={{ accessToken, userRoles, setAuth, isLoading, refreshToken }}>
|
<AuthContext.Provider value={{ accessToken, refreshToken, userRoles, setAuth, isLoading, refreshAuthToken, syncAccessToken }}>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -100,13 +149,13 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
backgroundColor: 'rgba(0, 0, 0, 0.1)', // Optional: light overlay
|
backgroundColor: 'rgba(0, 0, 0, 0.1)', // Optional: light overlay
|
||||||
zIndex: 9999, // Ensure it’s above everything
|
zIndex: 9999, // Ensure it's above everything
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CircularProgress
|
<CircularProgress
|
||||||
size={80} // Larger spinner
|
size={80} // Larger spinner
|
||||||
thickness={4} // Slightly thicker for visibility
|
thickness={4} // Slightly thicker for visibility
|
||||||
sx={{ color: 'primary.main' }} // Use theme’s primary color
|
sx={{ color: 'primary.main' }} // Use theme's primary color
|
||||||
/>
|
/>
|
||||||
<Typography
|
<Typography
|
||||||
variant="h6"
|
variant="h6"
|
||||||
|
|||||||
@ -60,6 +60,12 @@ const DrawerHeader = styled('div')(({ theme }) => ({
|
|||||||
justifyContent: 'flex-end',
|
justifyContent: 'flex-end',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const getSystemTheme = (): 'light' | 'dark' => {
|
||||||
|
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||||
|
? 'dark'
|
||||||
|
: 'light';
|
||||||
|
};
|
||||||
|
|
||||||
const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'open' })<{
|
const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'open' })<{
|
||||||
open?: boolean;
|
open?: boolean;
|
||||||
}>(({ theme, open }) => ({
|
}>(({ theme, open }) => ({
|
||||||
@ -103,7 +109,7 @@ const Layout = ({ children }: LayoutProps) => {
|
|||||||
{ text: 'Completed Mailings', icon: <CheckCircleIcon />, path: '/completedMailings' },
|
{ text: 'Completed Mailings', icon: <CheckCircleIcon />, path: '/completedMailings' },
|
||||||
{ text: 'Cancelled Mailings', icon: <CancelIcon />, path: '/cancelledMailings' },
|
{ text: 'Cancelled Mailings', icon: <CancelIcon />, path: '/cancelledMailings' },
|
||||||
];
|
];
|
||||||
const { userRoles, setAuth } = useAuth(); // Use context
|
const { userRoles, setAuth, refreshAuthToken } = useAuth(); // Use context
|
||||||
const [profileMenuAnchorEl, setProfileMenuAnchorEl] = React.useState<null | HTMLElement>(null);
|
const [profileMenuAnchorEl, setProfileMenuAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||||
const profileMenuOpen = Boolean(profileMenuAnchorEl);
|
const profileMenuOpen = Boolean(profileMenuAnchorEl);
|
||||||
const handleOpenProfileMenu = (event: React.MouseEvent<HTMLButtonElement>) => {
|
const handleOpenProfileMenu = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
@ -119,21 +125,8 @@ const Layout = ({ children }: LayoutProps) => {
|
|||||||
});
|
});
|
||||||
const handleRefreshUser = async () => {
|
const handleRefreshUser = async () => {
|
||||||
handleCloseProfileMenu();
|
handleCloseProfileMenu();
|
||||||
try {
|
const success = await refreshAuthToken();
|
||||||
const response = await customFetch('/api/authentication/refreshtoken', {
|
if (!success) {
|
||||||
method: 'POST',
|
|
||||||
credentials: 'include',
|
|
||||||
|
|
||||||
});
|
|
||||||
if (response.ok) {
|
|
||||||
const data = await response.json();
|
|
||||||
setAuth(data.accessToken); // Update context
|
|
||||||
} else {
|
|
||||||
setAuth(null); // Clear context on failure
|
|
||||||
navigate('/login');
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
setAuth(null); // Clear context on failure
|
|
||||||
navigate('/login');
|
navigate('/login');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -157,6 +150,8 @@ const Layout = ({ children }: LayoutProps) => {
|
|||||||
}
|
}
|
||||||
}, [isMobile]);
|
}, [isMobile]);
|
||||||
|
|
||||||
|
const effectiveMode = (mode === 'system' ? getSystemTheme() : mode) || "light";
|
||||||
|
|
||||||
const handleThemeChange = (event: SelectChangeEvent) => {
|
const handleThemeChange = (event: SelectChangeEvent) => {
|
||||||
setMode(event.target.value as 'light' | 'dark');
|
setMode(event.target.value as 'light' | 'dark');
|
||||||
if (iconButtonRef.current) {
|
if (iconButtonRef.current) {
|
||||||
@ -231,7 +226,7 @@ const Layout = ({ children }: LayoutProps) => {
|
|||||||
<Select
|
<Select
|
||||||
labelId="theme-select-label"
|
labelId="theme-select-label"
|
||||||
id="theme-select"
|
id="theme-select"
|
||||||
value={mode || 'light'}
|
value={effectiveMode || 'light'}
|
||||||
label="Theme"
|
label="Theme"
|
||||||
onChange={handleThemeChange}
|
onChange={handleThemeChange}
|
||||||
sx={{
|
sx={{
|
||||||
|
|||||||
@ -88,165 +88,165 @@ const App = () => {
|
|||||||
<ColorModeContext.Provider value={colorMode}>
|
<ColorModeContext.Provider value={colorMode}>
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<SetupDataProvider>
|
|
||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<Router basename="/">
|
<Router basename="/">
|
||||||
<AuthCheck />
|
<AuthCheck />
|
||||||
<ToastContainer />
|
<SetupDataProvider>
|
||||||
<Routes>
|
<ToastContainer />
|
||||||
<Route path="/" element={<Navigate to="/home" replace />} />
|
<Routes>
|
||||||
<Route
|
<Route path="/" element={<Navigate to="/home" replace />} />
|
||||||
path="/home"
|
<Route
|
||||||
element={
|
path="/home"
|
||||||
<PageWrapper title="Dashboard">
|
element={
|
||||||
<Layout>
|
<PageWrapper title="Dashboard">
|
||||||
<Home />
|
<Layout>
|
||||||
</Layout>
|
<Home />
|
||||||
</PageWrapper>
|
</Layout>
|
||||||
}
|
</PageWrapper>
|
||||||
/>
|
}
|
||||||
<Route
|
/>
|
||||||
path="/servers"
|
<Route
|
||||||
element={
|
path="/servers"
|
||||||
<PageWrapper title="Servers">
|
element={
|
||||||
<Layout>
|
<PageWrapper title="Servers">
|
||||||
<Servers />
|
<Layout>
|
||||||
</Layout>
|
<Servers />
|
||||||
</PageWrapper>
|
</Layout>
|
||||||
}
|
</PageWrapper>
|
||||||
/>
|
}
|
||||||
<Route
|
/>
|
||||||
path="/targets"
|
<Route
|
||||||
element={
|
path="/targets"
|
||||||
<PageWrapper title="Targets">
|
element={
|
||||||
<Layout>
|
<PageWrapper title="Targets">
|
||||||
<Targets />
|
<Layout>
|
||||||
</Layout>
|
<Targets />
|
||||||
</PageWrapper>
|
</Layout>
|
||||||
}
|
</PageWrapper>
|
||||||
/>
|
}
|
||||||
<Route
|
/>
|
||||||
path="/testEmailLists"
|
<Route
|
||||||
element={
|
path="/testEmailLists"
|
||||||
<PageWrapper title="Test Email Lists">
|
element={
|
||||||
<Layout>
|
<PageWrapper title="Test Email Lists">
|
||||||
<TestEmailLists />
|
<Layout>
|
||||||
</Layout>
|
<TestEmailLists />
|
||||||
</PageWrapper>
|
</Layout>
|
||||||
}
|
</PageWrapper>
|
||||||
/>
|
}
|
||||||
<Route
|
/>
|
||||||
path="/blockedEmails"
|
<Route
|
||||||
element={
|
path="/blockedEmails"
|
||||||
<PageWrapper title="Blocked Emails">
|
element={
|
||||||
<Layout>
|
<PageWrapper title="Blocked Emails">
|
||||||
<BouncedEmails />
|
<Layout>
|
||||||
</Layout>
|
<BouncedEmails />
|
||||||
</PageWrapper>
|
</Layout>
|
||||||
}
|
</PageWrapper>
|
||||||
/>
|
}
|
||||||
<Route
|
/>
|
||||||
path="/blockedEmails"
|
<Route
|
||||||
element={
|
path="/blockedEmails"
|
||||||
<PageWrapper title="Blocked Emails">
|
element={
|
||||||
<Layout>
|
<PageWrapper title="Blocked Emails">
|
||||||
<BouncedEmails />
|
<Layout>
|
||||||
</Layout>
|
<BouncedEmails />
|
||||||
</PageWrapper>
|
</Layout>
|
||||||
}
|
</PageWrapper>
|
||||||
/>
|
}
|
||||||
<Route
|
/>
|
||||||
path="/emailDomains"
|
<Route
|
||||||
element={
|
path="/emailDomains"
|
||||||
<PageWrapper title="Email Domains">
|
element={
|
||||||
<Layout>
|
<PageWrapper title="Email Domains">
|
||||||
<EmailDomains />
|
<Layout>
|
||||||
</Layout>
|
<EmailDomains />
|
||||||
</PageWrapper>
|
</Layout>
|
||||||
}
|
</PageWrapper>
|
||||||
/>
|
}
|
||||||
<Route
|
/>
|
||||||
path="/unsubscribeUrls"
|
<Route
|
||||||
element={
|
path="/unsubscribeUrls"
|
||||||
<PageWrapper title="Unsubscribe Urls">
|
element={
|
||||||
<Layout>
|
<PageWrapper title="Unsubscribe Urls">
|
||||||
<UnsubscribeUrls />
|
<Layout>
|
||||||
</Layout>
|
<UnsubscribeUrls />
|
||||||
</PageWrapper>
|
</Layout>
|
||||||
}
|
</PageWrapper>
|
||||||
/>
|
}
|
||||||
<Route
|
/>
|
||||||
path="/templates"
|
<Route
|
||||||
element={
|
path="/templates"
|
||||||
<PageWrapper title="Templates">
|
element={
|
||||||
<Layout>
|
<PageWrapper title="Templates">
|
||||||
<Templates />
|
<Layout>
|
||||||
</Layout>
|
<Templates />
|
||||||
</PageWrapper>
|
</Layout>
|
||||||
}
|
</PageWrapper>
|
||||||
/>
|
}
|
||||||
<Route
|
/>
|
||||||
path="/newMailings"
|
<Route
|
||||||
element={
|
path="/newMailings"
|
||||||
<PageWrapper title="New Mailings">
|
element={
|
||||||
<Layout>
|
<PageWrapper title="New Mailings">
|
||||||
<NewMailings />
|
<Layout>
|
||||||
</Layout>
|
<NewMailings />
|
||||||
</PageWrapper>
|
</Layout>
|
||||||
}
|
</PageWrapper>
|
||||||
/>
|
}
|
||||||
<Route
|
/>
|
||||||
path="/scheduledMailings"
|
<Route
|
||||||
element={
|
path="/scheduledMailings"
|
||||||
<PageWrapper title="Scheduled Mailings">
|
element={
|
||||||
<Layout>
|
<PageWrapper title="Scheduled Mailings">
|
||||||
<ScheduledMailings />
|
<Layout>
|
||||||
</Layout>
|
<ScheduledMailings />
|
||||||
</PageWrapper>
|
</Layout>
|
||||||
}
|
</PageWrapper>
|
||||||
/>
|
}
|
||||||
<Route
|
/>
|
||||||
path="/activeMailings"
|
<Route
|
||||||
element={
|
path="/activeMailings"
|
||||||
<PageWrapper title="Active Mailings">
|
element={
|
||||||
<Layout>
|
<PageWrapper title="Active Mailings">
|
||||||
<ActiveMailings />
|
<Layout>
|
||||||
</Layout>
|
<ActiveMailings />
|
||||||
</PageWrapper>
|
</Layout>
|
||||||
}
|
</PageWrapper>
|
||||||
/>
|
}
|
||||||
<Route
|
/>
|
||||||
path="/cancelledMailings"
|
<Route
|
||||||
element={
|
path="/cancelledMailings"
|
||||||
<PageWrapper title="Cancelled Mailings">
|
element={
|
||||||
<Layout>
|
<PageWrapper title="Cancelled Mailings">
|
||||||
<CancelledMailings />
|
<Layout>
|
||||||
</Layout>
|
<CancelledMailings />
|
||||||
</PageWrapper>
|
</Layout>
|
||||||
}
|
</PageWrapper>
|
||||||
/>
|
}
|
||||||
<Route
|
/>
|
||||||
path="/completedMailings"
|
<Route
|
||||||
element={
|
path="/completedMailings"
|
||||||
<PageWrapper title="Completed Mailings">
|
element={
|
||||||
<Layout>
|
<PageWrapper title="Completed Mailings">
|
||||||
<CompletedMailings />
|
<Layout>
|
||||||
</Layout>
|
<CompletedMailings />
|
||||||
</PageWrapper>
|
</Layout>
|
||||||
}
|
</PageWrapper>
|
||||||
/>
|
}
|
||||||
<Route
|
/>
|
||||||
path="/login"
|
<Route
|
||||||
element={
|
path="/login"
|
||||||
<LayoutLogin>
|
element={
|
||||||
<Login />
|
<LayoutLogin>
|
||||||
</LayoutLogin>
|
<Login />
|
||||||
}
|
</LayoutLogin>
|
||||||
/>
|
}
|
||||||
</Routes>
|
/>
|
||||||
|
</Routes>
|
||||||
|
</SetupDataProvider>
|
||||||
</Router>
|
</Router>
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
</SetupDataProvider>
|
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</ColorModeContext.Provider>
|
</ColorModeContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -9,12 +9,14 @@ import {
|
|||||||
Alert,
|
Alert,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { AuthResponse, User } from '@/types/auth';
|
import { AuthResponse, User } from '@/types/auth';
|
||||||
|
import { useAuth } from '@/components/auth/AuthContext';
|
||||||
//import ForgotPasswordModal from '@/components/modals/ForgotPasswordModal';
|
//import ForgotPasswordModal from '@/components/modals/ForgotPasswordModal';
|
||||||
|
|
||||||
type SpinnerState = Record<string, boolean>;
|
type SpinnerState = Record<string, boolean>;
|
||||||
type FormErrors = Record<string, string>;
|
type FormErrors = Record<string, string>;
|
||||||
|
|
||||||
function Login() {
|
function Login() {
|
||||||
|
const { accessToken, setAuth } = useAuth();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [spinners, setSpinnersState] = useState<SpinnerState>({});
|
const [spinners, setSpinnersState] = useState<SpinnerState>({});
|
||||||
const [formErrors, setFormErrors] = useState<FormErrors>({});
|
const [formErrors, setFormErrors] = useState<FormErrors>({});
|
||||||
@ -84,7 +86,7 @@ function Login() {
|
|||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const json: AuthResponse = await response.json();
|
const json: AuthResponse = await response.json();
|
||||||
try {
|
try {
|
||||||
localStorage.setItem('accessToken', json.accessToken);
|
setAuth(json.accessToken, json.refreshToken);
|
||||||
loggedInUser = json.user;
|
loggedInUser = json.user;
|
||||||
|
|
||||||
if (loggedInUser == null) {
|
if (loggedInUser == null) {
|
||||||
@ -130,18 +132,22 @@ function Login() {
|
|||||||
|
|
||||||
useEffect(() => { //Reset app settings to clear out prev login
|
useEffect(() => { //Reset app settings to clear out prev login
|
||||||
const resetAppSettings = async () => {
|
const resetAppSettings = async () => {
|
||||||
localStorage.removeItem('accessToken');
|
const originalAccessToken = accessToken;
|
||||||
localStorage.removeItem('session_currentUser');
|
setAuth(null);
|
||||||
|
//localStorage.removeItem('session_currentUser');
|
||||||
//localStorage.clear();
|
//localStorage.clear();
|
||||||
//sessionStorage.clear();
|
//sessionStorage.clear();
|
||||||
|
|
||||||
await fetch('/api/authentication/logout', { method: 'POST', credentials: 'include' });
|
if (originalAccessToken) await fetch('/api/authentication/logout', { method: 'POST', credentials: 'include' });
|
||||||
};
|
};
|
||||||
|
|
||||||
resetAppSettings();
|
resetAppSettings();
|
||||||
}, []);
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []); //No dependencies, only run once on page load.
|
||||||
|
|
||||||
|
const finishUserLogin = async (user: User) => {
|
||||||
|
if (!user) console.log("user is undefined");
|
||||||
|
|
||||||
const finishUserLogin = async (_: User) => {
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
setSpinners({ Login: false, LoginWithPasskey: false });
|
setSpinners({ Login: false, LoginWithPasskey: false });
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import BouncedEmail from '@/types/bouncedEmail';
|
|||||||
import UnsubscribeUrl from '@/types/unsubscribeUrl';
|
import UnsubscribeUrl from '@/types/unsubscribeUrl';
|
||||||
import Template from '@/types/template';
|
import Template from '@/types/template';
|
||||||
import EmailDomain from '@/types/emailDomain';
|
import EmailDomain from '@/types/emailDomain';
|
||||||
|
import { useCustomFetchNoNavigate } from "@/utils/customFetch";
|
||||||
|
import { useAuth } from '@/components/auth/AuthContext';
|
||||||
|
|
||||||
export type SetupData = {
|
export type SetupData = {
|
||||||
targets: Target[];
|
targets: Target[];
|
||||||
@ -51,6 +53,9 @@ export type SetupData = {
|
|||||||
const SetupDataContext = createContext<SetupData | undefined>(undefined);
|
const SetupDataContext = createContext<SetupData | undefined>(undefined);
|
||||||
|
|
||||||
export const SetupDataProvider = ({ children }: { children: React.ReactNode }) => {
|
export const SetupDataProvider = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
const customFetch = useCustomFetchNoNavigate();
|
||||||
|
const { accessToken } = useAuth();
|
||||||
|
|
||||||
const [targets, setTargets] = useState<Target[]>([]);
|
const [targets, setTargets] = useState<Target[]>([]);
|
||||||
const [targetsLoading, setTargetsLoading] = useState<boolean>(false);
|
const [targetsLoading, setTargetsLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
@ -80,6 +85,7 @@ export const SetupDataProvider = ({ children }: { children: React.ReactNode }) =
|
|||||||
}
|
}
|
||||||
const fetchSetupData = async () => {
|
const fetchSetupData = async () => {
|
||||||
try {
|
try {
|
||||||
|
if (!accessToken) return;
|
||||||
setDataLoading(true);
|
setDataLoading(true);
|
||||||
const cachedData = sessionStorage.getItem("setupData");
|
const cachedData = sessionStorage.getItem("setupData");
|
||||||
|
|
||||||
@ -150,7 +156,7 @@ export const SetupDataProvider = ({ children }: { children: React.ReactNode }) =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (loadTargets) {
|
if (loadTargets) {
|
||||||
const targetsResponse = await fetch("/api/targets/GetAll?activeOnly=false");
|
const targetsResponse = await customFetch("/api/targets/GetAll?activeOnly=false");
|
||||||
targetsData = await targetsResponse.json();
|
targetsData = await targetsResponse.json();
|
||||||
if (targetsData) {
|
if (targetsData) {
|
||||||
setTargets(targetsData);
|
setTargets(targetsData);
|
||||||
@ -163,7 +169,7 @@ export const SetupDataProvider = ({ children }: { children: React.ReactNode }) =
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (loadServers) {
|
if (loadServers) {
|
||||||
const serversResponse = await fetch("/api/servers/GetAll?activeOnly=false&returnPassword=false");
|
const serversResponse = await customFetch("/api/servers/GetAll?activeOnly=false&returnPassword=false");
|
||||||
serversData = await serversResponse.json();
|
serversData = await serversResponse.json();
|
||||||
if (serversData) {
|
if (serversData) {
|
||||||
setServers(serversData);
|
setServers(serversData);
|
||||||
@ -176,7 +182,7 @@ export const SetupDataProvider = ({ children }: { children: React.ReactNode }) =
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (loadTestEmailLists) {
|
if (loadTestEmailLists) {
|
||||||
const testEmailListsResponse = await fetch("/api/testEmailLists/GetAll?activeOnly=false");
|
const testEmailListsResponse = await customFetch("/api/testEmailLists/GetAll?activeOnly=false");
|
||||||
testEmailListsData = await testEmailListsResponse.json();
|
testEmailListsData = await testEmailListsResponse.json();
|
||||||
if (testEmailListsData) {
|
if (testEmailListsData) {
|
||||||
setTestEmailLists(testEmailListsData);
|
setTestEmailLists(testEmailListsData);
|
||||||
@ -189,7 +195,7 @@ export const SetupDataProvider = ({ children }: { children: React.ReactNode }) =
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (loadBouncedEmails) {
|
if (loadBouncedEmails) {
|
||||||
const bouncedEmailsResponse = await fetch("/api/bouncedEmails/GetAll?activeOnly=false");
|
const bouncedEmailsResponse = await customFetch("/api/bouncedEmails/GetAll?activeOnly=false");
|
||||||
bouncedEmailsData = await bouncedEmailsResponse.json();
|
bouncedEmailsData = await bouncedEmailsResponse.json();
|
||||||
if (bouncedEmailsData) {
|
if (bouncedEmailsData) {
|
||||||
setBouncedEmails(bouncedEmailsData);
|
setBouncedEmails(bouncedEmailsData);
|
||||||
@ -202,7 +208,7 @@ export const SetupDataProvider = ({ children }: { children: React.ReactNode }) =
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (loadUnsubscribeUrls) {
|
if (loadUnsubscribeUrls) {
|
||||||
const unsubscribeUrlsResponse = await fetch("/api/unsubscribeUrls/GetAll?activeOnly=false");
|
const unsubscribeUrlsResponse = await customFetch("/api/unsubscribeUrls/GetAll?activeOnly=false");
|
||||||
unsubscribeUrlsData = await unsubscribeUrlsResponse.json();
|
unsubscribeUrlsData = await unsubscribeUrlsResponse.json();
|
||||||
if (unsubscribeUrlsData) {
|
if (unsubscribeUrlsData) {
|
||||||
setUnsubscribeUrls(unsubscribeUrlsData);
|
setUnsubscribeUrls(unsubscribeUrlsData);
|
||||||
@ -215,7 +221,7 @@ export const SetupDataProvider = ({ children }: { children: React.ReactNode }) =
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (loadTemplates) {
|
if (loadTemplates) {
|
||||||
const templatesResponse = await fetch("/api/templates/GetAll?activeOnly=false");
|
const templatesResponse = await customFetch("/api/templates/GetAll?activeOnly=false");
|
||||||
templatesData = await templatesResponse.json();
|
templatesData = await templatesResponse.json();
|
||||||
if (templatesData) {
|
if (templatesData) {
|
||||||
setTemplates(templatesData);
|
setTemplates(templatesData);
|
||||||
@ -227,7 +233,7 @@ export const SetupDataProvider = ({ children }: { children: React.ReactNode }) =
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (loadEmailDomains) {
|
if (loadEmailDomains) {
|
||||||
const emailDomainsResponse = await fetch("/api/emailDomains/GetAll?activeOnly=false&returnPassword=false");
|
const emailDomainsResponse = await customFetch("/api/emailDomains/GetAll?activeOnly=false&returnPassword=false");
|
||||||
emailDomainsData = await emailDomainsResponse.json();
|
emailDomainsData = await emailDomainsResponse.json();
|
||||||
if (emailDomainsData) {
|
if (emailDomainsData) {
|
||||||
setEmailDomains(emailDomainsData);
|
setEmailDomains(emailDomainsData);
|
||||||
|
|||||||
@ -15,6 +15,7 @@ export interface User {
|
|||||||
|
|
||||||
export interface AuthResponse {
|
export interface AuthResponse {
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
|
refreshToken: string;
|
||||||
user: User;
|
user: User;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,22 +9,25 @@ const customFetch = async (
|
|||||||
url: string,
|
url: string,
|
||||||
options: FetchOptions = {},
|
options: FetchOptions = {},
|
||||||
authContext: ReturnType<typeof useAuth>,
|
authContext: ReturnType<typeof useAuth>,
|
||||||
navigate: ReturnType<typeof useNavigate>
|
navigate: ReturnType<typeof useNavigate> | null
|
||||||
): Promise<Response> => {
|
): Promise<Response> => {
|
||||||
const { accessToken, refreshToken } = authContext;
|
const { accessToken, refreshAuthToken, syncAccessToken } = authContext;
|
||||||
|
|
||||||
|
syncAccessToken(); // Ensure accessToken is up-to-date
|
||||||
const headers = {
|
const headers = {
|
||||||
...options.headers,
|
...options.headers,
|
||||||
...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}),
|
...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {})
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
...options,
|
...options,
|
||||||
headers,
|
headers,
|
||||||
|
credentials: options.credentials ? options.credentials : 'omit',
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.status === 401) {
|
if (response.status === 401) {
|
||||||
const refreshSuccess = await refreshToken();
|
|
||||||
|
const refreshSuccess = await refreshAuthToken();
|
||||||
if (refreshSuccess) {
|
if (refreshSuccess) {
|
||||||
const newAccessToken = authContext.accessToken;
|
const newAccessToken = authContext.accessToken;
|
||||||
const retryResponse = await fetch(url, {
|
const retryResponse = await fetch(url, {
|
||||||
@ -36,7 +39,7 @@ const customFetch = async (
|
|||||||
});
|
});
|
||||||
return retryResponse;
|
return retryResponse;
|
||||||
} else {
|
} else {
|
||||||
navigate('/login');
|
if (navigate) navigate('/login');
|
||||||
throw new Error('Authentication failed');
|
throw new Error('Authentication failed');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -52,4 +55,10 @@ export const useCustomFetch = () => {
|
|||||||
customFetch(url, options, authContext, navigate);
|
customFetch(url, options, authContext, navigate);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useCustomFetchNoNavigate = () => {
|
||||||
|
const authContext = useAuth();
|
||||||
|
|
||||||
|
return (url: string, options: FetchOptions = {}) =>
|
||||||
|
customFetch(url, options, authContext, null);
|
||||||
|
};
|
||||||
export default customFetch;
|
export default customFetch;
|
||||||
@ -20,6 +20,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||||||
README.md = README.md
|
README.md = README.md
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Surge365.Core", "..\..\Core\Surge365.Core\Surge365.Core.csproj", "{6CB40316-D334-B074-F904-B0ABC46FADC9}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -52,6 +54,10 @@ Global
|
|||||||
{A90972B7-7D32-4C1A-AB68-1043819F2A56}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{A90972B7-7D32-4C1A-AB68-1043819F2A56}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{A90972B7-7D32-4C1A-AB68-1043819F2A56}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{A90972B7-7D32-4C1A-AB68-1043819F2A56}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{A90972B7-7D32-4C1A-AB68-1043819F2A56}.Release|Any CPU.Build.0 = Release|Any CPU
|
{A90972B7-7D32-4C1A-AB68-1043819F2A56}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{6CB40316-D334-B074-F904-B0ABC46FADC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{6CB40316-D334-B074-F904-B0ABC46FADC9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{6CB40316-D334-B074-F904-B0ABC46FADC9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{6CB40316-D334-B074-F904-B0ABC46FADC9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user