Merged PR 14: Style: C# 12 Primary constructor
Ce pull request change les constructeurs pour utiliser les constructeur primaires qui ont été ajouté dans C# 12 en Novembre 2023 Ceci nous aideras a garder une codebase propre et non verbose
This commit is contained in:
@@ -5,19 +5,12 @@ using Hutopy.Application.Common.Security;
|
||||
|
||||
namespace Hutopy.Application.Common.Behaviours;
|
||||
|
||||
public class AuthorizationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : notnull
|
||||
public class AuthorizationBehaviour<TRequest, TResponse>(
|
||||
IUser user,
|
||||
IIdentityService identityService)
|
||||
: IPipelineBehavior<TRequest, TResponse>
|
||||
where TRequest : notnull
|
||||
{
|
||||
private readonly IUser _user;
|
||||
private readonly IIdentityService _identityService;
|
||||
|
||||
public AuthorizationBehaviour(
|
||||
IUser user,
|
||||
IIdentityService identityService)
|
||||
{
|
||||
_user = user;
|
||||
_identityService = identityService;
|
||||
}
|
||||
|
||||
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
|
||||
{
|
||||
var authorizeAttributes = request.GetType().GetCustomAttributes<AuthorizeAttribute>();
|
||||
@@ -25,7 +18,7 @@ public class AuthorizationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRe
|
||||
if (authorizeAttributes.Any())
|
||||
{
|
||||
// Must be authenticated user
|
||||
if (_user.Id == null)
|
||||
if (user.Id == null)
|
||||
{
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
@@ -41,7 +34,7 @@ public class AuthorizationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRe
|
||||
{
|
||||
foreach (var role in roles)
|
||||
{
|
||||
var isInRole = await _identityService.IsInRoleAsync(_user.Id, role.Trim());
|
||||
var isInRole = await identityService.IsInRoleAsync(user.Id, role.Trim());
|
||||
if (isInRole)
|
||||
{
|
||||
authorized = true;
|
||||
@@ -63,7 +56,7 @@ public class AuthorizationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRe
|
||||
{
|
||||
foreach (var policy in authorizeAttributesWithPolicies.Select(a => a.Policy))
|
||||
{
|
||||
var authorized = await _identityService.AuthorizeAsync(_user.Id, policy);
|
||||
var authorized = await identityService.AuthorizeAsync(user.Id, policy);
|
||||
|
||||
if (!authorized)
|
||||
{
|
||||
|
||||
@@ -4,28 +4,24 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Hutopy.Application.Common.Behaviours;
|
||||
|
||||
public class LoggingBehaviour<TRequest> : IRequestPreProcessor<TRequest> where TRequest : notnull
|
||||
public class LoggingBehaviour<TRequest>(
|
||||
ILogger<TRequest> logger,
|
||||
IUser user,
|
||||
IIdentityService identityService)
|
||||
: IRequestPreProcessor<TRequest>
|
||||
where TRequest : notnull
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IUser _user;
|
||||
private readonly IIdentityService _identityService;
|
||||
|
||||
public LoggingBehaviour(ILogger<TRequest> logger, IUser user, IIdentityService identityService)
|
||||
{
|
||||
_logger = logger;
|
||||
_user = user;
|
||||
_identityService = identityService;
|
||||
}
|
||||
private readonly ILogger _logger = logger;
|
||||
|
||||
public async Task Process(TRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
var requestName = typeof(TRequest).Name;
|
||||
var userId = _user.Id ?? string.Empty;
|
||||
var userId = user.Id ?? string.Empty;
|
||||
string? userName = string.Empty;
|
||||
|
||||
if (!string.IsNullOrEmpty(userId))
|
||||
{
|
||||
userName = await _identityService.GetUserNameAsync(userId);
|
||||
userName = await identityService.GetUserNameAsync(userId);
|
||||
}
|
||||
|
||||
_logger.LogInformation("Hutopy Request: {Name} {@UserId} {@UserName} {@Request}",
|
||||
|
||||
@@ -4,24 +4,14 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Hutopy.Application.Common.Behaviours;
|
||||
|
||||
public class PerformanceBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : notnull
|
||||
public class PerformanceBehaviour<TRequest, TResponse>(
|
||||
ILogger<TRequest> logger,
|
||||
IUser user,
|
||||
IIdentityService identityService)
|
||||
: IPipelineBehavior<TRequest, TResponse>
|
||||
where TRequest : notnull
|
||||
{
|
||||
private readonly Stopwatch _timer;
|
||||
private readonly ILogger<TRequest> _logger;
|
||||
private readonly IUser _user;
|
||||
private readonly IIdentityService _identityService;
|
||||
|
||||
public PerformanceBehaviour(
|
||||
ILogger<TRequest> logger,
|
||||
IUser user,
|
||||
IIdentityService identityService)
|
||||
{
|
||||
_timer = new Stopwatch();
|
||||
|
||||
_logger = logger;
|
||||
_user = user;
|
||||
_identityService = identityService;
|
||||
}
|
||||
private readonly Stopwatch _timer = new();
|
||||
|
||||
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -33,20 +23,16 @@ public class PerformanceBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequ
|
||||
|
||||
var elapsedMilliseconds = _timer.ElapsedMilliseconds;
|
||||
|
||||
if (elapsedMilliseconds > 500)
|
||||
{
|
||||
var requestName = typeof(TRequest).Name;
|
||||
var userId = _user.Id ?? string.Empty;
|
||||
var userName = string.Empty;
|
||||
if (elapsedMilliseconds <= 500) return response;
|
||||
|
||||
if (!string.IsNullOrEmpty(userId))
|
||||
{
|
||||
userName = await _identityService.GetUserNameAsync(userId);
|
||||
}
|
||||
var requestName = typeof(TRequest).Name;
|
||||
var userId = user.Id ?? string.Empty;
|
||||
var userName = string.Empty;
|
||||
|
||||
_logger.LogWarning("Hutopy Long Running Request: {Name} ({ElapsedMilliseconds} milliseconds) {@UserId} {@UserName} {@Request}",
|
||||
requestName, elapsedMilliseconds, userId, userName, request);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(userId)) userName = await identityService.GetUserNameAsync(userId);
|
||||
|
||||
logger.LogWarning("Hutopy Long Running Request: {Name} ({ElapsedMilliseconds} milliseconds) {@UserId} {@UserName} {@Request}",
|
||||
requestName, elapsedMilliseconds, userId, userName, request);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -2,15 +2,11 @@
|
||||
|
||||
namespace Hutopy.Application.Common.Behaviours;
|
||||
|
||||
public class UnhandledExceptionBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : notnull
|
||||
public class UnhandledExceptionBehaviour<TRequest, TResponse>(
|
||||
ILogger<TRequest> logger)
|
||||
: IPipelineBehavior<TRequest, TResponse>
|
||||
where TRequest : notnull
|
||||
{
|
||||
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
|
||||
@@ -21,7 +17,7 @@ public class UnhandledExceptionBehaviour<TRequest, TResponse> : IPipelineBehavio
|
||||
{
|
||||
var requestName = typeof(TRequest).Name;
|
||||
|
||||
_logger.LogError(ex, "Hutopy Request: Unhandled Exception for Request {Name} {@Request}", requestName, request);
|
||||
logger.LogError(ex, "Hutopy Request: Unhandled Exception for Request {Name} {@Request}", requestName, request);
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
@@ -2,34 +2,27 @@
|
||||
|
||||
namespace Hutopy.Application.Common.Behaviours;
|
||||
|
||||
public class ValidationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
|
||||
where TRequest : notnull
|
||||
public class ValidationBehaviour<TRequest, TResponse>(IEnumerable<IValidator<TRequest>> validators)
|
||||
: IPipelineBehavior<TRequest, TResponse>
|
||||
where TRequest : notnull
|
||||
{
|
||||
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);
|
||||
if (!validators.Any()) return await next();
|
||||
|
||||
var validationResults = await Task.WhenAll(
|
||||
_validators.Select(v =>
|
||||
v.ValidateAsync(context, cancellationToken)));
|
||||
var context = new ValidationContext<TRequest>(request);
|
||||
|
||||
var failures = validationResults
|
||||
.Where(r => r.Errors.Any())
|
||||
.SelectMany(r => r.Errors)
|
||||
.ToList();
|
||||
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.Count != 0) throw new ValidationException(failures);
|
||||
|
||||
if (failures.Any())
|
||||
throw new ValidationException(failures);
|
||||
}
|
||||
return await next();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,9 @@
|
||||
|
||||
namespace Hutopy.Application.Common.Exceptions;
|
||||
|
||||
public class ValidationException : Exception
|
||||
public class ValidationException()
|
||||
: Exception("One or more validation failures have occurred.")
|
||||
{
|
||||
public ValidationException()
|
||||
: base("One or more validation failures have occurred.")
|
||||
{
|
||||
Errors = new Dictionary<string, string[]>();
|
||||
}
|
||||
|
||||
public ValidationException(IEnumerable<ValidationFailure> failures)
|
||||
: this()
|
||||
{
|
||||
@@ -18,5 +13,5 @@ public class ValidationException : Exception
|
||||
.ToDictionary(failureGroup => failureGroup.Key, failureGroup => failureGroup.ToArray());
|
||||
}
|
||||
|
||||
public IDictionary<string, string[]> Errors { get; }
|
||||
public IDictionary<string, string[]> Errors { get; } = new Dictionary<string, string[]>();
|
||||
}
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
namespace Hutopy.Application.Common.Models;
|
||||
|
||||
public class PaginatedList<T>
|
||||
public class PaginatedList<T>(
|
||||
IReadOnlyCollection<T> items,
|
||||
int count,
|
||||
int pageNumber,
|
||||
int pageSize)
|
||||
{
|
||||
public IReadOnlyCollection<T> Items { get; }
|
||||
public int PageNumber { get; }
|
||||
public int TotalPages { get; }
|
||||
public int TotalCount { get; }
|
||||
|
||||
public PaginatedList(IReadOnlyCollection<T> items, int count, int pageNumber, int pageSize)
|
||||
{
|
||||
PageNumber = pageNumber;
|
||||
TotalPages = (int)Math.Ceiling(count / (double)pageSize);
|
||||
TotalCount = count;
|
||||
Items = items;
|
||||
}
|
||||
public IReadOnlyCollection<T> Items { get; } = items;
|
||||
public int PageNumber { get; } = pageNumber;
|
||||
public int TotalPages { get; } = (int)Math.Ceiling(count / (double)pageSize);
|
||||
public int TotalCount { get; } = count;
|
||||
|
||||
public bool HasPreviousPage => PageNumber > 1;
|
||||
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
namespace Hutopy.Application.Common.Models;
|
||||
|
||||
public class Result
|
||||
public class Result(
|
||||
bool succeeded,
|
||||
IEnumerable<string> errors)
|
||||
{
|
||||
internal Result(bool succeeded, IEnumerable<string> errors)
|
||||
{
|
||||
Succeeded = succeeded;
|
||||
Errors = errors.ToArray();
|
||||
}
|
||||
public bool Succeeded { get; init; } = succeeded;
|
||||
|
||||
public bool Succeeded { get; init; }
|
||||
|
||||
public string[] Errors { get; init; }
|
||||
public string[] Errors { get; init; } = errors.ToArray();
|
||||
|
||||
public static Result Success()
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/// <summary>
|
||||
/// Specifies the class this attribute is applied to requires authorization.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
|
||||
public class AuthorizeAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection;
|
||||
namespace Hutopy.Application;
|
||||
|
||||
public static class DependencyInjection
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@ using Hutopy.Domain.Entities;
|
||||
|
||||
namespace Hutopy.Application.FutureCreators.Commands;
|
||||
|
||||
public record CreateFutureCreatorCommand : IRequest<int>
|
||||
public abstract record CreateFutureCreatorCommand : IRequest<int>
|
||||
{
|
||||
public required string FirstName { get; init; }
|
||||
public required string LastName { get; init; }
|
||||
@@ -13,16 +13,10 @@ public record CreateFutureCreatorCommand : IRequest<int>
|
||||
public required string ReasonToJoin { get; init; }
|
||||
}
|
||||
|
||||
public class CreateFuturCreatorCommandHandler : IRequestHandler<CreateFutureCreatorCommand, int>
|
||||
public class CreateFutureCreatorCommandHandler(
|
||||
IApplicationDbContext context)
|
||||
: IRequestHandler<CreateFutureCreatorCommand, int>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
|
||||
public CreateFuturCreatorCommandHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<int> Handle(CreateFutureCreatorCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var entity = new FutureCreator
|
||||
@@ -35,9 +29,9 @@ public class CreateFuturCreatorCommandHandler : IRequestHandler<CreateFutureCrea
|
||||
ReasonToJoin = request.ReasonToJoin,
|
||||
};
|
||||
|
||||
_context.FutureCreators.Add(entity);
|
||||
context.FutureCreators.Add(entity);
|
||||
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
await context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return entity.Id;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
|
||||
namespace Hutopy.Application.Stripe.Commands;
|
||||
public record CreateSessionCheckoutCommand : IRequest<string>
|
||||
public abstract record CreateSessionCheckoutCommand : IRequest<string>
|
||||
{
|
||||
public required int Price { get; init; }
|
||||
|
||||
@@ -10,21 +10,16 @@ public record CreateSessionCheckoutCommand : IRequest<string>
|
||||
}
|
||||
|
||||
|
||||
public class CreateSessionCheckoutCommandHandler : IRequestHandler<CreateSessionCheckoutCommand, string>
|
||||
public class CreateSessionCheckoutCommandHandler(
|
||||
IApplicationDbContext context,
|
||||
IStripeService stripeService)
|
||||
: IRequestHandler<CreateSessionCheckoutCommand, string>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly IStripeService _stripeService;
|
||||
|
||||
|
||||
public CreateSessionCheckoutCommandHandler(IApplicationDbContext context, IStripeService stripeService)
|
||||
{
|
||||
_context = context;
|
||||
_stripeService = stripeService;
|
||||
}
|
||||
private readonly IApplicationDbContext _context = context;
|
||||
|
||||
public async Task<string> Handle(CreateSessionCheckoutCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var stripeSecret = await _stripeService.CreateCheckoutSession(request.Price, request.Currency);
|
||||
var stripeSecret = await stripeService.CreateCheckoutSession(request.Price, request.Currency);
|
||||
|
||||
return stripeSecret;
|
||||
}
|
||||
|
||||
@@ -11,15 +11,10 @@ public record CreateTodoItemCommand : IRequest<int>
|
||||
public string? Title { get; init; }
|
||||
}
|
||||
|
||||
public class CreateTodoItemCommandHandler : IRequestHandler<CreateTodoItemCommand, int>
|
||||
public class CreateTodoItemCommandHandler(
|
||||
IApplicationDbContext context)
|
||||
: IRequestHandler<CreateTodoItemCommand, int>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public CreateTodoItemCommandHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<int> Handle(CreateTodoItemCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var entity = new TodoItem
|
||||
@@ -31,9 +26,9 @@ public class CreateTodoItemCommandHandler : IRequestHandler<CreateTodoItemComman
|
||||
|
||||
entity.AddDomainEvent(new TodoItemCreatedEvent(entity));
|
||||
|
||||
_context.TodoItems.Add(entity);
|
||||
context.TodoItems.Add(entity);
|
||||
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
await context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return entity.Id;
|
||||
}
|
||||
|
||||
@@ -9,27 +9,22 @@ namespace Hutopy.Application.TodoItems.Commands.DeleteTodoItem;
|
||||
[Authorize(Policy = Policies.CanDelete)]
|
||||
public record DeleteTodoItemCommand(int Id) : IRequest;
|
||||
|
||||
public class DeleteTodoItemCommandHandler : IRequestHandler<DeleteTodoItemCommand>
|
||||
public class DeleteTodoItemCommandHandler(
|
||||
IApplicationDbContext context)
|
||||
: IRequestHandler<DeleteTodoItemCommand>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public DeleteTodoItemCommandHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task Handle(DeleteTodoItemCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var entity = await _context.TodoItems
|
||||
var entity = await context.TodoItems
|
||||
.FindAsync(new object[] { request.Id }, cancellationToken);
|
||||
|
||||
Guard.Against.NotFound(request.Id, entity);
|
||||
|
||||
_context.TodoItems.Remove(entity);
|
||||
context.TodoItems.Remove(entity);
|
||||
|
||||
entity.AddDomainEvent(new TodoItemDeletedEvent(entity));
|
||||
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
await context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,18 +11,13 @@ public record UpdateTodoItemCommand : IRequest
|
||||
public bool Done { get; init; }
|
||||
}
|
||||
|
||||
public class UpdateTodoItemCommandHandler : IRequestHandler<UpdateTodoItemCommand>
|
||||
public class UpdateTodoItemCommandHandler(
|
||||
IApplicationDbContext context)
|
||||
: IRequestHandler<UpdateTodoItemCommand>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public UpdateTodoItemCommandHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task Handle(UpdateTodoItemCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var entity = await _context.TodoItems
|
||||
var entity = await context.TodoItems
|
||||
.FindAsync(new object[] { request.Id }, cancellationToken);
|
||||
|
||||
Guard.Against.NotFound(request.Id, entity);
|
||||
@@ -30,6 +25,6 @@ public class UpdateTodoItemCommandHandler : IRequestHandler<UpdateTodoItemComman
|
||||
entity.Title = request.Title;
|
||||
entity.Done = request.Done;
|
||||
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
await context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,18 +14,13 @@ public record UpdateTodoItemDetailCommand : IRequest
|
||||
public string? Note { get; init; }
|
||||
}
|
||||
|
||||
public class UpdateTodoItemDetailCommandHandler : IRequestHandler<UpdateTodoItemDetailCommand>
|
||||
public class UpdateTodoItemDetailCommandHandler(
|
||||
IApplicationDbContext context)
|
||||
: IRequestHandler<UpdateTodoItemDetailCommand>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public UpdateTodoItemDetailCommandHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task Handle(UpdateTodoItemDetailCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var entity = await _context.TodoItems
|
||||
var entity = await context.TodoItems
|
||||
.FindAsync(new object[] { request.Id }, cancellationToken);
|
||||
|
||||
Guard.Against.NotFound(request.Id, entity);
|
||||
@@ -34,6 +29,6 @@ public class UpdateTodoItemDetailCommandHandler : IRequestHandler<UpdateTodoItem
|
||||
entity.Priority = request.Priority;
|
||||
entity.Note = request.Note;
|
||||
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
await context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,18 +3,13 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Hutopy.Application.TodoItems.EventHandlers;
|
||||
|
||||
public class TodoItemCompletedEventHandler : INotificationHandler<TodoItemCompletedEvent>
|
||||
public class TodoItemCompletedEventHandler(
|
||||
ILogger<TodoItemCompletedEventHandler> logger)
|
||||
: INotificationHandler<TodoItemCompletedEvent>
|
||||
{
|
||||
private readonly ILogger<TodoItemCompletedEventHandler> _logger;
|
||||
|
||||
public TodoItemCompletedEventHandler(ILogger<TodoItemCompletedEventHandler> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Task Handle(TodoItemCompletedEvent notification, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Hutopy Domain Event: {DomainEvent}", notification.GetType().Name);
|
||||
logger.LogInformation("Hutopy Domain Event: {DomainEvent}", notification.GetType().Name);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -3,18 +3,13 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Hutopy.Application.TodoItems.EventHandlers;
|
||||
|
||||
public class TodoItemCreatedEventHandler : INotificationHandler<TodoItemCreatedEvent>
|
||||
public class TodoItemCreatedEventHandler(
|
||||
ILogger<TodoItemCreatedEventHandler> logger)
|
||||
: INotificationHandler<TodoItemCreatedEvent>
|
||||
{
|
||||
private readonly ILogger<TodoItemCreatedEventHandler> _logger;
|
||||
|
||||
public TodoItemCreatedEventHandler(ILogger<TodoItemCreatedEventHandler> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Task Handle(TodoItemCreatedEvent notification, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Hutopy Domain Event: {DomainEvent}", notification.GetType().Name);
|
||||
logger.LogInformation("Hutopy Domain Event: {DomainEvent}", notification.GetType().Name);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -4,31 +4,25 @@ using Hutopy.Application.Common.Models;
|
||||
|
||||
namespace Hutopy.Application.TodoItems.Queries.GetTodoItemsWithPagination;
|
||||
|
||||
public record GetTodoItemsWithPaginationQuery : IRequest<PaginatedList<TodoItemBriefDto>>
|
||||
public abstract record GetTodoItemsWithPaginationQuery : IRequest<PaginatedList<TodoItemBriefDto>>
|
||||
{
|
||||
public int ListId { get; init; }
|
||||
public int PageNumber { get; init; } = 1;
|
||||
public int PageSize { get; init; } = 10;
|
||||
}
|
||||
|
||||
public class GetTodoItemsWithPaginationQueryHandler : IRequestHandler<GetTodoItemsWithPaginationQuery, PaginatedList<TodoItemBriefDto>>
|
||||
public class GetTodoItemsWithPaginationQueryHandler(
|
||||
IApplicationDbContext context,
|
||||
IMapper mapper)
|
||||
: IRequestHandler<GetTodoItemsWithPaginationQuery, PaginatedList<TodoItemBriefDto>>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public GetTodoItemsWithPaginationQueryHandler(IApplicationDbContext context, IMapper mapper)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public async Task<PaginatedList<TodoItemBriefDto>> Handle(GetTodoItemsWithPaginationQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
Console.WriteLine(request);
|
||||
return await _context.TodoItems
|
||||
return await context.TodoItems
|
||||
.Where(x => x.ListId == request.ListId)
|
||||
.OrderBy(x => x.Title)
|
||||
.ProjectTo<TodoItemBriefDto>(_mapper.ConfigurationProvider)
|
||||
.ProjectTo<TodoItemBriefDto>(mapper.ConfigurationProvider)
|
||||
.PaginatedListAsync(request.PageNumber, request.PageSize);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,24 +8,17 @@ public record CreateTodoListCommand : IRequest<int>
|
||||
public string? Title { get; init; }
|
||||
}
|
||||
|
||||
public class CreateTodoListCommandHandler : IRequestHandler<CreateTodoListCommand, int>
|
||||
public class CreateTodoListCommandHandler(
|
||||
IApplicationDbContext context)
|
||||
: IRequestHandler<CreateTodoListCommand, int>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public CreateTodoListCommandHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<int> Handle(CreateTodoListCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var entity = new TodoList();
|
||||
var entity = new TodoList { Title = request.Title };
|
||||
|
||||
entity.Title = request.Title;
|
||||
context.TodoLists.Add(entity);
|
||||
|
||||
_context.TodoLists.Add(entity);
|
||||
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
await context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return entity.Id;
|
||||
}
|
||||
|
||||
@@ -4,25 +4,20 @@ namespace Hutopy.Application.TodoLists.Commands.DeleteTodoList;
|
||||
|
||||
public record DeleteTodoListCommand(int Id) : IRequest;
|
||||
|
||||
public class DeleteTodoListCommandHandler : IRequestHandler<DeleteTodoListCommand>
|
||||
public class DeleteTodoListCommandHandler(
|
||||
IApplicationDbContext context)
|
||||
: IRequestHandler<DeleteTodoListCommand>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public DeleteTodoListCommandHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task Handle(DeleteTodoListCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var entity = await _context.TodoLists
|
||||
var entity = await context.TodoLists
|
||||
.Where(l => l.Id == request.Id)
|
||||
.SingleOrDefaultAsync(cancellationToken);
|
||||
|
||||
Guard.Against.NotFound(request.Id, entity);
|
||||
|
||||
_context.TodoLists.Remove(entity);
|
||||
context.TodoLists.Remove(entity);
|
||||
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
await context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,19 +8,14 @@ namespace Hutopy.Application.TodoLists.Commands.PurgeTodoLists;
|
||||
[Authorize(Policy = Policies.CanPurge)]
|
||||
public record PurgeTodoListsCommand : IRequest;
|
||||
|
||||
public class PurgeTodoListsCommandHandler : IRequestHandler<PurgeTodoListsCommand>
|
||||
public class PurgeTodoListsCommandHandler(
|
||||
IApplicationDbContext context)
|
||||
: IRequestHandler<PurgeTodoListsCommand>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public PurgeTodoListsCommandHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task Handle(PurgeTodoListsCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
_context.TodoLists.RemoveRange(_context.TodoLists);
|
||||
context.TodoLists.RemoveRange(context.TodoLists);
|
||||
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
await context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,25 +9,20 @@ public record UpdateTodoListCommand : IRequest
|
||||
public string? Title { get; init; }
|
||||
}
|
||||
|
||||
public class UpdateTodoListCommandHandler : IRequestHandler<UpdateTodoListCommand>
|
||||
public class UpdateTodoListCommandHandler(
|
||||
IApplicationDbContext context)
|
||||
: IRequestHandler<UpdateTodoListCommand>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public UpdateTodoListCommandHandler(IApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task Handle(UpdateTodoListCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var entity = await _context.TodoLists
|
||||
var entity = await context.TodoLists
|
||||
.FindAsync(new object[] { request.Id }, cancellationToken);
|
||||
|
||||
Guard.Against.NotFound(request.Id, entity);
|
||||
|
||||
entity.Title = request.Title;
|
||||
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
await context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ public class UpdateTodoListCommandValidator : AbstractValidator<UpdateTodoListCo
|
||||
.WithErrorCode("Unique");
|
||||
}
|
||||
|
||||
public async Task<bool> BeUniqueTitle(UpdateTodoListCommand model, string title, CancellationToken cancellationToken)
|
||||
private async Task<bool> BeUniqueTitle(UpdateTodoListCommand model, string title, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _context.TodoLists
|
||||
.Where(l => l.Id != model.Id)
|
||||
|
||||
@@ -8,17 +8,11 @@ namespace Hutopy.Application.TodoLists.Queries.GetTodos;
|
||||
[Authorize]
|
||||
public record GetTodosQuery : IRequest<TodosVm>;
|
||||
|
||||
public class GetTodosQueryHandler : IRequestHandler<GetTodosQuery, TodosVm>
|
||||
public class GetTodosQueryHandler(
|
||||
IApplicationDbContext context,
|
||||
IMapper mapper)
|
||||
: IRequestHandler<GetTodosQuery, TodosVm>
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public GetTodosQueryHandler(IApplicationDbContext context, IMapper mapper)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public async Task<TodosVm> Handle(GetTodosQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
return new TodosVm
|
||||
@@ -28,9 +22,9 @@ public class GetTodosQueryHandler : IRequestHandler<GetTodosQuery, TodosVm>
|
||||
.Select(p => new LookupDto { Id = (int)p, Title = p.ToString() })
|
||||
.ToList(),
|
||||
|
||||
Lists = await _context.TodoLists
|
||||
Lists = await context.TodoLists
|
||||
.AsNoTracking()
|
||||
.ProjectTo<TodoListDto>(_mapper.ConfigurationProvider)
|
||||
.ProjectTo<TodoListDto>(mapper.ConfigurationProvider)
|
||||
.OrderBy(t => t.Title)
|
||||
.ToListAsync(cancellationToken)
|
||||
};
|
||||
|
||||
@@ -4,18 +4,13 @@ namespace Hutopy.Application.TodoLists.Queries.GetTodos;
|
||||
|
||||
public class TodoListDto
|
||||
{
|
||||
public TodoListDto()
|
||||
{
|
||||
Items = Array.Empty<TodoItemDto>();
|
||||
}
|
||||
|
||||
public int Id { get; init; }
|
||||
|
||||
public string? Title { get; init; }
|
||||
|
||||
public string? Colour { get; init; }
|
||||
|
||||
public IReadOnlyCollection<TodoItemDto> Items { get; init; }
|
||||
public IReadOnlyCollection<TodoItemDto> Items { get; init; } = Array.Empty<TodoItemDto>();
|
||||
|
||||
private class Mapping : Profile
|
||||
{
|
||||
|
||||
@@ -8,10 +8,10 @@ public abstract class BaseEntity
|
||||
// Using non-generic integer types for simplicity
|
||||
public int Id { get; set; }
|
||||
|
||||
private readonly List<BaseEvent> _domainEvents = new();
|
||||
private readonly List<BaseEvent> _domainEvents = [];
|
||||
|
||||
[NotMapped]
|
||||
public IReadOnlyCollection<BaseEvent> DomainEvents => _domainEvents.AsReadOnly();
|
||||
public IEnumerable<BaseEvent> DomainEvents => _domainEvents.AsReadOnly();
|
||||
|
||||
public void AddDomainEvent(BaseEvent domainEvent)
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// Learn more: https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/microservice-ddd-cqrs-patterns/implement-value-objects
|
||||
public abstract class ValueObject
|
||||
{
|
||||
protected static bool EqualOperator(ValueObject left, ValueObject right)
|
||||
private static bool EqualOperator(ValueObject left, ValueObject right)
|
||||
{
|
||||
if (left is null ^ right is null)
|
||||
{
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
namespace Hutopy.Domain.Events;
|
||||
|
||||
public class TodoItemCompletedEvent : BaseEvent
|
||||
public class TodoItemCompletedEvent(
|
||||
TodoItem item)
|
||||
: BaseEvent
|
||||
{
|
||||
public TodoItemCompletedEvent(TodoItem item)
|
||||
{
|
||||
Item = item;
|
||||
}
|
||||
|
||||
public TodoItem Item { get; }
|
||||
public TodoItem Item { get; } = item;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
namespace Hutopy.Domain.Events;
|
||||
|
||||
public class TodoItemCreatedEvent : BaseEvent
|
||||
public class TodoItemCreatedEvent(
|
||||
TodoItem item)
|
||||
: BaseEvent
|
||||
{
|
||||
public TodoItemCreatedEvent(TodoItem item)
|
||||
{
|
||||
Item = item;
|
||||
}
|
||||
|
||||
public TodoItem Item { get; }
|
||||
public TodoItem Item { get; } = item;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
namespace Hutopy.Domain.Events;
|
||||
|
||||
public class TodoItemDeletedEvent : BaseEvent
|
||||
public class TodoItemDeletedEvent(
|
||||
TodoItem item)
|
||||
: BaseEvent
|
||||
{
|
||||
public TodoItemDeletedEvent(TodoItem item)
|
||||
{
|
||||
Item = item;
|
||||
}
|
||||
|
||||
public TodoItem Item { get; }
|
||||
public TodoItem Item { get; } = item;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
namespace Hutopy.Domain.Exceptions;
|
||||
|
||||
public class UnsupportedColourException : Exception
|
||||
{
|
||||
public UnsupportedColourException(string code)
|
||||
: base($"Colour \"{code}\" is unsupported.")
|
||||
{
|
||||
}
|
||||
}
|
||||
public class UnsupportedColourException(
|
||||
string code) : Exception($"Colour \"{code}\" is unsupported.");
|
||||
|
||||
@@ -47,7 +47,7 @@ public class Colour(string code) : ValueObject
|
||||
return Code;
|
||||
}
|
||||
|
||||
protected static IEnumerable<Colour> SupportedColours
|
||||
private static IEnumerable<Colour> SupportedColours
|
||||
{
|
||||
get
|
||||
{
|
||||
|
||||
@@ -7,10 +7,10 @@ using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Hutopy.Infrastructure.Data;
|
||||
|
||||
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>, IApplicationDbContext
|
||||
public class ApplicationDbContext(
|
||||
DbContextOptions<ApplicationDbContext> options)
|
||||
: IdentityDbContext<ApplicationUser>(options), IApplicationDbContext
|
||||
{
|
||||
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }
|
||||
|
||||
public DbSet<TodoList> TodoLists => Set<TodoList>();
|
||||
|
||||
public DbSet<TodoItem> TodoItems => Set<TodoItem>();
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using Hutopy.Domain.Constants;
|
||||
using Hutopy.Domain.Constants;
|
||||
using Hutopy.Domain.Entities;
|
||||
using Hutopy.Infrastructure.Identity;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
@@ -10,44 +9,35 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Hutopy.Infrastructure.Data;
|
||||
|
||||
public static class InitialiserExtensions
|
||||
public static class InitializerExtensions
|
||||
{
|
||||
public static async Task InitialiseDatabaseAsync(this WebApplication app)
|
||||
{
|
||||
using var scope = app.Services.CreateScope();
|
||||
|
||||
var initialiser = scope.ServiceProvider.GetRequiredService<ApplicationDbContextInitialiser>();
|
||||
var initializer = scope.ServiceProvider.GetRequiredService<ApplicationDbContextInitializer>();
|
||||
|
||||
await initialiser.InitialiseAsync();
|
||||
await initializer.InitialiseAsync();
|
||||
|
||||
await initialiser.SeedAsync();
|
||||
await initializer.SeedAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public class ApplicationDbContextInitialiser
|
||||
public class ApplicationDbContextInitializer(
|
||||
ILogger<ApplicationDbContextInitializer> logger,
|
||||
ApplicationDbContext context,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
RoleManager<IdentityRole> roleManager)
|
||||
{
|
||||
private readonly ILogger<ApplicationDbContextInitialiser> _logger;
|
||||
private readonly ApplicationDbContext _context;
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly RoleManager<IdentityRole> _roleManager;
|
||||
|
||||
public ApplicationDbContextInitialiser(ILogger<ApplicationDbContextInitialiser> logger, ApplicationDbContext context, UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_context = context;
|
||||
_userManager = userManager;
|
||||
_roleManager = roleManager;
|
||||
}
|
||||
|
||||
public async Task InitialiseAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await _context.Database.MigrateAsync();
|
||||
await context.Database.MigrateAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "An error occurred while initialising the database.");
|
||||
logger.LogError(ex, "An error occurred while initialising the database.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -60,38 +50,38 @@ public class ApplicationDbContextInitialiser
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "An error occurred while seeding the database.");
|
||||
logger.LogError(ex, "An error occurred while seeding the database.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task TrySeedAsync()
|
||||
private async Task TrySeedAsync()
|
||||
{
|
||||
// Default roles
|
||||
var administratorRole = new IdentityRole(Roles.Administrator);
|
||||
|
||||
if (_roleManager.Roles.All(r => r.Name != administratorRole.Name))
|
||||
if (roleManager.Roles.All(r => r.Name != administratorRole.Name))
|
||||
{
|
||||
await _roleManager.CreateAsync(administratorRole);
|
||||
await roleManager.CreateAsync(administratorRole);
|
||||
}
|
||||
|
||||
// Default users
|
||||
var administrator = new ApplicationUser { UserName = "administrator@localhost", Email = "administrator@localhost" };
|
||||
|
||||
if (_userManager.Users.All(u => u.UserName != administrator.UserName))
|
||||
if (userManager.Users.All(u => u.UserName != administrator.UserName))
|
||||
{
|
||||
await _userManager.CreateAsync(administrator, "Administrator1!");
|
||||
await userManager.CreateAsync(administrator, "Administrator1!");
|
||||
if (!string.IsNullOrWhiteSpace(administratorRole.Name))
|
||||
{
|
||||
await _userManager.AddToRolesAsync(administrator, new [] { administratorRole.Name });
|
||||
await userManager.AddToRolesAsync(administrator, new [] { administratorRole.Name });
|
||||
}
|
||||
}
|
||||
|
||||
// Default data
|
||||
// Seed, if necessary
|
||||
if (!_context.TodoLists.Any())
|
||||
if (!context.TodoLists.Any())
|
||||
{
|
||||
_context.TodoLists.Add(new TodoList
|
||||
context.TodoLists.Add(new TodoList
|
||||
{
|
||||
Title = "Todo List",
|
||||
Items =
|
||||
@@ -103,7 +93,7 @@ public class ApplicationDbContextInitialiser
|
||||
}
|
||||
});
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,19 +6,10 @@ using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||
|
||||
namespace Hutopy.Infrastructure.Data.Interceptors;
|
||||
|
||||
public class AuditableEntityInterceptor : SaveChangesInterceptor
|
||||
public class AuditableEntityInterceptor(
|
||||
IUser user,
|
||||
TimeProvider dateTime) : SaveChangesInterceptor
|
||||
{
|
||||
private readonly IUser _user;
|
||||
private readonly TimeProvider _dateTime;
|
||||
|
||||
public AuditableEntityInterceptor(
|
||||
IUser user,
|
||||
TimeProvider dateTime)
|
||||
{
|
||||
_user = user;
|
||||
_dateTime = dateTime;
|
||||
}
|
||||
|
||||
public override InterceptionResult<int> SavingChanges(DbContextEventData eventData, InterceptionResult<int> result)
|
||||
{
|
||||
UpdateEntities(eventData.Context);
|
||||
@@ -41,13 +32,13 @@ public class AuditableEntityInterceptor : SaveChangesInterceptor
|
||||
{
|
||||
if (entry.State is EntityState.Added or EntityState.Modified || entry.HasChangedOwnedEntities())
|
||||
{
|
||||
var utcNow = _dateTime.GetUtcNow();
|
||||
var utcNow = dateTime.GetUtcNow();
|
||||
if (entry.State == EntityState.Added)
|
||||
{
|
||||
entry.Entity.CreatedBy = _user.Id;
|
||||
entry.Entity.CreatedBy = user.Id;
|
||||
entry.Entity.Created = utcNow;
|
||||
}
|
||||
entry.Entity.LastModifiedBy = _user.Id;
|
||||
entry.Entity.LastModifiedBy = user.Id;
|
||||
entry.Entity.LastModified = utcNow;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,15 +5,10 @@ using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||
|
||||
namespace Hutopy.Infrastructure.Data.Interceptors;
|
||||
|
||||
public class DispatchDomainEventsInterceptor : SaveChangesInterceptor
|
||||
public class DispatchDomainEventsInterceptor(
|
||||
IPublisher mediator)
|
||||
: SaveChangesInterceptor
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
|
||||
public DispatchDomainEventsInterceptor(IMediator mediator)
|
||||
{
|
||||
_mediator = mediator;
|
||||
}
|
||||
|
||||
public override InterceptionResult<int> SavingChanges(DbContextEventData eventData, InterceptionResult<int> result)
|
||||
{
|
||||
DispatchDomainEvents(eventData.Context).GetAwaiter().GetResult();
|
||||
@@ -45,6 +40,6 @@ public class DispatchDomainEventsInterceptor : SaveChangesInterceptor
|
||||
entities.ToList().ForEach(e => e.ClearDomainEvents());
|
||||
|
||||
foreach (var domainEvent in domainEvents)
|
||||
await _mediator.Publish(domainEvent);
|
||||
await mediator.Publish(domainEvent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,9 @@ using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection;
|
||||
namespace Hutopy.Infrastructure;
|
||||
|
||||
public static class DependencyInjection
|
||||
{
|
||||
@@ -34,7 +35,7 @@ public static class DependencyInjection
|
||||
|
||||
services.AddScoped<IApplicationDbContext>(provider => provider.GetRequiredService<ApplicationDbContext>());
|
||||
|
||||
services.AddScoped<ApplicationDbContextInitialiser>();
|
||||
services.AddScoped<ApplicationDbContextInitializer>();
|
||||
|
||||
services.AddAuthentication()
|
||||
.AddBearerToken(IdentityConstants.BearerScheme);
|
||||
|
||||
@@ -5,25 +5,15 @@ using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Hutopy.Infrastructure.Identity;
|
||||
|
||||
public class IdentityService : IIdentityService
|
||||
public class IdentityService(
|
||||
UserManager<ApplicationUser> userManager,
|
||||
IUserClaimsPrincipalFactory<ApplicationUser> userClaimsPrincipalFactory,
|
||||
IAuthorizationService authorizationService)
|
||||
: IIdentityService
|
||||
{
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly IUserClaimsPrincipalFactory<ApplicationUser> _userClaimsPrincipalFactory;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
|
||||
public IdentityService(
|
||||
UserManager<ApplicationUser> userManager,
|
||||
IUserClaimsPrincipalFactory<ApplicationUser> userClaimsPrincipalFactory,
|
||||
IAuthorizationService authorizationService)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_userClaimsPrincipalFactory = userClaimsPrincipalFactory;
|
||||
_authorizationService = authorizationService;
|
||||
}
|
||||
|
||||
public async Task<string?> GetUserNameAsync(string userId)
|
||||
{
|
||||
var user = await _userManager.FindByIdAsync(userId);
|
||||
var user = await userManager.FindByIdAsync(userId);
|
||||
|
||||
return user?.UserName;
|
||||
}
|
||||
@@ -36,44 +26,44 @@ public class IdentityService : IIdentityService
|
||||
Email = userName,
|
||||
};
|
||||
|
||||
var result = await _userManager.CreateAsync(user, password);
|
||||
var result = await userManager.CreateAsync(user, password);
|
||||
|
||||
return (result.ToApplicationResult(), user.Id);
|
||||
}
|
||||
|
||||
public async Task<bool> IsInRoleAsync(string userId, string role)
|
||||
{
|
||||
var user = await _userManager.FindByIdAsync(userId);
|
||||
var user = await userManager.FindByIdAsync(userId);
|
||||
|
||||
return user != null && await _userManager.IsInRoleAsync(user, role);
|
||||
return user != null && await userManager.IsInRoleAsync(user, role);
|
||||
}
|
||||
|
||||
public async Task<bool> AuthorizeAsync(string userId, string policyName)
|
||||
{
|
||||
var user = await _userManager.FindByIdAsync(userId);
|
||||
var user = await userManager.FindByIdAsync(userId);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var principal = await _userClaimsPrincipalFactory.CreateAsync(user);
|
||||
var principal = await userClaimsPrincipalFactory.CreateAsync(user);
|
||||
|
||||
var result = await _authorizationService.AuthorizeAsync(principal, policyName);
|
||||
var result = await authorizationService.AuthorizeAsync(principal, policyName);
|
||||
|
||||
return result.Succeeded;
|
||||
}
|
||||
|
||||
public async Task<Result> DeleteUserAsync(string userId)
|
||||
{
|
||||
var user = await _userManager.FindByIdAsync(userId);
|
||||
var user = await userManager.FindByIdAsync(userId);
|
||||
|
||||
return user != null ? await DeleteUserAsync(user) : Result.Success();
|
||||
}
|
||||
|
||||
public async Task<Result> DeleteUserAsync(ApplicationUser user)
|
||||
{
|
||||
var result = await _userManager.DeleteAsync(user);
|
||||
var result = await userManager.DeleteAsync(user);
|
||||
|
||||
return result.ToApplicationResult();
|
||||
}
|
||||
|
||||
@@ -16,22 +16,20 @@ public class StripeService : IStripeService
|
||||
{
|
||||
var options = new SessionCreateOptions
|
||||
{
|
||||
LineItems = new List<SessionLineItemOptions>
|
||||
{
|
||||
LineItems =
|
||||
[
|
||||
new SessionLineItemOptions
|
||||
{
|
||||
PriceData = new SessionLineItemPriceDataOptions
|
||||
{
|
||||
UnitAmount = price,
|
||||
Currency = currency,
|
||||
ProductData = new SessionLineItemPriceDataProductDataOptions
|
||||
PriceData = new SessionLineItemPriceDataOptions
|
||||
{
|
||||
Name = "Tip",
|
||||
UnitAmount = price,
|
||||
Currency = currency,
|
||||
ProductData = new SessionLineItemPriceDataProductDataOptions { Name = "Tip", },
|
||||
},
|
||||
},
|
||||
Quantity = 1,
|
||||
},
|
||||
},
|
||||
Quantity = 1,
|
||||
}
|
||||
|
||||
],
|
||||
Mode = "payment",
|
||||
UiMode = "embedded",
|
||||
ReturnUrl = $"http://localhost:5173/creatorfolio",
|
||||
|
||||
@@ -3,11 +3,10 @@ using Hutopy.Application.Common.Interfaces;
|
||||
using Hutopy.Infrastructure.Data;
|
||||
using Hutopy.Web.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
using NSwag;
|
||||
using NSwag.Generation.Processors.Security;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection;
|
||||
namespace Hutopy.Web;
|
||||
|
||||
public static class DependencyInjection
|
||||
{
|
||||
|
||||
@@ -10,7 +10,7 @@ public class JoinUs : EndpointGroupBase
|
||||
.MapPost(CreateFutureCreator);
|
||||
}
|
||||
|
||||
public Task<int> CreateFutureCreator(ISender sender, CreateFutureCreatorCommand command)
|
||||
private static Task<int> CreateFutureCreator(ISender sender, CreateFutureCreatorCommand command)
|
||||
{
|
||||
return sender.Send(command);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ public class Stripe : EndpointGroupBase
|
||||
.MapPost(CreateSessionCheckout);
|
||||
}
|
||||
|
||||
public Task<string> CreateSessionCheckout(ISender sender, CreateSessionCheckoutCommand command)
|
||||
private static Task<string> CreateSessionCheckout(ISender sender, CreateSessionCheckoutCommand command)
|
||||
{
|
||||
return sender.Send(command);
|
||||
}
|
||||
|
||||
@@ -20,31 +20,31 @@ public class TodoItems : EndpointGroupBase
|
||||
.MapDelete(DeleteTodoItem, "{id}");
|
||||
}
|
||||
|
||||
public Task<PaginatedList<TodoItemBriefDto>> GetTodoItemsWithPagination(ISender sender, [AsParameters] GetTodoItemsWithPaginationQuery query)
|
||||
private static Task<PaginatedList<TodoItemBriefDto>> GetTodoItemsWithPagination(ISender sender, [AsParameters] GetTodoItemsWithPaginationQuery query)
|
||||
{
|
||||
return sender.Send(query);
|
||||
}
|
||||
|
||||
public Task<int> CreateTodoItem(ISender sender, CreateTodoItemCommand command)
|
||||
private static Task<int> CreateTodoItem(ISender sender, CreateTodoItemCommand command)
|
||||
{
|
||||
return sender.Send(command);
|
||||
}
|
||||
|
||||
public async Task<IResult> UpdateTodoItem(ISender sender, int id, UpdateTodoItemCommand command)
|
||||
private static async Task<IResult> UpdateTodoItem(ISender sender, int id, UpdateTodoItemCommand command)
|
||||
{
|
||||
if (id != command.Id) return Results.BadRequest();
|
||||
await sender.Send(command);
|
||||
return Results.NoContent();
|
||||
}
|
||||
|
||||
public async Task<IResult> UpdateTodoItemDetail(ISender sender, int id, UpdateTodoItemDetailCommand command)
|
||||
private static async Task<IResult> UpdateTodoItemDetail(ISender sender, int id, UpdateTodoItemDetailCommand command)
|
||||
{
|
||||
if (id != command.Id) return Results.BadRequest();
|
||||
await sender.Send(command);
|
||||
return Results.NoContent();
|
||||
}
|
||||
|
||||
public async Task<IResult> DeleteTodoItem(ISender sender, int id)
|
||||
private static async Task<IResult> DeleteTodoItem(ISender sender, int id)
|
||||
{
|
||||
await sender.Send(new DeleteTodoItemCommand(id));
|
||||
return Results.NoContent();
|
||||
|
||||
@@ -17,24 +17,24 @@ public class TodoLists : EndpointGroupBase
|
||||
.MapDelete(DeleteTodoList, "{id}");
|
||||
}
|
||||
|
||||
public Task<TodosVm> GetTodoLists(ISender sender)
|
||||
private static Task<TodosVm> GetTodoLists(ISender sender)
|
||||
{
|
||||
return sender.Send(new GetTodosQuery());
|
||||
return sender.Send(new GetTodosQuery());
|
||||
}
|
||||
|
||||
public Task<int> CreateTodoList(ISender sender, CreateTodoListCommand command)
|
||||
private static Task<int> CreateTodoList(ISender sender, CreateTodoListCommand command)
|
||||
{
|
||||
return sender.Send(command);
|
||||
}
|
||||
|
||||
public async Task<IResult> UpdateTodoList(ISender sender, int id, UpdateTodoListCommand command)
|
||||
private static async Task<IResult> UpdateTodoList(ISender sender, int id, UpdateTodoListCommand command)
|
||||
{
|
||||
if (id != command.Id) return Results.BadRequest();
|
||||
await sender.Send(command);
|
||||
return Results.NoContent();
|
||||
}
|
||||
|
||||
public async Task<IResult> DeleteTodoList(ISender sender, int id)
|
||||
private static async Task<IResult> DeleteTodoList(ISender sender, int id)
|
||||
{
|
||||
await sender.Send(new DeleteTodoListCommand(id));
|
||||
return Results.NoContent();
|
||||
|
||||
@@ -11,7 +11,7 @@ public class WeatherForecasts : EndpointGroupBase
|
||||
.MapGet(GetWeatherForecasts);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<WeatherForecast>> GetWeatherForecasts(ISender sender)
|
||||
private static async Task<IEnumerable<WeatherForecast>> GetWeatherForecasts(ISender sender)
|
||||
{
|
||||
return await sender.Send(new GetWeatherForecastsQuery());
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ public class CustomExceptionHandler : IExceptionHandler
|
||||
public CustomExceptionHandler()
|
||||
{
|
||||
// Register known exception types and handlers.
|
||||
_exceptionHandlers = new()
|
||||
{
|
||||
_exceptionHandlers = new Dictionary<Type, Func<HttpContext, Exception, Task>>
|
||||
{
|
||||
{ typeof(ValidationException), HandleValidationException },
|
||||
{ typeof(NotFoundException), HandleNotFoundException },
|
||||
{ typeof(UnauthorizedAccessException), HandleUnauthorizedAccessException },
|
||||
@@ -24,16 +24,14 @@ public class CustomExceptionHandler : IExceptionHandler
|
||||
{
|
||||
var exceptionType = exception.GetType();
|
||||
|
||||
if (_exceptionHandlers.ContainsKey(exceptionType))
|
||||
{
|
||||
await _exceptionHandlers[exceptionType].Invoke(httpContext, exception);
|
||||
return true;
|
||||
}
|
||||
if (!_exceptionHandlers.TryGetValue(exceptionType, out Func<HttpContext, Exception, Task>? value)) return false;
|
||||
|
||||
await value.Invoke(httpContext, exception);
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async Task HandleValidationException(HttpContext httpContext, Exception ex)
|
||||
private static async Task HandleValidationException(HttpContext httpContext, Exception ex)
|
||||
{
|
||||
var exception = (ValidationException)ex;
|
||||
|
||||
@@ -46,7 +44,7 @@ public class CustomExceptionHandler : IExceptionHandler
|
||||
});
|
||||
}
|
||||
|
||||
private async Task HandleNotFoundException(HttpContext httpContext, Exception ex)
|
||||
private static async Task HandleNotFoundException(HttpContext httpContext, Exception ex)
|
||||
{
|
||||
var exception = (NotFoundException)ex;
|
||||
|
||||
@@ -61,7 +59,7 @@ public class CustomExceptionHandler : IExceptionHandler
|
||||
});
|
||||
}
|
||||
|
||||
private async Task HandleUnauthorizedAccessException(HttpContext httpContext, Exception ex)
|
||||
private static async Task HandleUnauthorizedAccessException(HttpContext httpContext, Exception ex)
|
||||
{
|
||||
httpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
|
||||
|
||||
@@ -73,7 +71,7 @@ public class CustomExceptionHandler : IExceptionHandler
|
||||
});
|
||||
}
|
||||
|
||||
private async Task HandleForbiddenAccessException(HttpContext httpContext, Exception ex)
|
||||
private static async Task HandleForbiddenAccessException(HttpContext httpContext, Exception ex)
|
||||
{
|
||||
httpContext.Response.StatusCode = StatusCodes.Status403Forbidden;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace Hutopy.Web.Infrastructure;
|
||||
|
||||
public static class MethodInfoExtensions
|
||||
{
|
||||
public static bool IsAnonymous(this MethodInfo method)
|
||||
private static bool IsAnonymous(this MethodInfo method)
|
||||
{
|
||||
var invalidChars = new[] { '<', '>' };
|
||||
return method.Name.Any(invalidChars.Contains);
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using Hutopy.Application;
|
||||
using Hutopy.Infrastructure;
|
||||
using Hutopy.Infrastructure.Data;
|
||||
using Hutopy.Web;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
@@ -61,4 +64,4 @@ app.MapEndpoints();
|
||||
|
||||
app.Run();
|
||||
|
||||
public partial class Program { }
|
||||
public abstract partial class Program { }
|
||||
|
||||
@@ -4,14 +4,9 @@ using Hutopy.Application.Common.Interfaces;
|
||||
|
||||
namespace Hutopy.Web.Services;
|
||||
|
||||
public class CurrentUser : IUser
|
||||
public class CurrentUser(
|
||||
IHttpContextAccessor httpContextAccessor)
|
||||
: IUser
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
public CurrentUser(IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
public string? Id => _httpContextAccessor.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
public string? Id => httpContextAccessor.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user