feat(Notfication),feat(NotificationCQRS).feat(NotificationController)

- Add notification marten entity
- Add notification service by CQRS and mediatR
- Add notification controller
master
Amir Hossein Khademi 2024-05-29 19:17:41 +03:30
parent 8bb2713069
commit bb6cba1ed9
18 changed files with 187 additions and 31 deletions

View File

@ -0,0 +1,21 @@
namespace Brizco.Api.Controllers;
public class NotificationController : ICarterModule
{
public void AddRoutes(IEndpointRouteBuilder app)
{
var group = app.NewVersionedApi("Notifications")
.MapGroup("api/notification");
group.MapGet("", GetUserNotificationsAsync)
.WithDisplayName("Get User Notifications")
.WithDescription("Get user notifications , by JWT user id")
.RequireAuthorization(builder=>builder.AddAuthenticationSchemes("Bearer").RequireAuthenticatedUser())
.HasApiVersion(1.0);
}
private async Task<IResult> GetUserNotificationsAsync([FromQuery] int page, [FromQuery] int count,
[FromServices] IMediator mediator, CancellationToken cancellationToken)
=> TypedResults.Ok(await mediator.Send(new GetNotificationsQuery(page, count), cancellationToken));
}

View File

@ -1,4 +1,5 @@
using Marten;
using Brizco.Core.MartenServices.Abstracts;
using Marten;
using System.Text.Json;
namespace Brizco.Api.Controllers;

View File

@ -2,8 +2,8 @@
public interface IMartenEntity
{
string CreatedBy { get; }
string ModifiedBy { get; }
DateTime CreatedAt { get; }
DateTime ModifiedAt { get; }
//string CreatedBy { get; }
//string ModifiedBy { get; }
//DateTime CreatedAt { get; }
//DateTime ModifiedAt { get; }
}

View File

@ -6,21 +6,22 @@ public class MartenEntity : IMartenEntity
public Guid Id { get; set; }
[Display(Name = "تاریخ ساخت")]
public DateTime CreatedAt { get; set; }
//[Display(Name = "تاریخ ساخت")]
//public DateTime CreatedAt { get; set; }
[Display(Name = "ساخته شده توسط")]
public string CreatedBy { get; set; } = string.Empty;
//[Display(Name = "ساخته شده توسط")]
//public string CreatedBy { get; set; } = string.Empty;
[Display(Name = "اخرین تغییر در")]
public DateTime ModifiedAt { get; set; }
//[Display(Name = "اخرین تغییر در")]
//public DateTime ModifiedAt { get; set; }
[Display(Name = "اخرین تغییر توسط")]
public string ModifiedBy { get; set; } = string.Empty;
//[Display(Name = "اخرین تغییر توسط")]
//public string ModifiedBy { get; set; } = string.Empty;
public Guid ComplexId { get; set; }
public bool Equals(ApiEntity other)
public bool Equals(MartenEntity other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
@ -32,7 +33,7 @@ public class MartenEntity : IMartenEntity
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((ApiEntity)obj);
return Equals((MartenEntity)obj);
}
public override int GetHashCode()

View File

@ -34,6 +34,8 @@
<Using Include="Brizco.Core.CoreServices.Abstracts" />
<Using Include="Brizco.Core.CoreServices.ReportServices.Commands" />
<Using Include="Brizco.Core.EntityServices.Abstracts" />
<Using Include="Brizco.Core.MartenServices.Abstracts" />
<Using Include="Brizco.Domain" />
<Using Include="Brizco.Domain.CommandQueries.Commands" />
<Using Include="Brizco.Domain.CommandQueries.Queries" />
<Using Include="Brizco.Domain.Dtos.LargeDtos" />
@ -46,6 +48,7 @@
<Using Include="Brizco.Domain.Enums" />
<Using Include="Brizco.Domain.Mappers" />
<Using Include="Brizco.Domain.MartenEntities.Brews" />
<Using Include="Brizco.Domain.MartenEntities.Notifications" />
<Using Include="Brizco.Domain.Models.Settings" />
<Using Include="Brizco.Repository.Abstracts" />
<Using Include="Brizco.Repository.Repositories.Base.Contracts" />

View File

@ -1,8 +0,0 @@
namespace Brizco.Core.EntityServices.Abstracts;
public interface IBrewService : IScopedDependency
{
public Task<object> GetLastBrewAsync(string recipeName, CancellationToken cancellationToken = default);
public Task<BaseRecipeLDto> GetBrewAsync(string recipeName , CancellationToken cancellationToken = default);
public Task AddBrewAsync(string recipeName,JsonDocument recipeObj, CancellationToken cancellationToken = default);
}

View File

@ -0,0 +1,8 @@
namespace Brizco.Core.MartenServices.Abstracts;
public interface IBrewService : IScopedDependency
{
public Task<object> GetLastBrewAsync(string recipeName, CancellationToken cancellationToken = default);
public Task<BaseRecipeLDto> GetBrewAsync(string recipeName, CancellationToken cancellationToken = default);
public Task AddBrewAsync(string recipeName, JsonDocument recipeObj, CancellationToken cancellationToken = default);
}

View File

@ -1,15 +1,14 @@
using Newtonsoft.Json;
using System.Reflection;
using Brizco.Domain;
namespace Brizco.Core.EntityServices;
namespace Brizco.Core.MartenServices;
public class BrewService : IBrewService
{
private readonly IMartenRepositoryWrapper _martenRepositoryWrapper;
private readonly ICurrentUserService _currentUserService;
public BrewService(IMartenRepositoryWrapper martenRepositoryWrapper,ICurrentUserService currentUserService)
public BrewService(IMartenRepositoryWrapper martenRepositoryWrapper, ICurrentUserService currentUserService)
{
_martenRepositoryWrapper = martenRepositoryWrapper;
_currentUserService = currentUserService;
@ -20,10 +19,10 @@ public class BrewService : IBrewService
if (type == null)
throw new AppException("Recipe not found", ApiResultStatusCode.NotFound);
if (_currentUserService.ComplexId == null)
throw new BaseApiException(ApiResultStatusCode.BadRequest,"Complex id is null");
throw new BaseApiException(ApiResultStatusCode.BadRequest, "Complex id is null");
if (!Guid.TryParse(_currentUserService.ComplexId, out Guid complexId))
throw new BaseApiException(ApiResultStatusCode.BadRequest, "Complex id is wrong");
var baseRecipe = await _martenRepositoryWrapper.SetRepository<BaseBrew>()
.GetEntityAsync(s => s.ComplexId == complexId && s.Name == recipeName, cancellationToken);
object? recipe;
@ -49,7 +48,7 @@ public class BrewService : IBrewService
throw new BaseApiException(ApiResultStatusCode.BadRequest, "Complex id is wrong");
var baseRecipe = await _martenRepositoryWrapper.SetRepository<BaseBrew>()
.GetEntityAsync(s =>s.ComplexId==complexId && s.Name == recipeName, cancellationToken);
.GetEntityAsync(s => s.ComplexId == complexId && s.Name == recipeName, cancellationToken);
object? recipe;
if (baseRecipe == null)
{
@ -92,7 +91,7 @@ public class BrewService : IBrewService
throw new BaseApiException(ApiResultStatusCode.BadRequest, "Complex id is wrong");
var baseRecipe = await _martenRepositoryWrapper.SetRepository<BaseBrew>()
.GetEntityAsync(s =>s.ComplexId == complexId && s.Name == recipeName, cancellationToken);
.GetEntityAsync(s => s.ComplexId == complexId && s.Name == recipeName, cancellationToken);
if (baseRecipe == null)
{

View File

@ -0,0 +1,4 @@
namespace Brizco.Domain.CommandQueries.Commands;
public record CreateNotificationCommand(string Message,Guid UserId) : IRequest<Guid>;
public record ReadNotificationCommand(Guid Id) : IRequest<bool>;

View File

@ -0,0 +1,5 @@
using Brizco.Domain.MartenEntities.Notifications;
namespace Brizco.Domain.CommandQueries.Queries;
public record GetNotificationsQuery(int Page,int Count, Guid? UserId=null) : IRequest<List<Notification>>;

View File

@ -6,5 +6,4 @@ public class BaseBrew : MartenEntity
public string CurrentBrewJson { get; set; } = string.Empty;
public string DotnetType { get; set; } = string.Empty;
public List<string> PastBrewsJson { get; set; } = new();
public Guid ComplexId { get; set; }
}

View File

@ -0,0 +1,9 @@
namespace Brizco.Domain.MartenEntities.Notifications;
public class Notification : MartenEntity
{
public string Message { get; set; } = string.Empty;
public bool IsRead { get; set; }
public Guid UserId { get; set; }
public DateTime CreatedAt { get; set; }
}

View File

@ -24,6 +24,12 @@ public class MartenRepository<TMartenEntity> : IMartenRepository<TMartenEntity>
var entities = await session.Query<TMartenEntity>().Where(expression).ToListAsync(cancellation);
return entities.ToList();
}
public async Task<IQueryable<TMartenEntity>> GetQueryAsync(Expression<Func<TMartenEntity, bool>> expression)
{
await using var session = _documentStore.QuerySession();
var entities = session.Query<TMartenEntity>().Where(expression);
return entities;
}
public async Task<TMartenEntity> GetEntityAsync(Guid id, CancellationToken cancellation)
{

View File

@ -27,6 +27,7 @@
</ItemGroup>
<ItemGroup>
<Folder Include="MartenHandlers\Notifications\" />
<Folder Include="Models\" />
<Folder Include="Extensions\" />
<Folder Include="Services\Contracts\" />
@ -47,6 +48,7 @@
<Using Include="Brizco.Domain.Dtos.SmallDtos" />
<Using Include="Brizco.Domain.Entities.Complex" />
<Using Include="Brizco.Repository.Abstracts" />
<Using Include="Brizco.Repository.Repositories.Marten" />
<Using Include="MediatR" />
<Using Include="Brizco.Common.Extensions" />
<Using Include="Brizco.Common.Models" />

View File

@ -0,0 +1,33 @@
using Brizco.Domain.MartenEntities.Notifications;
namespace Brizco.Repository.MartenHandlers.Notifications;
public class CreateNotificationHandlerCommand : IRequestHandler<CreateNotificationCommand,Guid>
{
private readonly IMartenRepositoryWrapper _martenRepositoryWrapper;
private readonly ICurrentUserService _currentUserService;
public CreateNotificationHandlerCommand(IMartenRepositoryWrapper martenRepositoryWrapper, ICurrentUserService currentUserService)
{
_martenRepositoryWrapper = martenRepositoryWrapper;
_currentUserService = currentUserService;
}
public async Task<Guid> Handle(CreateNotificationCommand request, CancellationToken cancellationToken)
{
if (_currentUserService.ComplexId == null)
throw new BaseApiException(ApiResultStatusCode.BadRequest, "Complex id is null");
if (!Guid.TryParse(_currentUserService.ComplexId, out Guid complexId))
throw new BaseApiException(ApiResultStatusCode.BadRequest, "Complex id is wrong");
var notification = new Notification
{
Message = request.Message,
UserId = request.UserId,
ComplexId = complexId,
CreatedAt = DateTime.Now
};
await _martenRepositoryWrapper.SetRepository<Notification>()
.AddOrUpdateEntityAsync(notification, cancellationToken);
return notification.Id;
}
}

View File

@ -0,0 +1,44 @@
using Brizco.Domain.MartenEntities.Notifications;
namespace Brizco.Repository.MartenHandlers.Notifications;
public class GetNotificationsQueryHandler : IRequestHandler<GetNotificationsQuery,List<Notification>>
{
private readonly IMartenRepositoryWrapper _martenRepositoryWrapper;
private readonly ICurrentUserService _currentUserService;
public GetNotificationsQueryHandler(IMartenRepositoryWrapper martenRepositoryWrapper,ICurrentUserService currentUserService)
{
_martenRepositoryWrapper = martenRepositoryWrapper;
_currentUserService = currentUserService;
}
public async Task<List<Notification>> Handle(GetNotificationsQuery request, CancellationToken cancellationToken)
{
if (_currentUserService.ComplexId == null)
throw new BaseApiException(ApiResultStatusCode.BadRequest, "Complex id is null");
if (!Guid.TryParse(_currentUserService.ComplexId, out Guid complexId))
throw new BaseApiException(ApiResultStatusCode.BadRequest, "Complex id is wrong");
if (request.UserId == default)
throw new BaseApiException(ApiResultStatusCode.BadRequest, "User id is null");
Guid userId;
if (request.UserId == null)
{
if (_currentUserService.UserId == null)
throw new BaseApiException(ApiResultStatusCode.BadRequest, "User id is null");
if (!Guid.TryParse(_currentUserService.UserId, out userId))
throw new BaseApiException(ApiResultStatusCode.BadRequest, "User id is wrong");
}
else
userId = request.UserId.Value;
var notifications = await _martenRepositoryWrapper.SetRepository<Notification>()
.GetQueryAsync(n => n.ComplexId == complexId && n.UserId == userId);
var response = await notifications.Take(new Range(request.Page * request.Count, (request.Page * request.Count) + request.Count))
.ToListAsync(cancellationToken);
return response;
}
}

View File

@ -0,0 +1,28 @@
using Brizco.Domain.MartenEntities.Notifications;
namespace Brizco.Repository.MartenHandlers.Notifications;
public class ReadNotificationCommandHandler : IRequestHandler<ReadNotificationCommand, bool>
{
private readonly IMartenRepositoryWrapper _martenRepositoryWrapper;
private readonly ICurrentUserService _currentUserService;
public ReadNotificationCommandHandler(IMartenRepositoryWrapper martenRepositoryWrapper, ICurrentUserService currentUserService)
{
_martenRepositoryWrapper = martenRepositoryWrapper;
_currentUserService = currentUserService;
}
public async Task<bool> Handle(ReadNotificationCommand request, CancellationToken cancellationToken)
{
var notification = await _martenRepositoryWrapper.SetRepository<Notification>()
.GetEntityAsync(request.Id, cancellationToken);
if (notification == null)
throw new BaseApiException(ApiResultStatusCode.NotFound,"Notification not found");
notification.IsRead = true;
await _martenRepositoryWrapper.SetRepository<Notification>()
.AddOrUpdateEntityAsync(notification, cancellationToken);
return true;
}
}

View File

@ -6,6 +6,7 @@ public interface IMartenRepository<TMartenEntity> : IScopedDependency where TMar
{
Task<List<TMartenEntity>> GetEntitiesAsync(CancellationToken cancellation = default);
Task<List<TMartenEntity>> GetEntitiesAsync(Expression<Func<TMartenEntity, bool>> expression, CancellationToken cancellation = default);
Task<IQueryable<TMartenEntity>> GetQueryAsync(Expression<Func<TMartenEntity, bool>> expression);
Task<TMartenEntity> GetEntityAsync(Guid id, CancellationToken cancellation = default);
Task<TMartenEntity?> GetEntityAsync(Expression<Func<TMartenEntity, bool>> expression, CancellationToken cancellation = default);