This commit is contained in:
King
2025-09-28 15:24:13 +03:30
parent 514b3a5975
commit 4241523443
222 changed files with 8139 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
using MediatR.Pipeline;
using Microsoft.Extensions.Logging;
namespace BackOffice.BFF.Application.Common.Behaviours;
public class LoggingBehaviour<TRequest> : IRequestPreProcessor<TRequest> where TRequest : notnull
{
private readonly ILogger _logger;
private readonly ICurrentUserService _currentUserService;
public LoggingBehaviour(ILogger<TRequest> logger, ICurrentUserService currentUserService)
{
_logger = logger;
_currentUserService = currentUserService;
}
public async Task Process(TRequest request, CancellationToken cancellationToken)
{
var requestName = typeof(TRequest).Name;
var userId = _currentUserService.UserId ?? string.Empty;
_logger.LogInformation("Request: {Name} {@UserId} {@Request}",
requestName, userId, request);
}
}

View File

@@ -0,0 +1,43 @@
using System.Diagnostics;
using MediatR;
using Microsoft.Extensions.Logging;
namespace BackOffice.BFF.Application.Common.Behaviours;
public class PerformanceBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly Stopwatch _timer;
private readonly ILogger<TRequest> _logger;
private readonly ICurrentUserService _currentUserService;
public PerformanceBehaviour(ILogger<TRequest> logger, ICurrentUserService currentUserService)
{
_timer = new Stopwatch();
_logger = logger;
_currentUserService = currentUserService;
}
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
_timer.Start();
var response = await next();
_timer.Stop();
var elapsedMilliseconds = _timer.ElapsedMilliseconds;
if (elapsedMilliseconds > 500)
{
var requestName = typeof(TRequest).Name;
var userId = _currentUserService.UserId ?? string.Empty;
_logger.LogWarning("Long Running Request: {Name} ({ElapsedMilliseconds} milliseconds) {@UserId} {@Request}",
requestName, elapsedMilliseconds, userId, request);
}
return response;
}
}

View File

@@ -0,0 +1,31 @@
using Microsoft.Extensions.Logging;
namespace BackOffice.BFF.Application.Common.Behaviours;
public class UnhandledExceptionBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly ILogger<TRequest> _logger;
public UnhandledExceptionBehaviour(ILogger<TRequest> logger)
{
_logger = logger;
}
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
try
{
return await next();
}
catch (Exception ex)
{
var requestName = typeof(TRequest).Name;
_logger.LogError(ex, "Request: Unhandled Exception for Request {Name} {@Request}", requestName, request);
throw;
}
}
}

View File

@@ -0,0 +1,37 @@
using ValidationException = BackOffice.BFF.Application.Common.Exceptions.ValidationException;
namespace BackOffice.BFF.Application.Common.Behaviours;
public class ValidationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehaviour(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
if (_validators.Any())
{
var context = new ValidationContext<TRequest>(request);
var validationResults = await Task.WhenAll(
_validators.Select(v =>
v.ValidateAsync(context, cancellationToken)));
var failures = validationResults
.Where(r => r.Errors.Any())
.SelectMany(r => r.Errors)
.ToList();
if (failures.Any())
throw new ValidationException(failures);
}
return await next();
}
}

View File

@@ -0,0 +1,29 @@
namespace BackOffice.BFF.Application.Common.Exceptions;
public class DuplicateException : Exception
{
public DuplicateException()
: base()
{
}
public DuplicateException(string message)
: base(message)
{
}
public DuplicateException(string message, Exception innerException)
: base(message, innerException)
{
}
public DuplicateException(string name, object key)
: base($"Entity \"{name}\" ({key}) already exists.")
{
}
public DuplicateException(string name, string field, object? key)
: base($"Entity \"{name}\" field \"{field}\" ({key}) already exists.")
{
}
}

View File

@@ -0,0 +1,8 @@
namespace BackOffice.BFF.Application.Common.Exceptions;
public class ForbiddenAccessException : Exception
{
public ForbiddenAccessException() : base()
{
}
}

View File

@@ -0,0 +1,24 @@
namespace BackOffice.BFF.Application.Common.Exceptions;
public class NotFoundException : Exception
{
public NotFoundException()
: base()
{
}
public NotFoundException(string message)
: base(message)
{
}
public NotFoundException(string message, Exception innerException)
: base(message, innerException)
{
}
public NotFoundException(string name, object key)
: base($"Entity \"{name}\" ({key}) was not found.")
{
}
}

View File

@@ -0,0 +1,22 @@
using FluentValidation.Results;
namespace BackOffice.BFF.Application.Common.Exceptions;
public class ValidationException : Exception
{
public ValidationException()
: base("One or more validation failures have occurred.")
{
Errors = new Dictionary<string, string[]>();
}
public ValidationException(IEnumerable<ValidationFailure> failures)
: this()
{
Errors = failures
.GroupBy(e => e.PropertyName, e => e.ErrorMessage)
.ToDictionary(failureGroup => failureGroup.Key, failureGroup => failureGroup.ToArray());
}
public IDictionary<string, string[]> Errors { get; }
}

View File

@@ -0,0 +1,17 @@
using CMSMicroservice.Protobuf.Protos.Package;
using FMSMicroservice.Protobuf.Protos.FileInfo;
namespace BackOffice.BFF.Application.Common.Interfaces;
public interface IApplicationContractContext
{
#region FM
FileInfoContract.FileInfoContractClient FileInfos { get; }
#endregion
#region CMS
PackageContract.PackageContractClient Packages { get; }
#endregion
}

View File

@@ -0,0 +1,6 @@
namespace BackOffice.BFF.Application.Common.Interfaces;
public interface ICurrentUserService
{
string? UserId { get; }
}

View File

@@ -0,0 +1,6 @@
namespace BackOffice.BFF.Application.Common.Interfaces;
public interface ITokenProvider
{
Task<string> GetTokenAsync();
}

View File

@@ -0,0 +1,64 @@
using System.Globalization;
namespace BackOffice.BFF.Application.Common.Mappings;
public class GeneralMapping : IRegister
{
void IRegister.Register(TypeAdapterConfig config)
{
config.NewConfig<string, decimal>()
.MapWith(src => decimal.Parse(src));
config.NewConfig<decimal, string>()
.MapWith(src => src.ToString("R", new CultureInfo("en-us")));
config.NewConfig<decimal?, string>()
.MapWith(src => src == null ? string.Empty : src.Value.ToString("R", new CultureInfo("en-us")));
config.NewConfig<string, decimal?>()
.MapWith(src => string.IsNullOrEmpty(src) ? null : decimal.Parse(src));
config.NewConfig<Guid, string>()
.MapWith(src => src == Guid.Empty ? string.Empty : src.ToString());
config.NewConfig<string, Guid>()
.MapWith(src => string.IsNullOrEmpty(src) ? Guid.Empty : Guid.Parse(src));
config.NewConfig<string, Guid?>()
.MapWith(src => string.IsNullOrEmpty(src) ? null : Guid.Parse(src));
config.NewConfig<Timestamp, DateTime>()
.MapWith(src => src.ToDateTime());
config.NewConfig<Timestamp, DateTime?>()
.MapWith(src => src == null ? null : src.ToDateTime());
config.NewConfig<DateTime, Timestamp>()
.MapWith(src => Timestamp.FromDateTime(DateTime.SpecifyKind(src, DateTimeKind.Utc)));
config.NewConfig<DateTime?, Timestamp>()
.MapWith(src => src.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(src.Value, DateTimeKind.Utc)) : null);
config.NewConfig<Duration, TimeSpan>()
.MapWith(src => src.ToTimeSpan());
config.NewConfig<Duration, TimeSpan?>()
.MapWith(src => src == null ? null : src.ToTimeSpan());
config.NewConfig<TimeSpan, Duration>()
.MapWith(src => Duration.FromTimeSpan(src));
config.NewConfig<TimeSpan?, Duration>()
.MapWith(src => src.HasValue ? Duration.FromTimeSpan(src.Value) : null);
config.Default
.UseDestinationValue(member => member.SetterModifier == AccessModifier.None &&
member.Type.IsGenericType &&
member.Type.GetGenericTypeDefinition() == typeof(Google.Protobuf.Collections.RepeatedField<>));
config.NewConfig<Google.Protobuf.ByteString, byte[]>()
.MapWith(src => src.ToByteArray());
config.NewConfig<byte[], Google.Protobuf.ByteString>()
.MapWith(src => Google.Protobuf.ByteString.CopyFrom(src));
}
}

View File

@@ -0,0 +1,10 @@
namespace BackOffice.BFF.Application.Common.Mappings;
public class PackageProfile : IRegister
{
void IRegister.Register(TypeAdapterConfig config)
{
//config.NewConfig<Source,Destination>()
// .Map(dest => dest.FullName, src => $"{src.Firstname} {src.Lastname}");
}
}

View File

@@ -0,0 +1,10 @@
namespace BackOffice.BFF.Application.Common.Mappings;
public class RoleProfile : IRegister
{
void IRegister.Register(TypeAdapterConfig config)
{
//config.NewConfig<Source,Destination>()
// .Map(dest => dest.FullName, src => $"{src.Firstname} {src.Lastname}");
}
}

View File

@@ -0,0 +1,10 @@
namespace BackOffice.BFF.Application.Common.Mappings;
public class UserAddressProfile : IRegister
{
void IRegister.Register(TypeAdapterConfig config)
{
//config.NewConfig<Source,Destination>()
// .Map(dest => dest.FullName, src => $"{src.Firstname} {src.Lastname}");
}
}

View File

@@ -0,0 +1,10 @@
namespace BackOffice.BFF.Application.Common.Mappings;
public class UserOrderProfile : IRegister
{
void IRegister.Register(TypeAdapterConfig config)
{
//config.NewConfig<Source,Destination>()
// .Map(dest => dest.FullName, src => $"{src.Firstname} {src.Lastname}");
}
}

View File

@@ -0,0 +1,10 @@
namespace BackOffice.BFF.Application.Common.Mappings;
public class UserProfile : IRegister
{
void IRegister.Register(TypeAdapterConfig config)
{
//config.NewConfig<Source,Destination>()
// .Map(dest => dest.FullName, src => $"{src.Firstname} {src.Lastname}");
}
}

View File

@@ -0,0 +1,10 @@
namespace BackOffice.BFF.Application.Common.Mappings;
public class UserRoleProfile : IRegister
{
void IRegister.Register(TypeAdapterConfig config)
{
//config.NewConfig<Source,Destination>()
// .Map(dest => dest.FullName, src => $"{src.Firstname} {src.Lastname}");
}
}

View File

@@ -0,0 +1,22 @@
namespace BackOffice.BFF.Application.Common.Models;
public class MetaData
{
//صفحه جاری
public long CurrentPage { get; set; }
//تعداد کل صفحات
public long TotalPage { get; set; }
//تعداد در هر صفحه
public long PageSize { get; set; }
//تعداد کل آیتم‌ها
public long TotalCount { get; set; }
//قبلی دارد؟
public bool HasPrevious { get; set; }
//بعدی دارد؟
public bool HasNext { get; set; }
}

View File

@@ -0,0 +1,10 @@
namespace BackOffice.BFF.Application.Common.Models;
public class PaginationState
{
//شماره صفحه
public int PageNumber { get; set; }
//اندازه صفحه
public int PageSize { get; set; }
}