Skip to content

Commit

Permalink
Merge pull request #20 from snax4a/feature/inquiries-api
Browse files Browse the repository at this point in the history
Feature/inquiries api
  • Loading branch information
snax4a authored Feb 21, 2022
2 parents dc80c06 + 3f4a93e commit 6a2a8d3
Show file tree
Hide file tree
Showing 22 changed files with 286 additions and 34 deletions.
75 changes: 75 additions & 0 deletions src/Core/Application/Exchange/Inquiries/CreateInquiryRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using MassTransit;

namespace FSH.WebApi.Application.Exchange.Inquiries;

public class UserInquiriesSpec : Specification<Inquiry>, ISingleResultSpecification
{
public UserInquiriesSpec(Guid userId) => Query.Where(i => i.CreatedBy == userId);
}

public class CreateInquiryRequest : IRequest<Guid>
{
public string Name { get; set; } = default!;
public string Title { get; set; } = default!;
public IList<Guid> RecipientIds { get; set; } = default!;
public IList<InquiryProductDto> Products { get; set; } = default!;
}

public class InquiryProductValidator : CustomValidator<InquiryProductDto>
{
public InquiryProductValidator()
{
RuleFor(p => p.Name).NotEmpty().MinimumLength(3).MaximumLength(100);
RuleFor(p => p.Quantity).NotEmpty().GreaterThan(0);
RuleFor(p => p.PreferredDeliveryDate).NotEmpty().GreaterThanOrEqualTo(DateTime.UtcNow);
}
}

public class CreateInquiryRequestValidator : CustomValidator<CreateInquiryRequest>
{
public CreateInquiryRequestValidator(IStringLocalizer<CreateInquiryRequestValidator> localizer)
{
RuleFor(i => i.Name).NotEmpty().MinimumLength(3).MaximumLength(60);
RuleFor(i => i.Title).NotEmpty().MinimumLength(3).MaximumLength(100);

RuleFor(i => i.RecipientIds)
.Must(ids => ids.Count > 0)
.WithMessage(localizer["inquiry.norecipients"]);

RuleFor(i => i.Products)
.Must(products => products.Count > 0)
.WithMessage(localizer["inquiry.noproducts"]);

RuleForEach(i => i.Products).SetValidator(new InquiryProductValidator());
}
}

public class CreateInquiryRequestHandler : IRequestHandler<CreateInquiryRequest, Guid>
{
private readonly ICurrentUser _currentUser;

// Add Domain Events automatically by using IRepositoryWithEvents
private readonly IRepositoryWithEvents<Inquiry> _repository;

public CreateInquiryRequestHandler(ICurrentUser currentUser, IRepositoryWithEvents<Inquiry> repository) =>
(_currentUser, _repository) = (currentUser, repository);

public async Task<Guid> Handle(CreateInquiryRequest request, CancellationToken cancellationToken)
{
Guid inquiryId = NewId.Next().ToGuid();
List<InquiryProduct> products = new();

foreach (InquiryProductDto product in request.Products)
{
products.Add(new InquiryProduct(inquiryId, product.Name, product.Quantity, product.PreferredDeliveryDate));
}

var spec = new UserInquiriesSpec(_currentUser.GetUserId());
int referenceNumber = await _repository.CountAsync(spec, cancellationToken);
var inquiry = new Inquiry(inquiryId, referenceNumber + 1, request.Name, request.Title, products, request.RecipientIds);

await _repository.AddAsync(inquiry, cancellationToken);

return inquiry.Id;
}
}
36 changes: 36 additions & 0 deletions src/Core/Application/Exchange/Inquiries/GetInquiryRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
namespace FSH.WebApi.Application.Exchange.Inquiries;

public class GetInquiryRequest : IRequest<InquiryDetailsDto>
{
public Guid Id { get; set; }

public GetInquiryRequest(Guid id) => Id = id;
}

public class InquiryDetailsSpec : Specification<Inquiry, InquiryDetailsDto>, ISingleResultSpecification
{
public InquiryDetailsSpec(Guid id, Guid userId) =>
Query
.Where(i => i.Id == id && i.CreatedBy == userId)
.Include(i => i.Products)
.Include(i => i.InquiryRecipients);
}

public class GetInquiryRequestHandler : IRequestHandler<GetInquiryRequest, InquiryDetailsDto>
{
private readonly ICurrentUser _currentUser;
private readonly IRepository<Inquiry> _repository;
private readonly IStringLocalizer<GetInquiryRequestHandler> _localizer;

public GetInquiryRequestHandler(ICurrentUser currentUser, IRepository<Inquiry> repository, IStringLocalizer<GetInquiryRequestHandler> localizer) => (_currentUser, _repository, _localizer) = (currentUser, repository, localizer);

public async Task<InquiryDetailsDto> Handle(GetInquiryRequest request, CancellationToken cancellationToken)
{
ISpecification<Inquiry, InquiryDetailsDto> spec = new InquiryDetailsSpec(request.Id, _currentUser.GetUserId());
var inquiry = await _repository.GetBySpecAsync(spec, cancellationToken);

if (inquiry is not null) return inquiry;

throw new NotFoundException(string.Format(_localizer["inquiry.notfound"], request.Id));
}
}
16 changes: 16 additions & 0 deletions src/Core/Application/Exchange/Inquiries/InquiryDetailsDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using FSH.WebApi.Application.Exchange.Traders;

namespace FSH.WebApi.Application.Exchange.Inquiries;

public class InquiryDetailsDto : IDto
{
public Guid Id { get; set; }
public int ReferenceNumber { get; set; }
public string Name { get; set; } = default!;
public string Title { get; set; } = default!;
public DateTime CreatedOn { get; set; }
public int RecipientCount { get; set; }
public int OfferCount { get; set; }
public IList<InquiryProductDetailsDto> Products { get; set; } = default!;
public IList<TraderDto> Recipients { get; set; } = default!;
}
11 changes: 11 additions & 0 deletions src/Core/Application/Exchange/Inquiries/InquiryDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace FSH.WebApi.Application.Exchange.Inquiries;

public class InquiryDto : IDto
{
public Guid Id { get; set; }
public int ReferenceNumber { get; set; }
public string Name { get; set; } = default!;
public DateTime CreatedOn { get; set; }
public int RecipientCount { get; set; }
public int OfferCount { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace FSH.WebApi.Application.Exchange.Inquiries;

public class InquiryProductDetailsDto : IDto
{
public Guid Id { get; set; }
public Guid InquiryId { get; set; }
public string Name { get; set; } = default!;
public int Quantity { get; set; }
public DateTime PreferredDeliveryDate { get; set; }
}
8 changes: 8 additions & 0 deletions src/Core/Application/Exchange/Inquiries/InquiryProductDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace FSH.WebApi.Application.Exchange.Inquiries;

public class InquiryProductDto : IDto
{
public string Name { get; set; } = default!;
public int Quantity { get; set; }
public DateTime PreferredDeliveryDate { get; set; }
}
32 changes: 32 additions & 0 deletions src/Core/Application/Exchange/Inquiries/SearchInquiriesRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
namespace FSH.WebApi.Application.Exchange.Inquiries;

public class SearchInquiriesRequest : PaginationFilter, IRequest<PaginationResponse<InquiryDto>>
{
}

public class SearchInquiriesSpec : EntitiesByPaginationFilterSpec<Inquiry, InquiryDto>
{
public SearchInquiriesSpec(SearchInquiriesRequest request, Guid userId)
: base(request) => Query
.Where(i => i.CreatedBy == userId)
.OrderByDescending(i => i.ReferenceNumber, !request.HasOrderBy());
}

public class SearchInquiriesRequestHandler : IRequestHandler<SearchInquiriesRequest, PaginationResponse<InquiryDto>>
{
private readonly ICurrentUser _currentUser;
private readonly IReadRepository<Inquiry> _repository;

public SearchInquiriesRequestHandler(ICurrentUser currentUser, IReadRepository<Inquiry> repository) =>
(_currentUser, _repository) = (currentUser, repository);

public async Task<PaginationResponse<InquiryDto>> Handle(SearchInquiriesRequest request, CancellationToken cancellationToken)
{
var spec = new SearchInquiriesSpec(request, _currentUser.GetUserId());

var list = await _repository.ListAsync(spec, cancellationToken);
int count = await _repository.CountAsync(spec, cancellationToken);

return new PaginationResponse<InquiryDto>(list, count, request.PageNumber, request.PageSize);
}
}
8 changes: 4 additions & 4 deletions src/Core/Application/Exchange/Traders/GetTraderRequest.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
namespace FSH.WebApi.Application.Exchange.Traders;

public class GetTraderRequest : IRequest<TraderDto>
public class GetTraderRequest : IRequest<TraderDetailsDto>
{
public Guid Id { get; set; }

public GetTraderRequest(Guid id) => Id = id;
}

public class GetTraderRequestHandler : IRequestHandler<GetTraderRequest, TraderDto>
public class GetTraderRequestHandler : IRequestHandler<GetTraderRequest, TraderDetailsDto>
{
private readonly ICurrentUser _currentUser;
private readonly IRepository<Trader> _repository;
private readonly IStringLocalizer<GetTraderRequestHandler> _localizer;

public GetTraderRequestHandler(ICurrentUser currentUser, IRepository<Trader> repository, IStringLocalizer<GetTraderRequestHandler> localizer) => (_currentUser, _repository, _localizer) = (currentUser, repository, localizer);

public async Task<TraderDto> Handle(GetTraderRequest request, CancellationToken cancellationToken)
public async Task<TraderDetailsDto> Handle(GetTraderRequest request, CancellationToken cancellationToken)
{
ISpecification<Trader, TraderDto> spec = new TraderDetailsSpec(request.Id, _currentUser.GetUserId());
ISpecification<Trader, TraderDetailsDto> spec = new TraderDetailsSpec(request.Id, _currentUser.GetUserId());
var trader = await _repository.GetBySpecAsync(spec, cancellationToken);

if (trader is not null) return trader;
Expand Down
10 changes: 5 additions & 5 deletions src/Core/Application/Exchange/Traders/SearchTradersRequest.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
namespace FSH.WebApi.Application.Exchange.Traders;

public class SearchTradersRequest : PaginationFilter, IRequest<PaginationResponse<TraderDto>>
public class SearchTradersRequest : PaginationFilter, IRequest<PaginationResponse<TraderDetailsDto>>
{
}

public class SearchTradersSpec : EntitiesByPaginationFilterSpec<Trader, TraderDto>
public class SearchTradersSpec : EntitiesByPaginationFilterSpec<Trader, TraderDetailsDto>
{
public SearchTradersSpec(SearchTradersRequest request, Guid userId)
: base(request) => Query
Expand All @@ -14,21 +14,21 @@ public SearchTradersSpec(SearchTradersRequest request, Guid userId)
.OrderBy(t => new { t.FirstName, t.LastName }, !request.HasOrderBy());
}

public class SearchTradersRequestHandler : IRequestHandler<SearchTradersRequest, PaginationResponse<TraderDto>>
public class SearchTradersRequestHandler : IRequestHandler<SearchTradersRequest, PaginationResponse<TraderDetailsDto>>
{
private readonly ICurrentUser _currentUser;
private readonly IReadRepository<Trader> _repository;

public SearchTradersRequestHandler(ICurrentUser currentUser, IReadRepository<Trader> repository) =>
(_currentUser, _repository) = (currentUser, repository);

public async Task<PaginationResponse<TraderDto>> Handle(SearchTradersRequest request, CancellationToken cancellationToken)
public async Task<PaginationResponse<TraderDetailsDto>> Handle(SearchTradersRequest request, CancellationToken cancellationToken)
{
var spec = new SearchTradersSpec(request, _currentUser.GetUserId());

var list = await _repository.ListAsync(spec, cancellationToken);
int count = await _repository.CountAsync(spec, cancellationToken);

return new PaginationResponse<TraderDto>(list, count, request.PageNumber, request.PageSize);
return new PaginationResponse<TraderDetailsDto>(list, count, request.PageNumber, request.PageSize);
}
}
8 changes: 8 additions & 0 deletions src/Core/Application/Exchange/Traders/TraderDetailsDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using FSH.WebApi.Application.Exchange.Groups;

namespace FSH.WebApi.Application.Exchange.Traders;

public class TraderDetailsDto : TraderDto
{
public ICollection<GroupDto> Groups { get; set; } = default!;
}
2 changes: 1 addition & 1 deletion src/Core/Application/Exchange/Traders/TraderDetailsSpec.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace FSH.WebApi.Application.Exchange.Traders;

public class TraderDetailsSpec : Specification<Trader, TraderDto>, ISingleResultSpecification
public class TraderDetailsSpec : Specification<Trader, TraderDetailsDto>, ISingleResultSpecification
{
public TraderDetailsSpec(Guid id, Guid userId) =>
Query
Expand Down
3 changes: 0 additions & 3 deletions src/Core/Application/Exchange/Traders/TraderDto.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using FSH.WebApi.Application.Exchange.Groups;

namespace FSH.WebApi.Application.Exchange.Traders;

public class TraderDto : IDto
Expand All @@ -8,5 +6,4 @@ public class TraderDto : IDto
public string FirstName { get; set; } = default!;
public string LastName { get; set; } = default!;
public string Email { get; set; } = default!;
public ICollection<GroupDto> Groups { get; set; } = default!;
}
15 changes: 11 additions & 4 deletions src/Core/Domain/Exchange/Inquiry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,29 @@ private Inquiry() // Required by ef
}

public Inquiry(
Guid id,
int referenceNumber,
string name,
string title,
List<InquiryProduct> products,
List<InquiryRecipient> inquiryRecipients)
IList<InquiryProduct> products,
IList<Guid> recipientIds)
{
if (id == Guid.Empty) throw new ArgumentException("Cannot be empty Guid", nameof(id));
if (referenceNumber <= 0) throw new ArgumentException("Must be a positive number", nameof(referenceNumber));
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name));
if (string.IsNullOrWhiteSpace(title)) throw new ArgumentNullException(nameof(title));
if (products.Count == 0) throw new ArgumentException("Cannot be empty list", nameof(products));
if (inquiryRecipients.Count == 0) throw new ArgumentException("Cannot be empty list", nameof(inquiryRecipients));
if (recipientIds.Count == 0) throw new ArgumentException("Cannot be empty list", nameof(recipientIds));

Id = id;
ReferenceNumber = referenceNumber;
Name = name;
Title = title;
Products = products;
InquiryRecipients = inquiryRecipients;

foreach (Guid traderId in recipientIds)
{
InquiryRecipients.Add(new InquiryRecipient(id, traderId));
}
}
}
6 changes: 3 additions & 3 deletions src/Core/Domain/Exchange/InquiryProduct.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@ namespace FSH.WebApi.Domain.Exchange;

public class InquiryProduct : AuditableEntity, IAggregateRoot
{
public string Name { get; private set; }
public string Name { get; private set; } = default!;
public int Quantity { get; private set; }
public DateTime PreferredDeliveryDate { get; private set; }
public Guid InquiryId { get; private set; }
public virtual Inquiry Inquiry { get; private set; } = default!;
public ICollection<OfferProduct> OfferProducts { get; private set; } = new List<OfferProduct>();

public InquiryProduct(string name, int quantity, DateTime preferredDeliveryDate, Guid inquiryId)
public InquiryProduct(Guid inquiryId, string name, int quantity, DateTime preferredDeliveryDate)
{
InquiryId = inquiryId;
Name = name;
Quantity = quantity;
PreferredDeliveryDate = preferredDeliveryDate;
InquiryId = inquiryId;
}
}
6 changes: 6 additions & 0 deletions src/Core/Shared/Authorization/FSHPermissions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public static class FSHResource
public const string Brands = nameof(Brands);
public const string Groups = nameof(Groups);
public const string Traders = nameof(Traders);
public const string Inquiries = nameof(Inquiries);
}

public static class FSHPermissions
Expand Down Expand Up @@ -73,6 +74,11 @@ public static class FSHPermissions
new("Create Traders", FSHAction.Create, FSHResource.Traders, IsBasic: true),
new("Update Traders", FSHAction.Update, FSHResource.Traders, IsBasic: true),
new("Delete Traders", FSHAction.Delete, FSHResource.Traders, IsBasic: true),
new("Search Inquiries", FSHAction.Search, FSHResource.Inquiries, IsBasic: true),
new("View Inquiries", FSHAction.View, FSHResource.Inquiries, IsBasic: true),
new("Create Inquiries", FSHAction.Create, FSHResource.Inquiries, IsBasic: true),
new("Update Inquiries", FSHAction.Update, FSHResource.Inquiries, IsBasic: true),
new("Delete Inquiries", FSHAction.Delete, FSHResource.Inquiries, IsBasic: true),
new("View Tenants", FSHAction.View, FSHResource.Tenants, IsRoot: true),
new("Create Tenants", FSHAction.Create, FSHResource.Tenants, IsRoot: true),
new("Update Tenants", FSHAction.Update, FSHResource.Tenants, IsRoot: true),
Expand Down
Loading

0 comments on commit 6a2a8d3

Please sign in to comment.