Removed todos and added User endpoint combined with register. Id changed to guid

This commit is contained in:
Dominic Villemure
2024-04-03 23:57:01 -04:00
parent df8b42bdf1
commit 1f795c53bb
67 changed files with 1750 additions and 1895 deletions

View File

@@ -4,11 +4,9 @@ namespace Hutopy.Application.Common.Interfaces;
public interface IApplicationDbContext
{
DbSet<TodoList> TodoLists { get; }
DbSet<TodoItem> TodoItems { get; }
DbSet<FutureCreator> FutureCreators { get; }
DbSet<User> DomainUsers { get; }
Task<int> SaveChangesAsync(CancellationToken cancellationToken);
}

View File

@@ -1,19 +0,0 @@
using Hutopy.Domain.Entities;
namespace Hutopy.Application.Common.Models;
public class LookupDto
{
public int Id { get; init; }
public string? Title { get; init; }
private class Mapping : Profile
{
public Mapping()
{
CreateMap<TodoList, LookupDto>();
CreateMap<TodoItem, LookupDto>();
}
}
}

View File

@@ -3,7 +3,7 @@ using Hutopy.Domain.Entities;
namespace Hutopy.Application.FutureCreators.Commands;
public abstract record CreateFutureCreatorCommand : IRequest<int>
public record CreateFutureCreatorCommand : IRequest<Guid>
{
public required string FirstName { get; init; }
public required string LastName { get; init; }
@@ -15,9 +15,9 @@ public abstract record CreateFutureCreatorCommand : IRequest<int>
public class CreateFutureCreatorCommandHandler(
IApplicationDbContext context)
: IRequestHandler<CreateFutureCreatorCommand, int>
: IRequestHandler<CreateFutureCreatorCommand, Guid>
{
public async Task<int> Handle(CreateFutureCreatorCommand request, CancellationToken cancellationToken)
public async Task<Guid> Handle(CreateFutureCreatorCommand request, CancellationToken cancellationToken)
{
var entity = new FutureCreator
{

View File

@@ -0,0 +1,20 @@
using Hutopy.Domain.Entities;
namespace Hutopy.Application.FutureCreators.Queries;
public class FutureCreatorListDto
{
public Guid Id { get; init; }
public required string FirstName { get; init; }
public required string LastName { get; init; }
private class Mapping : Profile
{
public Mapping()
{
CreateMap<FutureCreator, FutureCreatorListDto>();
}
}
}

View File

@@ -0,0 +1,27 @@
using Hutopy.Application.Common.Interfaces;
using Hutopy.Application.Common.Mappings;
using Hutopy.Application.Common.Models;
namespace Hutopy.Application.FutureCreators.Queries;
public record GetFutureCreatorListQuery : IRequest<PaginatedList<FutureCreatorListDto>>
{
public int PageNumber { get; init; } = 1;
public int PageSize { get; init; } = 10;
}
public class GetFutureCreatorListQueryHandler(
IApplicationDbContext context,
IMapper mapper)
: IRequestHandler<GetFutureCreatorListQuery, PaginatedList<FutureCreatorListDto>>
{
public async Task<PaginatedList<FutureCreatorListDto>> Handle(GetFutureCreatorListQuery request, CancellationToken cancellationToken)
{
Console.WriteLine(request);
return await context.FutureCreators
.OrderBy(x => x.FirstName)
.ProjectTo<FutureCreatorListDto>(mapper.ConfigurationProvider)
.PaginatedListAsync(request.PageNumber, request.PageSize);
}
}

View File

@@ -1,35 +0,0 @@
using Hutopy.Application.Common.Interfaces;
using Hutopy.Domain.Entities;
using Hutopy.Domain.Events;
namespace Hutopy.Application.TodoItems.Commands.CreateTodoItem;
public record CreateTodoItemCommand : IRequest<int>
{
public int ListId { get; init; }
public string? Title { get; init; }
}
public class CreateTodoItemCommandHandler(
IApplicationDbContext context)
: IRequestHandler<CreateTodoItemCommand, int>
{
public async Task<int> Handle(CreateTodoItemCommand request, CancellationToken cancellationToken)
{
var entity = new TodoItem
{
ListId = request.ListId,
Title = request.Title,
Done = false
};
entity.AddDomainEvent(new TodoItemCreatedEvent(entity));
context.TodoItems.Add(entity);
await context.SaveChangesAsync(cancellationToken);
return entity.Id;
}
}

View File

@@ -1,11 +0,0 @@
namespace Hutopy.Application.TodoItems.Commands.CreateTodoItem;
public class CreateTodoItemCommandValidator : AbstractValidator<CreateTodoItemCommand>
{
public CreateTodoItemCommandValidator()
{
RuleFor(v => v.Title)
.MaximumLength(200)
.NotEmpty();
}
}

View File

@@ -1,30 +0,0 @@
using Hutopy.Application.Common.Interfaces;
using Hutopy.Domain.Events;
using Hutopy.Application.Common.Security;
using Hutopy.Domain.Constants;
namespace Hutopy.Application.TodoItems.Commands.DeleteTodoItem;
[Authorize(Roles = Roles.Administrator)]
[Authorize(Policy = Policies.CanDelete)]
public record DeleteTodoItemCommand(int Id) : IRequest;
public class DeleteTodoItemCommandHandler(
IApplicationDbContext context)
: IRequestHandler<DeleteTodoItemCommand>
{
public async Task Handle(DeleteTodoItemCommand request, CancellationToken cancellationToken)
{
var entity = await context.TodoItems
.FindAsync(new object[] { request.Id }, cancellationToken);
Guard.Against.NotFound(request.Id, entity);
context.TodoItems.Remove(entity);
entity.AddDomainEvent(new TodoItemDeletedEvent(entity));
await context.SaveChangesAsync(cancellationToken);
}
}

View File

@@ -1,30 +0,0 @@
using Hutopy.Application.Common.Interfaces;
namespace Hutopy.Application.TodoItems.Commands.UpdateTodoItem;
public record UpdateTodoItemCommand : IRequest
{
public int Id { get; init; }
public string? Title { get; init; }
public bool Done { get; init; }
}
public class UpdateTodoItemCommandHandler(
IApplicationDbContext context)
: IRequestHandler<UpdateTodoItemCommand>
{
public async Task Handle(UpdateTodoItemCommand request, CancellationToken cancellationToken)
{
var entity = await context.TodoItems
.FindAsync(new object[] { request.Id }, cancellationToken);
Guard.Against.NotFound(request.Id, entity);
entity.Title = request.Title;
entity.Done = request.Done;
await context.SaveChangesAsync(cancellationToken);
}
}

View File

@@ -1,11 +0,0 @@
namespace Hutopy.Application.TodoItems.Commands.UpdateTodoItem;
public class UpdateTodoItemCommandValidator : AbstractValidator<UpdateTodoItemCommand>
{
public UpdateTodoItemCommandValidator()
{
RuleFor(v => v.Title)
.MaximumLength(200)
.NotEmpty();
}
}

View File

@@ -1,34 +0,0 @@
using Hutopy.Application.Common.Interfaces;
using Hutopy.Domain.Enums;
namespace Hutopy.Application.TodoItems.Commands.UpdateTodoItemDetail;
public record UpdateTodoItemDetailCommand : IRequest
{
public int Id { get; init; }
public int ListId { get; init; }
public PriorityLevel Priority { get; init; }
public string? Note { get; init; }
}
public class UpdateTodoItemDetailCommandHandler(
IApplicationDbContext context)
: IRequestHandler<UpdateTodoItemDetailCommand>
{
public async Task Handle(UpdateTodoItemDetailCommand request, CancellationToken cancellationToken)
{
var entity = await context.TodoItems
.FindAsync(new object[] { request.Id }, cancellationToken);
Guard.Against.NotFound(request.Id, entity);
entity.ListId = request.ListId;
entity.Priority = request.Priority;
entity.Note = request.Note;
await context.SaveChangesAsync(cancellationToken);
}
}

View File

@@ -1,16 +0,0 @@
using Hutopy.Domain.Events;
using Microsoft.Extensions.Logging;
namespace Hutopy.Application.TodoItems.EventHandlers;
public class TodoItemCompletedEventHandler(
ILogger<TodoItemCompletedEventHandler> logger)
: INotificationHandler<TodoItemCompletedEvent>
{
public Task Handle(TodoItemCompletedEvent notification, CancellationToken cancellationToken)
{
logger.LogInformation("Hutopy Domain Event: {DomainEvent}", notification.GetType().Name);
return Task.CompletedTask;
}
}

View File

@@ -1,16 +0,0 @@
using Hutopy.Domain.Events;
using Microsoft.Extensions.Logging;
namespace Hutopy.Application.TodoItems.EventHandlers;
public class TodoItemCreatedEventHandler(
ILogger<TodoItemCreatedEventHandler> logger)
: INotificationHandler<TodoItemCreatedEvent>
{
public Task Handle(TodoItemCreatedEvent notification, CancellationToken cancellationToken)
{
logger.LogInformation("Hutopy Domain Event: {DomainEvent}", notification.GetType().Name);
return Task.CompletedTask;
}
}

View File

@@ -1,28 +0,0 @@
using Hutopy.Application.Common.Interfaces;
using Hutopy.Application.Common.Mappings;
using Hutopy.Application.Common.Models;
namespace Hutopy.Application.TodoItems.Queries.GetTodoItemsWithPagination;
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(
IApplicationDbContext context,
IMapper mapper)
: IRequestHandler<GetTodoItemsWithPaginationQuery, PaginatedList<TodoItemBriefDto>>
{
public async Task<PaginatedList<TodoItemBriefDto>> Handle(GetTodoItemsWithPaginationQuery request, CancellationToken cancellationToken)
{
Console.WriteLine(request);
return await context.TodoItems
.Where(x => x.ListId == request.ListId)
.OrderBy(x => x.Title)
.ProjectTo<TodoItemBriefDto>(mapper.ConfigurationProvider)
.PaginatedListAsync(request.PageNumber, request.PageSize);
}
}

View File

@@ -1,16 +0,0 @@
namespace Hutopy.Application.TodoItems.Queries.GetTodoItemsWithPagination;
public class GetTodoItemsWithPaginationQueryValidator : AbstractValidator<GetTodoItemsWithPaginationQuery>
{
public GetTodoItemsWithPaginationQueryValidator()
{
RuleFor(x => x.ListId)
.NotEmpty().WithMessage("ListId is required.");
RuleFor(x => x.PageNumber)
.GreaterThanOrEqualTo(1).WithMessage("PageNumber at least greater than or equal to 1.");
RuleFor(x => x.PageSize)
.GreaterThanOrEqualTo(1).WithMessage("PageSize at least greater than or equal to 1.");
}
}

View File

@@ -1,22 +0,0 @@
using Hutopy.Domain.Entities;
namespace Hutopy.Application.TodoItems.Queries.GetTodoItemsWithPagination;
public class TodoItemBriefDto
{
public int Id { get; init; }
public int ListId { get; init; }
public string? Title { get; init; }
public bool Done { get; init; }
private class Mapping : Profile
{
public Mapping()
{
CreateMap<TodoItem, TodoItemBriefDto>();
}
}
}

View File

@@ -1,25 +0,0 @@
using Hutopy.Application.Common.Interfaces;
using Hutopy.Domain.Entities;
namespace Hutopy.Application.TodoLists.Commands.CreateTodoList;
public record CreateTodoListCommand : IRequest<int>
{
public string? Title { get; init; }
}
public class CreateTodoListCommandHandler(
IApplicationDbContext context)
: IRequestHandler<CreateTodoListCommand, int>
{
public async Task<int> Handle(CreateTodoListCommand request, CancellationToken cancellationToken)
{
var entity = new TodoList { Title = request.Title };
context.TodoLists.Add(entity);
await context.SaveChangesAsync(cancellationToken);
return entity.Id;
}
}

View File

@@ -1,26 +0,0 @@
using Hutopy.Application.Common.Interfaces;
namespace Hutopy.Application.TodoLists.Commands.CreateTodoList;
public class CreateTodoListCommandValidator : AbstractValidator<CreateTodoListCommand>
{
private readonly IApplicationDbContext _context;
public CreateTodoListCommandValidator(IApplicationDbContext context)
{
_context = context;
RuleFor(v => v.Title)
.NotEmpty()
.MaximumLength(200)
.MustAsync(BeUniqueTitle)
.WithMessage("'{PropertyName}' must be unique.")
.WithErrorCode("Unique");
}
public async Task<bool> BeUniqueTitle(string title, CancellationToken cancellationToken)
{
return await _context.TodoLists
.AllAsync(l => l.Title != title, cancellationToken);
}
}

View File

@@ -1,23 +0,0 @@
using Hutopy.Application.Common.Interfaces;
namespace Hutopy.Application.TodoLists.Commands.DeleteTodoList;
public record DeleteTodoListCommand(int Id) : IRequest;
public class DeleteTodoListCommandHandler(
IApplicationDbContext context)
: IRequestHandler<DeleteTodoListCommand>
{
public async Task Handle(DeleteTodoListCommand request, CancellationToken cancellationToken)
{
var entity = await context.TodoLists
.Where(l => l.Id == request.Id)
.SingleOrDefaultAsync(cancellationToken);
Guard.Against.NotFound(request.Id, entity);
context.TodoLists.Remove(entity);
await context.SaveChangesAsync(cancellationToken);
}
}

View File

@@ -1,21 +0,0 @@
using Hutopy.Application.Common.Interfaces;
using Hutopy.Application.Common.Security;
using Hutopy.Domain.Constants;
namespace Hutopy.Application.TodoLists.Commands.PurgeTodoLists;
[Authorize(Roles = Roles.Administrator)]
[Authorize(Policy = Policies.CanPurge)]
public record PurgeTodoListsCommand : IRequest;
public class PurgeTodoListsCommandHandler(
IApplicationDbContext context)
: IRequestHandler<PurgeTodoListsCommand>
{
public async Task Handle(PurgeTodoListsCommand request, CancellationToken cancellationToken)
{
context.TodoLists.RemoveRange(context.TodoLists);
await context.SaveChangesAsync(cancellationToken);
}
}

View File

@@ -1,28 +0,0 @@
using Hutopy.Application.Common.Interfaces;
namespace Hutopy.Application.TodoLists.Commands.UpdateTodoList;
public record UpdateTodoListCommand : IRequest
{
public int Id { get; init; }
public string? Title { get; init; }
}
public class UpdateTodoListCommandHandler(
IApplicationDbContext context)
: IRequestHandler<UpdateTodoListCommand>
{
public async Task Handle(UpdateTodoListCommand request, CancellationToken cancellationToken)
{
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);
}
}

View File

@@ -1,27 +0,0 @@
using Hutopy.Application.Common.Interfaces;
namespace Hutopy.Application.TodoLists.Commands.UpdateTodoList;
public class UpdateTodoListCommandValidator : AbstractValidator<UpdateTodoListCommand>
{
private readonly IApplicationDbContext _context;
public UpdateTodoListCommandValidator(IApplicationDbContext context)
{
_context = context;
RuleFor(v => v.Title)
.NotEmpty()
.MaximumLength(200)
.MustAsync(BeUniqueTitle)
.WithMessage("'{PropertyName}' must be unique.")
.WithErrorCode("Unique");
}
private async Task<bool> BeUniqueTitle(UpdateTodoListCommand model, string title, CancellationToken cancellationToken)
{
return await _context.TodoLists
.Where(l => l.Id != model.Id)
.AllAsync(l => l.Title != title, cancellationToken);
}
}

View File

@@ -1,32 +0,0 @@
using Hutopy.Application.Common.Interfaces;
using Hutopy.Application.Common.Models;
using Hutopy.Application.Common.Security;
using Hutopy.Domain.Enums;
namespace Hutopy.Application.TodoLists.Queries.GetTodos;
[Authorize]
public record GetTodosQuery : IRequest<TodosVm>;
public class GetTodosQueryHandler(
IApplicationDbContext context,
IMapper mapper)
: IRequestHandler<GetTodosQuery, TodosVm>
{
public async Task<TodosVm> Handle(GetTodosQuery request, CancellationToken cancellationToken)
{
return new TodosVm
{
PriorityLevels = Enum.GetValues(typeof(PriorityLevel))
.Cast<PriorityLevel>()
.Select(p => new LookupDto { Id = (int)p, Title = p.ToString() })
.ToList(),
Lists = await context.TodoLists
.AsNoTracking()
.ProjectTo<TodoListDto>(mapper.ConfigurationProvider)
.OrderBy(t => t.Title)
.ToListAsync(cancellationToken)
};
}
}

View File

@@ -1,27 +0,0 @@
using Hutopy.Domain.Entities;
namespace Hutopy.Application.TodoLists.Queries.GetTodos;
public class TodoItemDto
{
public int Id { get; init; }
public int ListId { get; init; }
public string? Title { get; init; }
public bool Done { get; init; }
public int Priority { get; init; }
public string? Note { get; init; }
private class Mapping : Profile
{
public Mapping()
{
CreateMap<TodoItem, TodoItemDto>().ForMember(d => d.Priority,
opt => opt.MapFrom(s => (int)s.Priority));
}
}
}

View File

@@ -1,22 +0,0 @@
using Hutopy.Domain.Entities;
namespace Hutopy.Application.TodoLists.Queries.GetTodos;
public class TodoListDto
{
public int Id { get; init; }
public string? Title { get; init; }
public string? Colour { get; init; }
public IReadOnlyCollection<TodoItemDto> Items { get; init; } = Array.Empty<TodoItemDto>();
private class Mapping : Profile
{
public Mapping()
{
CreateMap<TodoList, TodoListDto>();
}
}
}

View File

@@ -1,10 +0,0 @@
using Hutopy.Application.Common.Models;
namespace Hutopy.Application.TodoLists.Queries.GetTodos;
public class TodosVm
{
public IReadOnlyCollection<LookupDto> PriorityLevels { get; init; } = Array.Empty<LookupDto>();
public IReadOnlyCollection<TodoListDto> Lists { get; init; } = Array.Empty<TodoListDto>();
}

View File

@@ -0,0 +1,39 @@
using Hutopy.Application.Common.Interfaces;
using Hutopy.Domain.Entities;
namespace Hutopy.Application.Users.Commands;
public record CreateUserCommand : IRequest<Guid>
{
public required string FirstName { get; init; }
public required string LastName { get; init; }
public required string EmailAddress { get; init; }
public required string UserName { get; init; }
public required string Password { get; init; }
}
public class CreateUserCommandHandler : IRequestHandler<CreateUserCommand, Guid>
{
private readonly IApplicationDbContext _context;
public CreateUserCommandHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<Guid> Handle(CreateUserCommand request, CancellationToken cancellationToken)
{
var entity = new User
{
FirstName = request.FirstName,
LastName = request.LastName,
EmailAddress = request.EmailAddress,
UserName = request.UserName,
};
_context.DomainUsers.Add(entity);
await _context.SaveChangesAsync(cancellationToken);
return entity.Id;
}
}