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.Mvc;
|
||||
using Surge365.Core.Controllers;
|
||||
using Surge365.MassEmailReact.Application.Interfaces;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
using System.Net.Mail;
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Surge365.Core.Controllers;
|
||||
using Surge365.MassEmailReact.Application.Interfaces;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Surge365.Core.Controllers;
|
||||
using Surge365.MassEmailReact.Application.Interfaces;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
using Surge365.MassEmailReact.Domain.Enums;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
using Microsoft.AspNetCore.DataProtection.KeyManagement;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Surge365.Core.Controllers;
|
||||
using Surge365.MassEmailReact.Application.DTOs;
|
||||
using Surge365.MassEmailReact.Application.Interfaces;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
using Microsoft.AspNetCore.DataProtection.KeyManagement;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Surge365.Core.Controllers;
|
||||
using Surge365.MassEmailReact.Application.DTOs;
|
||||
using Surge365.MassEmailReact.Application.Interfaces;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Surge365.Core.Controllers;
|
||||
using Surge365.MassEmailReact.Application.Interfaces;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Surge365.Core.Controllers;
|
||||
using Surge365.MassEmailReact.Application.Interfaces;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Surge365.Core.Controllers;
|
||||
using Surge365.MassEmailReact.Application.DTOs;
|
||||
using Surge365.MassEmailReact.Application.Interfaces;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
|
||||
@ -1,21 +1,24 @@
|
||||
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.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 Surge365.Core.Interfaces;
|
||||
using Surge365.MassEmailReact.Infrastructure.EntityMaps;
|
||||
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.AddCustomConfigurationSources();
|
||||
|
||||
WebApplication? app = null;
|
||||
try
|
||||
{
|
||||
@ -50,21 +53,41 @@ try
|
||||
IssuerSigningKey = new SymmetricSecurityKey(jwtKey),
|
||||
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 =>
|
||||
{
|
||||
client.BaseAddress = new Uri("https://api.sendgrid.com/"); // Optional, for clarity
|
||||
@ -74,10 +97,10 @@ try
|
||||
});
|
||||
// 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
|
||||
builder.Services.AddOpenApi();
|
||||
builder.Services.AddScoped<IAuthService, AuthService>();
|
||||
builder.Services.AddScoped<ITargetService, TargetService>();
|
||||
builder.Services.AddScoped<ITargetRepository, TargetRepository>();
|
||||
builder.Services.AddScoped<IServerService, ServerService>();
|
||||
@ -95,10 +118,12 @@ try
|
||||
builder.Services.AddScoped<IMailingService, MailingService>();
|
||||
builder.Services.AddScoped<IMailingRepository, MailingRepository>();
|
||||
|
||||
EntityMapperConfiguration.ConfigureCustomMaps();
|
||||
|
||||
app = builder.Build();
|
||||
|
||||
|
||||
app.UseCustomExceptionHandler();
|
||||
|
||||
app.UseDefaultFiles();
|
||||
app.MapStaticAssets();
|
||||
|
||||
@ -109,17 +134,17 @@ try
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
|
||||
DapperConfiguration.ConfigureMappings();
|
||||
}
|
||||
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();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\Core\Surge365.Core\Surge365.Core.csproj" />
|
||||
<ProjectReference Include="..\Surge365.MassEmailReact.Application\Surge365.MassEmailReact.Application.csproj" />
|
||||
<ProjectReference Include="..\Surge365.MassEmailReact.Infrastructure\Surge365.MassEmailReact.Infrastructure.csproj" />
|
||||
</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>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Enums\Extensions\" />
|
||||
</ItemGroup>
|
||||
|
||||
</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.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Dapper.FluentMap.Mapping;
|
||||
using Surge365.Core.Mapping;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
|
||||
namespace Surge365.MassEmailReact.Infrastructure.DapperMaps
|
||||
namespace Surge365.MassEmailReact.Infrastructure.EntityMaps
|
||||
{
|
||||
public class BouncedEmailMap : EntityMap<BouncedEmail>
|
||||
{
|
||||
@ -1,7 +1,7 @@
|
||||
using Dapper.FluentMap.Mapping;
|
||||
using Surge365.Core.Mapping;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
|
||||
namespace Surge365.MassEmailReact.Infrastructure.DapperMaps
|
||||
namespace Surge365.MassEmailReact.Infrastructure.EntityMaps
|
||||
{
|
||||
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;
|
||||
|
||||
namespace Surge365.MassEmailReact.Infrastructure.DapperMaps
|
||||
namespace Surge365.MassEmailReact.Infrastructure.EntityMaps
|
||||
{
|
||||
public class MailingEmailMap : EntityMap<MailingEmail>
|
||||
{
|
||||
@ -1,7 +1,7 @@
|
||||
using Dapper.FluentMap.Mapping;
|
||||
using Surge365.Core.Mapping;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
|
||||
namespace Surge365.MassEmailReact.Infrastructure.DapperMaps
|
||||
namespace Surge365.MassEmailReact.Infrastructure.EntityMaps
|
||||
{
|
||||
public class MailingMap : EntityMap<Mailing>
|
||||
{
|
||||
@ -1,7 +1,7 @@
|
||||
using Dapper.FluentMap.Mapping;
|
||||
using Surge365.Core.Mapping;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
|
||||
namespace Surge365.MassEmailReact.Infrastructure.DapperMaps
|
||||
namespace Surge365.MassEmailReact.Infrastructure.EntityMaps
|
||||
{
|
||||
public class MailingStatisticMap : EntityMap<MailingStatistic>
|
||||
{
|
||||
@ -3,10 +3,10 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Dapper.FluentMap.Mapping;
|
||||
using Surge365.Core.Mapping;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
|
||||
namespace Surge365.MassEmailReact.Infrastructure.DapperMaps
|
||||
namespace Surge365.MassEmailReact.Infrastructure.EntityMaps
|
||||
{
|
||||
public class MailingTargetMap : EntityMap<MailingTarget>
|
||||
{
|
||||
@ -1,7 +1,7 @@
|
||||
using Dapper.FluentMap.Mapping;
|
||||
using Surge365.Core.Mapping;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
|
||||
namespace Surge365.MassEmailReact.Infrastructure.DapperMaps
|
||||
namespace Surge365.MassEmailReact.Infrastructure.EntityMaps
|
||||
{
|
||||
public class MailingTemplateMap : EntityMap<MailingTemplate>
|
||||
{
|
||||
@ -3,10 +3,10 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Dapper.FluentMap.Mapping;
|
||||
using Surge365.Core.Mapping;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
|
||||
namespace Surge365.MassEmailReact.Infrastructure.DapperMaps
|
||||
namespace Surge365.MassEmailReact.Infrastructure.EntityMaps
|
||||
{
|
||||
public class ServerMap : EntityMap<Server>
|
||||
{
|
||||
@ -3,10 +3,10 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Dapper.FluentMap.Mapping;
|
||||
using Surge365.Core.Mapping;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
|
||||
namespace Surge365.MassEmailReact.Infrastructure.DapperMaps
|
||||
namespace Surge365.MassEmailReact.Infrastructure.EntityMaps
|
||||
{
|
||||
public class TargetColumnMap : EntityMap<TargetColumn>
|
||||
{
|
||||
@ -3,10 +3,10 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Dapper.FluentMap.Mapping;
|
||||
using Surge365.Core.Mapping;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
|
||||
namespace Surge365.MassEmailReact.Infrastructure.DapperMaps
|
||||
namespace Surge365.MassEmailReact.Infrastructure.EntityMaps
|
||||
{
|
||||
public class TargetMap : EntityMap<Target>
|
||||
{
|
||||
@ -1,7 +1,7 @@
|
||||
using Dapper.FluentMap.Mapping;
|
||||
using Surge365.Core.Mapping;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
|
||||
namespace Surge365.MassEmailReact.Infrastructure.DapperMaps
|
||||
namespace Surge365.MassEmailReact.Infrastructure.EntityMaps
|
||||
{
|
||||
public class TemplateMap : EntityMap<Template>
|
||||
{
|
||||
@ -1,4 +1,4 @@
|
||||
using Dapper.FluentMap.Mapping;
|
||||
using Surge365.Core.Mapping;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@ -6,7 +6,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Surge365.MassEmailReact.Infrastructure.DapperMaps
|
||||
namespace Surge365.MassEmailReact.Infrastructure.EntityMaps
|
||||
{
|
||||
public class TestEmailListMap : EntityMap<TestEmailList>
|
||||
{
|
||||
@ -1,7 +1,7 @@
|
||||
using Dapper.FluentMap.Mapping;
|
||||
using Surge365.Core.Mapping;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
|
||||
namespace Surge365.MassEmailReact.Infrastructure.DapperMaps
|
||||
namespace Surge365.MassEmailReact.Infrastructure.EntityMaps
|
||||
{
|
||||
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 Dapper.FluentMap;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Surge365.Core.Interfaces;
|
||||
using Surge365.Core.Mapping;
|
||||
using Surge365.Core.Services;
|
||||
using Surge365.MassEmailReact.Application.Interfaces;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
@ -14,22 +16,19 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
public class BouncedEmailRepository : IBouncedEmailRepository
|
||||
{
|
||||
private IConfiguration _config;
|
||||
private const string _connectionStringName = "MassEmail.ConnectionString";
|
||||
private string? ConnectionString
|
||||
{
|
||||
get
|
||||
{
|
||||
return _config.GetConnectionString(_connectionStringName);
|
||||
}
|
||||
}
|
||||
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 BouncedEmailRepository(IConfiguration config)
|
||||
public BouncedEmailRepository(IConfiguration config, DataAccessFactory dataAccessFactory, IQueryMapper queryMapper)
|
||||
{
|
||||
_config = config;
|
||||
_dataAccessFactory = dataAccessFactory ?? throw new ArgumentNullException(nameof(dataAccessFactory), "DataAccessFactory cannot be null.");
|
||||
_queryMapper = queryMapper;
|
||||
#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
|
||||
}
|
||||
@ -37,21 +36,28 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
public async Task<BouncedEmail?> GetByEmailAsync(string emailAddress)
|
||||
{
|
||||
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()
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(_config);
|
||||
ArgumentNullException.ThrowIfNull(_connectionStringName);
|
||||
var parameters = new List<SqlParameter>();
|
||||
|
||||
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)
|
||||
@ -59,22 +65,30 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
ArgumentNullException.ThrowIfNull(bouncedEmail);
|
||||
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();
|
||||
parameters.Add("@email_address", bouncedEmail.EmailAddress, DbType.String);
|
||||
parameters.Add("@spam", bouncedEmail.Spam, DbType.Boolean);
|
||||
parameters.Add("@unsubscribe", bouncedEmail.Unsubscribe, DbType.Boolean);
|
||||
parameters.Add("@entered_by_admin", bouncedEmail.EnteredByAdmin, DbType.Boolean);
|
||||
parameters.Add("@success", dbType: DbType.Boolean, direction: ParameterDirection.Output);
|
||||
parameters.Add("@bounced_email_key", dbType: DbType.Int32, direction: ParameterDirection.Output);
|
||||
List<SqlParameter> parameters = new List<SqlParameter>
|
||||
{
|
||||
pmBouncedEmailKey,
|
||||
new SqlParameter("@email_address", bouncedEmail.EmailAddress),
|
||||
new SqlParameter("@spam", bouncedEmail.Spam),
|
||||
new SqlParameter("@unsubscribe", bouncedEmail.Unsubscribe),
|
||||
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 parameters.Get<int>("@bounced_email_key");
|
||||
if (success) return (int?)pmBouncedEmailKey.Value;
|
||||
|
||||
return null;
|
||||
}
|
||||
@ -85,19 +99,24 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
ArgumentNullException.ThrowIfNullOrEmpty(originalEmailAddress);
|
||||
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();
|
||||
parameters.Add("@old_email_address", originalEmailAddress, DbType.String);
|
||||
parameters.Add("@email_address", bouncedEmail.EmailAddress, DbType.String);
|
||||
parameters.Add("@spam", bouncedEmail.Spam, DbType.Boolean);
|
||||
parameters.Add("@unsubscribe", bouncedEmail.Unsubscribe, DbType.Boolean);
|
||||
parameters.Add("@entered_by_admin", bouncedEmail.EnteredByAdmin, DbType.Boolean);
|
||||
parameters.Add("@success", dbType: DbType.Boolean, direction: ParameterDirection.Output);
|
||||
List<SqlParameter> parameters = new List<SqlParameter>
|
||||
{
|
||||
new SqlParameter("@old_email_address", originalEmailAddress),
|
||||
new SqlParameter("@email_address", bouncedEmail.EmailAddress),
|
||||
new SqlParameter("@spam", bouncedEmail.Spam),
|
||||
new SqlParameter("@unsubscribe", bouncedEmail.Unsubscribe),
|
||||
new SqlParameter("@entered_by_admin", bouncedEmail.EnteredByAdmin),
|
||||
pmSuccess
|
||||
};
|
||||
|
||||
await conn.ExecuteAsync("mem_save_bounced_email", parameters, commandType: CommandType.StoredProcedure);
|
||||
|
||||
bool success = parameters.Get<bool>("@success");
|
||||
DataAccess dataAccess = GetDataAccess();
|
||||
await dataAccess.CallActionProcedureAsync(parameters, "mem_save_bounced_email");
|
||||
bool success = pmSuccess.Value != null && (bool)pmSuccess.Value;
|
||||
|
||||
return success;
|
||||
}
|
||||
@ -106,15 +125,20 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
{
|
||||
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();
|
||||
parameters.Add("@email_address", emailAddress, DbType.String);
|
||||
parameters.Add("@success", dbType: DbType.Boolean, direction: ParameterDirection.Output);
|
||||
List<SqlParameter> parameters = new List<SqlParameter>
|
||||
{
|
||||
new SqlParameter("@email_address", emailAddress),
|
||||
pmSuccess
|
||||
};
|
||||
|
||||
await conn.ExecuteAsync("mem_delete_bounced_email", parameters, commandType: CommandType.StoredProcedure);
|
||||
|
||||
bool success = parameters.Get<bool>("@success");
|
||||
DataAccess dataAccess = GetDataAccess();
|
||||
await dataAccess.CallActionProcedureAsync(parameters, "mem_delete_bounced_email");
|
||||
bool success = pmSuccess.Value != null && (bool)pmSuccess.Value;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
using Dapper;
|
||||
using Dapper.FluentMap;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Surge365.Core.Interfaces;
|
||||
using Surge365.Core.Mapping;
|
||||
using Surge365.Core.Services;
|
||||
using Surge365.MassEmailReact.Application.Interfaces;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Runtime.Intrinsics.Arm;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
@ -16,22 +16,19 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
public class EmailDomainRepository : IEmailDomainRepository
|
||||
{
|
||||
private readonly IConfiguration _config;
|
||||
private const string _connectionStringName = "MassEmail.ConnectionString";
|
||||
private string? ConnectionString
|
||||
{
|
||||
get
|
||||
{
|
||||
return _config.GetConnectionString(_connectionStringName);
|
||||
}
|
||||
}
|
||||
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 EmailDomainRepository(IConfiguration config)
|
||||
public EmailDomainRepository(IConfiguration config, DataAccessFactory dataAccessFactory, IQueryMapper queryMapper)
|
||||
{
|
||||
_config = config;
|
||||
_dataAccessFactory = dataAccessFactory ?? throw new ArgumentNullException(nameof(dataAccessFactory), "DataAccessFactory cannot be null.");
|
||||
_queryMapper = queryMapper;
|
||||
#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
|
||||
}
|
||||
@ -39,10 +36,18 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
public async Task<EmailDomain?> GetByIdAsync(int id, bool returnPassword = false)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(_config);
|
||||
ArgumentNullException.ThrowIfNull(_connectionStringName);
|
||||
|
||||
using SqlConnection conn = new SqlConnection(ConnectionString);
|
||||
EmailDomain? domain = (await conn.QueryAsync<EmailDomain>("mem_get_domain_by_id", new { domain_key = id }, commandType: CommandType.StoredProcedure)).FirstOrDefault();
|
||||
var parameters = new List<SqlParameter>
|
||||
{
|
||||
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)
|
||||
{
|
||||
domain.Password = "";
|
||||
@ -52,11 +57,17 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
|
||||
public async Task<List<EmailDomain>> GetAllAsync(bool activeOnly = true, bool returnPassword = false)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(_config);
|
||||
ArgumentNullException.ThrowIfNull(_connectionStringName);
|
||||
var parameters = new List<SqlParameter>
|
||||
{
|
||||
new SqlParameter("@active_only", activeOnly)
|
||||
};
|
||||
|
||||
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();
|
||||
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();
|
||||
|
||||
if(!returnPassword)
|
||||
{
|
||||
foreach (var domain in domains)
|
||||
@ -73,21 +84,33 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
if (emailDomain.Id != null && emailDomain.Id > 0)
|
||||
throw new Exception("ID must be null");
|
||||
|
||||
using SqlConnection conn = new SqlConnection(ConnectionString);
|
||||
var parameters = new DynamicParameters();
|
||||
parameters.Add("@domain_key", dbType: 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);
|
||||
SqlParameter pmDomainKey = new SqlParameter("@domain_key", SqlDbType.Int)
|
||||
{
|
||||
Direction = ParameterDirection.Output
|
||||
};
|
||||
SqlParameter pmSuccess = new SqlParameter("@success", SqlDbType.Bit)
|
||||
{
|
||||
Direction = ParameterDirection.Output
|
||||
};
|
||||
|
||||
await conn.ExecuteAsync("mem_save_domain", parameters, commandType: CommandType.StoredProcedure);
|
||||
bool success = parameters.Get<bool>("@success");
|
||||
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;
|
||||
|
||||
if (success)
|
||||
return parameters.Get<int>("@domain_key");
|
||||
return (int?)pmDomainKey.Value;
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -96,19 +119,26 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
ArgumentNullException.ThrowIfNull(emailDomain);
|
||||
ArgumentNullException.ThrowIfNull(emailDomain.Id);
|
||||
|
||||
using SqlConnection conn = new SqlConnection(ConnectionString);
|
||||
var parameters = new DynamicParameters();
|
||||
parameters.Add("@domain_key", emailDomain.Id, DbType.Int32);
|
||||
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);
|
||||
SqlParameter pmSuccess = new SqlParameter("@success", SqlDbType.Bit)
|
||||
{
|
||||
Direction = ParameterDirection.Output
|
||||
};
|
||||
|
||||
await conn.ExecuteAsync("mem_save_domain", parameters, commandType: CommandType.StoredProcedure);
|
||||
bool success = parameters.Get<bool>("@success");
|
||||
List<SqlParameter> parameters = new List<SqlParameter>
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
using Dapper;
|
||||
using Dapper.FluentMap;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Surge365.Core.Interfaces;
|
||||
using Surge365.Core.Mapping;
|
||||
using Surge365.Core.Services;
|
||||
using Surge365.MassEmailReact.Application.Interfaces;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
using System;
|
||||
@ -16,38 +17,43 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
public class MailingRepository : IMailingRepository
|
||||
{
|
||||
private readonly IConfiguration _config;
|
||||
private const string _connectionStringName = "MassEmail.ConnectionString";
|
||||
private string? ConnectionString => _config.GetConnectionString(_connectionStringName);
|
||||
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 MailingRepository(IConfiguration config)
|
||||
public MailingRepository(IConfiguration config, DataAccessFactory dataAccessFactory, IQueryMapper queryMapper)
|
||||
{
|
||||
_config = config;
|
||||
_dataAccessFactory = dataAccessFactory ?? throw new ArgumentNullException(nameof(dataAccessFactory), "DataAccessFactory cannot be null.");
|
||||
_queryMapper = queryMapper;
|
||||
#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
|
||||
}
|
||||
|
||||
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(
|
||||
"mem_get_blast_by_id",
|
||||
new { blast_key = id },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
|
||||
var mailing = await multi.ReadSingleOrDefaultAsync<Mailing>();
|
||||
// Handle multiple result sets
|
||||
var mailings = await _queryMapper.MapAsync<Mailing>(dataSet);
|
||||
var mailing = mailings.FirstOrDefault();
|
||||
if (mailing == null) return null;
|
||||
|
||||
var template = await multi.ReadSingleOrDefaultAsync<MailingTemplate>();
|
||||
if (mailing != null)
|
||||
var templates = await _queryMapper.MapAsync<MailingTemplate>(dataSet);
|
||||
var template = templates.FirstOrDefault();
|
||||
if (mailing != null && template != null)
|
||||
mailing.Template = template;
|
||||
|
||||
return mailing;
|
||||
@ -55,24 +61,23 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
|
||||
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(
|
||||
"mem_get_blast_all",
|
||||
new { active_only = activeOnly },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
var templates = await _queryMapper.MapAsync<MailingTemplate>(dataSet);
|
||||
var templateList = templates.ToList();
|
||||
|
||||
var mailings = (await multi.ReadAsync<Mailing>()).ToList();
|
||||
if (!mailings.Any()) return mailings;
|
||||
|
||||
var templates = (await multi.ReadAsync<MailingTemplate>()).ToList();
|
||||
|
||||
var mailingDictionary = mailings.ToDictionary(t => t.Id!.Value);
|
||||
foreach (var template in templates)
|
||||
var mailingDictionary = mailingList.ToDictionary(t => t.Id!.Value);
|
||||
foreach (var template in templateList)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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);
|
||||
await conn.OpenAsync();
|
||||
DataAccess dataAccess = GetDataAccess();
|
||||
var dataSet = await dataAccess.CallRetrievalProcedureAsync(parameters, "mem_get_blast_by_status");
|
||||
|
||||
using var multi = await conn.QueryMultipleAsync(
|
||||
"mem_get_blast_by_status",
|
||||
new { blast_status_codes = codes, start_date = startDate, end_date = endDate },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
var mailings = await _queryMapper.MapAsync<Mailing>(dataSet);
|
||||
var mailingList = mailings.ToList();
|
||||
if (!mailingList.Any()) return mailingList;
|
||||
|
||||
var mailings = (await multi.ReadAsync<Mailing>()).ToList();
|
||||
if (!mailings.Any()) return mailings;
|
||||
var templates = await _queryMapper.MapAsync<MailingTemplate>(dataSet);
|
||||
var templateList = templates.ToList();
|
||||
|
||||
var templates = (await multi.ReadAsync<MailingTemplate>()).ToList();
|
||||
|
||||
var mailingDictionary = mailings.ToDictionary(t => t.Id!.Value);
|
||||
foreach (var template in templates)
|
||||
var mailingDictionary = mailingList.ToDictionary(t => t.Id!.Value);
|
||||
foreach (var template in templateList)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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);
|
||||
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();
|
||||
DataAccess dataAccess = GetDataAccess();
|
||||
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)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(ConnectionString);
|
||||
var parameters = new List<SqlParameter>
|
||||
{
|
||||
new SqlParameter("@blast_key", id)
|
||||
};
|
||||
|
||||
using SqlConnection conn = new SqlConnection(ConnectionString);
|
||||
return (await conn.QueryAsync<MailingStatistic>("mem_get_blast_statistic_by_blast", new { blast_key = id }, commandType: CommandType.StoredProcedure)).FirstOrDefault();
|
||||
DataAccess dataAccess = GetDataAccess();
|
||||
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)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(ConnectionString);
|
||||
var parameters = new List<SqlParameter>
|
||||
{
|
||||
new SqlParameter("@blast_key", id)
|
||||
};
|
||||
|
||||
using SqlConnection conn = new SqlConnection(ConnectionString);
|
||||
return (await conn.QueryAsync<MailingEmail>("mem_get_blast_email_by_blast_id", new { blast_key = id }, commandType: CommandType.StoredProcedure)).ToList();
|
||||
DataAccess dataAccess = GetDataAccess();
|
||||
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)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(ConnectionString);
|
||||
var parameters = new List<SqlParameter>
|
||||
{
|
||||
new SqlParameter("@blast_key", id)
|
||||
};
|
||||
|
||||
using SqlConnection conn = new SqlConnection(ConnectionString);
|
||||
return (await conn.QueryAsync<MailingTemplate>("mem_get_blast_template_by_blast_id", new { blast_key = id }, commandType: CommandType.StoredProcedure)).FirstOrDefault();
|
||||
DataAccess dataAccess = GetDataAccess();
|
||||
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)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(ConnectionString);
|
||||
var parameters = new List<SqlParameter>
|
||||
{
|
||||
new SqlParameter("@blast_key", id)
|
||||
};
|
||||
|
||||
using SqlConnection conn = new SqlConnection(ConnectionString);
|
||||
return (await conn.QueryAsync<MailingTarget>("mem_get_blast_target_by_blast_id", new { blast_key = id }, commandType: CommandType.StoredProcedure)).FirstOrDefault();
|
||||
DataAccess dataAccess = GetDataAccess();
|
||||
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)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(ConnectionString);
|
||||
using var conn = new SqlConnection(ConnectionString);
|
||||
SqlParameter pmAvailable = new SqlParameter("@available", SqlDbType.Bit)
|
||||
{
|
||||
Direction = ParameterDirection.Output
|
||||
};
|
||||
|
||||
var parameters = new DynamicParameters();
|
||||
parameters.Add("@blast_key", id, DbType.Int32);
|
||||
parameters.Add("@blast_name", name, DbType.String);
|
||||
parameters.Add("@available", dbType: DbType.Boolean, direction: ParameterDirection.Output);
|
||||
var parameters = new List<SqlParameter>
|
||||
{
|
||||
new SqlParameter("@blast_key", id ?? (object)DBNull.Value),
|
||||
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)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(ConnectionString);
|
||||
using var conn = new SqlConnection(ConnectionString);
|
||||
SqlParameter pmNextBlastName = new SqlParameter("@next_blast_name", SqlDbType.NVarChar, -1)
|
||||
{
|
||||
Direction = ParameterDirection.Output
|
||||
};
|
||||
|
||||
var parameters = new DynamicParameters();
|
||||
parameters.Add("@blast_key", id, DbType.Int32);
|
||||
parameters.Add("@blast_name", name, DbType.String);
|
||||
parameters.Add("@next_blast_name", dbType: DbType.String, size:-1, direction: ParameterDirection.Output);
|
||||
var parameters = new List<SqlParameter>
|
||||
{
|
||||
new SqlParameter("@blast_key", id ?? (object)DBNull.Value),
|
||||
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)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(ConnectionString);
|
||||
ArgumentNullException.ThrowIfNull(mailing);
|
||||
if (mailing.Id != null && mailing.Id > 0)
|
||||
throw new Exception("ID must be null");
|
||||
|
||||
using SqlConnection conn = new SqlConnection(ConnectionString);
|
||||
var parameters = new DynamicParameters();
|
||||
parameters.Add("@blast_key", dbType: 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);
|
||||
SqlParameter pmBlastKey = new SqlParameter("@blast_key", SqlDbType.Int)
|
||||
{
|
||||
Direction = ParameterDirection.Output
|
||||
};
|
||||
SqlParameter pmSuccess = new SqlParameter("@success", SqlDbType.Bit)
|
||||
{
|
||||
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)
|
||||
return parameters.Get<int>("@blast_key");
|
||||
return (int?)pmBlastKey.Value;
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateAsync(Mailing mailing)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(ConnectionString);
|
||||
ArgumentNullException.ThrowIfNull(mailing);
|
||||
ArgumentNullException.ThrowIfNull(mailing.Id);
|
||||
|
||||
using SqlConnection conn = new SqlConnection(ConnectionString);
|
||||
var parameters = new DynamicParameters();
|
||||
parameters.Add("@blast_key", mailing.Id, DbType.Int32);
|
||||
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);
|
||||
SqlParameter pmSuccess = new SqlParameter("@success", SqlDbType.Bit)
|
||||
{
|
||||
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)
|
||||
{
|
||||
SqlParameter pmSuccess = new SqlParameter("@success", SqlDbType.Bit)
|
||||
{
|
||||
Direction = ParameterDirection.Output
|
||||
};
|
||||
|
||||
using SqlConnection conn = new SqlConnection(ConnectionString);
|
||||
var parameters = new DynamicParameters();
|
||||
parameters.Add("@blast_key", id, DbType.Int32);
|
||||
parameters.Add("@success", dbType: DbType.Boolean, direction: ParameterDirection.Output);
|
||||
var parameters = new List<SqlParameter>
|
||||
{
|
||||
new SqlParameter("@blast_key", id),
|
||||
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 Dapper.FluentMap;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Surge365.Core.Interfaces;
|
||||
using Surge365.Core.Mapping;
|
||||
using Surge365.Core.Services;
|
||||
using Surge365.MassEmailReact.Application.Interfaces;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
using Surge365.MassEmailReact.Domain.Enums;
|
||||
using Surge365.MassEmailReact.Domain.Enums.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
@ -18,32 +17,37 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
public class ServerRepository : IServerRepository
|
||||
{
|
||||
private IConfiguration _config;
|
||||
private const string _connectionStringName = "MassEmail.ConnectionString";
|
||||
private string? ConnectionString
|
||||
{
|
||||
get
|
||||
{
|
||||
return _config.GetConnectionString(_connectionStringName);
|
||||
}
|
||||
}
|
||||
public ServerRepository(IConfiguration config)
|
||||
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 ServerRepository(IConfiguration config, DataAccessFactory dataAccessFactory, IQueryMapper queryMapper)
|
||||
{
|
||||
_config = config;
|
||||
_dataAccessFactory = dataAccessFactory ?? throw new ArgumentNullException(nameof(dataAccessFactory), "DataAccessFactory cannot be null.");
|
||||
_queryMapper = queryMapper;
|
||||
#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
|
||||
}
|
||||
public async Task<Server?> GetByIdAsync(int serverKey, bool returnPassword = false)
|
||||
{
|
||||
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)
|
||||
{
|
||||
server.Password = "";
|
||||
@ -52,18 +56,15 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
}
|
||||
public async Task<List<Server>> GetAllAsync(bool activeOnly = true, bool returnPassword = false)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(_config);
|
||||
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)
|
||||
var parameters = new List<SqlParameter>
|
||||
{
|
||||
foreach (Server server in servers)
|
||||
server.Password = "";
|
||||
}
|
||||
return servers;
|
||||
new SqlParameter("@active_only", activeOnly)
|
||||
};
|
||||
DataAccess dataAccess = GetDataAccess();
|
||||
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)
|
||||
@ -72,26 +73,30 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
if (server.Id != null && server.Id > 0)
|
||||
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();
|
||||
parameters.Add("@server_key", dbType: DbType.Int32, direction: ParameterDirection.Output);
|
||||
parameters.Add("@name", server.Name, DbType.String);
|
||||
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);
|
||||
DataAccess dataAccess = GetDataAccess();
|
||||
await dataAccess.CallActionProcedureAsync(parameters, "mem_save_server");
|
||||
bool success = pmSuccess.Value != null && (bool)pmSuccess.Value;
|
||||
|
||||
// Output parameter
|
||||
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");
|
||||
if (success) return (int?)pmServerKey.Value;
|
||||
|
||||
return null;
|
||||
}
|
||||
@ -99,31 +104,27 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(server);
|
||||
ArgumentNullException.ThrowIfNull(server.Id);
|
||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
||||
|
||||
var parameters = new DynamicParameters();
|
||||
parameters.Add("@server_key", server.Id, DbType.Int32);
|
||||
parameters.Add("@name", server.Name, DbType.String);
|
||||
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);
|
||||
SqlParameter pmSuccess = new SqlParameter("@success", SqlDbType.Bit)
|
||||
{
|
||||
Direction = ParameterDirection.Output
|
||||
};
|
||||
|
||||
// Output parameter
|
||||
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");
|
||||
List<SqlParameter> parameters = new List<SqlParameter>
|
||||
{
|
||||
new SqlParameter("@server_key", server.Id),
|
||||
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
|
||||
};
|
||||
|
||||
DataAccess dataAccess = GetDataAccess();
|
||||
await dataAccess.CallActionProcedureAsync(parameters, "mem_save_server");
|
||||
bool success = pmSuccess.Value != null && (bool)pmSuccess.Value;
|
||||
return success;
|
||||
}
|
||||
|
||||
//public void Add(Server server)
|
||||
//{
|
||||
// Servers.Add(server);
|
||||
//}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
using Dapper;
|
||||
using Dapper.FluentMap;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Data.SqlClient;
|
||||
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.Interfaces;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
using Surge365.MassEmailReact.Domain.Enums;
|
||||
using Surge365.MassEmailReact.Domain.Enums.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
@ -21,70 +20,69 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
public class TargetRepository : ITargetRepository
|
||||
{
|
||||
private IConfiguration _config;
|
||||
private const string _connectionStringName = "MassEmail.ConnectionString";
|
||||
private string? ConnectionString
|
||||
{
|
||||
get
|
||||
{
|
||||
return _config.GetConnectionString(_connectionStringName);
|
||||
}
|
||||
}
|
||||
public TargetRepository(IConfiguration config)
|
||||
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 TargetRepository(IConfiguration config, DataAccessFactory dataAccessFactory, IQueryMapper queryMapper)
|
||||
{
|
||||
_config = config;
|
||||
_dataAccessFactory = dataAccessFactory ?? throw new ArgumentNullException(nameof(dataAccessFactory), "DataAccessFactory cannot be null.");
|
||||
_queryMapper = queryMapper;
|
||||
#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
|
||||
}
|
||||
|
||||
public async Task<Target?> GetByIdAsync(int targetKey)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(_config);
|
||||
ArgumentNullException.ThrowIfNull(_connectionStringName);
|
||||
|
||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
||||
await conn.OpenAsync();
|
||||
var parameters = new List<SqlParameter>
|
||||
{
|
||||
new SqlParameter("@target_key", targetKey)
|
||||
};
|
||||
|
||||
using var multi = await conn.QueryMultipleAsync(
|
||||
"mem_get_target_by_id",
|
||||
new { target_key = targetKey },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
DataAccess dataAccess = GetDataAccess();
|
||||
var dataSet = await dataAccess.CallRetrievalProcedureAsync(parameters, "mem_get_target_by_id");
|
||||
|
||||
// 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;
|
||||
|
||||
// Read the second result set (TargetColumns)
|
||||
var columns = await multi.ReadAsync<TargetColumn>();
|
||||
var columns = await _queryMapper.MapAsync<TargetColumn>(dataSet);
|
||||
target.Columns = columns.ToList();
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
public async Task<List<Target>> GetAllAsync(bool activeOnly = true)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(_config);
|
||||
ArgumentNullException.ThrowIfNull(_connectionStringName);
|
||||
var parameters = new List<SqlParameter>
|
||||
{
|
||||
new SqlParameter("@active_only", activeOnly)
|
||||
};
|
||||
|
||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
||||
await conn.OpenAsync();
|
||||
|
||||
using var multi = await conn.QueryMultipleAsync(
|
||||
"mem_get_target_all",
|
||||
new { active_only = activeOnly },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
DataAccess dataAccess = GetDataAccess();
|
||||
var dataSet = await dataAccess.CallRetrievalProcedureAsync(parameters, "mem_get_target_all");
|
||||
|
||||
// Read the first result set (Targets)
|
||||
var targets = (await multi.ReadAsync<Target>()).ToList();
|
||||
if (!targets.Any()) return targets;
|
||||
var targets = await _queryMapper.MapAsync<Target>(dataSet);
|
||||
var targetList = targets.ToList();
|
||||
if (!targetList.Any()) return targetList;
|
||||
|
||||
// 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
|
||||
var targetDictionary = targets.ToDictionary(t => t.Id!.Value);
|
||||
foreach (var column in columns)
|
||||
var targetDictionary = targetList.ToDictionary(t => t.Id!.Value);
|
||||
foreach (var column in columnList)
|
||||
{
|
||||
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)
|
||||
@ -101,61 +99,70 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
if (target.Id != null && target.Id > 0)
|
||||
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();
|
||||
parameters.Add("@target_key", dbType: DbType.Int32, direction: ParameterDirection.Output);
|
||||
parameters.Add("@server_key", target.ServerId, DbType.Int32);
|
||||
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);
|
||||
List<SqlParameter> parameters = new List<SqlParameter>
|
||||
{
|
||||
pmTargetKey,
|
||||
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
|
||||
};
|
||||
|
||||
// Output parameter
|
||||
parameters.Add("@success", dbType: DbType.Boolean, direction: ParameterDirection.Output);
|
||||
|
||||
await conn.ExecuteAsync("mem_save_target", parameters, commandType: CommandType.StoredProcedure);
|
||||
|
||||
// Retrieve the output parameter value
|
||||
bool success = parameters.Get<bool>("@success");
|
||||
DataAccess dataAccess = GetDataAccess();
|
||||
await dataAccess.CallActionProcedureAsync(parameters, "mem_save_target");
|
||||
|
||||
bool success = pmSuccess.Value != null && (bool)pmSuccess.Value;
|
||||
if (success)
|
||||
return parameters.Get<int>("@target_key");
|
||||
return (int?)pmTargetKey.Value;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateAsync(Target target)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(target);
|
||||
ArgumentNullException.ThrowIfNull(target.Id);
|
||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
||||
|
||||
var parameters = new DynamicParameters();
|
||||
parameters.Add("@target_key", target.Id, DbType.Int32);
|
||||
parameters.Add("@server_key", target.ServerId, DbType.Int32);
|
||||
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);
|
||||
SqlParameter pmSuccess = new SqlParameter("@success", SqlDbType.Bit)
|
||||
{
|
||||
Direction = ParameterDirection.Output
|
||||
};
|
||||
|
||||
// Output parameter
|
||||
parameters.Add("@success", dbType: DbType.Boolean, direction: ParameterDirection.Output);
|
||||
List<SqlParameter> parameters = new List<SqlParameter>
|
||||
{
|
||||
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);
|
||||
|
||||
// Retrieve the output parameter value
|
||||
bool success = parameters.Get<bool>("@success");
|
||||
DataAccess dataAccess = GetDataAccess();
|
||||
await dataAccess.CallActionProcedureAsync(parameters, "mem_save_target");
|
||||
|
||||
bool success = pmSuccess.Value != null && (bool)pmSuccess.Value;
|
||||
return success;
|
||||
}
|
||||
|
||||
public async Task<TargetSample?> TestTargetAsync(
|
||||
string serverName,
|
||||
short port,
|
||||
@ -179,6 +186,7 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
|
||||
// Clean up server name
|
||||
serverName = serverName.Replace("110494-db", "www.surge365.com");
|
||||
|
||||
// Get configuration
|
||||
string sql = _config["TestTargetSql"] ?? "";
|
||||
string connectionStringTemplate = _config["ConnectionStringTemplate"] ?? "";
|
||||
@ -201,20 +209,42 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
using var connection = new SqlConnection(connectionString);
|
||||
await connection.OpenAsync();
|
||||
|
||||
// Assuming the SQL returns three result sets: columns, sample data, and row count
|
||||
using var multi = await connection.QueryMultipleAsync(sql, commandTimeout: 300);
|
||||
// Execute the SQL and get multiple result sets
|
||||
using var command = new SqlCommand(sql, connection);
|
||||
command.CommandTimeout = 300;
|
||||
|
||||
using var adapter = new SqlDataAdapter(command);
|
||||
var dataSet = new DataSet();
|
||||
adapter.Fill(dataSet);
|
||||
|
||||
// Read column definitions
|
||||
var columnEntities = await multi.ReadAsync<dynamic>();
|
||||
if (!columnEntities.Any())
|
||||
if (dataSet.Tables.Count < 3)
|
||||
return null;
|
||||
|
||||
// Read sample data
|
||||
var sampleRows = (await multi.ReadAsync<dynamic>()).ToList();
|
||||
// Read column definitions (first table)
|
||||
var columnTable = dataSet.Tables[0];
|
||||
if (columnTable.Rows.Count == 0)
|
||||
return null;
|
||||
|
||||
// Read row count
|
||||
var rowCountResult = await multi.ReadSingleAsync<dynamic>();
|
||||
int rowCount = Convert.ToInt32(rowCountResult.row_count);
|
||||
// Read sample data (second table)
|
||||
var sampleTable = dataSet.Tables[1];
|
||||
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
|
||||
var targetSample = new TargetSample();
|
||||
@ -224,10 +254,10 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
bool bounceFound = false;
|
||||
bool unsubFound = false;
|
||||
|
||||
foreach (var col in columnEntities)
|
||||
foreach (DataRow col in columnTable.Rows)
|
||||
{
|
||||
string name = col.name;
|
||||
string dataType = col.data_type;
|
||||
string name = col["name"].ToString();
|
||||
string dataType = col["data_type"].ToString();
|
||||
|
||||
string typeCode = TargetColumnType.General;
|
||||
if (!emailFound && name.ToUpper().Contains("EMAIL"))
|
||||
@ -259,13 +289,8 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
};
|
||||
}
|
||||
|
||||
// Convert sample rows to dictionary format
|
||||
foreach (var row in sampleRows)
|
||||
{
|
||||
var dict = ((IDictionary<string, object>)row)
|
||||
.ToDictionary(k => k.Key, v => v.Value?.ToString() ?? "");
|
||||
targetSample.Rows.Add(dict);
|
||||
}
|
||||
// Add sample rows
|
||||
targetSample.Rows.AddRange(sampleRows);
|
||||
|
||||
return targetSample;
|
||||
}
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
using Dapper;
|
||||
using Dapper.FluentMap;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Surge365.Core.Interfaces;
|
||||
using Surge365.Core.Mapping;
|
||||
using Surge365.Core.Services;
|
||||
using Surge365.MassEmailReact.Application.Interfaces;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
using System;
|
||||
@ -15,22 +16,19 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
public class TemplateRepository : ITemplateRepository
|
||||
{
|
||||
private IConfiguration _config;
|
||||
private const string _connectionStringName = "MassEmail.ConnectionString";
|
||||
private string? ConnectionString
|
||||
{
|
||||
get
|
||||
{
|
||||
return _config.GetConnectionString(_connectionStringName);
|
||||
}
|
||||
}
|
||||
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 TemplateRepository(IConfiguration config)
|
||||
public TemplateRepository(IConfiguration config, DataAccessFactory dataAccessFactory, IQueryMapper queryMapper)
|
||||
{
|
||||
_config = config;
|
||||
_dataAccessFactory = dataAccessFactory ?? throw new ArgumentNullException(nameof(dataAccessFactory), "DataAccessFactory cannot be null.");
|
||||
_queryMapper = queryMapper;
|
||||
#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
|
||||
}
|
||||
@ -38,21 +36,28 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
public async Task<Template?> GetByIdAsync(int id)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(_config);
|
||||
ArgumentNullException.ThrowIfNull(_connectionStringName);
|
||||
|
||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
||||
return (await conn.QueryAsync<Template>("mem_get_template_by_id", new { template_key = id },
|
||||
commandType: CommandType.StoredProcedure)).FirstOrDefault();
|
||||
var parameters = new List<SqlParameter>
|
||||
{
|
||||
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)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(_config);
|
||||
ArgumentNullException.ThrowIfNull(_connectionStringName);
|
||||
var parameters = new List<SqlParameter>();
|
||||
|
||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
||||
return (await conn.QueryAsync<Template>("mem_get_template_all", new { },
|
||||
commandType: CommandType.StoredProcedure)).ToList();
|
||||
DataAccess dataAccess = GetDataAccess();
|
||||
var dataSet = await dataAccess.CallRetrievalProcedureAsync(parameters, "mem_get_template_all");
|
||||
|
||||
var results = await _queryMapper.MapAsync<Template>(dataSet);
|
||||
return results.ToList();
|
||||
}
|
||||
|
||||
public async Task<int?> CreateAsync(Template template)
|
||||
@ -61,27 +66,38 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
if (template.Id != null && template.Id > 0)
|
||||
throw new Exception("ID must be null");
|
||||
|
||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
||||
var parameters = new DynamicParameters();
|
||||
parameters.Add("@template_key", dbType: 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);
|
||||
SqlParameter pmTemplateKey = new SqlParameter("@template_key", SqlDbType.Int)
|
||||
{
|
||||
Direction = ParameterDirection.Output
|
||||
};
|
||||
SqlParameter pmSuccess = new SqlParameter("@success", SqlDbType.Bit)
|
||||
{
|
||||
Direction = ParameterDirection.Output
|
||||
};
|
||||
|
||||
await conn.ExecuteAsync("mem_save_template", parameters, commandType: CommandType.StoredProcedure);
|
||||
bool success = parameters.Get<bool>("@success");
|
||||
return success ? parameters.Get<int>("@template_key") : null;
|
||||
List<SqlParameter> parameters = new List<SqlParameter>
|
||||
{
|
||||
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)
|
||||
@ -89,26 +105,34 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
ArgumentNullException.ThrowIfNull(template);
|
||||
ArgumentNullException.ThrowIfNull(template.Id);
|
||||
|
||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
||||
var parameters = new DynamicParameters();
|
||||
parameters.Add("@template_key", template.Id, DbType.Int32);
|
||||
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);
|
||||
SqlParameter pmSuccess = new SqlParameter("@success", SqlDbType.Bit)
|
||||
{
|
||||
Direction = ParameterDirection.Output
|
||||
};
|
||||
|
||||
await conn.ExecuteAsync("mem_save_template", parameters, commandType: CommandType.StoredProcedure);
|
||||
return parameters.Get<bool>("@success");
|
||||
List<SqlParameter> parameters = new List<SqlParameter>
|
||||
{
|
||||
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 Surge365.Core.Interfaces;
|
||||
using Surge365.Core.Mapping;
|
||||
using Surge365.Core.Services;
|
||||
using Surge365.MassEmailReact.Application.Interfaces;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
using System;
|
||||
@ -14,36 +16,48 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
public class TestEmailListRepository : ITestEmailListRepository
|
||||
{
|
||||
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;
|
||||
_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)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(_config);
|
||||
ArgumentNullException.ThrowIfNull(_connectionStringName);
|
||||
|
||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
||||
return (await conn.QueryAsync<TestEmailList>(
|
||||
"mem_get_test_email_list_by_id",
|
||||
new { test_email_list_key = id },
|
||||
commandType: CommandType.StoredProcedure
|
||||
)).FirstOrDefault();
|
||||
var parameters = new List<SqlParameter>
|
||||
{
|
||||
new SqlParameter("@test_email_list_key", id)
|
||||
};
|
||||
|
||||
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()
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(_config);
|
||||
ArgumentNullException.ThrowIfNull(_connectionStringName);
|
||||
var parameters = new List<SqlParameter>();
|
||||
|
||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
||||
return (await conn.QueryAsync<TestEmailList>(
|
||||
"mem_get_test_email_list_all",
|
||||
commandType: CommandType.StoredProcedure
|
||||
)).ToList();
|
||||
DataAccess dataAccess = GetDataAccess();
|
||||
var dataSet = await dataAccess.CallRetrievalProcedureAsync(parameters, "mem_get_test_email_list_all");
|
||||
|
||||
var results = await _queryMapper.MapAsync<TestEmailList>(dataSet);
|
||||
return results.ToList();
|
||||
}
|
||||
|
||||
public async Task<int?> CreateAsync(TestEmailList testEmailList)
|
||||
@ -52,17 +66,28 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
if (testEmailList.Id != null && testEmailList.Id > 0)
|
||||
throw new Exception("ID must be null");
|
||||
|
||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
||||
var parameters = new DynamicParameters();
|
||||
parameters.Add("@test_email_list_key", dbType: 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);
|
||||
SqlParameter pmTestEmailListKey = new SqlParameter("@test_email_list_key", SqlDbType.Int)
|
||||
{
|
||||
Direction = ParameterDirection.Output
|
||||
};
|
||||
SqlParameter pmSuccess = new SqlParameter("@success", SqlDbType.Bit)
|
||||
{
|
||||
Direction = ParameterDirection.Output
|
||||
};
|
||||
|
||||
await conn.ExecuteAsync("mem_save_test_email_list", parameters, commandType: CommandType.StoredProcedure);
|
||||
bool success = parameters.Get<bool>("@success");
|
||||
List<SqlParameter> parameters = new List<SqlParameter>
|
||||
{
|
||||
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)
|
||||
return parameters.Get<int>("@test_email_list_key");
|
||||
return (int?)pmTestEmailListKey.Value;
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -71,15 +96,23 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
ArgumentNullException.ThrowIfNull(testEmailList);
|
||||
ArgumentNullException.ThrowIfNull(testEmailList.Id);
|
||||
|
||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
||||
var parameters = new DynamicParameters();
|
||||
parameters.Add("@test_email_list_key", testEmailList.Id, DbType.Int32);
|
||||
parameters.Add("@name", testEmailList.Name, DbType.String);
|
||||
parameters.Add("@list", testEmailList.GetListXml(), DbType.String);
|
||||
parameters.Add("@success", dbType: DbType.Boolean, direction: ParameterDirection.Output);
|
||||
SqlParameter pmSuccess = new SqlParameter("@success", SqlDbType.Bit)
|
||||
{
|
||||
Direction = ParameterDirection.Output
|
||||
};
|
||||
|
||||
await conn.ExecuteAsync("mem_save_test_email_list", parameters, commandType: CommandType.StoredProcedure);
|
||||
return parameters.Get<bool>("@success");
|
||||
List<SqlParameter> parameters = new List<SqlParameter>
|
||||
{
|
||||
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 Dapper.FluentMap;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Surge365.Core.Interfaces;
|
||||
using Surge365.Core.Mapping;
|
||||
using Surge365.Core.Services;
|
||||
using Surge365.MassEmailReact.Application.Interfaces;
|
||||
using Surge365.MassEmailReact.Domain.Entities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
@ -14,81 +16,104 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
||||
public class UnsubscribeUrlRepository : IUnsubscribeUrlRepository
|
||||
{
|
||||
private IConfiguration _config;
|
||||
private const string _connectionStringName = "MassEmail.ConnectionString";
|
||||
private string? ConnectionString
|
||||
{
|
||||
get
|
||||
{
|
||||
return _config.GetConnectionString(_connectionStringName);
|
||||
}
|
||||
}
|
||||
public UnsubscribeUrlRepository(IConfiguration config)
|
||||
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 UnsubscribeUrlRepository(IConfiguration config, DataAccessFactory dataAccessFactory, IQueryMapper queryMapper)
|
||||
{
|
||||
_config = config;
|
||||
_dataAccessFactory = dataAccessFactory ?? throw new ArgumentNullException(nameof(dataAccessFactory), "DataAccessFactory cannot be null.");
|
||||
_queryMapper = queryMapper;
|
||||
#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
|
||||
}
|
||||
|
||||
public async Task<UnsubscribeUrl?> GetByIdAsync(int unsubscribeUrlKey)
|
||||
{
|
||||
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)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(_config);
|
||||
ArgumentNullException.ThrowIfNull(_connectionStringName);
|
||||
var parameters = new List<SqlParameter>();
|
||||
|
||||
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)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(unsubscribeUrl);
|
||||
if (unsubscribeUrl.Id != null && unsubscribeUrl.Id > 0)
|
||||
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();
|
||||
parameters.Add("@unsubscribe_url_key", dbType: DbType.Int32, direction: ParameterDirection.Output);
|
||||
parameters.Add("@name", unsubscribeUrl.Name, DbType.String);
|
||||
parameters.Add("@url", unsubscribeUrl.Url, DbType.String);
|
||||
parameters.Add("@success", dbType: DbType.Boolean, direction: ParameterDirection.Output);
|
||||
List<SqlParameter> parameters = new List<SqlParameter>
|
||||
{
|
||||
pmUnsubscribeUrlKey,
|
||||
new SqlParameter("@name", unsubscribeUrl.Name),
|
||||
new SqlParameter("@url", unsubscribeUrl.Url),
|
||||
pmSuccess
|
||||
};
|
||||
|
||||
await conn.ExecuteAsync("mem_save_unsubscribe_url", parameters, commandType: CommandType.StoredProcedure);
|
||||
|
||||
bool success = parameters.Get<bool>("@success");
|
||||
DataAccess dataAccess = GetDataAccess();
|
||||
await dataAccess.CallActionProcedureAsync(parameters, "mem_save_unsubscribe_url");
|
||||
bool success = pmSuccess.Value != null && (bool)pmSuccess.Value;
|
||||
|
||||
if (success)
|
||||
return parameters.Get<int>("@unsubscribe_url_key");
|
||||
return (int?)pmUnsubscribeUrlKey.Value;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateAsync(UnsubscribeUrl unsubscribeUrl)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(unsubscribeUrl);
|
||||
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();
|
||||
parameters.Add("@unsubscribe_url_key", unsubscribeUrl.Id, DbType.Int32);
|
||||
parameters.Add("@name", unsubscribeUrl.Name, DbType.String);
|
||||
parameters.Add("@url", unsubscribeUrl.Url, DbType.String);
|
||||
parameters.Add("@success", dbType: DbType.Boolean, direction: ParameterDirection.Output);
|
||||
List<SqlParameter> parameters = new List<SqlParameter>
|
||||
{
|
||||
new SqlParameter("@unsubscribe_url_key", unsubscribeUrl.Id),
|
||||
new SqlParameter("@name", unsubscribeUrl.Name),
|
||||
new SqlParameter("@url", unsubscribeUrl.Url),
|
||||
pmSuccess
|
||||
};
|
||||
|
||||
await conn.ExecuteAsync("mem_save_unsubscribe_url", parameters, commandType: CommandType.StoredProcedure);
|
||||
|
||||
bool success = parameters.Get<bool>("@success");
|
||||
DataAccess dataAccess = GetDataAccess();
|
||||
await dataAccess.CallActionProcedureAsync(parameters, "mem_save_unsubscribe_url");
|
||||
bool success = pmSuccess.Value != null && (bool)pmSuccess.Value;
|
||||
|
||||
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 SendGrid;
|
||||
using SendGrid.Helpers.Mail;
|
||||
using System.Net.Http;
|
||||
|
||||
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.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
@ -7,23 +7,14 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\Core\Surge365.Core\Surge365.Core.csproj" />
|
||||
<ProjectReference Include="..\Surge365.MassEmailReact.Application\Surge365.MassEmailReact.Application.csproj" />
|
||||
<ProjectReference Include="..\Surge365.MassEmailReact.Domain\Surge365.MassEmailReact.Domain.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.4.0" />
|
||||
<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="Microsoft.Extensions.Http" Version="9.0.6" />
|
||||
<PackageReference Include="SendGrid" Version="9.29.3" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.8.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</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 navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const { accessToken, setAuth, isLoading } = useAuth();
|
||||
const { accessToken, refreshToken, isLoading, refreshAuthToken } = useAuth();
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoading) return; // Wait for AuthProvider to finish
|
||||
@ -14,30 +14,18 @@ const AuthCheck: React.FC = () => {
|
||||
if (currentPath.toLowerCase() === "/login") return;
|
||||
|
||||
const tryRefreshToken = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/authentication/refreshtoken', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
});
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setAuth(data.accessToken);
|
||||
} else {
|
||||
setAuth(null);
|
||||
navigate('/login');
|
||||
}
|
||||
} catch {
|
||||
setAuth(null);
|
||||
const success = await refreshAuthToken();
|
||||
if (!success) {
|
||||
navigate('/login');
|
||||
}
|
||||
};
|
||||
|
||||
if (!accessToken || utils.isTokenExpired(accessToken)) {
|
||||
if (refreshToken && (!accessToken || utils.isTokenExpired(accessToken))) {
|
||||
tryRefreshToken();
|
||||
}
|
||||
}, [navigate, location.pathname, accessToken, setAuth, isLoading]);
|
||||
}, [navigate, accessToken, refreshToken, location.pathname, isLoading, refreshAuthToken]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default AuthCheck;
|
||||
export default AuthCheck;
|
||||
@ -4,46 +4,71 @@ import { Box, CircularProgress, Typography } from '@mui/material';
|
||||
|
||||
interface AuthContextType {
|
||||
accessToken: string | null;
|
||||
refreshToken: string | null;
|
||||
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
|
||||
refreshToken: () => Promise<boolean>;
|
||||
refreshAuthToken: () => Promise<boolean>;
|
||||
syncAccessToken: () => Promise<void>;
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthContextType>({
|
||||
accessToken: null,
|
||||
refreshToken: null,
|
||||
userRoles: [],
|
||||
setAuth: () => { },
|
||||
isLoading: true, // Default to loading
|
||||
refreshToken: async () => false,
|
||||
refreshAuthToken: async () => false,
|
||||
syncAccessToken: async () => { },
|
||||
});
|
||||
|
||||
export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
||||
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 [isLoading, setIsLoading] = useState(true); // Track loading
|
||||
|
||||
const setAuth = (token: string | null) => {
|
||||
if (token) {
|
||||
localStorage.setItem('accessToken', token);
|
||||
setAccessToken(token);
|
||||
setUserRoles(utils.getUserRoles(token));
|
||||
const setAuth = (accessToken: string | null, refreshToken?: string | null) => {
|
||||
if (accessToken) {
|
||||
localStorage.setItem('accessToken', accessToken);
|
||||
setAccessToken(accessToken);
|
||||
setUserRoles(utils.getUserRoles(accessToken));
|
||||
|
||||
// Handle refreshToken if provided
|
||||
if (refreshToken !== undefined && refreshToken !== null) {
|
||||
localStorage.setItem('refreshToken', refreshToken);
|
||||
setRefreshToken(refreshToken);
|
||||
}
|
||||
} else {
|
||||
localStorage.removeItem('accessToken');
|
||||
localStorage.removeItem('refreshToken');
|
||||
setAccessToken(null);
|
||||
setRefreshToken(null);
|
||||
setUserRoles([]);
|
||||
}
|
||||
};
|
||||
|
||||
const refreshToken = async (): Promise<boolean> => {
|
||||
const refreshAuthToken = async (): Promise<boolean> => {
|
||||
try {
|
||||
const storedRefreshToken = localStorage.getItem('refreshToken');
|
||||
if (!storedRefreshToken) {
|
||||
setAuth(null);
|
||||
return false;
|
||||
}
|
||||
|
||||
const response = await fetch('/api/authentication/refreshtoken', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ refreshToken: storedRefreshToken }),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setAuth(data.accessToken);
|
||||
setAuth(data.accessToken, data.refreshToken);
|
||||
return true;
|
||||
} else {
|
||||
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
|
||||
useEffect(() => {
|
||||
const initializeAuth = async () => {
|
||||
const storedToken = localStorage.getItem('accessToken');
|
||||
if (storedToken && !utils.isTokenExpired(storedToken)) {
|
||||
setAccessToken(storedToken);
|
||||
setUserRoles(utils.getUserRoles(storedToken));
|
||||
const storedAccessToken = localStorage.getItem('accessToken');
|
||||
const storedRefreshToken = localStorage.getItem('refreshToken');
|
||||
|
||||
if (storedAccessToken && !utils.isTokenExpired(storedAccessToken)) {
|
||||
setAccessToken(storedAccessToken);
|
||||
setRefreshToken(storedRefreshToken);
|
||||
setUserRoles(utils.getUserRoles(storedAccessToken));
|
||||
setIsLoading(false);
|
||||
} else {
|
||||
try {
|
||||
const response = await fetch('/api/authentication/refreshtoken', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
});
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setAuth(data.accessToken);
|
||||
if (storedRefreshToken) {
|
||||
const response = await fetch('/api/authentication/refreshtoken', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ refreshToken: storedRefreshToken }),
|
||||
});
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setAuth(data.accessToken, data.refreshToken);
|
||||
} else {
|
||||
setAuth(null);
|
||||
}
|
||||
} else {
|
||||
setAuth(null);
|
||||
}
|
||||
@ -86,7 +135,7 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ accessToken, userRoles, setAuth, isLoading, refreshToken }}>
|
||||
<AuthContext.Provider value={{ accessToken, refreshToken, userRoles, setAuth, isLoading, refreshAuthToken, syncAccessToken }}>
|
||||
{isLoading ? (
|
||||
<Box
|
||||
sx={{
|
||||
@ -100,13 +149,13 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
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
|
||||
size={80} // Larger spinner
|
||||
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
|
||||
variant="h6"
|
||||
|
||||
@ -60,6 +60,12 @@ const DrawerHeader = styled('div')(({ theme }) => ({
|
||||
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' })<{
|
||||
open?: boolean;
|
||||
}>(({ theme, open }) => ({
|
||||
@ -103,7 +109,7 @@ const Layout = ({ children }: LayoutProps) => {
|
||||
{ text: 'Completed Mailings', icon: <CheckCircleIcon />, path: '/completedMailings' },
|
||||
{ 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 profileMenuOpen = Boolean(profileMenuAnchorEl);
|
||||
const handleOpenProfileMenu = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
@ -119,21 +125,8 @@ const Layout = ({ children }: LayoutProps) => {
|
||||
});
|
||||
const handleRefreshUser = async () => {
|
||||
handleCloseProfileMenu();
|
||||
try {
|
||||
const response = await customFetch('/api/authentication/refreshtoken', {
|
||||
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
|
||||
const success = await refreshAuthToken();
|
||||
if (!success) {
|
||||
navigate('/login');
|
||||
}
|
||||
}
|
||||
@ -156,6 +149,8 @@ const Layout = ({ children }: LayoutProps) => {
|
||||
setOpen(false);
|
||||
}
|
||||
}, [isMobile]);
|
||||
|
||||
const effectiveMode = (mode === 'system' ? getSystemTheme() : mode) || "light";
|
||||
|
||||
const handleThemeChange = (event: SelectChangeEvent) => {
|
||||
setMode(event.target.value as 'light' | 'dark');
|
||||
@ -231,7 +226,7 @@ const Layout = ({ children }: LayoutProps) => {
|
||||
<Select
|
||||
labelId="theme-select-label"
|
||||
id="theme-select"
|
||||
value={mode || 'light'}
|
||||
value={effectiveMode || 'light'}
|
||||
label="Theme"
|
||||
onChange={handleThemeChange}
|
||||
sx={{
|
||||
|
||||
@ -88,165 +88,165 @@ const App = () => {
|
||||
<ColorModeContext.Provider value={colorMode}>
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
<SetupDataProvider>
|
||||
<AuthProvider>
|
||||
<Router basename="/">
|
||||
<AuthCheck />
|
||||
<ToastContainer />
|
||||
<Routes>
|
||||
<Route path="/" element={<Navigate to="/home" replace />} />
|
||||
<Route
|
||||
path="/home"
|
||||
element={
|
||||
<PageWrapper title="Dashboard">
|
||||
<Layout>
|
||||
<Home />
|
||||
</Layout>
|
||||
</PageWrapper>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/servers"
|
||||
element={
|
||||
<PageWrapper title="Servers">
|
||||
<Layout>
|
||||
<Servers />
|
||||
</Layout>
|
||||
</PageWrapper>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/targets"
|
||||
element={
|
||||
<PageWrapper title="Targets">
|
||||
<Layout>
|
||||
<Targets />
|
||||
</Layout>
|
||||
</PageWrapper>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/testEmailLists"
|
||||
element={
|
||||
<PageWrapper title="Test Email Lists">
|
||||
<Layout>
|
||||
<TestEmailLists />
|
||||
</Layout>
|
||||
</PageWrapper>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/blockedEmails"
|
||||
element={
|
||||
<PageWrapper title="Blocked Emails">
|
||||
<Layout>
|
||||
<BouncedEmails />
|
||||
</Layout>
|
||||
</PageWrapper>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/blockedEmails"
|
||||
element={
|
||||
<PageWrapper title="Blocked Emails">
|
||||
<Layout>
|
||||
<BouncedEmails />
|
||||
</Layout>
|
||||
</PageWrapper>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/emailDomains"
|
||||
element={
|
||||
<PageWrapper title="Email Domains">
|
||||
<Layout>
|
||||
<EmailDomains />
|
||||
</Layout>
|
||||
</PageWrapper>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/unsubscribeUrls"
|
||||
element={
|
||||
<PageWrapper title="Unsubscribe Urls">
|
||||
<Layout>
|
||||
<UnsubscribeUrls />
|
||||
</Layout>
|
||||
</PageWrapper>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/templates"
|
||||
element={
|
||||
<PageWrapper title="Templates">
|
||||
<Layout>
|
||||
<Templates />
|
||||
</Layout>
|
||||
</PageWrapper>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/newMailings"
|
||||
element={
|
||||
<PageWrapper title="New Mailings">
|
||||
<Layout>
|
||||
<NewMailings />
|
||||
</Layout>
|
||||
</PageWrapper>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/scheduledMailings"
|
||||
element={
|
||||
<PageWrapper title="Scheduled Mailings">
|
||||
<Layout>
|
||||
<ScheduledMailings />
|
||||
</Layout>
|
||||
</PageWrapper>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/activeMailings"
|
||||
element={
|
||||
<PageWrapper title="Active Mailings">
|
||||
<Layout>
|
||||
<ActiveMailings />
|
||||
</Layout>
|
||||
</PageWrapper>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/cancelledMailings"
|
||||
element={
|
||||
<PageWrapper title="Cancelled Mailings">
|
||||
<Layout>
|
||||
<CancelledMailings />
|
||||
</Layout>
|
||||
</PageWrapper>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/completedMailings"
|
||||
element={
|
||||
<PageWrapper title="Completed Mailings">
|
||||
<Layout>
|
||||
<CompletedMailings />
|
||||
</Layout>
|
||||
</PageWrapper>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/login"
|
||||
element={
|
||||
<LayoutLogin>
|
||||
<Login />
|
||||
</LayoutLogin>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
<AuthCheck />
|
||||
<SetupDataProvider>
|
||||
<ToastContainer />
|
||||
<Routes>
|
||||
<Route path="/" element={<Navigate to="/home" replace />} />
|
||||
<Route
|
||||
path="/home"
|
||||
element={
|
||||
<PageWrapper title="Dashboard">
|
||||
<Layout>
|
||||
<Home />
|
||||
</Layout>
|
||||
</PageWrapper>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/servers"
|
||||
element={
|
||||
<PageWrapper title="Servers">
|
||||
<Layout>
|
||||
<Servers />
|
||||
</Layout>
|
||||
</PageWrapper>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/targets"
|
||||
element={
|
||||
<PageWrapper title="Targets">
|
||||
<Layout>
|
||||
<Targets />
|
||||
</Layout>
|
||||
</PageWrapper>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/testEmailLists"
|
||||
element={
|
||||
<PageWrapper title="Test Email Lists">
|
||||
<Layout>
|
||||
<TestEmailLists />
|
||||
</Layout>
|
||||
</PageWrapper>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/blockedEmails"
|
||||
element={
|
||||
<PageWrapper title="Blocked Emails">
|
||||
<Layout>
|
||||
<BouncedEmails />
|
||||
</Layout>
|
||||
</PageWrapper>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/blockedEmails"
|
||||
element={
|
||||
<PageWrapper title="Blocked Emails">
|
||||
<Layout>
|
||||
<BouncedEmails />
|
||||
</Layout>
|
||||
</PageWrapper>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/emailDomains"
|
||||
element={
|
||||
<PageWrapper title="Email Domains">
|
||||
<Layout>
|
||||
<EmailDomains />
|
||||
</Layout>
|
||||
</PageWrapper>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/unsubscribeUrls"
|
||||
element={
|
||||
<PageWrapper title="Unsubscribe Urls">
|
||||
<Layout>
|
||||
<UnsubscribeUrls />
|
||||
</Layout>
|
||||
</PageWrapper>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/templates"
|
||||
element={
|
||||
<PageWrapper title="Templates">
|
||||
<Layout>
|
||||
<Templates />
|
||||
</Layout>
|
||||
</PageWrapper>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/newMailings"
|
||||
element={
|
||||
<PageWrapper title="New Mailings">
|
||||
<Layout>
|
||||
<NewMailings />
|
||||
</Layout>
|
||||
</PageWrapper>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/scheduledMailings"
|
||||
element={
|
||||
<PageWrapper title="Scheduled Mailings">
|
||||
<Layout>
|
||||
<ScheduledMailings />
|
||||
</Layout>
|
||||
</PageWrapper>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/activeMailings"
|
||||
element={
|
||||
<PageWrapper title="Active Mailings">
|
||||
<Layout>
|
||||
<ActiveMailings />
|
||||
</Layout>
|
||||
</PageWrapper>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/cancelledMailings"
|
||||
element={
|
||||
<PageWrapper title="Cancelled Mailings">
|
||||
<Layout>
|
||||
<CancelledMailings />
|
||||
</Layout>
|
||||
</PageWrapper>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/completedMailings"
|
||||
element={
|
||||
<PageWrapper title="Completed Mailings">
|
||||
<Layout>
|
||||
<CompletedMailings />
|
||||
</Layout>
|
||||
</PageWrapper>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/login"
|
||||
element={
|
||||
<LayoutLogin>
|
||||
<Login />
|
||||
</LayoutLogin>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
</SetupDataProvider>
|
||||
</Router>
|
||||
</AuthProvider>
|
||||
</SetupDataProvider>
|
||||
</ThemeProvider>
|
||||
</ColorModeContext.Provider>
|
||||
);
|
||||
|
||||
@ -9,12 +9,14 @@ import {
|
||||
Alert,
|
||||
} from '@mui/material';
|
||||
import { AuthResponse, User } from '@/types/auth';
|
||||
import { useAuth } from '@/components/auth/AuthContext';
|
||||
//import ForgotPasswordModal from '@/components/modals/ForgotPasswordModal';
|
||||
|
||||
type SpinnerState = Record<string, boolean>;
|
||||
type FormErrors = Record<string, string>;
|
||||
|
||||
function Login() {
|
||||
const { accessToken, setAuth } = useAuth();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [spinners, setSpinnersState] = useState<SpinnerState>({});
|
||||
const [formErrors, setFormErrors] = useState<FormErrors>({});
|
||||
@ -84,7 +86,7 @@ function Login() {
|
||||
if (response.ok) {
|
||||
const json: AuthResponse = await response.json();
|
||||
try {
|
||||
localStorage.setItem('accessToken', json.accessToken);
|
||||
setAuth(json.accessToken, json.refreshToken);
|
||||
loggedInUser = json.user;
|
||||
|
||||
if (loggedInUser == null) {
|
||||
@ -130,18 +132,22 @@ function Login() {
|
||||
|
||||
useEffect(() => { //Reset app settings to clear out prev login
|
||||
const resetAppSettings = async () => {
|
||||
localStorage.removeItem('accessToken');
|
||||
localStorage.removeItem('session_currentUser');
|
||||
const originalAccessToken = accessToken;
|
||||
setAuth(null);
|
||||
//localStorage.removeItem('session_currentUser');
|
||||
//localStorage.clear();
|
||||
//sessionStorage.clear();
|
||||
|
||||
await fetch('/api/authentication/logout', { method: 'POST', credentials: 'include' });
|
||||
if (originalAccessToken) await fetch('/api/authentication/logout', { method: 'POST', credentials: 'include' });
|
||||
};
|
||||
|
||||
resetAppSettings();
|
||||
}, []);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []); //No dependencies, only run once on page load.
|
||||
|
||||
const finishUserLogin = async (_: User) => {
|
||||
const finishUserLogin = async (user: User) => {
|
||||
if (!user) console.log("user is undefined");
|
||||
|
||||
setIsLoading(false);
|
||||
setSpinners({ Login: false, LoginWithPasskey: false });
|
||||
|
||||
|
||||
@ -6,6 +6,8 @@ import BouncedEmail from '@/types/bouncedEmail';
|
||||
import UnsubscribeUrl from '@/types/unsubscribeUrl';
|
||||
import Template from '@/types/template';
|
||||
import EmailDomain from '@/types/emailDomain';
|
||||
import { useCustomFetchNoNavigate } from "@/utils/customFetch";
|
||||
import { useAuth } from '@/components/auth/AuthContext';
|
||||
|
||||
export type SetupData = {
|
||||
targets: Target[];
|
||||
@ -51,6 +53,9 @@ export type SetupData = {
|
||||
const SetupDataContext = createContext<SetupData | undefined>(undefined);
|
||||
|
||||
export const SetupDataProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const customFetch = useCustomFetchNoNavigate();
|
||||
const { accessToken } = useAuth();
|
||||
|
||||
const [targets, setTargets] = useState<Target[]>([]);
|
||||
const [targetsLoading, setTargetsLoading] = useState<boolean>(false);
|
||||
|
||||
@ -80,6 +85,7 @@ export const SetupDataProvider = ({ children }: { children: React.ReactNode }) =
|
||||
}
|
||||
const fetchSetupData = async () => {
|
||||
try {
|
||||
if (!accessToken) return;
|
||||
setDataLoading(true);
|
||||
const cachedData = sessionStorage.getItem("setupData");
|
||||
|
||||
@ -150,7 +156,7 @@ export const SetupDataProvider = ({ children }: { children: React.ReactNode }) =
|
||||
}
|
||||
}
|
||||
if (loadTargets) {
|
||||
const targetsResponse = await fetch("/api/targets/GetAll?activeOnly=false");
|
||||
const targetsResponse = await customFetch("/api/targets/GetAll?activeOnly=false");
|
||||
targetsData = await targetsResponse.json();
|
||||
if (targetsData) {
|
||||
setTargets(targetsData);
|
||||
@ -163,7 +169,7 @@ export const SetupDataProvider = ({ children }: { children: React.ReactNode }) =
|
||||
}
|
||||
|
||||
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();
|
||||
if (serversData) {
|
||||
setServers(serversData);
|
||||
@ -176,7 +182,7 @@ export const SetupDataProvider = ({ children }: { children: React.ReactNode }) =
|
||||
}
|
||||
|
||||
if (loadTestEmailLists) {
|
||||
const testEmailListsResponse = await fetch("/api/testEmailLists/GetAll?activeOnly=false");
|
||||
const testEmailListsResponse = await customFetch("/api/testEmailLists/GetAll?activeOnly=false");
|
||||
testEmailListsData = await testEmailListsResponse.json();
|
||||
if (testEmailListsData) {
|
||||
setTestEmailLists(testEmailListsData);
|
||||
@ -189,7 +195,7 @@ export const SetupDataProvider = ({ children }: { children: React.ReactNode }) =
|
||||
}
|
||||
|
||||
if (loadBouncedEmails) {
|
||||
const bouncedEmailsResponse = await fetch("/api/bouncedEmails/GetAll?activeOnly=false");
|
||||
const bouncedEmailsResponse = await customFetch("/api/bouncedEmails/GetAll?activeOnly=false");
|
||||
bouncedEmailsData = await bouncedEmailsResponse.json();
|
||||
if (bouncedEmailsData) {
|
||||
setBouncedEmails(bouncedEmailsData);
|
||||
@ -202,7 +208,7 @@ export const SetupDataProvider = ({ children }: { children: React.ReactNode }) =
|
||||
}
|
||||
|
||||
if (loadUnsubscribeUrls) {
|
||||
const unsubscribeUrlsResponse = await fetch("/api/unsubscribeUrls/GetAll?activeOnly=false");
|
||||
const unsubscribeUrlsResponse = await customFetch("/api/unsubscribeUrls/GetAll?activeOnly=false");
|
||||
unsubscribeUrlsData = await unsubscribeUrlsResponse.json();
|
||||
if (unsubscribeUrlsData) {
|
||||
setUnsubscribeUrls(unsubscribeUrlsData);
|
||||
@ -215,7 +221,7 @@ export const SetupDataProvider = ({ children }: { children: React.ReactNode }) =
|
||||
}
|
||||
|
||||
if (loadTemplates) {
|
||||
const templatesResponse = await fetch("/api/templates/GetAll?activeOnly=false");
|
||||
const templatesResponse = await customFetch("/api/templates/GetAll?activeOnly=false");
|
||||
templatesData = await templatesResponse.json();
|
||||
if (templatesData) {
|
||||
setTemplates(templatesData);
|
||||
@ -227,7 +233,7 @@ export const SetupDataProvider = ({ children }: { children: React.ReactNode }) =
|
||||
}
|
||||
|
||||
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();
|
||||
if (emailDomainsData) {
|
||||
setEmailDomains(emailDomainsData);
|
||||
|
||||
@ -15,6 +15,7 @@ export interface User {
|
||||
|
||||
export interface AuthResponse {
|
||||
accessToken: string;
|
||||
refreshToken: string;
|
||||
user: User;
|
||||
}
|
||||
|
||||
|
||||
@ -9,22 +9,25 @@ const customFetch = async (
|
||||
url: string,
|
||||
options: FetchOptions = {},
|
||||
authContext: ReturnType<typeof useAuth>,
|
||||
navigate: ReturnType<typeof useNavigate>
|
||||
navigate: ReturnType<typeof useNavigate> | null
|
||||
): Promise<Response> => {
|
||||
const { accessToken, refreshToken } = authContext;
|
||||
const { accessToken, refreshAuthToken, syncAccessToken } = authContext;
|
||||
|
||||
syncAccessToken(); // Ensure accessToken is up-to-date
|
||||
const headers = {
|
||||
...options.headers,
|
||||
...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}),
|
||||
...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {})
|
||||
};
|
||||
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
headers,
|
||||
credentials: options.credentials ? options.credentials : 'omit',
|
||||
});
|
||||
|
||||
if (response.status === 401) {
|
||||
const refreshSuccess = await refreshToken();
|
||||
|
||||
const refreshSuccess = await refreshAuthToken();
|
||||
if (refreshSuccess) {
|
||||
const newAccessToken = authContext.accessToken;
|
||||
const retryResponse = await fetch(url, {
|
||||
@ -36,7 +39,7 @@ const customFetch = async (
|
||||
});
|
||||
return retryResponse;
|
||||
} else {
|
||||
navigate('/login');
|
||||
if (navigate) navigate('/login');
|
||||
throw new Error('Authentication failed');
|
||||
}
|
||||
}
|
||||
@ -52,4 +55,10 @@ export const useCustomFetch = () => {
|
||||
customFetch(url, options, authContext, navigate);
|
||||
};
|
||||
|
||||
export const useCustomFetchNoNavigate = () => {
|
||||
const authContext = useAuth();
|
||||
|
||||
return (url: string, options: FetchOptions = {}) =>
|
||||
customFetch(url, options, authContext, null);
|
||||
};
|
||||
export default customFetch;
|
||||
@ -20,6 +20,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||
README.md = README.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Surge365.Core", "..\..\Core\Surge365.Core\Surge365.Core.csproj", "{6CB40316-D334-B074-F904-B0ABC46FADC9}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
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}.Release|Any CPU.ActiveCfg = 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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user