mirror of
https://github.com/farcasclaudiu/PracticeCalendar.git
synced 2026-06-22 07:01:16 +03:00
Merge pull request #1 from farcasclaudiu/new_stuffadded
refactoring, testing, improvements
This commit is contained in:
@@ -0,0 +1,14 @@
|
|||||||
|
using MediatR;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.API.Controllers
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
public class ApiControllerBase : ControllerBase
|
||||||
|
{
|
||||||
|
private ISender _mediator = null!;
|
||||||
|
|
||||||
|
protected ISender Mediator => _mediator ??= HttpContext.RequestServices.GetRequiredService<ISender>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,106 +1,61 @@
|
|||||||
using AutoMapper;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using PracticeCalendar.API.Model;
|
using PracticeCalendar.API.Controllers;
|
||||||
using PracticeCalendar.Domain.Common.Interfaces;
|
using PracticeCalendar.Application.PracticeEvents.Commands;
|
||||||
using PracticeCalendar.Domain.Entities;
|
using PracticeCalendar.Application.PracticeEvents.Queries;
|
||||||
using PracticeCalendar.Domain.Entities.Specifications;
|
using PracticeCalendar.Application.PracticeEvents.Queries.GetPracticeEvents;
|
||||||
|
|
||||||
namespace PrcaticeCalendar.Controllers
|
namespace PrcaticeCalendar.Controllers
|
||||||
{
|
{
|
||||||
[ApiController]
|
public class EventsController : ApiControllerBase
|
||||||
[Route("api/[controller]")]
|
|
||||||
public class EventsController : ControllerBase
|
|
||||||
{
|
{
|
||||||
private readonly IRepository<PracticeEvent> eventsRepo;
|
private readonly ILogger<EventsController> logger;
|
||||||
private readonly IMapper mapper;
|
|
||||||
private readonly ILogger<EventsController> _logger;
|
|
||||||
|
|
||||||
public EventsController(IRepository<PracticeEvent> eventsRepo,
|
public EventsController(ILogger<EventsController> logger)
|
||||||
IMapper mapper,
|
|
||||||
ILogger<EventsController> logger)
|
|
||||||
{
|
{
|
||||||
this.eventsRepo = eventsRepo;
|
this.logger = logger;
|
||||||
this.mapper = mapper;
|
|
||||||
_logger = logger;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet(Name = "GetAll")]
|
[HttpGet(Name = "GetAll")]
|
||||||
public async Task<ActionResult<List<EventModel>>> Get()
|
public async Task<ActionResult<List<PracticeEventDto>>> Get()
|
||||||
{
|
{
|
||||||
var spec = new PracticeEventsWithAttendees();
|
return await Mediator.Send(new GetPracticeEventsQuery());
|
||||||
var repoList = await eventsRepo.ListAsync(spec);
|
|
||||||
var evList = repoList.Select(x=> {
|
|
||||||
var model = mapper.Map<EventModel>(x);
|
|
||||||
model.Attendees = x.Attendees.Select(m=>mapper.Map<AttendeeModel>(m)).ToArray();
|
|
||||||
return model;
|
|
||||||
})
|
|
||||||
.ToList();
|
|
||||||
return evList;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost(Name = "Create practice event")]
|
[HttpPost(Name = "Create practice event")]
|
||||||
public async Task<IActionResult> CreateEvent(EventModel eventModel)
|
public async Task<ActionResult<PracticeEventDto>> CreateEvent(PracticeEventDto eventModel)
|
||||||
{
|
{
|
||||||
var practiceEvent = new PracticeEvent(eventModel.Title, eventModel.Description);
|
return await Mediator.Send(new CreatePracticeEventCommand(eventModel));
|
||||||
foreach (var att in eventModel.Attendees)
|
|
||||||
{
|
|
||||||
practiceEvent.AddAttendee(new Attendee(att.Name, att.EmailAddress));
|
|
||||||
}
|
|
||||||
var result = await eventsRepo.AddAsync(practiceEvent);
|
|
||||||
await eventsRepo.SaveChangesAsync();
|
|
||||||
return Ok(mapper.Map<EventModel>(result));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPut(Name = "Update practice event")]
|
[HttpPut(Name = "Update practice event")]
|
||||||
public async Task<IActionResult> UpdateEvent(EventModel eventModel)
|
public async Task<ActionResult<PracticeEventDto>> UpdateEvent(PracticeEventDto eventModel)
|
||||||
{
|
{
|
||||||
var practiceEvent = await eventsRepo.GetByIdAsync(eventModel.Id);
|
return await Mediator.Send(new UpdatePracticeEventCommand(eventModel));
|
||||||
if(practiceEvent == null)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
practiceEvent.UpdateTitleAndDescription(eventModel.Title, eventModel.Description);
|
|
||||||
await eventsRepo.UpdateAsync(practiceEvent);
|
|
||||||
await eventsRepo.SaveChangesAsync();
|
|
||||||
return Ok(mapper.Map<EventModel>(practiceEvent));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete(Name = "Delete practice event")]
|
[HttpDelete(Name = "Delete practice event")]
|
||||||
public async Task<IActionResult> DeleteEvent(int practiceEventId)
|
public async Task<ActionResult> DeleteEvent(int practiceEventId)
|
||||||
{
|
{
|
||||||
var org = await eventsRepo.GetByIdAsync(practiceEventId);
|
await Mediator.Send(new DeletePracticeEventCommand(practiceEventId));
|
||||||
await eventsRepo.DeleteAsync(org);
|
|
||||||
await eventsRepo.SaveChangesAsync();
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("accept/{eventId}/{attendeeId}")]
|
[Route("accept/{eventId}/{attendeeId}")]
|
||||||
public async Task<IActionResult> AttendeeAcceptEvent(int eventId, int attendeeId)
|
public async Task<ActionResult> AttendeeAcceptEvent(int eventId, int attendeeId)
|
||||||
{
|
{
|
||||||
var spec = new PracticeEventByIdWithAttendees(eventId);
|
await Mediator.Send(new AttendeeAcceptEventCommand(eventId, attendeeId));
|
||||||
var practiceEvent = await eventsRepo.GetBySpecAsync(spec);
|
|
||||||
if (practiceEvent == null)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
practiceEvent.AttendeeAcceptEvent(attendeeId);
|
|
||||||
await eventsRepo.SaveChangesAsync();
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("decline/{eventId}/{attendeeId}")]
|
[Route("decline/{eventId}/{attendeeId}")]
|
||||||
public async Task<IActionResult> AttendeeDeclineEvent(int eventId, int attendeeId)
|
public async Task<ActionResult> AttendeeDeclineEvent(int eventId, int attendeeId)
|
||||||
{
|
{
|
||||||
var spec = new PracticeEventByIdWithAttendees(eventId);
|
await Mediator.Send(new AttendeeDeclineEventCommand(eventId, attendeeId));
|
||||||
var practiceEvent = await eventsRepo.GetBySpecAsync(spec);
|
|
||||||
if (practiceEvent == null)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
practiceEvent.AttendeeDeclineEvent(attendeeId);
|
|
||||||
await eventsRepo.SaveChangesAsync();
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
using AutoMapper;
|
|
||||||
using PracticeCalendar.Domain.Entities;
|
|
||||||
|
|
||||||
namespace PracticeCalendar.API.Model
|
|
||||||
{
|
|
||||||
public class EventModel
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public string Title { get; set; }
|
|
||||||
public string Description { get; set; }
|
|
||||||
|
|
||||||
public DateTime StartTime { get; set; }
|
|
||||||
public DateTime EndTime { get; set; }
|
|
||||||
|
|
||||||
public AttendeeModel[] Attendees { get; set; } = new AttendeeModel[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AttendeeModel
|
|
||||||
{
|
|
||||||
public string Name { get; set; }
|
|
||||||
public string EmailAddress { get; set; }
|
|
||||||
public bool IsAttending { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class EventModelProfile : Profile
|
|
||||||
{
|
|
||||||
public EventModelProfile()
|
|
||||||
{
|
|
||||||
CreateMap<PracticeEvent, EventModel>();
|
|
||||||
CreateMap<EventModel, PracticeEvent>();
|
|
||||||
CreateMap<Attendee, AttendeeModel>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,8 +9,8 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.0" />
|
|
||||||
<PackageReference Include="Hellang.Middleware.ProblemDetails" Version="6.5.1" />
|
<PackageReference Include="Hellang.Middleware.ProblemDetails" Version="6.5.1" />
|
||||||
|
<PackageReference Include="Mapster.Core" Version="1.2.0" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0" />
|
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -19,4 +19,8 @@
|
|||||||
<ProjectReference Include="..\PracticeCalendar.Infrastructure\PracticeCalendar.Infrastructure.csproj" />
|
<ProjectReference Include="..\PracticeCalendar.Infrastructure\PracticeCalendar.Infrastructure.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Model\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Hellang.Middleware.ProblemDetails;
|
using Hellang.Middleware.ProblemDetails;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using PracticeCalendar.Application;
|
||||||
using PracticeCalendar.Infrastructure;
|
using PracticeCalendar.Infrastructure;
|
||||||
using PracticeCalendar.Infrastructure.Persistence;
|
using PracticeCalendar.Infrastructure.Persistence;
|
||||||
using System;
|
using System;
|
||||||
@@ -16,14 +17,16 @@ namespace PracticeCalendar
|
|||||||
|
|
||||||
builder.Services.AddProblemDetails();
|
builder.Services.AddProblemDetails();
|
||||||
|
|
||||||
builder.Services.AddAutoMapper(typeof(Program));
|
|
||||||
builder.Services.AddControllers();
|
builder.Services.AddControllers();
|
||||||
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
builder.Services.AddSwaggerGen();
|
builder.Services.AddSwaggerGen();
|
||||||
|
|
||||||
|
//inject application
|
||||||
|
builder.Services.AddApplicationServices();
|
||||||
//inject infrastructure
|
//inject infrastructure
|
||||||
builder.Services.AddInfrastructure(builder.Configuration);
|
builder.Services.AddInfrastructureServices(builder.Configuration);
|
||||||
|
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"Logging": {
|
"Logging": {
|
||||||
"LogLevel": {
|
"LogLevel": {
|
||||||
"Default": "Information",
|
"Default": "Debug",
|
||||||
"Microsoft.AspNetCore": "Warning"
|
"Microsoft.AspNetCore": "Warning"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"Logging": {
|
"Logging": {
|
||||||
"LogLevel": {
|
"LogLevel": {
|
||||||
"Default": "Information",
|
"Default": "Debug",
|
||||||
"Microsoft.AspNetCore": "Warning"
|
"Microsoft.AspNetCore": "Warning"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,18 @@
|
|||||||
|
using MapsterMapper;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.Application
|
||||||
|
{
|
||||||
|
public static class ConfigureServices
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddApplicationServices(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddSingleton<IMapper>(new Mapper(new Mapster.TypeAdapterConfig()));
|
||||||
|
services.AddMediatR(Assembly.GetExecutingAssembly());
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\PracticeCalendar.Domain\PracticeCalendar.Domain.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Mapster.DependencyInjection" Version="1.0.0" />
|
||||||
|
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="11.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
using MediatR;
|
||||||
|
using PracticeCalendar.Domain.Common.Interfaces;
|
||||||
|
using PracticeCalendar.Domain.Entities;
|
||||||
|
using PracticeCalendar.Domain.Entities.Specifications;
|
||||||
|
using PracticeCalendar.Domain.Exceptions;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.Application.PracticeEvents.Commands
|
||||||
|
{
|
||||||
|
public record AttendeeAcceptEventCommand : IRequest
|
||||||
|
{
|
||||||
|
public AttendeeAcceptEventCommand(int eventId, int attendeeId)
|
||||||
|
{
|
||||||
|
EventId = eventId;
|
||||||
|
AttendeeId = attendeeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int EventId { get; }
|
||||||
|
public int AttendeeId { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AttendeeAcceptEventCommandHandler : IRequestHandler<AttendeeAcceptEventCommand>
|
||||||
|
{
|
||||||
|
private readonly IRepository<PracticeEvent> eventsRepo;
|
||||||
|
|
||||||
|
public AttendeeAcceptEventCommandHandler(IRepository<PracticeEvent> eventsRepo)
|
||||||
|
{
|
||||||
|
this.eventsRepo = eventsRepo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Unit> Handle(AttendeeAcceptEventCommand request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var spec = new PracticeEventByIdWithAttendees(request.EventId);
|
||||||
|
var practiceEvent = await eventsRepo.FirstOrDefaultAsync(spec, cancellationToken);
|
||||||
|
if (practiceEvent == null)
|
||||||
|
{
|
||||||
|
throw new PracticeEventNotFoundException();
|
||||||
|
}
|
||||||
|
practiceEvent.AttendeeAcceptEvent(request.AttendeeId);
|
||||||
|
await eventsRepo.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
return Unit.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
using MediatR;
|
||||||
|
using PracticeCalendar.Domain.Common.Interfaces;
|
||||||
|
using PracticeCalendar.Domain.Entities;
|
||||||
|
using PracticeCalendar.Domain.Entities.Specifications;
|
||||||
|
using PracticeCalendar.Domain.Exceptions;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.Application.PracticeEvents.Commands
|
||||||
|
{
|
||||||
|
public record AttendeeDeclineEventCommand : IRequest
|
||||||
|
{
|
||||||
|
public AttendeeDeclineEventCommand(int eventId, int attendeeId)
|
||||||
|
{
|
||||||
|
EventId = eventId;
|
||||||
|
AttendeeId = attendeeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int EventId { get; }
|
||||||
|
public int AttendeeId { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AttendeeDeclineEventCommandHandler : IRequestHandler<AttendeeDeclineEventCommand>
|
||||||
|
{
|
||||||
|
private readonly IRepository<PracticeEvent> eventsRepo;
|
||||||
|
|
||||||
|
public AttendeeDeclineEventCommandHandler(IRepository<PracticeEvent> eventsRepo)
|
||||||
|
{
|
||||||
|
this.eventsRepo = eventsRepo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Unit> Handle(AttendeeDeclineEventCommand request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var spec = new PracticeEventByIdWithAttendees(request.EventId);
|
||||||
|
var practiceEvent = await eventsRepo.FirstOrDefaultAsync(spec, cancellationToken);
|
||||||
|
if (practiceEvent == null)
|
||||||
|
{
|
||||||
|
throw new PracticeEventNotFoundException();
|
||||||
|
}
|
||||||
|
practiceEvent.AttendeeDeclineEvent(request.AttendeeId);
|
||||||
|
|
||||||
|
return Unit.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using Mapster;
|
||||||
|
using MediatR;
|
||||||
|
using PracticeCalendar.Application.PracticeEvents.Queries;
|
||||||
|
using PracticeCalendar.Domain.Common.Interfaces;
|
||||||
|
using PracticeCalendar.Domain.Entities;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.Application.PracticeEvents.Commands
|
||||||
|
{
|
||||||
|
public record CreatePracticeEventCommand : IRequest<PracticeEventDto>
|
||||||
|
{
|
||||||
|
public CreatePracticeEventCommand(PracticeEventDto eventDto)
|
||||||
|
{
|
||||||
|
Event = eventDto;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PracticeEventDto Event { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CreatePracticeEventCommandHandler : IRequestHandler<CreatePracticeEventCommand, PracticeEventDto>
|
||||||
|
{
|
||||||
|
private readonly IRepository<PracticeEvent> eventsRepo;
|
||||||
|
|
||||||
|
public CreatePracticeEventCommandHandler(IRepository<PracticeEvent> eventsRepo)
|
||||||
|
{
|
||||||
|
this.eventsRepo = eventsRepo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PracticeEventDto> Handle(CreatePracticeEventCommand request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var input = request.Event;
|
||||||
|
var practiceEvent = new PracticeEvent(input.Title, input.Description);
|
||||||
|
foreach (var att in input.Attendees)
|
||||||
|
{
|
||||||
|
practiceEvent.AddAttendee(new Attendee(att.Name, att.EmailAddress));
|
||||||
|
}
|
||||||
|
var result = await eventsRepo.AddAsync(practiceEvent, cancellationToken);
|
||||||
|
await eventsRepo.SaveChangesAsync(cancellationToken);
|
||||||
|
return result.Adapt<PracticeEventDto>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
using MediatR;
|
||||||
|
using PracticeCalendar.Domain.Common.Interfaces;
|
||||||
|
using PracticeCalendar.Domain.Entities;
|
||||||
|
using PracticeCalendar.Domain.Exceptions;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.Application.PracticeEvents.Commands
|
||||||
|
{
|
||||||
|
public record DeletePracticeEventCommand : IRequest
|
||||||
|
{
|
||||||
|
public DeletePracticeEventCommand(int practiceEventId)
|
||||||
|
{
|
||||||
|
PracticeEventId = practiceEventId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int PracticeEventId { get; }
|
||||||
|
}
|
||||||
|
public class DeletePracticeEventCommandHandler : IRequestHandler<DeletePracticeEventCommand>
|
||||||
|
{
|
||||||
|
private readonly IRepository<PracticeEvent> eventsRepo;
|
||||||
|
|
||||||
|
public DeletePracticeEventCommandHandler(IRepository<PracticeEvent> eventsRepo)
|
||||||
|
{
|
||||||
|
this.eventsRepo = eventsRepo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Unit> Handle(DeletePracticeEventCommand request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var org = await eventsRepo.GetByIdAsync(request.PracticeEventId, cancellationToken);
|
||||||
|
if (org == null)
|
||||||
|
{
|
||||||
|
throw new PracticeEventNotFoundException();
|
||||||
|
}
|
||||||
|
await eventsRepo.DeleteAsync(org, cancellationToken);
|
||||||
|
await eventsRepo.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
return Unit.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
using Mapster;
|
||||||
|
using MediatR;
|
||||||
|
using PracticeCalendar.Application.PracticeEvents.Queries;
|
||||||
|
using PracticeCalendar.Domain.Common.Interfaces;
|
||||||
|
using PracticeCalendar.Domain.Entities;
|
||||||
|
using PracticeCalendar.Domain.Exceptions;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.Application.PracticeEvents.Commands
|
||||||
|
{
|
||||||
|
public record UpdatePracticeEventCommand : IRequest<PracticeEventDto>
|
||||||
|
{
|
||||||
|
public UpdatePracticeEventCommand(PracticeEventDto eventDto)
|
||||||
|
{
|
||||||
|
Event = eventDto;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PracticeEventDto Event { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UpdatePracticeEventCommandHandler : IRequestHandler<UpdatePracticeEventCommand, PracticeEventDto>
|
||||||
|
{
|
||||||
|
private readonly IRepository<PracticeEvent> eventsRepo;
|
||||||
|
|
||||||
|
public UpdatePracticeEventCommandHandler(IRepository<PracticeEvent> eventsRepo)
|
||||||
|
{
|
||||||
|
this.eventsRepo = eventsRepo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PracticeEventDto> Handle(UpdatePracticeEventCommand request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var eventModel = request.Event;
|
||||||
|
var practiceEvent = await eventsRepo.GetByIdAsync(eventModel.Id, cancellationToken);
|
||||||
|
if (practiceEvent == null)
|
||||||
|
{
|
||||||
|
throw new PracticeEventNotFoundException();
|
||||||
|
}
|
||||||
|
practiceEvent.UpdateTitleAndDescription(eventModel.Title, eventModel.Description);
|
||||||
|
await eventsRepo.UpdateAsync(practiceEvent, cancellationToken);
|
||||||
|
await eventsRepo.SaveChangesAsync(cancellationToken);
|
||||||
|
return practiceEvent.Adapt<PracticeEventDto>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using MediatR;
|
||||||
|
using PracticeCalendar.Domain.Common;
|
||||||
|
using PracticeCalendar.Domain.Events;
|
||||||
|
using PracticeCalendar.Domain.Interfaces;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.Application.PracticeEvents.Events
|
||||||
|
{
|
||||||
|
public class AttendeeAddedEventNotification : INotificationHandler<DomainEventNotification<AttendeeAddedEvent>>
|
||||||
|
{
|
||||||
|
private readonly IEmailSender emailSender;
|
||||||
|
|
||||||
|
public AttendeeAddedEventNotification(IEmailSender emailSender)
|
||||||
|
{
|
||||||
|
this.emailSender = emailSender;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Handle(DomainEventNotification<AttendeeAddedEvent> notification, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var sendTo = notification.DomainEvent.AddedAtendee.EmailAddress;
|
||||||
|
await emailSender.SendEmailAsync(sendTo, "system",
|
||||||
|
"You have been added to the event", "Confirmed you have been added to the event.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace PracticeCalendar.Application.PracticeEvents.Queries
|
||||||
|
{
|
||||||
|
public class AttendeeDto
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public string EmailAddress { get; set; } = string.Empty;
|
||||||
|
public bool IsAttending { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
using Mapster;
|
||||||
|
using MapsterMapper;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using PracticeCalendar.Domain.Common.Interfaces;
|
||||||
|
using PracticeCalendar.Domain.Entities;
|
||||||
|
using PracticeCalendar.Domain.Entities.Specifications;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.Application.PracticeEvents.Queries.GetPracticeEvents
|
||||||
|
{
|
||||||
|
public record class GetPracticeEventsQuery : IRequest<List<PracticeEventDto>>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GetPracticeEventsQueryHandler : IRequestHandler<GetPracticeEventsQuery, List<PracticeEventDto>>
|
||||||
|
{
|
||||||
|
private readonly ILogger<GetPracticeEventsQueryHandler> logger;
|
||||||
|
private readonly IRepository<PracticeEvent> eventsRepo;
|
||||||
|
private readonly IMapper mapper;
|
||||||
|
|
||||||
|
public GetPracticeEventsQueryHandler(IRepository<PracticeEvent> eventsRepo,
|
||||||
|
ILogger<GetPracticeEventsQueryHandler> logger,
|
||||||
|
IMapper mapper)
|
||||||
|
{
|
||||||
|
this.eventsRepo = eventsRepo;
|
||||||
|
this.logger = logger;
|
||||||
|
this.mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<PracticeEventDto>> Handle(GetPracticeEventsQuery request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var spec = new PracticeEventsWithAttendees();
|
||||||
|
var evList = await eventsRepo.ListAsync(spec, cancellationToken);
|
||||||
|
var lst = evList.Adapt<List<PracticeEventDto>>(mapper.Config);
|
||||||
|
return lst;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
namespace PracticeCalendar.Application.PracticeEvents.Queries
|
||||||
|
{
|
||||||
|
public class PracticeEventDto
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Title { get; set; } = string.Empty;
|
||||||
|
public string Description { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public DateTime StartTime { get; set; }
|
||||||
|
public DateTime EndTime { get; set; }
|
||||||
|
|
||||||
|
public IList<AttendeeDto> Attendees { get; set; } = new List<AttendeeDto>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,5 +3,6 @@
|
|||||||
public class DomainEventBase
|
public class DomainEventBase
|
||||||
{
|
{
|
||||||
public DateTime EventDate { get; protected set; } = DateTime.UtcNow;
|
public DateTime EventDate { get; protected set; } = DateTime.UtcNow;
|
||||||
|
public bool IsPublished { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.Domain.Common
|
||||||
|
{
|
||||||
|
public class DomainEventNotification<TDomainEvent> : INotification where TDomainEvent : DomainEventBase
|
||||||
|
{
|
||||||
|
public TDomainEvent DomainEvent { get; }
|
||||||
|
|
||||||
|
public DomainEventNotification(TDomainEvent domainEvent)
|
||||||
|
{
|
||||||
|
DomainEvent = domainEvent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace PracticeCalendar.Domain.Common.Interfaces
|
||||||
|
{
|
||||||
|
public interface IDomainEventService
|
||||||
|
{
|
||||||
|
Task Publish(DomainEventBase domainEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
using Ardalis.GuardClauses;
|
using Ardalis.GuardClauses;
|
||||||
using PracticeCalendar.Domain.Common;
|
using PracticeCalendar.Domain.Common;
|
||||||
using System.Diagnostics.Contracts;
|
|
||||||
|
|
||||||
namespace PracticeCalendar.Domain.Entities
|
namespace PracticeCalendar.Domain.Entities
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,8 +22,7 @@ namespace PracticeCalendar.Domain.Entities
|
|||||||
public string Title { get; private set; } = string.Empty;
|
public string Title { get; private set; } = string.Empty;
|
||||||
public string Description{ get; private set; } = string.Empty;
|
public string Description{ get; private set; } = string.Empty;
|
||||||
|
|
||||||
private List<Attendee> attendees = new List<Attendee>();
|
public IList<Attendee> Attendees { get; private set; } = new List<Attendee>();
|
||||||
public IEnumerable<Attendee> Attendees => attendees.AsReadOnly();
|
|
||||||
|
|
||||||
public DateTime StartTime { get; private set; }
|
public DateTime StartTime { get; private set; }
|
||||||
public DateTime EndTime { get; private set; }
|
public DateTime EndTime { get; private set; }
|
||||||
@@ -32,7 +31,7 @@ namespace PracticeCalendar.Domain.Entities
|
|||||||
{
|
{
|
||||||
Guard.Against.Null(attendee, nameof(attendee));
|
Guard.Against.Null(attendee, nameof(attendee));
|
||||||
attendee.AssignToEvent(this.Id);
|
attendee.AssignToEvent(this.Id);
|
||||||
attendees.Add(attendee);
|
Attendees.Add(attendee);
|
||||||
|
|
||||||
var attendeeAddedEvent = new AttendeeAddedEvent(this, attendee);
|
var attendeeAddedEvent = new AttendeeAddedEvent(this, attendee);
|
||||||
base.RegisterDomainEvent(attendeeAddedEvent);
|
base.RegisterDomainEvent(attendeeAddedEvent);
|
||||||
@@ -42,6 +41,9 @@ namespace PracticeCalendar.Domain.Entities
|
|||||||
{
|
{
|
||||||
this.Title = title;
|
this.Title = title;
|
||||||
this.Description = description;
|
this.Description = description;
|
||||||
|
|
||||||
|
var titleDescUpdatedEvent = new EventUpdateTitleAndDescriptionEvent(this);
|
||||||
|
base.RegisterDomainEvent(titleDescUpdatedEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AttendeeAcceptEvent(int attendeeId)
|
public void AttendeeAcceptEvent(int attendeeId)
|
||||||
@@ -50,6 +52,9 @@ namespace PracticeCalendar.Domain.Entities
|
|||||||
if (attendee == null)
|
if (attendee == null)
|
||||||
throw new InvalidAttendeeException(attendeeId);
|
throw new InvalidAttendeeException(attendeeId);
|
||||||
attendee.SetIsAttending(true);
|
attendee.SetIsAttending(true);
|
||||||
|
|
||||||
|
var attendeeAcceptedEvent = new AttendeeAcceptedEvent(this, attendee);
|
||||||
|
base.RegisterDomainEvent(attendeeAcceptedEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AttendeeDeclineEvent(int attendeeId)
|
public void AttendeeDeclineEvent(int attendeeId)
|
||||||
@@ -58,6 +63,9 @@ namespace PracticeCalendar.Domain.Entities
|
|||||||
if (attendee == null)
|
if (attendee == null)
|
||||||
throw new InvalidAttendeeException(attendeeId);
|
throw new InvalidAttendeeException(attendeeId);
|
||||||
attendee.SetIsAttending(false);
|
attendee.SetIsAttending(false);
|
||||||
|
|
||||||
|
var attendeeDeclinedEvent = new AttendeeDeclinedEvent(this, attendee);
|
||||||
|
base.RegisterDomainEvent(attendeeDeclinedEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ namespace PracticeCalendar.Domain.Entities.Specifications
|
|||||||
{
|
{
|
||||||
public PracticeEventsWithAttendees()
|
public PracticeEventsWithAttendees()
|
||||||
{
|
{
|
||||||
Query.Include(x => x.Attendees);
|
Query.AsNoTracking()
|
||||||
|
.Include(x => x.Attendees);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using PracticeCalendar.Domain.Common;
|
||||||
|
using PracticeCalendar.Domain.Entities;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.Domain.Events
|
||||||
|
{
|
||||||
|
public sealed class AttendeeAcceptedEvent : DomainEventBase
|
||||||
|
{
|
||||||
|
public AttendeeAcceptedEvent(PracticeEvent practiceEvent, Attendee attendee)
|
||||||
|
{
|
||||||
|
PracticeEvent = practiceEvent;
|
||||||
|
Attendee = attendee;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PracticeEvent PracticeEvent { get; }
|
||||||
|
public Attendee Attendee { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using PracticeCalendar.Domain.Common;
|
||||||
|
using PracticeCalendar.Domain.Entities;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.Domain.Events
|
||||||
|
{
|
||||||
|
public sealed class AttendeeDeclinedEvent : DomainEventBase
|
||||||
|
{
|
||||||
|
public AttendeeDeclinedEvent(PracticeEvent practiceEvent, Attendee attendee)
|
||||||
|
{
|
||||||
|
PracticeEvent = practiceEvent;
|
||||||
|
Attendee = attendee;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PracticeEvent PracticeEvent { get; }
|
||||||
|
public Attendee Attendee { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using PracticeCalendar.Domain.Common;
|
||||||
|
using PracticeCalendar.Domain.Entities;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.Domain.Events
|
||||||
|
{
|
||||||
|
public sealed class EventUpdateTitleAndDescriptionEvent : DomainEventBase
|
||||||
|
{
|
||||||
|
public EventUpdateTitleAndDescriptionEvent(PracticeEvent practiceEvent)
|
||||||
|
{
|
||||||
|
PracticeEvent = practiceEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PracticeEvent PracticeEvent { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using PracticeCalendar.Domain.Common;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.Domain.Exceptions
|
||||||
|
{
|
||||||
|
public class PracticeEventNotFoundException : DomainException
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-3
@@ -5,18 +5,20 @@ using PracticeCalendar.Domain.Common.Interfaces;
|
|||||||
using PracticeCalendar.Domain.Interfaces;
|
using PracticeCalendar.Domain.Interfaces;
|
||||||
using PracticeCalendar.Infrastructure.Notification;
|
using PracticeCalendar.Infrastructure.Notification;
|
||||||
using PracticeCalendar.Infrastructure.Persistence;
|
using PracticeCalendar.Infrastructure.Persistence;
|
||||||
using System;
|
using PracticeCalendar.Infrastructure.Services;
|
||||||
|
|
||||||
namespace PracticeCalendar.Infrastructure
|
namespace PracticeCalendar.Infrastructure
|
||||||
{
|
{
|
||||||
public static class InfrastructureDI
|
public static class ConfigureServices
|
||||||
{
|
{
|
||||||
public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfigurationRoot configuration)
|
public static IServiceCollection AddInfrastructureServices(this IServiceCollection services,
|
||||||
|
IConfigurationRoot configuration)
|
||||||
{
|
{
|
||||||
string connectionString = configuration.GetConnectionString("SqliteConnection");
|
string connectionString = configuration.GetConnectionString("SqliteConnection");
|
||||||
|
|
||||||
services.AddDbContext(connectionString);
|
services.AddDbContext(connectionString);
|
||||||
services.AddTransient<IEmailSender, FileEmailSender>();
|
services.AddTransient<IEmailSender, FileEmailSender>();
|
||||||
|
services.AddTransient<IDomainEventService, DomainEventService>();
|
||||||
|
|
||||||
services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>));
|
services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>));
|
||||||
|
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using PracticeCalendar.Domain.Common;
|
||||||
|
using PracticeCalendar.Domain.Common.Interfaces;
|
||||||
using PracticeCalendar.Domain.Entities;
|
using PracticeCalendar.Domain.Entities;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
@@ -6,9 +8,15 @@ namespace PracticeCalendar.Infrastructure.Persistence
|
|||||||
{
|
{
|
||||||
public class ApplicationDbContext : DbContext
|
public class ApplicationDbContext : DbContext
|
||||||
{
|
{
|
||||||
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
|
private readonly IDomainEventService domainEventService;
|
||||||
|
|
||||||
|
public DbSet<Attendee> Atendees => Set<Attendee>();
|
||||||
|
public DbSet<PracticeEvent> PracticeEvents => Set<PracticeEvent>();
|
||||||
|
|
||||||
|
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, IDomainEventService domainEventService)
|
||||||
: base(options)
|
: base(options)
|
||||||
{
|
{
|
||||||
|
this.domainEventService = domainEventService;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
@@ -17,7 +25,30 @@ namespace PracticeCalendar.Infrastructure.Persistence
|
|||||||
modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
|
modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
|
||||||
}
|
}
|
||||||
|
|
||||||
public DbSet<Attendee> Atendees => Set<Attendee>();
|
|
||||||
public DbSet<PracticeEvent> PracticeEvents => Set<PracticeEvent>();
|
|
||||||
|
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
|
||||||
|
{
|
||||||
|
var events = ChangeTracker.Entries<EntityBase>()
|
||||||
|
.Select(x => x.Entity.DomainEvents)
|
||||||
|
.SelectMany(x => x)
|
||||||
|
.Where(domainEvent => !domainEvent.IsPublished)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
var result = await base.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
await DispatchEvents(events);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DispatchEvents(DomainEventBase[] events)
|
||||||
|
{
|
||||||
|
foreach (var @event in events)
|
||||||
|
{
|
||||||
|
@event.IsPublished = true;
|
||||||
|
await domainEventService.Publish(@event);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\PracticeCalendar.Domain\PracticeCalendar.Domain.csproj" />
|
<ProjectReference Include="..\PracticeCalendar.Application\PracticeCalendar.Application.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using MediatR;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using PracticeCalendar.Domain.Common;
|
||||||
|
using PracticeCalendar.Domain.Common.Interfaces;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.Infrastructure.Services
|
||||||
|
{
|
||||||
|
public class DomainEventService : IDomainEventService
|
||||||
|
{
|
||||||
|
private readonly ILogger<DomainEventService> logger;
|
||||||
|
private readonly IPublisher mediator;
|
||||||
|
|
||||||
|
public DomainEventService(ILogger<DomainEventService> logger, IPublisher mediator)
|
||||||
|
{
|
||||||
|
this.logger = logger;
|
||||||
|
this.mediator = mediator;
|
||||||
|
}
|
||||||
|
public async Task Publish(DomainEventBase domainEvent)
|
||||||
|
{
|
||||||
|
logger.LogInformation("Publishing domain event. Event - {event}", domainEvent.GetType().Name);
|
||||||
|
await mediator.Publish(GetNotificationCorrespondingToDomainEvent(domainEvent));
|
||||||
|
}
|
||||||
|
|
||||||
|
private INotification GetNotificationCorrespondingToDomainEvent(DomainEventBase domainEvent)
|
||||||
|
{
|
||||||
|
return (INotification)Activator.CreateInstance(
|
||||||
|
typeof(DomainEventNotification<>).MakeGenericType(domainEvent.GetType()), domainEvent)!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+15
-12
@@ -1,15 +1,14 @@
|
|||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using PracticeCalendar.Domain.Entities;
|
using PracticeCalendar.Domain.Entities;
|
||||||
|
|
||||||
namespace PracticeCalendar.UnitTests
|
namespace PracticeCalendar.UnitTests.Domain
|
||||||
{
|
{
|
||||||
public class PracticeEventTest
|
public class PracticeEventTest
|
||||||
{
|
{
|
||||||
string _eventTitle = "Event1";
|
readonly string _eventTitle = "Event1";
|
||||||
string _eventDescription = "Description";
|
readonly string _eventDescription = "Description";
|
||||||
|
readonly string _attendeeName = "Claudiu Farcas";
|
||||||
string _attendeeName = "Claudiu Farcas";
|
readonly string _atendeeEmail = "claudiu.farcas@testingbee.com";
|
||||||
string _atendeeEmail = "claudiu.farcas@testingbee.com";
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void InitializeProperties()
|
public void InitializeProperties()
|
||||||
@@ -23,22 +22,26 @@ namespace PracticeCalendar.UnitTests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void InitializeWithNullShouldThrowException()
|
public void InitializeWithNullShouldThrowException()
|
||||||
{
|
{
|
||||||
Action act = () => {
|
Action act = () =>
|
||||||
var practiceEvent = new PracticeEvent(null, _eventDescription);
|
{
|
||||||
|
var practiceEvent = new PracticeEvent(null!, _eventDescription);
|
||||||
};
|
};
|
||||||
act.Should().Throw<ArgumentNullException>();
|
act.Should().Throw<ArgumentNullException>();
|
||||||
|
|
||||||
act = () => {
|
act = () =>
|
||||||
var practiceEvent = new PracticeEvent(_eventTitle, null);
|
{
|
||||||
|
var practiceEvent = new PracticeEvent(_eventTitle, null!);
|
||||||
};
|
};
|
||||||
act.Should().Throw<ArgumentNullException>();
|
act.Should().Throw<ArgumentNullException>();
|
||||||
|
|
||||||
act = () => {
|
act = () =>
|
||||||
|
{
|
||||||
var practiceEvent = new PracticeEvent(string.Empty, _eventDescription);
|
var practiceEvent = new PracticeEvent(string.Empty, _eventDescription);
|
||||||
};
|
};
|
||||||
act.Should().Throw<ArgumentException>();
|
act.Should().Throw<ArgumentException>();
|
||||||
|
|
||||||
act = () => {
|
act = () =>
|
||||||
|
{
|
||||||
var practiceEvent = new PracticeEvent(_eventTitle, string.Empty);
|
var practiceEvent = new PracticeEvent(_eventTitle, string.Empty);
|
||||||
};
|
};
|
||||||
act.Should().Throw<ArgumentException>();
|
act.Should().Throw<ArgumentException>();
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using static PracticeCalendar.UnitTests.Integration.Testing;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.UnitTests.Integration
|
||||||
|
{
|
||||||
|
public class BaseTest
|
||||||
|
{
|
||||||
|
public BaseTest()
|
||||||
|
{
|
||||||
|
ResetState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Testing;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using PracticeCalendar.Infrastructure.Persistence;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.UnitTests.Integration
|
||||||
|
{
|
||||||
|
public class CustomWebApplicationFactory : WebApplicationFactory<Program>
|
||||||
|
{
|
||||||
|
protected override void ConfigureWebHost(IWebHostBuilder builder)
|
||||||
|
{
|
||||||
|
builder.ConfigureAppConfiguration(configurationBuilder =>
|
||||||
|
{
|
||||||
|
var integrationConfig = new ConfigurationBuilder()
|
||||||
|
.AddJsonFile("appsettings.json")
|
||||||
|
.AddEnvironmentVariables()
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
configurationBuilder.AddConfiguration(integrationConfig);
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.ConfigureServices((builder, services) =>
|
||||||
|
{
|
||||||
|
services.Remove<DbContextOptions<ApplicationDbContext>>();
|
||||||
|
services.AddDbContext<ApplicationDbContext>(options =>
|
||||||
|
options.UseInMemoryDatabase("InMemoryDbForTesting")
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using PracticeCalendar.Application.PracticeEvents.Commands;
|
||||||
|
using PracticeCalendar.Application.PracticeEvents.Queries;
|
||||||
|
|
||||||
|
using static PracticeCalendar.UnitTests.Integration.Testing;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.UnitTests.Integration.PracticeEvents
|
||||||
|
{
|
||||||
|
public class CreatePracticeEventsTest : BaseTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task ShouldCreatePracticeEvent()
|
||||||
|
{
|
||||||
|
await RunBeforeAnyTests();
|
||||||
|
|
||||||
|
var query = new CreatePracticeEventCommand(new PracticeEventDto
|
||||||
|
{
|
||||||
|
Title = "Some title",
|
||||||
|
Description = "Some desc",
|
||||||
|
StartTime = DateTime.Now,
|
||||||
|
EndTime = DateTime.Now,
|
||||||
|
Attendees = {
|
||||||
|
new AttendeeDto
|
||||||
|
{
|
||||||
|
Name = "Claudiu F",
|
||||||
|
EmailAddress = "claudiuf@somewhere.com"
|
||||||
|
},
|
||||||
|
new AttendeeDto
|
||||||
|
{
|
||||||
|
Name = "Claudiu F 2",
|
||||||
|
EmailAddress = "claudiuf2@somewhere.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var result = await SendAsync(query);
|
||||||
|
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result.Id.Should().NotBe(0);
|
||||||
|
result.Attendees.Count.Should().Be(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using PracticeCalendar.Application.PracticeEvents.Queries.GetPracticeEvents;
|
||||||
|
using PracticeCalendar.Domain.Entities;
|
||||||
|
|
||||||
|
using static PracticeCalendar.UnitTests.Integration.Testing;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.UnitTests.Integration.PracticeEvents
|
||||||
|
{
|
||||||
|
public class GetPracticeEventsTest : BaseTest
|
||||||
|
{
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ShouldReturnZeroResult()
|
||||||
|
{
|
||||||
|
await RunBeforeAnyTests();
|
||||||
|
|
||||||
|
var query = new GetPracticeEventsQuery();
|
||||||
|
|
||||||
|
var result = await SendAsync(query);
|
||||||
|
|
||||||
|
result.Count.Should().Be(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ShouldReturnAllListsAndItems()
|
||||||
|
{
|
||||||
|
await RunBeforeAnyTests();
|
||||||
|
|
||||||
|
await AddAsync(new PracticeEvent("Test Event", "Event description")
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
Attendees = {
|
||||||
|
new Attendee("Claudiu F", "claudiuf@busybee.com")
|
||||||
|
{
|
||||||
|
Id = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var query = new GetPracticeEventsQuery();
|
||||||
|
|
||||||
|
var result = await SendAsync(query);
|
||||||
|
|
||||||
|
result.Should().HaveCount(1);
|
||||||
|
result.First().Attendees.Should().HaveCount(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.UnitTests.Integration
|
||||||
|
{
|
||||||
|
public static class ServiceCollectionExtensions
|
||||||
|
{
|
||||||
|
public static IServiceCollection Remove<TService>(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
var serviceDescriptor = services.FirstOrDefault(d =>
|
||||||
|
d.ServiceType == typeof(TService));
|
||||||
|
|
||||||
|
if (serviceDescriptor != null)
|
||||||
|
{
|
||||||
|
services.Remove(serviceDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Testing;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using PracticeCalendar.Infrastructure.Persistence;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using PracticeCalendar.Domain.Entities;
|
||||||
|
|
||||||
|
namespace PracticeCalendar.UnitTests.Integration
|
||||||
|
{
|
||||||
|
public partial class Testing
|
||||||
|
{
|
||||||
|
private static WebApplicationFactory<Program> _factory = null!;
|
||||||
|
private static IConfiguration _configuration = null!;
|
||||||
|
private static IServiceScopeFactory _scopeFactory = null!;
|
||||||
|
|
||||||
|
public static async Task RunBeforeAnyTests()
|
||||||
|
{
|
||||||
|
_factory = new CustomWebApplicationFactory();
|
||||||
|
_scopeFactory = _factory.Services.GetRequiredService<IServiceScopeFactory>();
|
||||||
|
_configuration = _factory.Services.GetRequiredService<IConfiguration>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<TResponse> SendAsync<TResponse>(IRequest<TResponse> request)
|
||||||
|
{
|
||||||
|
using var scope = _scopeFactory.CreateScope();
|
||||||
|
|
||||||
|
var mediator = scope.ServiceProvider.GetRequiredService<ISender>();
|
||||||
|
|
||||||
|
return await mediator.Send(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<TEntity?> FindAsync<TEntity>(params object[] keyValues)
|
||||||
|
where TEntity : class
|
||||||
|
{
|
||||||
|
using var scope = _scopeFactory.CreateScope();
|
||||||
|
|
||||||
|
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||||
|
|
||||||
|
return await context.FindAsync<TEntity>(keyValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task AddAsync<TEntity>(TEntity entity)
|
||||||
|
where TEntity : class
|
||||||
|
{
|
||||||
|
using var scope = _scopeFactory.CreateScope();
|
||||||
|
|
||||||
|
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||||
|
|
||||||
|
context.Add(entity);
|
||||||
|
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<int> CountAsync<TEntity>() where TEntity : class
|
||||||
|
{
|
||||||
|
using var scope = _scopeFactory.CreateScope();
|
||||||
|
|
||||||
|
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||||
|
|
||||||
|
return await context.Set<TEntity>().CountAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ResetState()
|
||||||
|
{
|
||||||
|
if (_scopeFactory != null)
|
||||||
|
{
|
||||||
|
using var scope = _scopeFactory.CreateScope();
|
||||||
|
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||||
|
context.Set<Attendee>().RemoveRange(context.Set<Attendee>().ToList());
|
||||||
|
context.Set<PracticeEvent>().RemoveRange(context.Set<PracticeEvent>().ToList());
|
||||||
|
context.SaveChanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,9 +8,24 @@
|
|||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="appsettings.json" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="appsettings.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||||
|
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="FluentAssertions" Version="6.7.0" />
|
<PackageReference Include="FluentAssertions" Version="6.7.0" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.9" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.9" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
||||||
|
<PackageReference Include="Moq" Version="4.18.2" />
|
||||||
<PackageReference Include="xunit" Version="2.4.2" />
|
<PackageReference Include="xunit" Version="2.4.2" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
@@ -23,7 +38,17 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\PracticeCalendar.Domain\PracticeCalendar.Domain.csproj" />
|
<ProjectReference Include="..\PracticeCalendar.Api\PracticeCalendar.API.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Application\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="xunit.runner.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Debug",
|
||||||
|
"System": "Information",
|
||||||
|
"Microsoft": "Information"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"UseInMemoryDatabase": true, //integration tests always with real database
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"TestSqliteConnection": "Data Source=practicecalendar_test.sqlite"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"parallelizeAssembly": false,
|
||||||
|
"parallelizeTestCollections": false
|
||||||
|
}
|
||||||
@@ -7,15 +7,17 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PracticeCalendar.API", "Pra
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PracticeCalendar.Domain", "PracticeCalendar.Domain\PracticeCalendar.Domain.csproj", "{002B8118-8B5A-4CF3-A29D-12A06803221B}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PracticeCalendar.Domain", "PracticeCalendar.Domain\PracticeCalendar.Domain.csproj", "{002B8118-8B5A-4CF3-A29D-12A06803221B}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PracticeCalendar.Infrastructure", "PracticeCalendar.Infrastructure\PracticeCalendar.Infrastructure.csproj", "{211BEF2A-5FB1-4F55-84FB-88FEF90A8316}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PracticeCalendar.Infrastructure", "PracticeCalendar.Infrastructure\PracticeCalendar.Infrastructure.csproj", "{211BEF2A-5FB1-4F55-84FB-88FEF90A8316}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PracticeCalendar.UnitTests", "PracticeCalendar.UnitTests\PracticeCalendar.UnitTests.csproj", "{74849455-5E08-43FE-A718-0872DE7BC350}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PracticeCalendar.UnitTests", "PracticeCalendar.UnitTests\PracticeCalendar.UnitTests.csproj", "{74849455-5E08-43FE-A718-0872DE7BC350}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{5F2B7855-F03D-48C9-8733-FF1E077F18F5}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{5F2B7855-F03D-48C9-8733-FF1E077F18F5}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
README.md = README.md
|
README.md = README.md
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PracticeCalendar.Application", "PracticeCalendar.Application\PracticeCalendar.Application.csproj", "{094CA45E-92DD-47A5-A7EF-F867DB8B0625}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -38,6 +40,10 @@ Global
|
|||||||
{74849455-5E08-43FE-A718-0872DE7BC350}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{74849455-5E08-43FE-A718-0872DE7BC350}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{74849455-5E08-43FE-A718-0872DE7BC350}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{74849455-5E08-43FE-A718-0872DE7BC350}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{74849455-5E08-43FE-A718-0872DE7BC350}.Release|Any CPU.Build.0 = Release|Any CPU
|
{74849455-5E08-43FE-A718-0872DE7BC350}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{094CA45E-92DD-47A5-A7EF-F867DB8B0625}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{094CA45E-92DD-47A5-A7EF-F867DB8B0625}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{094CA45E-92DD-47A5-A7EF-F867DB8B0625}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{094CA45E-92DD-47A5-A7EF-F867DB8B0625}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
Reference in New Issue
Block a user