refactor(Extensions): move to common
This commit is contained in:
@@ -4,7 +4,7 @@ using EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiver;
|
||||
using EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiverReadOnly;
|
||||
using EnvelopeGenerator.Application.Common.Dto.Messaging;
|
||||
using EnvelopeGenerator.Application.Common.Dto.Receiver;
|
||||
using EnvelopeGenerator.Application.Extensions;
|
||||
using EnvelopeGenerator.Application.Common.Extensions;
|
||||
using EnvelopeGenerator.Application.Receivers.Commands;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
using Microsoft.Extensions.Caching.Distributed;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Extensions;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static class CacheExtensions
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="cache"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="cToken"></param>
|
||||
/// <returns></returns>
|
||||
public static Task SetLongAsync(this IDistributedCache cache, string key, long value, DistributedCacheEntryOptions? options = null, CancellationToken cToken = default)
|
||||
=> options is null
|
||||
? cache.SetAsync(key, BitConverter.GetBytes(value), token: cToken)
|
||||
: cache.SetAsync(key, BitConverter.GetBytes(value), options: options, token: cToken);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="cache"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="cToken"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<long?> GetLongAsync(this IDistributedCache cache, string key, CancellationToken cToken = default)
|
||||
{
|
||||
var value = await cache.GetAsync(key, cToken);
|
||||
return value is null ? null : BitConverter.ToInt64(value, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="cache"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="cToken"></param>
|
||||
/// <returns></returns>
|
||||
public static Task SetDateTimeAsync(this IDistributedCache cache, string key, DateTime value, DistributedCacheEntryOptions? options = null, CancellationToken cToken = default)
|
||||
=> cache.SetLongAsync(key: key, value: value.Ticks, options: options, cToken: cToken);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="cache"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="cToken"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<DateTime?> GetDateTimeAsync(this IDistributedCache cache, string key, CancellationToken cToken = default)
|
||||
{
|
||||
var value = await cache.GetAsync(key, cToken);
|
||||
return value is null ? null : new(BitConverter.ToInt64(value, 0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="cache"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="cToken"></param>
|
||||
/// <returns></returns>
|
||||
public static Task SetTimeSpanAsync(this IDistributedCache cache, string key, TimeSpan value, DistributedCacheEntryOptions? options = null, CancellationToken cToken = default)
|
||||
=> cache.SetLongAsync(key: key, value: value.Ticks, options: options, cToken);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="cache"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="cToken"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<TimeSpan?> GetTimeSpanAsync(this IDistributedCache cache, string key, CancellationToken cToken = default)
|
||||
{
|
||||
var value = await cache.GetAsync(key, cToken);
|
||||
return value is null ? null : new(BitConverter.ToInt64(value, 0));
|
||||
}
|
||||
|
||||
//TODO: use code generator
|
||||
#region GetOrSetAsync
|
||||
|
||||
#region string
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="cache"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="factory"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="cacheInBackground"></param>
|
||||
/// <param name="cToken"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<string> GetOrSetAsync(this IDistributedCache cache, string key, Func<string> factory, DistributedCacheEntryOptions? options = null, bool cacheInBackground = false, CancellationToken cToken = default)
|
||||
{
|
||||
var value = await cache.GetStringAsync(key, cToken);
|
||||
if (value is null)
|
||||
{
|
||||
// create new and save
|
||||
value = factory();
|
||||
|
||||
Task CacheAsync() => options is null
|
||||
? cache.SetStringAsync(key, value, cToken)
|
||||
: cache.SetStringAsync(key, value, options, cToken);
|
||||
|
||||
if (cacheInBackground)
|
||||
_ = Task.Run(async () => await CacheAsync(), cToken);
|
||||
else
|
||||
await CacheAsync();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="cache"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="factoryAsync"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="cacheInBackground"></param>
|
||||
/// <param name="cToken"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<string> GetOrSetAsync(this IDistributedCache cache, string key, Func<Task<string>> factoryAsync, DistributedCacheEntryOptions? options = null, bool cacheInBackground = false, CancellationToken cToken = default)
|
||||
{
|
||||
var value = await cache.GetStringAsync(key, cToken);
|
||||
if(value is null)
|
||||
{
|
||||
// create new and save
|
||||
value = await factoryAsync();
|
||||
|
||||
Task CacheAsync() => options is null
|
||||
? cache.SetStringAsync(key: key, value: value, token: cToken)
|
||||
: cache.SetStringAsync(key: key, value: value, options: options, token: cToken);
|
||||
|
||||
if (cacheInBackground)
|
||||
_ = Task.Run(async () => await CacheAsync(), cToken);
|
||||
else
|
||||
await CacheAsync();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region DateTime
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="cache"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="factory"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="cacheInBackground"></param>
|
||||
/// <param name="cToken"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<DateTime> GetOrSetAsync(this IDistributedCache cache, string key, Func<DateTime> factory, DistributedCacheEntryOptions? options = null, bool cacheInBackground = false, CancellationToken cToken = default)
|
||||
{
|
||||
if (await cache.GetDateTimeAsync(key, cToken) is DateTime dateTimeValue)
|
||||
return dateTimeValue;
|
||||
else
|
||||
{
|
||||
// create new and save
|
||||
var newValue = factory();
|
||||
|
||||
Task CacheAsync() => options is null
|
||||
? cache.SetDateTimeAsync(key, newValue, cToken: cToken)
|
||||
: cache.SetDateTimeAsync(key, newValue, options, cToken);
|
||||
|
||||
if (cacheInBackground)
|
||||
_ = Task.Run(async () => await CacheAsync(), cToken);
|
||||
else
|
||||
await CacheAsync();
|
||||
|
||||
return newValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="cache"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="factory"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="cacheInBackground"></param>
|
||||
/// <param name="cToken"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<DateTime> GetOrSetAsync(this IDistributedCache cache, string key, Func<Task<DateTime>> factory, DistributedCacheEntryOptions? options = null, bool cacheInBackground = false, CancellationToken cToken = default)
|
||||
{
|
||||
if (await cache.GetDateTimeAsync(key, cToken) is DateTime dateTimeValue)
|
||||
return dateTimeValue;
|
||||
else
|
||||
{
|
||||
// create new and save
|
||||
var newValue = await factory();
|
||||
|
||||
Task CacheAsync() => options is null
|
||||
? cache.SetDateTimeAsync(key, newValue, cToken: cToken)
|
||||
: cache.SetDateTimeAsync(key, newValue, options, cToken);
|
||||
|
||||
if (cacheInBackground)
|
||||
_ = Task.Run(async () => await CacheAsync(), cToken);
|
||||
else
|
||||
await CacheAsync();
|
||||
|
||||
return newValue;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
using System.Text;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Extensions;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static class DecodingExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Validates whether a given string is a correctly formatted Base-64 encoded string.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method checks the string for proper Base-64 formatting, which includes validating
|
||||
/// the length of the string (must be divisible by 4). It also checks each character to ensure
|
||||
/// it belongs to the Base-64 character set (A-Z, a-z, 0-9, '+', '/', and '=' for padding).
|
||||
/// The method ensures that padding characters ('=') only appear at the end of the string and
|
||||
/// are in a valid configuration (either one '=' at the end if the string's length % 4 is 3,
|
||||
/// or two '==' if the length % 4 is 2).
|
||||
/// </remarks>
|
||||
/// <param name="input">The Base-64 encoded string to validate.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the string is a valid Base-64 encoded string; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// string testString = "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnk=";
|
||||
/// bool isValid = IsValidBase64String(testString);
|
||||
/// Console.WriteLine(isValid); // Output: true
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static bool IsBase64String(this string input)
|
||||
{
|
||||
// Check if the string is null or empty
|
||||
if (string.IsNullOrEmpty(input))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Replace valid base-64 padding
|
||||
input = input.Trim();
|
||||
int mod4 = input.Length % 4;
|
||||
if (mod4 > 0)
|
||||
{
|
||||
// Base-64 string lengths should be divisible by 4
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check each character to ensure it is valid base-64
|
||||
foreach (char c in input)
|
||||
{
|
||||
if (!char.IsLetterOrDigit(c) && c != '+' && c != '/' && c != '=')
|
||||
{
|
||||
// Invalid character detected
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure no invalid padding scenarios exist
|
||||
if (input.EndsWith("==") && input.Length % 4 == 0 ||
|
||||
input.EndsWith("=") && input.Length % 4 == 3)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return input.IndexOf('=') == -1; // No padding allowed except at the end
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="encodedKey"></param>
|
||||
/// <param name="decodedKeys"></param>
|
||||
/// <returns></returns>
|
||||
public static bool TryDecode(this string encodedKey, out string[] decodedKeys)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] bytes = Convert.FromBase64String(encodedKey);
|
||||
string decodedString = Encoding.UTF8.GetString(bytes);
|
||||
decodedKeys = decodedString.Split(new string[] { "::" }, StringSplitOptions.None);
|
||||
return true;
|
||||
}
|
||||
catch(ArgumentNullException) { }
|
||||
catch (FormatException) { }
|
||||
catch(ArgumentException) { }
|
||||
|
||||
decodedKeys = Array.Empty<string>();
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="decodedKeys"></param>
|
||||
/// <returns></returns>
|
||||
public static EncodeType GetEncodeType(this string[] decodedKeys) => decodedKeys.Length switch
|
||||
{
|
||||
2 => EncodeType.EnvelopeReceiver,
|
||||
3 => long.TryParse(decodedKeys[1], out var _) ? EncodeType.EnvelopeReceiverReadOnly : EncodeType.Undefined,
|
||||
_ => EncodeType.Undefined,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="decodedKeys"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="InvalidOperationException"></exception>
|
||||
public static (string? EnvelopeUuid, string? ReceiverSignature) ParseEnvelopeReceiverId(this string[] decodedKeys)
|
||||
=> decodedKeys.GetEncodeType() == EncodeType.EnvelopeReceiver
|
||||
? (EnvelopeUuid: decodedKeys[0], ReceiverSignature: decodedKeys[1])
|
||||
: throw new InvalidOperationException("Attempted to convert a decoded other than type EnvelopeReceiver to EnvelopeReceiver.");
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="decodedKeys"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="InvalidOperationException"></exception>
|
||||
public static long ParseReadOnlyId(this string[] decodedKeys)
|
||||
=> decodedKeys.GetEncodeType() == EncodeType.EnvelopeReceiverReadOnly
|
||||
? long.Parse(decodedKeys[1])
|
||||
: throw new InvalidOperationException("Attempted to convert a decoded other than type EnvelopeReceiver to EnvelopeReceiver. ");
|
||||
|
||||
/// <summary>
|
||||
/// Decodes the envelope receiver ID and extracts the envelope UUID and receiver signature.
|
||||
/// </summary>
|
||||
/// <param name="envelopeReceiverId">The base64 encoded string containing the envelope UUID and receiver signature.</param>
|
||||
/// <returns>A tuple containing the envelope UUID and receiver signature.</returns>
|
||||
public static (string? EnvelopeUuid, string? ReceiverSignature) DecodeEnvelopeReceiverId(this string envelopeReceiverId)
|
||||
{
|
||||
if (!envelopeReceiverId.IsBase64String())
|
||||
{
|
||||
return (null, null);
|
||||
}
|
||||
byte[] bytes = Convert.FromBase64String(envelopeReceiverId);
|
||||
string decodedString = Encoding.UTF8.GetString(bytes);
|
||||
string[] parts = decodedString.Split(new string[] { "::" }, StringSplitOptions.None);
|
||||
|
||||
if (parts.Length > 1)
|
||||
return (EnvelopeUuid: parts[0], ReceiverSignature: parts[1]);
|
||||
else
|
||||
return (string.Empty, string.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="envelopeReceiverReadOnlyId"></param>
|
||||
/// <returns></returns>
|
||||
public static long? DecodeEnvelopeReceiverReadOnlyId(this string envelopeReceiverReadOnlyId)
|
||||
{
|
||||
if (!envelopeReceiverReadOnlyId.IsBase64String())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
byte[] bytes = Convert.FromBase64String(envelopeReceiverReadOnlyId);
|
||||
string decodedString = Encoding.UTF8.GetString(bytes);
|
||||
string[] parts = decodedString.Split(new string[] { "::" }, StringSplitOptions.None);
|
||||
|
||||
if (parts.Length > 2)
|
||||
return long.TryParse(parts[1], out long readOnlyId) ? readOnlyId : null;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the envelope UUID from the decoded envelope receiver ID.
|
||||
/// </summary>
|
||||
/// <param name="envelopeReceiverId">The base64 encoded string to decode.</param>
|
||||
/// <returns>The envelope UUID.</returns>
|
||||
public static string? GetEnvelopeUuid(this string envelopeReceiverId) => envelopeReceiverId.DecodeEnvelopeReceiverId().EnvelopeUuid;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the receiver signature from the decoded envelope receiver ID.
|
||||
/// </summary>
|
||||
/// <param name="envelopeReceiverId">The base64 encoded string to decode.</param>
|
||||
/// <returns>The receiver signature.</returns>
|
||||
public static string? GetReceiverSignature(this string envelopeReceiverId) => envelopeReceiverId.DecodeEnvelopeReceiverId().ReceiverSignature;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.Text;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for decoding and extracting information from an envelope receiver ID.
|
||||
/// </summary>
|
||||
public static class EncodingExtensions
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="readOnlyId"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToEnvelopeKey(this long readOnlyId)
|
||||
{
|
||||
//The random number is used as a salt to increase security but it is not saved in the database.
|
||||
string combinedString = $"{Random.Shared.Next()}::{readOnlyId}::{Random.Shared.Next()}";
|
||||
byte[] bytes = Encoding.UTF8.GetBytes(combinedString);
|
||||
string base64String = Convert.ToBase64String(bytes);
|
||||
|
||||
return base64String;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToEnvelopeKey(this (string envelopeUuid, string receiverSignature) input)
|
||||
{
|
||||
string combinedString = $"{input.envelopeUuid}::{input.receiverSignature}";
|
||||
byte[] bytes = Encoding.UTF8.GetBytes(combinedString);
|
||||
string base64String = Convert.ToBase64String(bytes);
|
||||
|
||||
return base64String;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Text;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Extensions
|
||||
{
|
||||
public static class LoggerExtensions
|
||||
{
|
||||
public static void LogEnvelopeError(this ILogger logger, string envelopeReceiverId, Exception? exception = null, string? message = null, params object?[] args)
|
||||
{
|
||||
var sb = new StringBuilder().AppendLine(envelopeReceiverId.DecodeEnvelopeReceiverId().ToTitle());
|
||||
|
||||
if (message is not null)
|
||||
sb.AppendLine(message);
|
||||
|
||||
if (exception is null)
|
||||
logger.Log(LogLevel.Error, sb.ToString(), args);
|
||||
else
|
||||
logger.Log(LogLevel.Error, exception, sb.AppendLine(exception.Message).ToString(), args);
|
||||
}
|
||||
|
||||
public static void LogEnvelopeError(this ILogger logger, string? uuid, string? signature = null, Exception? exception = null, string? message = null, params object?[] args)
|
||||
{
|
||||
var sb = new StringBuilder($"Envelope Uuid: {uuid}");
|
||||
|
||||
if (signature is not null)
|
||||
sb.AppendLine().Append($"Receiver Signature: {signature}");
|
||||
|
||||
if (message is not null)
|
||||
sb.AppendLine().Append(message);
|
||||
|
||||
if (exception is null)
|
||||
logger.Log(LogLevel.Error, sb.ToString(), args);
|
||||
else
|
||||
logger.Log(LogLevel.Error, exception, sb.ToString(), args);
|
||||
}
|
||||
|
||||
public static string ToTitle(this (string? UUID, string? Signature) envelopeReceiverTuple)
|
||||
{
|
||||
return $"UUID is {envelopeReceiverTuple.UUID} and signature is {envelopeReceiverTuple.Signature}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using EnvelopeGenerator.Application.Common.Dto.Messaging;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for common mapping and conversion operations.
|
||||
/// </summary>
|
||||
public static class MappingExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether the response indicates a successful "OK" message status.
|
||||
/// </summary>
|
||||
/// <param name="gtxMessagingResponse">The response object to evaluate.</param>
|
||||
/// <returns><see langword="true"/> if the response contains a "message-status" key with a value of "ok" (case-insensitive);
|
||||
/// otherwise, <see langword="false"/>.</returns>
|
||||
public static bool Ok(this GtxMessagingResponse gtxMessagingResponse)
|
||||
=> gtxMessagingResponse.TryGetValue("message-status", out var status)
|
||||
&& status?.ToString()?.ToLower() == "ok";
|
||||
|
||||
/// <summary>
|
||||
/// Converts the specified byte array to its equivalent string representation encoded in base-64.
|
||||
/// </summary>
|
||||
/// <param name="bytes">The byte array to encode.</param>
|
||||
/// <returns>A base-64 encoded string representation of the input byte array.</returns>
|
||||
public static string ToBase64String(this byte[] bytes)
|
||||
=> Convert.ToBase64String(bytes);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using EnvelopeGenerator.Application.Extensions;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Extensions;
|
||||
|
||||
public static class MemoryCacheExtensions
|
||||
{
|
||||
private static readonly Guid BaseId = Guid.NewGuid();
|
||||
|
||||
public static IDictionary<string, int> GetEnumAsDictionary<TEnum>(this IMemoryCache memoryCache, string key = "", params object[] ignores)
|
||||
where TEnum : Enum
|
||||
=> memoryCache.GetOrCreate(BaseId + typeof(TEnum).FullName + key, _ =>
|
||||
{
|
||||
var mergedIgnores = new List<TEnum>();
|
||||
|
||||
foreach (var ignore in ignores)
|
||||
{
|
||||
if (ignore is IEnumerable<TEnum> ignoreList)
|
||||
mergedIgnores.AddRange(ignoreList);
|
||||
else if (ignore is TEnum ignoreVal)
|
||||
mergedIgnores.Add(ignoreVal);
|
||||
}
|
||||
|
||||
return Enum.GetValues(typeof(TEnum))
|
||||
.Cast<TEnum>()
|
||||
.Where(e => !mergedIgnores.Contains(e))
|
||||
.ToDictionary(e => e.ToString(), e => Convert.ToInt32(e));
|
||||
})
|
||||
?? throw new InvalidOperationException($"Failed to cache or retrieve enum dictionary for type '{typeof(TEnum).FullName}'.");
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using DigitalData.Core.Exceptions;
|
||||
using EnvelopeGenerator.Application.Common.Extensions;
|
||||
using EnvelopeGenerator.Application.Common.Model;
|
||||
using EnvelopeGenerator.Domain.Interfaces;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Extensions;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static class QueryExtensions
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
/// <param name="root"></param>
|
||||
/// <param name="query"></param>
|
||||
/// <param name="notnull"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="BadRequestException"></exception>
|
||||
public static IQueryable<TEntity> Where<TEntity>(this IQueryable<TEntity> root, EnvelopeQueryBase query, bool notnull = true)
|
||||
where TEntity : IHasEnvelope
|
||||
{
|
||||
if (query.Id is not null)
|
||||
root = root.Where(e => e.Envelope!.Id == query.Id);
|
||||
else if (query.Uuid is not null)
|
||||
root = root.Where(e => e.Envelope!.Uuid == query.Uuid);
|
||||
else if (notnull)
|
||||
throw new BadRequestException(
|
||||
"Either Envelope Id or Envelope Uuid must be provided in the query."
|
||||
);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
/// <param name="root"></param>
|
||||
/// <param name="query"></param>
|
||||
/// <param name="notnull"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="BadRequestException"></exception>
|
||||
public static IQueryable<TEntity> Where<TEntity>(this IQueryable<TEntity> root, ReceiverQueryBase query, bool notnull = true)
|
||||
where TEntity : IHasReceiver
|
||||
{
|
||||
if (query.Id is not null)
|
||||
root = root.Where(e => e.Receiver!.Id == query.Id);
|
||||
else if (query.EmailAddress is not null)
|
||||
root = root.Where(e => e.Receiver!.EmailAddress == query.EmailAddress);
|
||||
else if (query.Signature is not null)
|
||||
root = root.Where(e => e.Receiver!.Signature == query.Signature);
|
||||
else if (notnull)
|
||||
throw new BadRequestException(
|
||||
"Receiver must have at least one identifier (Id, EmailAddress, or Signature)."
|
||||
);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
/// <typeparam name="TEnvelopeQuery"></typeparam>
|
||||
/// <typeparam name="TReceiverQuery"></typeparam>
|
||||
/// <param name="root"></param>
|
||||
/// <param name="query"></param>
|
||||
/// <param name="notnull"></param>
|
||||
/// <returns></returns>
|
||||
public static IQueryable<TEntity> Where<TEntity, TEnvelopeQuery, TReceiverQuery>(this IQueryable<TEntity> root, EnvelopeReceiverQueryBase<TEnvelopeQuery, TReceiverQuery> query, bool notnull = true)
|
||||
where TEntity : IHasEnvelope, IHasReceiver
|
||||
where TEnvelopeQuery : EnvelopeQueryBase, new()
|
||||
where TReceiverQuery : ReceiverQueryBase, new()
|
||||
=> root.Where(query.Envelope, notnull).Where(query.Receiver, notnull);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using OtpNet;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Extensions
|
||||
{
|
||||
public static class StringExtension
|
||||
{
|
||||
public static bool IsValidTotp(this string totp, string secret)
|
||||
{
|
||||
var secret_bytes = Base32Encoding.ToBytes(secret);
|
||||
var secret_totp = new Totp(secret_bytes);
|
||||
return secret_totp.VerifyTotp(totp, out _, VerificationWindow.RfcSpecifiedNetworkDelay);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using DigitalData.Core.Exceptions;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for tasks
|
||||
/// </summary>
|
||||
public static class TaskExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Awaits the specified task and ensures that the result is not <c>null</c>.
|
||||
/// If the result is <c>null</c>, the exception created by factory-method is thrown.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the result.</typeparam>
|
||||
/// <typeparam name="TException">The type of the exception.</typeparam>
|
||||
/// <param name="task">The task to await.</param>
|
||||
/// <param name="factory">Exception provider</param>
|
||||
/// <returns>The awaited result if not <c>null</c>.</returns>
|
||||
/// <exception>Thrown if the result is <c>null</c>.</exception>
|
||||
public static async Task<T> ThrowIfNull<T, TException>(this Task<T?> task, Func<TException> factory) where TException : Exception
|
||||
{
|
||||
var result = await task;
|
||||
return result ?? throw factory();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Awaits the specified task and ensures that the result is not <c>empty</c>.
|
||||
/// If the result contains no elements, the exception created by factory-method is thrown.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The element type of the collection.</typeparam>
|
||||
/// <typeparam name="TException">The type of the exception.</typeparam>
|
||||
/// <param name="task">The task to await.</param>
|
||||
/// <param name="factory">Exception provider</param>
|
||||
/// <returns>The awaited collection if it is not <c>null</c> or empty.</returns>
|
||||
/// <exception cref="NotFoundException">Thrown if the result is <c>null</c> or empty.</exception>
|
||||
public static async Task<IEnumerable<T>> ThrowIfEmpty<T, TException>(this Task<IEnumerable<T>> task, Func<TException> factory) where TException : Exception
|
||||
{
|
||||
var result = await task;
|
||||
return result?.Any() ?? false ? result : throw factory();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <typeparam name="I"></typeparam>
|
||||
/// <param name="task"></param>
|
||||
/// <param name="act"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<I> Then<T, I>(this Task<T> task, Func<T, I> act)
|
||||
{
|
||||
var res = await task;
|
||||
return act(res);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static class Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static NotFoundException NotFound() => new();
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static BadRequestException BadRequest() => new();
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static ForbiddenException Forbidden() => new();
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using Ganss.Xss;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using System.Text.Encodings.Web;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Extensions
|
||||
{
|
||||
public static class XSSExtensions
|
||||
{
|
||||
public static string? TryEncode(this string? value, UrlEncoder encoder) => value is null ? value : encoder.Encode(value);
|
||||
|
||||
public static string? TryEncode(this LocalizedString? value, UrlEncoder encoder) => value is null ? null : encoder.Encode(value);
|
||||
|
||||
public static string? TrySanitize(this string? html, HtmlSanitizer sanitizer) => html is null ? html : sanitizer.Sanitize(html);
|
||||
|
||||
public static string? TrySanitize(this LocalizedString? html, HtmlSanitizer sanitizer) => html is null ? null : sanitizer.Sanitize(html);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
using DigitalData.Core.Exceptions;
|
||||
using EnvelopeGenerator.Application.Extensions;
|
||||
using EnvelopeGenerator.Application.Common.Extensions;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Model;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user