diff --git a/AutobusApi.Api/AutobusApi.Api.csproj b/AutobusApi.Api/AutobusApi.Api.csproj
index c67fb11..5d6c11d 100644
--- a/AutobusApi.Api/AutobusApi.Api.csproj
+++ b/AutobusApi.Api/AutobusApi.Api.csproj
@@ -7,13 +7,13 @@
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
-
+
+
diff --git a/AutobusApi.Api/Controllers/AddressController.cs b/AutobusApi.Api/Controllers/AddressController.cs
new file mode 100644
index 0000000..0bbeb11
--- /dev/null
+++ b/AutobusApi.Api/Controllers/AddressController.cs
@@ -0,0 +1,46 @@
+using AutobusApi.Application.Common.Models;
+using AutobusApi.Application.Addresses.Commands.CreateAddress;
+using AutobusApi.Application.Addresses.Commands.DeleteAddress;
+using AutobusApi.Application.Addresses.Commands.UpdateAddress;
+using AutobusApi.Application.Addresses.Queries;
+using AutobusApi.Application.Addresses.Queries.GetAddressesWithPagination;
+using AutobusApi.Application.Addresses.Queries.GetAddress;
+using Microsoft.AspNetCore.Mvc;
+
+namespace AutobusApi.Api.Controllers;
+
+[Route("addresses")]
+public class AddressController : BaseController
+{
+ [HttpPost]
+ public async Task Create([FromBody] CreateAddressCommand command, CancellationToken cancellationToken)
+ {
+ return await Mediator.Send(command, cancellationToken);
+ }
+
+ [HttpGet]
+ public async Task> GetPage([FromQuery] GetAddressesWithPaginationQuery query, CancellationToken cancellationToken)
+ {
+ return await Mediator.Send(query, cancellationToken);
+ }
+
+ [HttpGet("{id}")]
+ public async Task Get(int id, /* [FromQuery] GetAddressQuery query, */ CancellationToken cancellationToken)
+ {
+ var query = new GetAddressQuery() { Id = id };
+ return await Mediator.Send(query, cancellationToken);
+ }
+
+ [HttpPut]
+ public async Task Update([FromBody] UpdateAddressCommand command, CancellationToken cancellationToken)
+ {
+ await Mediator.Send(command, cancellationToken);
+ }
+
+ [HttpDelete("{id}")]
+ public async Task Delete(int id, /* [FromBody] DeleteAddressCommand command, */ CancellationToken cancellationToken)
+ {
+ var command = new DeleteAddressCommand() { Id = id };
+ await Mediator.Send(command, cancellationToken);
+ }
+}
diff --git a/AutobusApi.Api/Controllers/BaseController.cs b/AutobusApi.Api/Controllers/BaseController.cs
index 39137b3..4528bbc 100644
--- a/AutobusApi.Api/Controllers/BaseController.cs
+++ b/AutobusApi.Api/Controllers/BaseController.cs
@@ -4,7 +4,6 @@ using Microsoft.AspNetCore.Mvc;
namespace AutobusApi.Api.Controllers;
[ApiController]
-[Route("[controller]")]
public class BaseController : ControllerBase
{
private IMediator _mediator;
diff --git a/AutobusApi.Api/Controllers/BusController.cs b/AutobusApi.Api/Controllers/BusController.cs
new file mode 100644
index 0000000..970f33d
--- /dev/null
+++ b/AutobusApi.Api/Controllers/BusController.cs
@@ -0,0 +1,46 @@
+using AutobusApi.Application.Common.Models;
+using AutobusApi.Application.Buses.Commands.CreateBus;
+using AutobusApi.Application.Buses.Commands.DeleteBus;
+using AutobusApi.Application.Buses.Commands.UpdateBus;
+using AutobusApi.Application.Buses.Queries;
+using AutobusApi.Application.Buses.Queries.GetBusesWithPagination;
+using AutobusApi.Application.Buses.Queries.GetBus;
+using Microsoft.AspNetCore.Mvc;
+
+namespace AutobusApi.Api.Controllers;
+
+[Route("vehicle/buses")]
+public class BusController : BaseController
+{
+ [HttpPost]
+ public async Task Create([FromBody] CreateBusCommand command, CancellationToken cancellationToken)
+ {
+ return await Mediator.Send(command, cancellationToken);
+ }
+
+ [HttpGet]
+ public async Task> GetPage([FromQuery] GetBusesWithPaginationQuery query, CancellationToken cancellationToken)
+ {
+ return await Mediator.Send(query, cancellationToken);
+ }
+
+ [HttpGet("{id}")]
+ public async Task Get(int id, /* [FromQuery] GetBusQuery query, */ CancellationToken cancellationToken)
+ {
+ var query = new GetBusQuery() { Id = id };
+ return await Mediator.Send(query, cancellationToken);
+ }
+
+ [HttpPut]
+ public async Task Update([FromBody] UpdateBusCommand command, CancellationToken cancellationToken)
+ {
+ await Mediator.Send(command, cancellationToken);
+ }
+
+ [HttpDelete("{id}")]
+ public async Task Delete(int id, /* [FromBody] DeleteBusCommand command, */ CancellationToken cancellationToken)
+ {
+ var command = new DeleteBusCommand() { Id = id };
+ await Mediator.Send(command, cancellationToken);
+ }
+}
diff --git a/AutobusApi.Api/Controllers/CityController.cs b/AutobusApi.Api/Controllers/CityController.cs
new file mode 100644
index 0000000..70aa734
--- /dev/null
+++ b/AutobusApi.Api/Controllers/CityController.cs
@@ -0,0 +1,46 @@
+using AutobusApi.Application.Common.Models;
+using AutobusApi.Application.Cities.Commands.CreateCity;
+using AutobusApi.Application.Cities.Commands.DeleteCity;
+using AutobusApi.Application.Cities.Commands.UpdateCity;
+using AutobusApi.Application.Cities.Queries;
+using AutobusApi.Application.Cities.Queries.GetCitiesWithPagination;
+using AutobusApi.Application.Cities.Queries.GetCity;
+using Microsoft.AspNetCore.Mvc;
+
+namespace AutobusApi.Api.Controllers;
+
+[Route("cities")]
+public class CityController : BaseController
+{
+ [HttpPost]
+ public async Task Create([FromBody] CreateCityCommand command, CancellationToken cancellationToken)
+ {
+ return await Mediator.Send(command, cancellationToken);
+ }
+
+ [HttpGet]
+ public async Task> GetPage([FromQuery] GetCitiesWithPaginationQuery query, CancellationToken cancellationToken)
+ {
+ return await Mediator.Send(query, cancellationToken);
+ }
+
+ [HttpGet("{id}")]
+ public async Task Get(int id, /* [FromQuery] GetCityQuery query, */ CancellationToken cancellationToken)
+ {
+ var query = new GetCityQuery() { Id = id };
+ return await Mediator.Send(query, cancellationToken);
+ }
+
+ [HttpPut]
+ public async Task Update([FromBody] UpdateCityCommand command, CancellationToken cancellationToken)
+ {
+ await Mediator.Send(command, cancellationToken);
+ }
+
+ [HttpDelete("{id}")]
+ public async Task Delete(int id, /* [FromBody] DeleteCityCommand command, */ CancellationToken cancellationToken)
+ {
+ var command = new DeleteCityCommand() { Id = id };
+ await Mediator.Send(command, cancellationToken);
+ }
+}
diff --git a/AutobusApi.Api/Controllers/CompanyController.cs b/AutobusApi.Api/Controllers/CompanyController.cs
new file mode 100644
index 0000000..3c3b4df
--- /dev/null
+++ b/AutobusApi.Api/Controllers/CompanyController.cs
@@ -0,0 +1,46 @@
+using AutobusApi.Application.Common.Models;
+using AutobusApi.Application.Companies.Commands.CreateCompany;
+using AutobusApi.Application.Companies.Commands.DeleteCompany;
+using AutobusApi.Application.Companies.Commands.UpdateCompany;
+using AutobusApi.Application.Companies.Queries;
+using AutobusApi.Application.Companies.Queries.GetCompaniesWithPagination;
+using AutobusApi.Application.Companies.Queries.GetCompany;
+using Microsoft.AspNetCore.Mvc;
+
+namespace AutobusApi.Api.Controllers;
+
+[Route("companies")]
+public class CompanyController : BaseController
+{
+ [HttpPost]
+ public async Task Create([FromBody] CreateCompanyCommand command, CancellationToken cancellationToken)
+ {
+ return await Mediator.Send(command, cancellationToken);
+ }
+
+ [HttpGet]
+ public async Task> GetPage([FromQuery] GetCompaniesWithPaginationQuery query, CancellationToken cancellationToken)
+ {
+ return await Mediator.Send(query, cancellationToken);
+ }
+
+ [HttpGet("{id}")]
+ public async Task Get(int id, /* [FromQuery] GetCompanyQuery query, */ CancellationToken cancellationToken)
+ {
+ var query = new GetCompanyQuery() { Id = id };
+ return await Mediator.Send(query, cancellationToken);
+ }
+
+ [HttpPut]
+ public async Task Update([FromBody] UpdateCompanyCommand command, CancellationToken cancellationToken)
+ {
+ await Mediator.Send(command, cancellationToken);
+ }
+
+ [HttpDelete("{id}")]
+ public async Task Delete(int id, /* [FromBody] DeleteCompanyCommand command, */ CancellationToken cancellationToken)
+ {
+ var command = new DeleteCompanyCommand() { Id = id };
+ await Mediator.Send(command, cancellationToken);
+ }
+}
diff --git a/AutobusApi.Api/Controllers/CountryController.cs b/AutobusApi.Api/Controllers/CountryController.cs
new file mode 100644
index 0000000..46ac0a1
--- /dev/null
+++ b/AutobusApi.Api/Controllers/CountryController.cs
@@ -0,0 +1,46 @@
+using AutobusApi.Application.Common.Models;
+using AutobusApi.Application.Countries.Commands.CreateCountry;
+using AutobusApi.Application.Countries.Commands.DeleteCountry;
+using AutobusApi.Application.Countries.Commands.UpdateCountry;
+using AutobusApi.Application.Countries.Queries;
+using AutobusApi.Application.Countries.Queries.GetCountriesWithPagination;
+using AutobusApi.Application.Countries.Queries.GetCountry;
+using Microsoft.AspNetCore.Mvc;
+
+namespace AutobusApi.Api.Controllers;
+
+[Route("countries")]
+public class CountryController : BaseController
+{
+ [HttpPost]
+ public async Task Create([FromBody] CreateCountryCommand command, CancellationToken cancellationToken)
+ {
+ return await Mediator.Send(command, cancellationToken);
+ }
+
+ [HttpGet]
+ public async Task> GetPage([FromQuery] GetCountriesWithPaginationQuery query, CancellationToken cancellationToken)
+ {
+ return await Mediator.Send(query, cancellationToken);
+ }
+
+ [HttpGet("{id}")]
+ public async Task Get(int id, /* [FromQuery] GetCountryQuery query, */ CancellationToken cancellationToken)
+ {
+ var query = new GetCountryQuery() { Id = id };
+ return await Mediator.Send(query, cancellationToken);
+ }
+
+ [HttpPut]
+ public async Task Update([FromBody] UpdateCountryCommand command, CancellationToken cancellationToken)
+ {
+ await Mediator.Send(command, cancellationToken);
+ }
+
+ [HttpDelete("{id}")]
+ public async Task Delete(int id, /* [FromBody] DeleteCountryCommand command, */ CancellationToken cancellationToken)
+ {
+ var command = new DeleteCountryCommand() { Id = id };
+ await Mediator.Send(command, cancellationToken);
+ }
+}
diff --git a/AutobusApi.Api/Controllers/EmployeeController.cs b/AutobusApi.Api/Controllers/EmployeeController.cs
new file mode 100644
index 0000000..5f4ea5c
--- /dev/null
+++ b/AutobusApi.Api/Controllers/EmployeeController.cs
@@ -0,0 +1,46 @@
+using AutobusApi.Application.Common.Models;
+using AutobusApi.Application.Employees.Commands.CreateEmployee;
+using AutobusApi.Application.Employees.Commands.DeleteEmployee;
+using AutobusApi.Application.Employees.Commands.UpdateEmployee;
+using AutobusApi.Application.Employees.Queries;
+using AutobusApi.Application.Employees.Queries.GetEmployeesWithPagination;
+using AutobusApi.Application.Employees.Queries.GetEmployee;
+using Microsoft.AspNetCore.Mvc;
+
+namespace AutobusApi.Api.Controllers;
+
+[Route("employees")]
+public class EmployeeController : BaseController
+{
+ [HttpPost]
+ public async Task Create([FromBody] CreateEmployeeCommand command, CancellationToken cancellationToken)
+ {
+ return await Mediator.Send(command, cancellationToken);
+ }
+
+ [HttpGet]
+ public async Task> GetPage([FromQuery] GetEmployeesWithPaginationQuery query, CancellationToken cancellationToken)
+ {
+ return await Mediator.Send(query, cancellationToken);
+ }
+
+ [HttpGet("{id}")]
+ public async Task Get(int id, /* [FromQuery] GetEmployeeQuery query, */ CancellationToken cancellationToken)
+ {
+ var query = new GetEmployeeQuery() { Id = id };
+ return await Mediator.Send(query, cancellationToken);
+ }
+
+ [HttpPut]
+ public async Task Update([FromBody] UpdateEmployeeCommand command, CancellationToken cancellationToken)
+ {
+ await Mediator.Send(command, cancellationToken);
+ }
+
+ [HttpDelete("{id}")]
+ public async Task Delete(int id, /* [FromBody] DeleteEmployeeCommand command, */ CancellationToken cancellationToken)
+ {
+ var command = new DeleteEmployeeCommand() { Id = id };
+ await Mediator.Send(command, cancellationToken);
+ }
+}
diff --git a/AutobusApi.Api/Controllers/IdentityController.cs b/AutobusApi.Api/Controllers/IdentityController.cs
index efb2864..235f2c1 100644
--- a/AutobusApi.Api/Controllers/IdentityController.cs
+++ b/AutobusApi.Api/Controllers/IdentityController.cs
@@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Mvc;
namespace AutobusApi.Api.Controllers;
+[Route("identity")]
public class IdentityController : BaseController
{
[HttpPost("register")]
diff --git a/AutobusApi.Api/Controllers/RegionController.cs b/AutobusApi.Api/Controllers/RegionController.cs
new file mode 100644
index 0000000..201d7a6
--- /dev/null
+++ b/AutobusApi.Api/Controllers/RegionController.cs
@@ -0,0 +1,46 @@
+using AutobusApi.Application.Common.Models;
+using AutobusApi.Application.Regions.Commands.CreateRegion;
+using AutobusApi.Application.Regions.Commands.DeleteRegion;
+using AutobusApi.Application.Regions.Commands.UpdateRegion;
+using AutobusApi.Application.Regions.Queries;
+using AutobusApi.Application.Regions.Queries.GetRegionsWithPagination;
+using AutobusApi.Application.Regions.Queries.GetRegion;
+using Microsoft.AspNetCore.Mvc;
+
+namespace AutobusApi.Api.Controllers;
+
+[Route("regions")]
+public class RegionController : BaseController
+{
+ [HttpPost]
+ public async Task Create([FromBody] CreateRegionCommand command, CancellationToken cancellationToken)
+ {
+ return await Mediator.Send(command, cancellationToken);
+ }
+
+ [HttpGet]
+ public async Task> GetPage([FromQuery] GetRegionsWithPaginationQuery query, CancellationToken cancellationToken)
+ {
+ return await Mediator.Send(query, cancellationToken);
+ }
+
+ [HttpGet("{id}")]
+ public async Task Get(int id, /* [FromQuery] GetRegionQuery query, */ CancellationToken cancellationToken)
+ {
+ var query = new GetRegionQuery() { Id = id };
+ return await Mediator.Send(query, cancellationToken);
+ }
+
+ [HttpPut]
+ public async Task Update([FromBody] UpdateRegionCommand command, CancellationToken cancellationToken)
+ {
+ await Mediator.Send(command, cancellationToken);
+ }
+
+ [HttpDelete("{id}")]
+ public async Task Delete(int id, /* [FromBody] DeleteRegionCommand command, */ CancellationToken cancellationToken)
+ {
+ var command = new DeleteRegionCommand() { Id = id };
+ await Mediator.Send(command, cancellationToken);
+ }
+}
diff --git a/AutobusApi.Api/Controllers/RouteController.cs b/AutobusApi.Api/Controllers/RouteController.cs
new file mode 100644
index 0000000..f8583de
--- /dev/null
+++ b/AutobusApi.Api/Controllers/RouteController.cs
@@ -0,0 +1,46 @@
+using AutobusApi.Application.Common.Models;
+using AutobusApi.Application.Routes.Commands.CreateRoute;
+using AutobusApi.Application.Routes.Commands.DeleteRoute;
+using AutobusApi.Application.Routes.Commands.UpdateRoute;
+using AutobusApi.Application.Routes.Queries;
+using AutobusApi.Application.Routes.Queries.GetRoutesWithPagination;
+using AutobusApi.Application.Routes.Queries.GetRoute;
+using Microsoft.AspNetCore.Mvc;
+
+namespace AutobusApi.Api.Controllers;
+
+[Route("routes")]
+public class RouteController : BaseController
+{
+ [HttpPost]
+ public async Task Create([FromBody] CreateRouteCommand command, CancellationToken cancellationToken)
+ {
+ return await Mediator.Send(command, cancellationToken);
+ }
+
+ [HttpGet]
+ public async Task> GetPage([FromQuery] GetRoutesWithPaginationQuery query, CancellationToken cancellationToken)
+ {
+ return await Mediator.Send(query, cancellationToken);
+ }
+
+ [HttpGet("{id}")]
+ public async Task Get(int id, /* [FromQuery] GetRouteQuery query, */ CancellationToken cancellationToken)
+ {
+ var query = new GetRouteQuery() { Id = id };
+ return await Mediator.Send(query, cancellationToken);
+ }
+
+ [HttpPut]
+ public async Task Update([FromBody] UpdateRouteCommand command, CancellationToken cancellationToken)
+ {
+ await Mediator.Send(command, cancellationToken);
+ }
+
+ [HttpDelete("{id}")]
+ public async Task Delete(int id, /* [FromBody] DeleteRouteCommand command, */ CancellationToken cancellationToken)
+ {
+ var command = new DeleteRouteCommand() { Id = id };
+ await Mediator.Send(command, cancellationToken);
+ }
+}
diff --git a/AutobusApi.Api/Controllers/TicketGroupController.cs b/AutobusApi.Api/Controllers/TicketGroupController.cs
new file mode 100644
index 0000000..af968cf
--- /dev/null
+++ b/AutobusApi.Api/Controllers/TicketGroupController.cs
@@ -0,0 +1,46 @@
+using AutobusApi.Application.Common.Models;
+using AutobusApi.Application.TicketGroups.Commands.CreateTicketGroup;
+using AutobusApi.Application.TicketGroups.Commands.DeleteTicketGroup;
+using AutobusApi.Application.TicketGroups.Commands.UpdateTicketGroup;
+using AutobusApi.Application.TicketGroups.Queries;
+using AutobusApi.Application.TicketGroups.Queries.GetTicketGroupsWithPagination;
+using AutobusApi.Application.TicketGroups.Queries.GetTicketGroup;
+using Microsoft.AspNetCore.Mvc;
+
+namespace AutobusApi.Api.Controllers;
+
+[Route("ticketgroups")]
+public class TicketGroupController : BaseController
+{
+ [HttpPost]
+ public async Task Create([FromBody] CreateTicketGroupCommand command, CancellationToken cancellationToken)
+ {
+ return await Mediator.Send(command, cancellationToken);
+ }
+
+ [HttpGet]
+ public async Task> GetPage([FromQuery] GetTicketGroupsWithPaginationQuery query, CancellationToken cancellationToken)
+ {
+ return await Mediator.Send(query, cancellationToken);
+ }
+
+ [HttpGet("{id}")]
+ public async Task Get(int id, /* [FromQuery] GetTicketGroupQuery query, */ CancellationToken cancellationToken)
+ {
+ var query = new GetTicketGroupQuery() { Id = id };
+ return await Mediator.Send(query, cancellationToken);
+ }
+
+ [HttpPut]
+ public async Task Update([FromBody] UpdateTicketGroupCommand command, CancellationToken cancellationToken)
+ {
+ await Mediator.Send(command, cancellationToken);
+ }
+
+ [HttpDelete("{id}")]
+ public async Task Delete(int id, /* [FromBody] DeleteTicketGroupCommand command, */ CancellationToken cancellationToken)
+ {
+ var command = new DeleteTicketGroupCommand() { Id = id };
+ await Mediator.Send(command, cancellationToken);
+ }
+}
diff --git a/AutobusApi.Api/Controllers/VehicleEnrollmentController.cs b/AutobusApi.Api/Controllers/VehicleEnrollmentController.cs
new file mode 100644
index 0000000..c651e9c
--- /dev/null
+++ b/AutobusApi.Api/Controllers/VehicleEnrollmentController.cs
@@ -0,0 +1,46 @@
+using AutobusApi.Application.Common.Models;
+using AutobusApi.Application.VehicleEnrollments.Commands.CreateVehicleEnrollment;
+using AutobusApi.Application.VehicleEnrollments.Commands.DeleteVehicleEnrollment;
+using AutobusApi.Application.VehicleEnrollments.Commands.UpdateVehicleEnrollment;
+using AutobusApi.Application.VehicleEnrollments.Queries;
+using AutobusApi.Application.VehicleEnrollments.Queries.GetVehicleEnrollmentsWithPagination;
+using AutobusApi.Application.VehicleEnrollments.Queries.GetVehicleEnrollment;
+using Microsoft.AspNetCore.Mvc;
+
+namespace AutobusApi.Api.Controllers;
+
+[Route("vehicleenrollments")]
+public class VehicleEnrollmentController : BaseController
+{
+ [HttpPost]
+ public async Task Create([FromBody] CreateVehicleEnrollmentCommand command, CancellationToken cancellationToken)
+ {
+ return await Mediator.Send(command, cancellationToken);
+ }
+
+ [HttpGet]
+ public async Task> GetPage([FromQuery] GetVehicleEnrollmentsWithPaginationQuery query, CancellationToken cancellationToken)
+ {
+ return await Mediator.Send(query, cancellationToken);
+ }
+
+ [HttpGet("{id}")]
+ public async Task Get(int id, /* [FromQuery] GetVehicleEnrollmentQuery query, */ CancellationToken cancellationToken)
+ {
+ var query = new GetVehicleEnrollmentQuery() { Id = id };
+ return await Mediator.Send(query, cancellationToken);
+ }
+
+ [HttpPut]
+ public async Task Update([FromBody] UpdateVehicleEnrollmentCommand command, CancellationToken cancellationToken)
+ {
+ await Mediator.Send(command, cancellationToken);
+ }
+
+ [HttpDelete("{id}")]
+ public async Task Delete(int id, /* [FromBody] DeleteVehicleEnrollmentCommand command, */ CancellationToken cancellationToken)
+ {
+ var command = new DeleteVehicleEnrollmentCommand() { Id = id };
+ await Mediator.Send(command, cancellationToken);
+ }
+}
diff --git a/AutobusApi.Api/Middlewares/GlobalExceptionHandlerMiddleware.cs b/AutobusApi.Api/Middlewares/GlobalExceptionHandlerMiddleware.cs
index 9e67ff6..9fb9d22 100644
--- a/AutobusApi.Api/Middlewares/GlobalExceptionHandlerMiddleware.cs
+++ b/AutobusApi.Api/Middlewares/GlobalExceptionHandlerMiddleware.cs
@@ -124,6 +124,7 @@ public class GlobalExceptionHandlerMiddleware : IMiddleware
Detail = "Report this error to service's support team.",
});
+ await Console.Error.WriteLineAsync(exception.Message);
await Console.Error.WriteLineAsync(exception.StackTrace);
}
}
diff --git a/AutobusApi.Api/appsettings.Development.json b/AutobusApi.Api/appsettings.Development.json
index 7075fba..ddfb952 100644
--- a/AutobusApi.Api/appsettings.Development.json
+++ b/AutobusApi.Api/appsettings.Development.json
@@ -1,6 +1,6 @@
{
"ConnectionStrings": {
- "DefaultConnection": "Host=10.0.0.20:5432;Database=autobus;Username=postgres;Password=12345678"
+ "DefaultConnection": "Host=10.0.1.20:5432;Database=autobus;Username=postgres;Password=12345678"
},
"Jwt": {
"Issuer": "",
diff --git a/AutobusApi.Application/Addresses/Commands/CreateAddress/CreateAddressCommand.cs b/AutobusApi.Application/Addresses/Commands/CreateAddress/CreateAddressCommand.cs
new file mode 100644
index 0000000..9172ed1
--- /dev/null
+++ b/AutobusApi.Application/Addresses/Commands/CreateAddress/CreateAddressCommand.cs
@@ -0,0 +1,16 @@
+using MediatR;
+
+namespace AutobusApi.Application.Addresses.Commands.CreateAddress;
+
+public record CreateAddressCommand : IRequest
+{
+ public string Name { get; set; } = null!;
+
+ public int CityId { get; set; }
+
+ public double Latitude { get; set; }
+
+ public double Longitude { get; set; }
+
+ public string VehicleType { get; set; } = null!;
+}
diff --git a/AutobusApi.Application/Addresses/Commands/CreateAddress/CreateAddressCommandHandler.cs b/AutobusApi.Application/Addresses/Commands/CreateAddress/CreateAddressCommandHandler.cs
new file mode 100644
index 0000000..c909910
--- /dev/null
+++ b/AutobusApi.Application/Addresses/Commands/CreateAddress/CreateAddressCommandHandler.cs
@@ -0,0 +1,36 @@
+using AutobusApi.Application.Common.Interfaces;
+using AutobusApi.Domain.Entities;
+using AutobusApi.Domain.Enums;
+using MediatR;
+
+namespace AutobusApi.Application.Addresses.Commands.CreateAddress;
+
+public class CreateAddressCommandHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _dbContext;
+
+ public CreateAddressCommandHandler(IApplicationDbContext dbContext)
+ {
+ _dbContext = dbContext;
+ }
+
+ public async Task Handle(
+ CreateAddressCommand request,
+ CancellationToken cancellationToken)
+ {
+ var address = new Address();
+
+ address.Name = request.Name;
+ address.CityId = request.CityId;
+ // TODO: Fix abstraction problems
+ // address.Location.Latitude = request.Latitude;
+ // address.Location.Longitude = request.Longitude;
+ address.VehicleType = Enum.Parse(request.VehicleType);
+
+ _dbContext.Addresses.Add(address);
+
+ await _dbContext.SaveChangesAsync(cancellationToken);
+
+ return address.Id;
+ }
+}
diff --git a/AutobusApi.Application/Addresses/Commands/CreateAddress/CreateAddressCommandValidator.cs b/AutobusApi.Application/Addresses/Commands/CreateAddress/CreateAddressCommandValidator.cs
new file mode 100644
index 0000000..1fd3e15
--- /dev/null
+++ b/AutobusApi.Application/Addresses/Commands/CreateAddress/CreateAddressCommandValidator.cs
@@ -0,0 +1,20 @@
+using AutobusApi.Domain.Enums;
+using FluentValidation;
+
+namespace AutobusApi.Application.Addresses.Commands.CreateAddress;
+
+public class CreateAddressCommandValidator : AbstractValidator
+{
+ public CreateAddressCommandValidator()
+ {
+ RuleFor(v => v.Name).MinimumLength(2).MaximumLength(64);
+
+ RuleFor(v => v.CityId).GreaterThan(0);
+
+ RuleFor(v => v.Latitude).GreaterThanOrEqualTo(-180).LessThanOrEqualTo(180);
+
+ RuleFor(v => v.Longitude).GreaterThanOrEqualTo(-180).LessThanOrEqualTo(180);
+
+ RuleFor(v => v.VehicleType).Must(value => Enum.TryParse(value, true, out _));
+ }
+}
diff --git a/AutobusApi.Application/Addresses/Commands/DeleteAddress/DeleteAddressCommand.cs b/AutobusApi.Application/Addresses/Commands/DeleteAddress/DeleteAddressCommand.cs
new file mode 100644
index 0000000..3a26650
--- /dev/null
+++ b/AutobusApi.Application/Addresses/Commands/DeleteAddress/DeleteAddressCommand.cs
@@ -0,0 +1,8 @@
+using MediatR;
+
+namespace AutobusApi.Application.Addresses.Commands.DeleteAddress;
+
+public record DeleteAddressCommand : IRequest
+{
+ public int Id { get; set; }
+}
diff --git a/AutobusApi.Application/Addresses/Commands/DeleteAddress/DeleteAddressCommandHandler.cs b/AutobusApi.Application/Addresses/Commands/DeleteAddress/DeleteAddressCommandHandler.cs
new file mode 100644
index 0000000..aee4d25
--- /dev/null
+++ b/AutobusApi.Application/Addresses/Commands/DeleteAddress/DeleteAddressCommandHandler.cs
@@ -0,0 +1,33 @@
+using AutobusApi.Application.Common.Exceptions;
+using AutobusApi.Application.Common.Interfaces;
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+
+namespace AutobusApi.Application.Addresses.Commands.DeleteAddress;
+
+public class DeleteAddressCommandHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _dbContext;
+
+ public DeleteAddressCommandHandler(IApplicationDbContext dbContext)
+ {
+ _dbContext = dbContext;
+ }
+
+ public async Task Handle(
+ DeleteAddressCommand request,
+ CancellationToken cancellationToken)
+ {
+ var address = await _dbContext.Addresses
+ .SingleOrDefaultAsync(c => c.Id == request.Id, cancellationToken);
+
+ if (address == null)
+ {
+ throw new NotFoundException();
+ }
+
+ _dbContext.Addresses.Remove(address);
+
+ await _dbContext.SaveChangesAsync(cancellationToken);
+ }
+}
diff --git a/AutobusApi.Application/Addresses/Commands/DeleteAddress/DeleteAddressCommandValidator.cs b/AutobusApi.Application/Addresses/Commands/DeleteAddress/DeleteAddressCommandValidator.cs
new file mode 100644
index 0000000..0b86a2a
--- /dev/null
+++ b/AutobusApi.Application/Addresses/Commands/DeleteAddress/DeleteAddressCommandValidator.cs
@@ -0,0 +1,11 @@
+using FluentValidation;
+
+namespace AutobusApi.Application.Addresses.Commands.DeleteAddress;
+
+public class DeleteAddressCommandValidator : AbstractValidator
+{
+ public DeleteAddressCommandValidator()
+ {
+ RuleFor(v => v.Id).GreaterThan(0);
+ }
+}
diff --git a/AutobusApi.Application/Addresses/Commands/UpdateAddress/UpdateAddressCommand.cs b/AutobusApi.Application/Addresses/Commands/UpdateAddress/UpdateAddressCommand.cs
new file mode 100644
index 0000000..7df4774
--- /dev/null
+++ b/AutobusApi.Application/Addresses/Commands/UpdateAddress/UpdateAddressCommand.cs
@@ -0,0 +1,18 @@
+using MediatR;
+
+namespace AutobusApi.Application.Addresses.Commands.UpdateAddress;
+
+public record UpdateAddressCommand : IRequest
+{
+ public int Id { get; set; }
+
+ public string Name { get; set; } = null!;
+
+ public int CityId { get; set; }
+
+ public double Latitude { get; set; }
+
+ public double Longitude { get; set; }
+
+ public string VehicleType { get; set; } = null!;
+}
diff --git a/AutobusApi.Application/Addresses/Commands/UpdateAddress/UpdateAddressCommandHandler.cs b/AutobusApi.Application/Addresses/Commands/UpdateAddress/UpdateAddressCommandHandler.cs
new file mode 100644
index 0000000..b9d8b76
--- /dev/null
+++ b/AutobusApi.Application/Addresses/Commands/UpdateAddress/UpdateAddressCommandHandler.cs
@@ -0,0 +1,38 @@
+using AutobusApi.Application.Common.Exceptions;
+using AutobusApi.Application.Common.Interfaces;
+using AutobusApi.Domain.Enums;
+using MediatR;
+
+namespace AutobusApi.Application.Addresses.Commands.UpdateAddress;
+
+public class UpdateAddressCommandHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _dbContext;
+
+ public UpdateAddressCommandHandler(IApplicationDbContext dbContext)
+ {
+ _dbContext = dbContext;
+ }
+
+ public async Task Handle(
+ UpdateAddressCommand request,
+ CancellationToken cancellationToken)
+ {
+ var address = await _dbContext.Addresses
+ .FindAsync(new object[] { request.Id }, cancellationToken);
+
+ if (address == null)
+ {
+ throw new NotFoundException();
+ }
+
+ address.Name = request.Name;
+ address.CityId = request.CityId;
+ // TODO: Fix abstraction problems
+ // address.Location.Latitude = request.Latitude;
+ // address.Location.Longitude = request.Longitude;
+ address.VehicleType = Enum.Parse(request.VehicleType);
+
+ await _dbContext.SaveChangesAsync(cancellationToken);
+ }
+}
diff --git a/AutobusApi.Application/Addresses/Commands/UpdateAddress/UpdateAddressCommandValidator.cs b/AutobusApi.Application/Addresses/Commands/UpdateAddress/UpdateAddressCommandValidator.cs
new file mode 100644
index 0000000..b71f39c
--- /dev/null
+++ b/AutobusApi.Application/Addresses/Commands/UpdateAddress/UpdateAddressCommandValidator.cs
@@ -0,0 +1,22 @@
+using AutobusApi.Domain.Enums;
+using FluentValidation;
+
+namespace AutobusApi.Application.Addresses.Commands.UpdateAddress;
+
+public class UpdateAddressCommandValidator : AbstractValidator
+{
+ public UpdateAddressCommandValidator()
+ {
+ RuleFor(v => v.Name).MinimumLength(2).MaximumLength(64);
+
+ RuleFor(v => v.Id).GreaterThan(0);
+
+ RuleFor(v => v.CityId).GreaterThan(0);
+
+ RuleFor(v => v.Latitude).GreaterThanOrEqualTo(-180).LessThanOrEqualTo(180);
+
+ RuleFor(v => v.Longitude).GreaterThanOrEqualTo(-180).LessThanOrEqualTo(180);
+
+ RuleFor(v => v.VehicleType).Must(value => Enum.TryParse(value, true, out _));
+ }
+}
diff --git a/AutobusApi.Application/Addresses/Queries/AddressDto.cs b/AutobusApi.Application/Addresses/Queries/AddressDto.cs
new file mode 100644
index 0000000..2e345ca
--- /dev/null
+++ b/AutobusApi.Application/Addresses/Queries/AddressDto.cs
@@ -0,0 +1,44 @@
+using AutobusApi.Application.Common.Mappings;
+using AutobusApi.Domain.Entities;
+using AutoMapper;
+
+namespace AutobusApi.Application.Addresses.Queries;
+
+public class AddressDto : IMapFrom
+{
+ public int Id { get; set; }
+
+ public string Name { get; set; } = null!;
+
+ public double Latitude { get; set; }
+
+ public double Longitude { get; set; }
+
+ public string VehicleType { get; set; } = null!;
+
+ public int CityId { get; set; }
+
+ public string CityName { get; set; } = null!;
+
+ public int RegionId { get; set; }
+
+ public string RegionName { get; set; } = null!;
+
+ public int CountryId { get; set; }
+
+ public string CountryName { get; set; } = null!;
+
+ public void Mapping(Profile profile)
+ {
+ profile.CreateMap()
+ .ForMember(d => d.CityId, opt => opt.MapFrom(s => s.CityId))
+ .ForMember(d => d.CityName, opt => opt.MapFrom(s => s.City.Name))
+ .ForMember(d => d.RegionId, opt => opt.MapFrom(s => s.City.RegionId))
+ .ForMember(d => d.RegionName, opt => opt.MapFrom(s => s.City.Region.Name))
+ .ForMember(d => d.CountryId, opt => opt.MapFrom(s => s.City.Region.CountryId))
+ .ForMember(d => d.CountryName, opt => opt.MapFrom(s => s.City.Region.Country.Name))
+ .ForMember(d => d.Latitude, opt => opt.MapFrom(s => s.Location.Latitude))
+ .ForMember(d => d.Longitude, opt => opt.MapFrom(s => s.Location.Longitude))
+ .ForMember(d => d.VehicleType, opt => opt.MapFrom(s => s.VehicleType.ToString()));
+ }
+}
diff --git a/AutobusApi.Application/Addresses/Queries/GetAddress/GetAddressQuery.cs b/AutobusApi.Application/Addresses/Queries/GetAddress/GetAddressQuery.cs
new file mode 100644
index 0000000..bfaf0ca
--- /dev/null
+++ b/AutobusApi.Application/Addresses/Queries/GetAddress/GetAddressQuery.cs
@@ -0,0 +1,8 @@
+using MediatR;
+
+namespace AutobusApi.Application.Addresses.Queries.GetAddress;
+
+public record GetAddressQuery : IRequest
+{
+ public int Id { get; set; }
+}
diff --git a/AutobusApi.Application/Addresses/Queries/GetAddress/GetAddressQueryHandler.cs b/AutobusApi.Application/Addresses/Queries/GetAddress/GetAddressQueryHandler.cs
new file mode 100644
index 0000000..64193b9
--- /dev/null
+++ b/AutobusApi.Application/Addresses/Queries/GetAddress/GetAddressQueryHandler.cs
@@ -0,0 +1,36 @@
+using AutobusApi.Application.Common.Exceptions;
+using AutobusApi.Application.Common.Interfaces;
+using AutoMapper;
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+
+namespace AutobusApi.Application.Addresses.Queries.GetAddress;
+
+public class GetAddressQueryHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _dbContext;
+ private readonly IMapper _mapper;
+
+ public GetAddressQueryHandler(
+ IApplicationDbContext dbContext,
+ IMapper mapper)
+ {
+ _dbContext = dbContext;
+ _mapper = mapper;
+ }
+
+ public async Task Handle(
+ GetAddressQuery request,
+ CancellationToken cancellationToken)
+ {
+ var address = await _dbContext.Addresses
+ .SingleOrDefaultAsync(c => c.Id == request.Id);
+
+ if (address == null)
+ {
+ throw new NotFoundException();
+ }
+
+ return _mapper.Map(address);
+ }
+}
diff --git a/AutobusApi.Application/Addresses/Queries/GetAddress/GetAddressQueryValidator.cs b/AutobusApi.Application/Addresses/Queries/GetAddress/GetAddressQueryValidator.cs
new file mode 100644
index 0000000..cc942da
--- /dev/null
+++ b/AutobusApi.Application/Addresses/Queries/GetAddress/GetAddressQueryValidator.cs
@@ -0,0 +1,11 @@
+using FluentValidation;
+
+namespace AutobusApi.Application.Addresses.Queries.GetAddress;
+
+public class GetAddressQueryValidator : AbstractValidator
+{
+ public GetAddressQueryValidator()
+ {
+ RuleFor(v => v.Id).GreaterThan(0);
+ }
+}
diff --git a/AutobusApi.Application/Addresses/Queries/GetAddressesWithPagination/GetAddressesWithPaginationQuery.cs b/AutobusApi.Application/Addresses/Queries/GetAddressesWithPagination/GetAddressesWithPaginationQuery.cs
new file mode 100644
index 0000000..63a7a1b
--- /dev/null
+++ b/AutobusApi.Application/Addresses/Queries/GetAddressesWithPagination/GetAddressesWithPaginationQuery.cs
@@ -0,0 +1,13 @@
+using AutobusApi.Application.Common.Models;
+using MediatR;
+
+namespace AutobusApi.Application.Addresses.Queries.GetAddressesWithPagination;
+
+public record GetAddressesWithPaginationQuery : IRequest>
+{
+ public string Sort { get; set; } = "";
+
+ public int PageNumber { get; set; } = 1;
+
+ public int PageSize { get; set; } = 10;
+}
diff --git a/AutobusApi.Application/Addresses/Queries/GetAddressesWithPagination/GetAddressesWithPaginationQueryHandler.cs b/AutobusApi.Application/Addresses/Queries/GetAddressesWithPagination/GetAddressesWithPaginationQueryHandler.cs
new file mode 100644
index 0000000..22338f3
--- /dev/null
+++ b/AutobusApi.Application/Addresses/Queries/GetAddressesWithPagination/GetAddressesWithPaginationQueryHandler.cs
@@ -0,0 +1,32 @@
+using AutobusApi.Application.Common.Interfaces;
+using AutobusApi.Application.Common.Mappings;
+using AutobusApi.Application.Common.Models;
+using AutoMapper;
+using AutoMapper.QueryableExtensions;
+using MediatR;
+
+namespace AutobusApi.Application.Addresses.Queries.GetAddressesWithPagination;
+
+public class GetAddressesWithPaginationQueryHandler : IRequestHandler>
+{
+ private readonly IApplicationDbContext _dbContext;
+ private readonly IMapper _mapper;
+
+ public GetAddressesWithPaginationQueryHandler(
+ IApplicationDbContext dbContext,
+ IMapper mapper)
+ {
+ _dbContext = dbContext;
+ _mapper = mapper;
+ }
+
+ public async Task> Handle(
+ GetAddressesWithPaginationQuery request,
+ CancellationToken cancellationToken)
+ {
+ return await _dbContext.Addresses
+ .ProjectTo(_mapper.ConfigurationProvider)
+ .ApplySort(request.Sort)
+ .PaginatedListAsync(request.PageNumber, request.PageSize);
+ }
+}
diff --git a/AutobusApi.Application/Addresses/Queries/GetAddressesWithPagination/GetAddressesWithPaginationQueryValidator.cs b/AutobusApi.Application/Addresses/Queries/GetAddressesWithPagination/GetAddressesWithPaginationQueryValidator.cs
new file mode 100644
index 0000000..a371312
--- /dev/null
+++ b/AutobusApi.Application/Addresses/Queries/GetAddressesWithPagination/GetAddressesWithPaginationQueryValidator.cs
@@ -0,0 +1,13 @@
+using FluentValidation;
+
+namespace AutobusApi.Application.Addresses.Queries.GetAddressesWithPagination;
+
+public class GetAddressesWithPaginationQueryValidator : AbstractValidator
+{
+ public GetAddressesWithPaginationQueryValidator()
+ {
+ RuleFor(v => v.PageNumber).GreaterThanOrEqualTo(1);
+
+ RuleFor(v => v.PageSize).GreaterThanOrEqualTo(1).LessThanOrEqualTo(50);
+ }
+}
diff --git a/AutobusApi.Application/AutobusApi.Application.csproj b/AutobusApi.Application/AutobusApi.Application.csproj
index b821f29..79ffc69 100644
--- a/AutobusApi.Application/AutobusApi.Application.csproj
+++ b/AutobusApi.Application/AutobusApi.Application.csproj
@@ -12,12 +12,13 @@
12.0.1
- 11.8.0
+ 11.8.1
-
+
- 7.0.13
+ 7.0.14
+
diff --git a/AutobusApi.Application/Buses/Commands/CreateBus/CreateBusCommand.cs b/AutobusApi.Application/Buses/Commands/CreateBus/CreateBusCommand.cs
new file mode 100644
index 0000000..41448ef
--- /dev/null
+++ b/AutobusApi.Application/Buses/Commands/CreateBus/CreateBusCommand.cs
@@ -0,0 +1,24 @@
+using MediatR;
+
+namespace AutobusApi.Application.Buses.Commands.CreateBus;
+
+public record CreateBusCommand : IRequest
+{
+ public int CompanyId { get; set; }
+
+ public string Number { get; set; } = null!;
+
+ public string Model { get; set; } = null!;
+
+ public int Capacity { get; set; }
+
+ public bool HasClimateControl { get; set; }
+
+ public bool HasWiFi { get; set; }
+
+ public bool HasMultimedia { get; set; }
+
+ public bool HasWC { get; set; }
+
+ public bool HasOutlets { get; set; }
+}
diff --git a/AutobusApi.Application/Buses/Commands/CreateBus/CreateBusCommandHandler.cs b/AutobusApi.Application/Buses/Commands/CreateBus/CreateBusCommandHandler.cs
new file mode 100644
index 0000000..aab36d8
--- /dev/null
+++ b/AutobusApi.Application/Buses/Commands/CreateBus/CreateBusCommandHandler.cs
@@ -0,0 +1,38 @@
+using AutobusApi.Application.Common.Interfaces;
+using AutobusApi.Domain.Entities;
+using MediatR;
+
+namespace AutobusApi.Application.Buses.Commands.CreateBus;
+
+public class CreateBusCommandHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _dbContext;
+
+ public CreateBusCommandHandler(IApplicationDbContext dbContext)
+ {
+ _dbContext = dbContext;
+ }
+
+ public async Task Handle(
+ CreateBusCommand request,
+ CancellationToken cancellationToken)
+ {
+ var bus = new Bus();
+
+ bus.CompanyId = request.CompanyId;
+ bus.Number = request.Number;
+ bus.Model = request.Model;
+ bus.Capacity = request.Capacity;
+ bus.HasClimateControl = request.HasClimateControl;
+ bus.HasWiFi = request.HasWiFi;
+ bus.HasMultimedia = request.HasMultimedia;
+ bus.HasWC = request.HasWC;
+ bus.HasOutlets = request.HasOutlets;
+
+ _dbContext.Vehicles.Add(bus);
+
+ await _dbContext.SaveChangesAsync(cancellationToken);
+
+ return bus.Id;
+ }
+}
diff --git a/AutobusApi.Application/Buses/Commands/CreateBus/CreateBusCommandValidator.cs b/AutobusApi.Application/Buses/Commands/CreateBus/CreateBusCommandValidator.cs
new file mode 100644
index 0000000..f1fa161
--- /dev/null
+++ b/AutobusApi.Application/Buses/Commands/CreateBus/CreateBusCommandValidator.cs
@@ -0,0 +1,15 @@
+using FluentValidation;
+
+namespace AutobusApi.Application.Buses.Commands.CreateBus;
+
+public class CreateBusCommandValidator : AbstractValidator
+{
+ public CreateBusCommandValidator()
+ {
+ RuleFor(v => v.Number).MinimumLength(8).MaximumLength(8);
+
+ RuleFor(v => v.Model).MinimumLength(2).MaximumLength(64);
+
+ RuleFor(v => v.Capacity).GreaterThan(0);
+ }
+}
diff --git a/AutobusApi.Application/Buses/Commands/DeleteBus/DeleteBusCommand.cs b/AutobusApi.Application/Buses/Commands/DeleteBus/DeleteBusCommand.cs
new file mode 100644
index 0000000..51ac7de
--- /dev/null
+++ b/AutobusApi.Application/Buses/Commands/DeleteBus/DeleteBusCommand.cs
@@ -0,0 +1,8 @@
+using MediatR;
+
+namespace AutobusApi.Application.Buses.Commands.DeleteBus;
+
+public record DeleteBusCommand : IRequest
+{
+ public int Id { get; set; }
+}
diff --git a/AutobusApi.Application/Buses/Commands/DeleteBus/DeleteBusCommandHandler.cs b/AutobusApi.Application/Buses/Commands/DeleteBus/DeleteBusCommandHandler.cs
new file mode 100644
index 0000000..58a1180
--- /dev/null
+++ b/AutobusApi.Application/Buses/Commands/DeleteBus/DeleteBusCommandHandler.cs
@@ -0,0 +1,33 @@
+using AutobusApi.Application.Common.Exceptions;
+using AutobusApi.Application.Common.Interfaces;
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+
+namespace AutobusApi.Application.Buses.Commands.DeleteBus;
+
+public class DeleteBusCommandHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _dbContext;
+
+ public DeleteBusCommandHandler(IApplicationDbContext dbContext)
+ {
+ _dbContext = dbContext;
+ }
+
+ public async Task Handle(
+ DeleteBusCommand request,
+ CancellationToken cancellationToken)
+ {
+ var bus = await _dbContext.Buses
+ .SingleOrDefaultAsync(c => c.Id == request.Id, cancellationToken);
+
+ if (bus == null)
+ {
+ throw new NotFoundException();
+ }
+
+ _dbContext.Buses.Remove(bus);
+
+ await _dbContext.SaveChangesAsync(cancellationToken);
+ }
+}
diff --git a/AutobusApi.Application/Buses/Commands/DeleteBus/DeleteBusCommandValidator.cs b/AutobusApi.Application/Buses/Commands/DeleteBus/DeleteBusCommandValidator.cs
new file mode 100644
index 0000000..6cfaad3
--- /dev/null
+++ b/AutobusApi.Application/Buses/Commands/DeleteBus/DeleteBusCommandValidator.cs
@@ -0,0 +1,11 @@
+using FluentValidation;
+
+namespace AutobusApi.Application.Buses.Commands.DeleteBus;
+
+public class DeleteBusCommandValidator : AbstractValidator
+{
+ public DeleteBusCommandValidator()
+ {
+ RuleFor(v => v.Id).GreaterThan(0);
+ }
+}
diff --git a/AutobusApi.Application/Buses/Commands/UpdateBus/UpdateBusCommand.cs b/AutobusApi.Application/Buses/Commands/UpdateBus/UpdateBusCommand.cs
new file mode 100644
index 0000000..18cc24a
--- /dev/null
+++ b/AutobusApi.Application/Buses/Commands/UpdateBus/UpdateBusCommand.cs
@@ -0,0 +1,26 @@
+using MediatR;
+
+namespace AutobusApi.Application.Buses.Commands.UpdateBus;
+
+public record UpdateBusCommand : IRequest
+{
+ public int Id { get; set; }
+
+ public int CompanyId { get; set; }
+
+ public string Number { get; set; } = null!;
+
+ public string Model { get; set; } = null!;
+
+ public int Capacity { get; set; }
+
+ public bool HasClimateControl { get; set; }
+
+ public bool HasWiFi { get; set; }
+
+ public bool HasMultimedia { get; set; }
+
+ public bool HasWC { get; set; }
+
+ public bool HasOutlets { get; set; }
+}
diff --git a/AutobusApi.Application/Buses/Commands/UpdateBus/UpdateBusCommandHandler.cs b/AutobusApi.Application/Buses/Commands/UpdateBus/UpdateBusCommandHandler.cs
new file mode 100644
index 0000000..ec60b3e
--- /dev/null
+++ b/AutobusApi.Application/Buses/Commands/UpdateBus/UpdateBusCommandHandler.cs
@@ -0,0 +1,40 @@
+using AutobusApi.Application.Common.Exceptions;
+using AutobusApi.Application.Common.Interfaces;
+using MediatR;
+
+namespace AutobusApi.Application.Buses.Commands.UpdateBus;
+
+public class UpdateBusCommandHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _dbContext;
+
+ public UpdateBusCommandHandler(IApplicationDbContext dbContext)
+ {
+ _dbContext = dbContext;
+ }
+
+ public async Task Handle(
+ UpdateBusCommand request,
+ CancellationToken cancellationToken)
+ {
+ var bus = await _dbContext.Buses
+ .FindAsync(new object[] { request.Id }, cancellationToken);
+
+ if (bus == null)
+ {
+ throw new NotFoundException();
+ }
+
+ bus.CompanyId = request.CompanyId;
+ bus.Number = request.Number;
+ bus.Model = request.Model;
+ bus.Capacity = request.Capacity;
+ bus.HasClimateControl = request.HasClimateControl;
+ bus.HasWiFi = request.HasWiFi;
+ bus.HasMultimedia = request.HasMultimedia;
+ bus.HasWC = request.HasWC;
+ bus.HasOutlets = request.HasOutlets;
+
+ await _dbContext.SaveChangesAsync(cancellationToken);
+ }
+}
diff --git a/AutobusApi.Application/Buses/Commands/UpdateBus/UpdateBusCommandValidator.cs b/AutobusApi.Application/Buses/Commands/UpdateBus/UpdateBusCommandValidator.cs
new file mode 100644
index 0000000..989cfdd
--- /dev/null
+++ b/AutobusApi.Application/Buses/Commands/UpdateBus/UpdateBusCommandValidator.cs
@@ -0,0 +1,19 @@
+using FluentValidation;
+
+namespace AutobusApi.Application.Buses.Commands.UpdateBus;
+
+public class UpdateBusCommandValidator : AbstractValidator
+{
+ public UpdateBusCommandValidator()
+ {
+ RuleFor(v => v.Id).GreaterThan(0);
+
+ RuleFor(v => v.CompanyId).GreaterThan(0);
+
+ RuleFor(v => v.Number).MinimumLength(8).MaximumLength(8);
+
+ RuleFor(v => v.Model).MinimumLength(2).MaximumLength(64);
+
+ RuleFor(v => v.Capacity).GreaterThan(0);
+ }
+}
diff --git a/AutobusApi.Application/Buses/Queries/BusDto.cs b/AutobusApi.Application/Buses/Queries/BusDto.cs
new file mode 100644
index 0000000..2228b5f
--- /dev/null
+++ b/AutobusApi.Application/Buses/Queries/BusDto.cs
@@ -0,0 +1,27 @@
+using AutobusApi.Application.Common.Mappings;
+using AutobusApi.Domain.Entities;
+
+namespace AutobusApi.Application.Buses.Queries;
+
+public class BusDto : IMapFrom
+{
+ public int Id { get; set; }
+
+ public int CompanyId { get; set; }
+
+ public string Number { get; set; } = null!;
+
+ public string Model { get; set; } = null!;
+
+ public int Capacity { get; set; }
+
+ public bool HasClimateControl { get; set; }
+
+ public bool HasWiFi { get; set; }
+
+ public bool HasMultimedia { get; set; }
+
+ public bool HasWC { get; set; }
+
+ public bool HasOutlets { get; set; }
+}
diff --git a/AutobusApi.Application/Buses/Queries/GetBus/GetBusQuery.cs b/AutobusApi.Application/Buses/Queries/GetBus/GetBusQuery.cs
new file mode 100644
index 0000000..3373865
--- /dev/null
+++ b/AutobusApi.Application/Buses/Queries/GetBus/GetBusQuery.cs
@@ -0,0 +1,8 @@
+using MediatR;
+
+namespace AutobusApi.Application.Buses.Queries.GetBus;
+
+public record GetBusQuery : IRequest
+{
+ public int Id { get; set; }
+}
diff --git a/AutobusApi.Application/Buses/Queries/GetBus/GetBusQueryHandler.cs b/AutobusApi.Application/Buses/Queries/GetBus/GetBusQueryHandler.cs
new file mode 100644
index 0000000..8fdf17a
--- /dev/null
+++ b/AutobusApi.Application/Buses/Queries/GetBus/GetBusQueryHandler.cs
@@ -0,0 +1,38 @@
+using AutobusApi.Application.Common.Exceptions;
+using AutobusApi.Application.Common.Interfaces;
+using AutoMapper;
+using AutoMapper.QueryableExtensions;
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+
+namespace AutobusApi.Application.Buses.Queries.GetBus;
+
+public class GetBusQueryHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _dbContext;
+ private readonly IMapper _mapper;
+
+ public GetBusQueryHandler(
+ IApplicationDbContext dbContext,
+ IMapper mapper)
+ {
+ _dbContext = dbContext;
+ _mapper = mapper;
+ }
+
+ public async Task Handle(
+ GetBusQuery request,
+ CancellationToken cancellationToken)
+ {
+ var bus = await _dbContext.Buses
+ .ProjectTo(_mapper.ConfigurationProvider)
+ .SingleOrDefaultAsync(c => c.Id == request.Id);
+
+ if (bus == null)
+ {
+ throw new NotFoundException();
+ }
+
+ return bus;
+ }
+}
diff --git a/AutobusApi.Application/Buses/Queries/GetBus/GetBusQueryValidator.cs b/AutobusApi.Application/Buses/Queries/GetBus/GetBusQueryValidator.cs
new file mode 100644
index 0000000..c7fc7ff
--- /dev/null
+++ b/AutobusApi.Application/Buses/Queries/GetBus/GetBusQueryValidator.cs
@@ -0,0 +1,11 @@
+using FluentValidation;
+
+namespace AutobusApi.Application.Buses.Queries.GetBus;
+
+public class GetBusQueryValidator : AbstractValidator
+{
+ public GetBusQueryValidator()
+ {
+ RuleFor(v => v.Id).GreaterThan(0);
+ }
+}
diff --git a/AutobusApi.Application/Buses/Queries/GetBusesWithPagination/GetBusesWithPaginationQuery.cs b/AutobusApi.Application/Buses/Queries/GetBusesWithPagination/GetBusesWithPaginationQuery.cs
new file mode 100644
index 0000000..0ddafe1
--- /dev/null
+++ b/AutobusApi.Application/Buses/Queries/GetBusesWithPagination/GetBusesWithPaginationQuery.cs
@@ -0,0 +1,13 @@
+using AutobusApi.Application.Common.Models;
+using MediatR;
+
+namespace AutobusApi.Application.Buses.Queries.GetBusesWithPagination;
+
+public record GetBusesWithPaginationQuery : IRequest>
+{
+ public string Sort { get; set; } = "";
+
+ public int PageNumber { get; set; } = 1;
+
+ public int PageSize { get; set; } = 10;
+}
diff --git a/AutobusApi.Application/Buses/Queries/GetBusesWithPagination/GetBusesWithPaginationQueryHandler.cs b/AutobusApi.Application/Buses/Queries/GetBusesWithPagination/GetBusesWithPaginationQueryHandler.cs
new file mode 100644
index 0000000..377fe36
--- /dev/null
+++ b/AutobusApi.Application/Buses/Queries/GetBusesWithPagination/GetBusesWithPaginationQueryHandler.cs
@@ -0,0 +1,32 @@
+using AutobusApi.Application.Common.Interfaces;
+using AutobusApi.Application.Common.Mappings;
+using AutobusApi.Application.Common.Models;
+using AutoMapper;
+using AutoMapper.QueryableExtensions;
+using MediatR;
+
+namespace AutobusApi.Application.Buses.Queries.GetBusesWithPagination;
+
+public class GetBusesWithPaginationQueryHandler : IRequestHandler>
+{
+ private readonly IApplicationDbContext _dbContext;
+ private readonly IMapper _mapper;
+
+ public GetBusesWithPaginationQueryHandler(
+ IApplicationDbContext dbContext,
+ IMapper mapper)
+ {
+ _dbContext = dbContext;
+ _mapper = mapper;
+ }
+
+ public async Task> Handle(
+ GetBusesWithPaginationQuery request,
+ CancellationToken cancellationToken)
+ {
+ return await _dbContext.Buses
+ .ProjectTo(_mapper.ConfigurationProvider)
+ .ApplySort(request.Sort)
+ .PaginatedListAsync(request.PageNumber, request.PageSize);
+ }
+}
diff --git a/AutobusApi.Application/Buses/Queries/GetBusesWithPagination/GetBusesWithPaginationQueryValidator.cs b/AutobusApi.Application/Buses/Queries/GetBusesWithPagination/GetBusesWithPaginationQueryValidator.cs
new file mode 100644
index 0000000..9ed2094
--- /dev/null
+++ b/AutobusApi.Application/Buses/Queries/GetBusesWithPagination/GetBusesWithPaginationQueryValidator.cs
@@ -0,0 +1,13 @@
+using FluentValidation;
+
+namespace AutobusApi.Application.Buses.Queries.GetBusesWithPagination;
+
+public class GetBusesWithPaginationQueryValidator : AbstractValidator
+{
+ public GetBusesWithPaginationQueryValidator()
+ {
+ RuleFor(v => v.PageNumber).GreaterThanOrEqualTo(1);
+
+ RuleFor(v => v.PageSize).GreaterThanOrEqualTo(1).LessThanOrEqualTo(50);
+ }
+}
diff --git a/AutobusApi.Application/Cities/Commands/CreateCity/CreateCityCommand.cs b/AutobusApi.Application/Cities/Commands/CreateCity/CreateCityCommand.cs
new file mode 100644
index 0000000..a7e48e9
--- /dev/null
+++ b/AutobusApi.Application/Cities/Commands/CreateCity/CreateCityCommand.cs
@@ -0,0 +1,10 @@
+using MediatR;
+
+namespace AutobusApi.Application.Cities.Commands.CreateCity;
+
+public record CreateCityCommand : IRequest
+{
+ public string Name { get; set; } = null!;
+
+ public int RegionId { get; set; }
+}
diff --git a/AutobusApi.Application/Cities/Commands/CreateCity/CreateCityCommandHandler.cs b/AutobusApi.Application/Cities/Commands/CreateCity/CreateCityCommandHandler.cs
new file mode 100644
index 0000000..d07b20c
--- /dev/null
+++ b/AutobusApi.Application/Cities/Commands/CreateCity/CreateCityCommandHandler.cs
@@ -0,0 +1,31 @@
+using AutobusApi.Application.Common.Interfaces;
+using AutobusApi.Domain.Entities;
+using MediatR;
+
+namespace AutobusApi.Application.Cities.Commands.CreateCity;
+
+public class CreateCityCommandHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _dbContext;
+
+ public CreateCityCommandHandler(IApplicationDbContext dbContext)
+ {
+ _dbContext = dbContext;
+ }
+
+ public async Task Handle(
+ CreateCityCommand request,
+ CancellationToken cancellationToken)
+ {
+ var city = new City();
+
+ city.Name = request.Name;
+ city.RegionId = request.RegionId;
+
+ _dbContext.Cities.Add(city);
+
+ await _dbContext.SaveChangesAsync(cancellationToken);
+
+ return city.Id;
+ }
+}
diff --git a/AutobusApi.Application/Cities/Commands/CreateCity/CreateCityCommandValidator.cs b/AutobusApi.Application/Cities/Commands/CreateCity/CreateCityCommandValidator.cs
new file mode 100644
index 0000000..ac67c9c
--- /dev/null
+++ b/AutobusApi.Application/Cities/Commands/CreateCity/CreateCityCommandValidator.cs
@@ -0,0 +1,13 @@
+using FluentValidation;
+
+namespace AutobusApi.Application.Cities.Commands.CreateCity;
+
+public class CreateCityCommandValidator : AbstractValidator
+{
+ public CreateCityCommandValidator()
+ {
+ RuleFor(v => v.Name).MinimumLength(2).MaximumLength(64);
+
+ RuleFor(v => v.RegionId).GreaterThan(0);
+ }
+}
diff --git a/AutobusApi.Application/Cities/Commands/DeleteCity/DeleteCityCommand.cs b/AutobusApi.Application/Cities/Commands/DeleteCity/DeleteCityCommand.cs
new file mode 100644
index 0000000..818e7e7
--- /dev/null
+++ b/AutobusApi.Application/Cities/Commands/DeleteCity/DeleteCityCommand.cs
@@ -0,0 +1,8 @@
+using MediatR;
+
+namespace AutobusApi.Application.Cities.Commands.DeleteCity;
+
+public record DeleteCityCommand : IRequest
+{
+ public int Id { get; set; }
+}
diff --git a/AutobusApi.Application/Cities/Commands/DeleteCity/DeleteCityCommandHandler.cs b/AutobusApi.Application/Cities/Commands/DeleteCity/DeleteCityCommandHandler.cs
new file mode 100644
index 0000000..54658d1
--- /dev/null
+++ b/AutobusApi.Application/Cities/Commands/DeleteCity/DeleteCityCommandHandler.cs
@@ -0,0 +1,33 @@
+using AutobusApi.Application.Common.Exceptions;
+using AutobusApi.Application.Common.Interfaces;
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+
+namespace AutobusApi.Application.Cities.Commands.DeleteCity;
+
+public class DeleteCityCommandHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _dbContext;
+
+ public DeleteCityCommandHandler(IApplicationDbContext dbContext)
+ {
+ _dbContext = dbContext;
+ }
+
+ public async Task Handle(
+ DeleteCityCommand request,
+ CancellationToken cancellationToken)
+ {
+ var city = await _dbContext.Cities
+ .SingleOrDefaultAsync(c => c.Id == request.Id, cancellationToken);
+
+ if (city == null)
+ {
+ throw new NotFoundException();
+ }
+
+ _dbContext.Cities.Remove(city);
+
+ await _dbContext.SaveChangesAsync(cancellationToken);
+ }
+}
diff --git a/AutobusApi.Application/Cities/Commands/DeleteCity/DeleteCityCommandValidator.cs b/AutobusApi.Application/Cities/Commands/DeleteCity/DeleteCityCommandValidator.cs
new file mode 100644
index 0000000..9f4a58e
--- /dev/null
+++ b/AutobusApi.Application/Cities/Commands/DeleteCity/DeleteCityCommandValidator.cs
@@ -0,0 +1,11 @@
+using FluentValidation;
+
+namespace AutobusApi.Application.Cities.Commands.DeleteCity;
+
+public class DeleteCityCommandValidator : AbstractValidator
+{
+ public DeleteCityCommandValidator()
+ {
+ RuleFor(v => v.Id).GreaterThan(0);
+ }
+}
diff --git a/AutobusApi.Application/Cities/Commands/UpdateCity/UpdateCityCommand.cs b/AutobusApi.Application/Cities/Commands/UpdateCity/UpdateCityCommand.cs
new file mode 100644
index 0000000..ecb8461
--- /dev/null
+++ b/AutobusApi.Application/Cities/Commands/UpdateCity/UpdateCityCommand.cs
@@ -0,0 +1,12 @@
+using MediatR;
+
+namespace AutobusApi.Application.Cities.Commands.UpdateCity;
+
+public record UpdateCityCommand : IRequest
+{
+ public int Id { get; set; }
+
+ public string Name { get; set; } = null!;
+
+ public int RegionId { get; set; }
+}
diff --git a/AutobusApi.Application/Cities/Commands/UpdateCity/UpdateCityCommandHandler.cs b/AutobusApi.Application/Cities/Commands/UpdateCity/UpdateCityCommandHandler.cs
new file mode 100644
index 0000000..23716c2
--- /dev/null
+++ b/AutobusApi.Application/Cities/Commands/UpdateCity/UpdateCityCommandHandler.cs
@@ -0,0 +1,33 @@
+using AutobusApi.Application.Common.Exceptions;
+using AutobusApi.Application.Common.Interfaces;
+using MediatR;
+
+namespace AutobusApi.Application.Cities.Commands.UpdateCity;
+
+public class UpdateCityCommandHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _dbContext;
+
+ public UpdateCityCommandHandler(IApplicationDbContext dbContext)
+ {
+ _dbContext = dbContext;
+ }
+
+ public async Task Handle(
+ UpdateCityCommand request,
+ CancellationToken cancellationToken)
+ {
+ var city = await _dbContext.Cities
+ .FindAsync(new object[] { request.Id }, cancellationToken);
+
+ if (city == null)
+ {
+ throw new NotFoundException();
+ }
+
+ city.Name = request.Name;
+ city.RegionId = request.RegionId;
+
+ await _dbContext.SaveChangesAsync(cancellationToken);
+ }
+}
diff --git a/AutobusApi.Application/Cities/Commands/UpdateCity/UpdateCityCommandValidator.cs b/AutobusApi.Application/Cities/Commands/UpdateCity/UpdateCityCommandValidator.cs
new file mode 100644
index 0000000..9981a10
--- /dev/null
+++ b/AutobusApi.Application/Cities/Commands/UpdateCity/UpdateCityCommandValidator.cs
@@ -0,0 +1,15 @@
+using FluentValidation;
+
+namespace AutobusApi.Application.Cities.Commands.UpdateCity;
+
+public class UpdateCityCommandValidator : AbstractValidator
+{
+ public UpdateCityCommandValidator()
+ {
+ RuleFor(v => v.Name).MinimumLength(2).MaximumLength(64);
+
+ RuleFor(v => v.Id).GreaterThan(0);
+
+ RuleFor(v => v.RegionId).GreaterThan(0);
+ }
+}
diff --git a/AutobusApi.Application/Cities/Queries/CityDto.cs b/AutobusApi.Application/Cities/Queries/CityDto.cs
new file mode 100644
index 0000000..2be8c99
--- /dev/null
+++ b/AutobusApi.Application/Cities/Queries/CityDto.cs
@@ -0,0 +1,27 @@
+using AutobusApi.Application.Common.Mappings;
+using AutobusApi.Domain.Entities;
+using AutoMapper;
+
+namespace AutobusApi.Application.Cities.Queries;
+
+public class CityDto : IMapFrom
+{
+ public int Id { get; set; }
+
+ public string Name { get; set; } = null!;
+
+ public int CountryId { get; set; }
+
+ public string CountryName { get; set; } = null!;
+
+ public int RegionId { get; set; }
+
+ public string RegionName { get; set; } = null!;
+
+ public void Mapping(Profile profile)
+ {
+ profile.CreateMap()
+ .ForMember(d => d.CountryId, opt => opt.MapFrom(s => s.Region.Country.Id))
+ .ForMember(d => d.CountryName, opt => opt.MapFrom(s => s.Region.Country.Name));
+ }
+}
diff --git a/AutobusApi.Application/Cities/Queries/GetCitiesWithPagination/GetCitiesWithPaginationQuery.cs b/AutobusApi.Application/Cities/Queries/GetCitiesWithPagination/GetCitiesWithPaginationQuery.cs
new file mode 100644
index 0000000..afa283c
--- /dev/null
+++ b/AutobusApi.Application/Cities/Queries/GetCitiesWithPagination/GetCitiesWithPaginationQuery.cs
@@ -0,0 +1,13 @@
+using AutobusApi.Application.Common.Models;
+using MediatR;
+
+namespace AutobusApi.Application.Cities.Queries.GetCitiesWithPagination;
+
+public record GetCitiesWithPaginationQuery : IRequest>
+{
+ public string Sort { get; set; } = "";
+
+ public int PageNumber { get; set; } = 1;
+
+ public int PageSize { get; set; } = 10;
+}
diff --git a/AutobusApi.Application/Cities/Queries/GetCitiesWithPagination/GetCitiesWithPaginationQueryHandler.cs b/AutobusApi.Application/Cities/Queries/GetCitiesWithPagination/GetCitiesWithPaginationQueryHandler.cs
new file mode 100644
index 0000000..47a0df0
--- /dev/null
+++ b/AutobusApi.Application/Cities/Queries/GetCitiesWithPagination/GetCitiesWithPaginationQueryHandler.cs
@@ -0,0 +1,32 @@
+using AutobusApi.Application.Common.Interfaces;
+using AutobusApi.Application.Common.Mappings;
+using AutobusApi.Application.Common.Models;
+using AutoMapper;
+using AutoMapper.QueryableExtensions;
+using MediatR;
+
+namespace AutobusApi.Application.Cities.Queries.GetCitiesWithPagination;
+
+public class GetCitiesWithPaginationQueryHandler : IRequestHandler>
+{
+ private readonly IApplicationDbContext _dbContext;
+ private readonly IMapper _mapper;
+
+ public GetCitiesWithPaginationQueryHandler(
+ IApplicationDbContext dbContext,
+ IMapper mapper)
+ {
+ _dbContext = dbContext;
+ _mapper = mapper;
+ }
+
+ public async Task> Handle(
+ GetCitiesWithPaginationQuery request,
+ CancellationToken cancellationToken)
+ {
+ return await _dbContext.Cities
+ .ProjectTo(_mapper.ConfigurationProvider)
+ .ApplySort(request.Sort)
+ .PaginatedListAsync(request.PageNumber, request.PageSize);
+ }
+}
diff --git a/AutobusApi.Application/Cities/Queries/GetCitiesWithPagination/GetCitiesWithPaginationQueryValidator.cs b/AutobusApi.Application/Cities/Queries/GetCitiesWithPagination/GetCitiesWithPaginationQueryValidator.cs
new file mode 100644
index 0000000..8cb79e1
--- /dev/null
+++ b/AutobusApi.Application/Cities/Queries/GetCitiesWithPagination/GetCitiesWithPaginationQueryValidator.cs
@@ -0,0 +1,13 @@
+using FluentValidation;
+
+namespace AutobusApi.Application.Cities.Queries.GetCitiesWithPagination;
+
+public class GetCitiesWithPaginationQueryValidator : AbstractValidator
+{
+ public GetCitiesWithPaginationQueryValidator()
+ {
+ RuleFor(v => v.PageNumber).GreaterThanOrEqualTo(1);
+
+ RuleFor(v => v.PageSize).GreaterThanOrEqualTo(1).LessThanOrEqualTo(50);
+ }
+}
diff --git a/AutobusApi.Application/Cities/Queries/GetCity/GetCityQuery.cs b/AutobusApi.Application/Cities/Queries/GetCity/GetCityQuery.cs
new file mode 100644
index 0000000..072e7e0
--- /dev/null
+++ b/AutobusApi.Application/Cities/Queries/GetCity/GetCityQuery.cs
@@ -0,0 +1,8 @@
+using MediatR;
+
+namespace AutobusApi.Application.Cities.Queries.GetCity;
+
+public record GetCityQuery : IRequest
+{
+ public int Id { get; set; }
+}
diff --git a/AutobusApi.Application/Cities/Queries/GetCity/GetCityQueryHandler.cs b/AutobusApi.Application/Cities/Queries/GetCity/GetCityQueryHandler.cs
new file mode 100644
index 0000000..492b626
--- /dev/null
+++ b/AutobusApi.Application/Cities/Queries/GetCity/GetCityQueryHandler.cs
@@ -0,0 +1,36 @@
+using AutobusApi.Application.Common.Exceptions;
+using AutobusApi.Application.Common.Interfaces;
+using AutoMapper;
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+
+namespace AutobusApi.Application.Cities.Queries.GetCity;
+
+public class GetCityQueryHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _dbContext;
+ private readonly IMapper _mapper;
+
+ public GetCityQueryHandler(
+ IApplicationDbContext dbContext,
+ IMapper mapper)
+ {
+ _dbContext = dbContext;
+ _mapper = mapper;
+ }
+
+ public async Task Handle(
+ GetCityQuery request,
+ CancellationToken cancellationToken)
+ {
+ var city = await _dbContext.Cities
+ .SingleOrDefaultAsync(c => c.Id == request.Id);
+
+ if (city == null)
+ {
+ throw new NotFoundException();
+ }
+
+ return _mapper.Map(city);
+ }
+}
diff --git a/AutobusApi.Application/Cities/Queries/GetCity/GetCityQueryValidator.cs b/AutobusApi.Application/Cities/Queries/GetCity/GetCityQueryValidator.cs
new file mode 100644
index 0000000..1442d69
--- /dev/null
+++ b/AutobusApi.Application/Cities/Queries/GetCity/GetCityQueryValidator.cs
@@ -0,0 +1,11 @@
+using FluentValidation;
+
+namespace AutobusApi.Application.Cities.Queries.GetCity;
+
+public class GetCityQueryValidator : AbstractValidator
+{
+ public GetCityQueryValidator()
+ {
+ RuleFor(v => v.Id).GreaterThan(0);
+ }
+}
diff --git a/AutobusApi.Application/Common/Exceptions/NotFoundException.cs b/AutobusApi.Application/Common/Exceptions/NotFoundException.cs
new file mode 100644
index 0000000..56731b0
--- /dev/null
+++ b/AutobusApi.Application/Common/Exceptions/NotFoundException.cs
@@ -0,0 +1,3 @@
+namespace AutobusApi.Application.Common.Exceptions;
+
+public class NotFoundException : Exception { }
diff --git a/AutobusApi.Application/Common/Interfaces/IIdentityService.cs b/AutobusApi.Application/Common/Interfaces/IIdentityService.cs
index 675f88c..bb08611 100644
--- a/AutobusApi.Application/Common/Interfaces/IIdentityService.cs
+++ b/AutobusApi.Application/Common/Interfaces/IIdentityService.cs
@@ -4,7 +4,7 @@ namespace AutobusApi.Application.Common.Interfaces;
public interface IIdentityService
{
- Task RegisterAsync(string email, string password, CancellationToken cancellationToken);
+ Task RegisterAsync(string email, string password, CancellationToken cancellationToken);
Task LoginAsync(string email, string password, CancellationToken cancellationToken);
diff --git a/AutobusApi.Application/Common/Mappings/IMapFrom.cs b/AutobusApi.Application/Common/Mappings/IMapFrom.cs
new file mode 100644
index 0000000..c79ab81
--- /dev/null
+++ b/AutobusApi.Application/Common/Mappings/IMapFrom.cs
@@ -0,0 +1,8 @@
+using AutoMapper;
+
+namespace AutobusApi.Application.Common.Mappings;
+
+public interface IMapFrom
+{
+ void Mapping(Profile profile) => profile.CreateMap(typeof(T), GetType());
+}
diff --git a/AutobusApi.Application/Common/Mappings/MappingExtensions.cs b/AutobusApi.Application/Common/Mappings/MappingExtensions.cs
new file mode 100644
index 0000000..56797fc
--- /dev/null
+++ b/AutobusApi.Application/Common/Mappings/MappingExtensions.cs
@@ -0,0 +1,156 @@
+using System.Reflection;
+using System.Linq.Dynamic.Core;
+using System.Text;
+using AutobusApi.Application.Common.Models;
+using AutoMapper;
+using AutoMapper.QueryableExtensions;
+using Microsoft.EntityFrameworkCore;
+using System.Dynamic;
+
+namespace AutobusApi.Application.Common.Mappings;
+
+public static class MappingExtensions
+{
+ public static Task> PaginatedListAsync(
+ this IQueryable queryable,
+ int pageNumber,
+ int pageSize)
+ where T : class
+ {
+ return PaginatedList.CreateAsync(queryable.AsNoTracking(), pageNumber, pageSize);
+ }
+
+ public static Task> ProjectToListAsync(
+ this IQueryable queryable,
+ IConfigurationProvider configuration)
+ where T : class
+ {
+ return queryable.ProjectTo(configuration).AsNoTracking().ToListAsync();
+ }
+
+ public static IQueryable ApplySort(
+ this IQueryable entities,
+ string? orderByQueryString)
+ {
+ if (!entities.Any() || String.IsNullOrWhiteSpace(orderByQueryString))
+ {
+ return entities;
+ }
+
+ var orderParams = orderByQueryString.Trim().Split(",");
+ var propertyInfos = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
+ var orderQueryBuilder = new StringBuilder();
+
+ foreach (var param in orderParams)
+ {
+ if (string.IsNullOrWhiteSpace(param))
+ {
+ continue;
+ }
+
+ var propertyFromQueryName = param[0] == '-' || param[0] == '+' ? param.Substring(1) : param;
+ var objectProperty = propertyInfos.FirstOrDefault(pi =>
+ pi.Name.Equals(propertyFromQueryName, StringComparison.InvariantCultureIgnoreCase));
+
+ if (objectProperty == null)
+ {
+ continue;
+ }
+
+ var sortingOrder = param[0] == '-' ? "descending" : "ascending";
+
+ orderQueryBuilder.Append($"{objectProperty.Name} {sortingOrder}, ");
+ }
+
+ var orderQuery = orderQueryBuilder.ToString().TrimEnd(',', ' ');
+
+ return entities.OrderBy(orderQuery);
+ }
+
+
+
+
+
+
+ public static IQueryable ShapeData(
+ this IQueryable entities,
+ string? fieldsString)
+ {
+ var allProperties = GetAllProperties();
+ var requiredProperties = GetRequiredProperties(fieldsString, allProperties);
+ return FetchData(entities, requiredProperties);
+ }
+
+ public static ExpandoObject ShapeData(
+ this T entity,
+ string? fieldsString)
+ {
+ var allProperties = GetAllProperties();
+ var requiredProperties = GetRequiredProperties(fieldsString, allProperties);
+ return FetchDataForEntity(entity, requiredProperties);
+ }
+
+ private static IEnumerable GetAllProperties()
+ {
+ return typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
+ }
+
+ private static IEnumerable GetRequiredProperties(
+ string? fieldsString,
+ IEnumerable properties)
+ {
+ var requiredProperties = new List();
+
+ if (!string.IsNullOrWhiteSpace(fieldsString))
+ {
+ var fields = fieldsString.Split(',', StringSplitOptions.RemoveEmptyEntries);
+
+ foreach (var field in fields)
+ {
+ var property = properties.FirstOrDefault(pi => pi.Name.Equals(field.Trim(), StringComparison.InvariantCultureIgnoreCase));
+
+ if (property == null)
+ continue;
+
+ requiredProperties.Add(property);
+ }
+ }
+ else
+ {
+ requiredProperties = properties.ToList();
+ }
+
+ return requiredProperties;
+ }
+
+ private static IQueryable FetchData(
+ IQueryable entities,
+ IEnumerable requiredProperties)
+ {
+ var shapedData = new List();
+
+ foreach (var entity in entities)
+ {
+ var shapedObject = FetchDataForEntity(entity, requiredProperties);
+ shapedData.Add(shapedObject);
+ }
+
+ return shapedData.AsQueryable();
+ }
+
+ private static ExpandoObject FetchDataForEntity(
+ T entity,
+ IEnumerable requiredProperties)
+ {
+ var shapedObject = new ExpandoObject();
+
+ foreach (var property in requiredProperties)
+ {
+ var objectPropertyValue = property.GetValue(entity);
+ shapedObject.TryAdd(property.Name, objectPropertyValue);
+ }
+
+ return shapedObject;
+ }
+
+}
diff --git a/AutobusApi.Application/Common/Mappings/MappingProfile.cs b/AutobusApi.Application/Common/Mappings/MappingProfile.cs
new file mode 100644
index 0000000..03c184e
--- /dev/null
+++ b/AutobusApi.Application/Common/Mappings/MappingProfile.cs
@@ -0,0 +1,35 @@
+using System.Reflection;
+using AutoMapper;
+
+namespace AutobusApi.Application.Common.Mappings;
+
+public class MappingProfile : Profile
+{
+ public MappingProfile()
+ {
+ ApplyMappingsFromAssembly(Assembly.GetExecutingAssembly());
+ }
+
+ private void ApplyMappingsFromAssembly(Assembly assembly)
+ {
+ var types = assembly.GetExportedTypes()
+ .Where(t => t.GetInterfaces()
+ .Any(i =>
+ i.IsGenericType &&
+ i.GetGenericTypeDefinition() == typeof(IMapFrom<>)
+ )
+ )
+ .ToList();
+
+ foreach (var type in types)
+ {
+ var instance = Activator.CreateInstance(type);
+
+ var methodInfo =
+ type.GetMethod("Mapping") ??
+ type.GetInterface("IMapFrom`1")?.GetMethod("Mapping");
+
+ methodInfo?.Invoke(instance, new object[] { this });
+ }
+ }
+}
diff --git a/AutobusApi.Application/Common/Models/PaginatedList.cs b/AutobusApi.Application/Common/Models/PaginatedList.cs
new file mode 100644
index 0000000..435ee49
--- /dev/null
+++ b/AutobusApi.Application/Common/Models/PaginatedList.cs
@@ -0,0 +1,31 @@
+using Microsoft.EntityFrameworkCore;
+
+namespace AutobusApi.Application.Common.Models;
+
+public class PaginatedList
+{
+ public IReadOnlyCollection Items { get; }
+ public int PageNumber { get; }
+ public int TotalPages { get; }
+ public int TotalCount { get; }
+
+ public PaginatedList(IReadOnlyCollection items, int count, int pageNumber, int pageSize)
+ {
+ PageNumber = pageNumber;
+ TotalPages = (int)Math.Ceiling(count / (double)pageSize);
+ TotalCount = count;
+ Items = items;
+ }
+
+ public bool HasPreviousPage => PageNumber > 1;
+
+ public bool HasNextPage => PageNumber < TotalPages;
+
+ public static async Task> CreateAsync(IQueryable source, int pageNumber, int pageSize)
+ {
+ var count = await source.CountAsync();
+ var items = await source.Skip((pageNumber - 1) * pageSize).Take(pageSize).ToListAsync();
+
+ return new PaginatedList(items, count, pageNumber, pageSize);
+ }
+}
diff --git a/AutobusApi.Application/Companies/Commands/CreateCompany/CreateCompanyCommand.cs b/AutobusApi.Application/Companies/Commands/CreateCompany/CreateCompanyCommand.cs
new file mode 100644
index 0000000..be44f03
--- /dev/null
+++ b/AutobusApi.Application/Companies/Commands/CreateCompany/CreateCompanyCommand.cs
@@ -0,0 +1,32 @@
+using MediatR;
+
+namespace AutobusApi.Application.Companies.Commands.CreateCompany;
+
+public record CreateCompanyCommand : IRequest
+{
+ public string Name { get; set; } = null!;
+
+ public string LegalAddress { get; set; } = null!;
+
+ public string ContactEmail { get; set; } = null!;
+
+ public string ContactPhoneNumber { get; set; } = null!;
+
+ public string OwnerEmail { get; set; } = null!;
+
+ public string OwnerPassword { get; set; } = null!;
+
+ public string OwnerFirstName { get; set; } = null!;
+
+ public string OwnerLastName { get; set; } = null!;
+
+ public string OwnerPatronymic { get; set; } = null!;
+
+ public string OwnerSex { get; set; } = null!;
+
+ public DateOnly OwnerBirthDate { get; set; }
+
+ public string OwnerDocumentType { get; set; } = null!;
+
+ public string OwnerDocumentInformation { get; set; } = null!;
+}
diff --git a/AutobusApi.Application/Companies/Commands/CreateCompany/CreateCompanyCommandHandler.cs b/AutobusApi.Application/Companies/Commands/CreateCompany/CreateCompanyCommandHandler.cs
new file mode 100644
index 0000000..ddf3264
--- /dev/null
+++ b/AutobusApi.Application/Companies/Commands/CreateCompany/CreateCompanyCommandHandler.cs
@@ -0,0 +1,61 @@
+using AutobusApi.Application.Common.Interfaces;
+using AutobusApi.Domain.Entities;
+using AutobusApi.Domain.Enums;
+using MediatR;
+
+namespace AutobusApi.Application.Companies.Commands.CreateCompany;
+
+public class CreateCompanyCommandHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _dbContext;
+ private readonly IIdentityService _identityService;
+
+ public CreateCompanyCommandHandler(
+ IApplicationDbContext dbContext,
+ IIdentityService identityService)
+ {
+ _dbContext = dbContext;
+ _identityService = identityService;
+ }
+
+ public async Task Handle(
+ CreateCompanyCommand request,
+ CancellationToken cancellationToken)
+ {
+ var company = new Company();
+
+ company.Name = request.Name;
+ company.LegalAddress = request.LegalAddress;
+ company.ContactPhoneNumber = request.ContactPhoneNumber;
+ company.ContactEmail = request.ContactEmail;
+
+ var userId = await _identityService.RegisterAsync(request.OwnerEmail, request.OwnerPassword, cancellationToken);
+
+ company.Employees = new List()
+ {
+ new Employee()
+ {
+ IdentityId = userId,
+ FirstName = request.OwnerFirstName,
+ LastName = request.OwnerLastName,
+ Patronymic = request.OwnerPatronymic,
+ Sex = Enum.Parse(request.OwnerSex),
+ BirthDate = request.OwnerBirthDate,
+ Documents = new List()
+ {
+ new EmployeeDocument()
+ {
+ Type = Enum.Parse(request.OwnerDocumentType),
+ Information = request.OwnerDocumentInformation
+ }
+ }
+ }
+ };
+
+ _dbContext.Companies.Add(company);
+
+ await _dbContext.SaveChangesAsync(cancellationToken);
+
+ return company.Id;
+ }
+}
diff --git a/AutobusApi.Application/Companies/Commands/CreateCompany/CreateCompanyCommandValidator.cs b/AutobusApi.Application/Companies/Commands/CreateCompany/CreateCompanyCommandValidator.cs
new file mode 100644
index 0000000..2fbea28
--- /dev/null
+++ b/AutobusApi.Application/Companies/Commands/CreateCompany/CreateCompanyCommandValidator.cs
@@ -0,0 +1,47 @@
+using AutobusApi.Domain.Enums;
+using FluentValidation;
+
+namespace AutobusApi.Application.Companies.Commands.CreateCompany;
+
+public class CreateCompanyCommandValidator : AbstractValidator
+{
+ public CreateCompanyCommandValidator()
+ {
+ RuleFor(v => v.Name).MinimumLength(2).MaximumLength(64);
+
+ RuleFor(v => v.LegalAddress).MinimumLength(2).MaximumLength(256);
+
+ RuleFor(v => v.ContactPhoneNumber)
+ .NotEmpty().WithMessage("Phone number is required.")
+ .Matches(@"^\s*(?:\+?(\d{1,3}))?([-. (]*(\d{3})[-. )]*)?((\d{3})[-. ]*(\d{2,4})(?:[-.x ]*(\d+))?)\s*$").WithMessage("Phone number is invalid.");
+
+ RuleFor(v => v.ContactEmail)
+ .NotEmpty().WithMessage("Email address is required.")
+ .Matches(@"\b[\w\.-]+@[\w\.-]+\.\w{2,4}\b").WithMessage("Email address is invalid.");
+
+ RuleFor(v => v.OwnerEmail)
+ .NotEmpty().WithMessage("Email address is required.")
+ .Matches(@"\b[\w\.-]+@[\w\.-]+\.\w{2,4}\b").WithMessage("Email address is invalid.");
+
+ RuleFor(v => v.OwnerPassword)
+ .NotEmpty().WithMessage("Password is required.")
+ .MinimumLength(8).WithMessage("Password must be at least 8 characters long.")
+ .MaximumLength(64).WithMessage("Password must be at most 64 characters long.")
+ .Matches(@"(?=.*[A-Z]).*").WithMessage("Password must contain at least one uppercase letter.")
+ .Matches(@"(?=.*[a-z]).*").WithMessage("Password must contain at least one lowercase letter.")
+ .Matches(@"(?=.*[\d]).*").WithMessage("Password must contain at least one digit.")
+ .Matches(@"(?=.*[!@#$%^&*()]).*").WithMessage("Password must contain at least one of the following special charactters: !@#$%^&*().");
+
+ RuleFor(v => v.OwnerFirstName).MinimumLength(2).MaximumLength(32);
+
+ RuleFor(v => v.OwnerLastName).MinimumLength(2).MaximumLength(32);
+
+ RuleFor(v => v.OwnerPatronymic).MinimumLength(2).MaximumLength(32);
+
+ RuleFor(v => v.OwnerSex).Must(value => Enum.TryParse(value, true, out _));
+
+ RuleFor(v => v.OwnerDocumentType).Must(value => Enum.TryParse(value, true, out _));
+
+ RuleFor(v => v.OwnerDocumentInformation).MinimumLength(2).MaximumLength(256);
+ }
+}
diff --git a/AutobusApi.Application/Companies/Commands/DeleteCompany/DeleteCompanyCommand.cs b/AutobusApi.Application/Companies/Commands/DeleteCompany/DeleteCompanyCommand.cs
new file mode 100644
index 0000000..2a99c49
--- /dev/null
+++ b/AutobusApi.Application/Companies/Commands/DeleteCompany/DeleteCompanyCommand.cs
@@ -0,0 +1,8 @@
+using MediatR;
+
+namespace AutobusApi.Application.Companies.Commands.DeleteCompany;
+
+public record DeleteCompanyCommand : IRequest
+{
+ public int Id { get; set; }
+}
diff --git a/AutobusApi.Application/Companies/Commands/DeleteCompany/DeleteCompanyCommandHandler.cs b/AutobusApi.Application/Companies/Commands/DeleteCompany/DeleteCompanyCommandHandler.cs
new file mode 100644
index 0000000..a844f3a
--- /dev/null
+++ b/AutobusApi.Application/Companies/Commands/DeleteCompany/DeleteCompanyCommandHandler.cs
@@ -0,0 +1,33 @@
+using AutobusApi.Application.Common.Exceptions;
+using AutobusApi.Application.Common.Interfaces;
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+
+namespace AutobusApi.Application.Companies.Commands.DeleteCompany;
+
+public class DeleteCompanyCommandHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _dbContext;
+
+ public DeleteCompanyCommandHandler(IApplicationDbContext dbContext)
+ {
+ _dbContext = dbContext;
+ }
+
+ public async Task Handle(
+ DeleteCompanyCommand request,
+ CancellationToken cancellationToken)
+ {
+ var company = await _dbContext.Companies
+ .SingleOrDefaultAsync(c => c.Id == request.Id, cancellationToken);
+
+ if (company == null)
+ {
+ throw new NotFoundException();
+ }
+
+ _dbContext.Companies.Remove(company);
+
+ await _dbContext.SaveChangesAsync(cancellationToken);
+ }
+}
diff --git a/AutobusApi.Application/Companies/Commands/DeleteCompany/DeleteCompanyCommandValidator.cs b/AutobusApi.Application/Companies/Commands/DeleteCompany/DeleteCompanyCommandValidator.cs
new file mode 100644
index 0000000..3b7884d
--- /dev/null
+++ b/AutobusApi.Application/Companies/Commands/DeleteCompany/DeleteCompanyCommandValidator.cs
@@ -0,0 +1,11 @@
+using FluentValidation;
+
+namespace AutobusApi.Application.Companies.Commands.DeleteCompany;
+
+public class DeleteCompanyCommandValidator : AbstractValidator
+{
+ public DeleteCompanyCommandValidator()
+ {
+ RuleFor(v => v.Id).GreaterThan(0);
+ }
+}
diff --git a/AutobusApi.Application/Companies/Commands/UpdateCompany/UpdateCompanyCommand.cs b/AutobusApi.Application/Companies/Commands/UpdateCompany/UpdateCompanyCommand.cs
new file mode 100644
index 0000000..5bbb6e7
--- /dev/null
+++ b/AutobusApi.Application/Companies/Commands/UpdateCompany/UpdateCompanyCommand.cs
@@ -0,0 +1,16 @@
+using MediatR;
+
+namespace AutobusApi.Application.Companies.Commands.UpdateCompany;
+
+public record UpdateCompanyCommand : IRequest
+{
+ public int Id { get; set; }
+
+ public string Name { get; set; } = null!;
+
+ public string LegalAddress { get; set; } = null!;
+
+ public string ContactEmail { get; set; } = null!;
+
+ public string ContactPhoneNumber { get; set; } = null!;
+}
diff --git a/AutobusApi.Application/Companies/Commands/UpdateCompany/UpdateCompanyCommandHandler.cs b/AutobusApi.Application/Companies/Commands/UpdateCompany/UpdateCompanyCommandHandler.cs
new file mode 100644
index 0000000..4293baa
--- /dev/null
+++ b/AutobusApi.Application/Companies/Commands/UpdateCompany/UpdateCompanyCommandHandler.cs
@@ -0,0 +1,35 @@
+using AutobusApi.Application.Common.Exceptions;
+using AutobusApi.Application.Common.Interfaces;
+using MediatR;
+
+namespace AutobusApi.Application.Companies.Commands.UpdateCompany;
+
+public class UpdateCompanyCommandHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _dbContext;
+
+ public UpdateCompanyCommandHandler(IApplicationDbContext dbContext)
+ {
+ _dbContext = dbContext;
+ }
+
+ public async Task Handle(
+ UpdateCompanyCommand request,
+ CancellationToken cancellationToken)
+ {
+ var company = await _dbContext.Companies
+ .FindAsync(new object[] { request.Id }, cancellationToken);
+
+ if (company == null)
+ {
+ throw new NotFoundException();
+ }
+
+ company.Name = request.Name;
+ company.LegalAddress = request.LegalAddress;
+ company.ContactEmail = request.ContactEmail;
+ company.ContactPhoneNumber = request.ContactPhoneNumber;
+
+ await _dbContext.SaveChangesAsync(cancellationToken);
+ }
+}
diff --git a/AutobusApi.Application/Companies/Commands/UpdateCompany/UpdateCompanyCommandValidator.cs b/AutobusApi.Application/Companies/Commands/UpdateCompany/UpdateCompanyCommandValidator.cs
new file mode 100644
index 0000000..15665c8
--- /dev/null
+++ b/AutobusApi.Application/Companies/Commands/UpdateCompany/UpdateCompanyCommandValidator.cs
@@ -0,0 +1,23 @@
+using FluentValidation;
+
+namespace AutobusApi.Application.Companies.Commands.UpdateCompany;
+
+public class UpdateCompanyCommandValidator : AbstractValidator
+{
+ public UpdateCompanyCommandValidator()
+ {
+ RuleFor(v => v.Id).GreaterThan(0);
+
+ RuleFor(v => v.Name).MinimumLength(2).MaximumLength(64);
+
+ RuleFor(v => v.LegalAddress).MinimumLength(2).MaximumLength(256);
+
+ RuleFor(v => v.ContactPhoneNumber)
+ .NotEmpty().WithMessage("Phone number is required.")
+ .Matches(@"^\s*(?:\+?(\d{1,3}))?([-. (]*(\d{3})[-. )]*)?((\d{3})[-. ]*(\d{2,4})(?:[-.x ]*(\d+))?)\s*$").WithMessage("Phone number is invalid.");
+
+ RuleFor(v => v.ContactEmail)
+ .NotEmpty().WithMessage("Email address is required.")
+ .Matches(@"\b[\w\.-]+@[\w\.-]+\.\w{2,4}\b").WithMessage("Email address is invalid.");
+ }
+}
diff --git a/AutobusApi.Application/Companies/Queries/CompanyDto.cs b/AutobusApi.Application/Companies/Queries/CompanyDto.cs
new file mode 100644
index 0000000..b972db9
--- /dev/null
+++ b/AutobusApi.Application/Companies/Queries/CompanyDto.cs
@@ -0,0 +1,17 @@
+using AutobusApi.Application.Common.Mappings;
+using AutobusApi.Domain.Entities;
+
+namespace AutobusApi.Application.Companies.Queries;
+
+public class CompanyDto : IMapFrom
+{
+ public int Id { get; set; }
+
+ public string Name { get; set; } = null!;
+
+ public string LegalAddress { get; set; } = null!;
+
+ public string ContactEmail { get; set; } = null!;
+
+ public string ContactPhoneNumber { get; set; } = null!;
+}
diff --git a/AutobusApi.Application/Companies/Queries/GetCompaniesWithPagination/GetCompaniesWithPaginationQuery.cs b/AutobusApi.Application/Companies/Queries/GetCompaniesWithPagination/GetCompaniesWithPaginationQuery.cs
new file mode 100644
index 0000000..c370af3
--- /dev/null
+++ b/AutobusApi.Application/Companies/Queries/GetCompaniesWithPagination/GetCompaniesWithPaginationQuery.cs
@@ -0,0 +1,13 @@
+using AutobusApi.Application.Common.Models;
+using MediatR;
+
+namespace AutobusApi.Application.Companies.Queries.GetCompaniesWithPagination;
+
+public record GetCompaniesWithPaginationQuery : IRequest>
+{
+ public string Sort { get; set; } = "";
+
+ public int PageNumber { get; set; } = 1;
+
+ public int PageSize { get; set; } = 10;
+}
diff --git a/AutobusApi.Application/Companies/Queries/GetCompaniesWithPagination/GetCompaniesWithPaginationQueryHandler.cs b/AutobusApi.Application/Companies/Queries/GetCompaniesWithPagination/GetCompaniesWithPaginationQueryHandler.cs
new file mode 100644
index 0000000..6464163
--- /dev/null
+++ b/AutobusApi.Application/Companies/Queries/GetCompaniesWithPagination/GetCompaniesWithPaginationQueryHandler.cs
@@ -0,0 +1,32 @@
+using AutobusApi.Application.Common.Interfaces;
+using AutobusApi.Application.Common.Mappings;
+using AutobusApi.Application.Common.Models;
+using AutoMapper;
+using AutoMapper.QueryableExtensions;
+using MediatR;
+
+namespace AutobusApi.Application.Companies.Queries.GetCompaniesWithPagination;
+
+public class GetCompaniesWithPaginationQueryHandler : IRequestHandler>
+{
+ private readonly IApplicationDbContext _dbContext;
+ private readonly IMapper _mapper;
+
+ public GetCompaniesWithPaginationQueryHandler(
+ IApplicationDbContext dbContext,
+ IMapper mapper)
+ {
+ _dbContext = dbContext;
+ _mapper = mapper;
+ }
+
+ public async Task> Handle(
+ GetCompaniesWithPaginationQuery request,
+ CancellationToken cancellationToken)
+ {
+ return await _dbContext.Companies
+ .ProjectTo(_mapper.ConfigurationProvider)
+ .ApplySort(request.Sort)
+ .PaginatedListAsync(request.PageNumber, request.PageSize);
+ }
+}
diff --git a/AutobusApi.Application/Companies/Queries/GetCompaniesWithPagination/GetCompaniesWithPaginationQueryValidator.cs b/AutobusApi.Application/Companies/Queries/GetCompaniesWithPagination/GetCompaniesWithPaginationQueryValidator.cs
new file mode 100644
index 0000000..a2be5ea
--- /dev/null
+++ b/AutobusApi.Application/Companies/Queries/GetCompaniesWithPagination/GetCompaniesWithPaginationQueryValidator.cs
@@ -0,0 +1,13 @@
+using FluentValidation;
+
+namespace AutobusApi.Application.Companies.Queries.GetCompaniesWithPagination;
+
+public class GetCompaniesWithPaginationQueryValidator : AbstractValidator
+{
+ public GetCompaniesWithPaginationQueryValidator()
+ {
+ RuleFor(v => v.PageNumber).GreaterThanOrEqualTo(1);
+
+ RuleFor(v => v.PageSize).GreaterThanOrEqualTo(1).LessThanOrEqualTo(50);
+ }
+}
diff --git a/AutobusApi.Application/Companies/Queries/GetCompany/GetCompanyQuery.cs b/AutobusApi.Application/Companies/Queries/GetCompany/GetCompanyQuery.cs
new file mode 100644
index 0000000..199fb12
--- /dev/null
+++ b/AutobusApi.Application/Companies/Queries/GetCompany/GetCompanyQuery.cs
@@ -0,0 +1,8 @@
+using MediatR;
+
+namespace AutobusApi.Application.Companies.Queries.GetCompany;
+
+public record GetCompanyQuery : IRequest
+{
+ public int Id { get; set; }
+}
diff --git a/AutobusApi.Application/Companies/Queries/GetCompany/GetCompanyQueryHandler.cs b/AutobusApi.Application/Companies/Queries/GetCompany/GetCompanyQueryHandler.cs
new file mode 100644
index 0000000..5a09ad7
--- /dev/null
+++ b/AutobusApi.Application/Companies/Queries/GetCompany/GetCompanyQueryHandler.cs
@@ -0,0 +1,38 @@
+using AutobusApi.Application.Common.Exceptions;
+using AutobusApi.Application.Common.Interfaces;
+using AutoMapper;
+using AutoMapper.QueryableExtensions;
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+
+namespace AutobusApi.Application.Companies.Queries.GetCompany;
+
+public class GetCompanyQueryHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _dbContext;
+ private readonly IMapper _mapper;
+
+ public GetCompanyQueryHandler(
+ IApplicationDbContext dbContext,
+ IMapper mapper)
+ {
+ _dbContext = dbContext;
+ _mapper = mapper;
+ }
+
+ public async Task Handle(
+ GetCompanyQuery request,
+ CancellationToken cancellationToken)
+ {
+ var company = await _dbContext.Companies
+ .ProjectTo(_mapper.ConfigurationProvider)
+ .SingleOrDefaultAsync(c => c.Id == request.Id);
+
+ if (company == null)
+ {
+ throw new NotFoundException();
+ }
+
+ return company;
+ }
+}
diff --git a/AutobusApi.Application/Companies/Queries/GetCompany/GetCompanyQueryValidator.cs b/AutobusApi.Application/Companies/Queries/GetCompany/GetCompanyQueryValidator.cs
new file mode 100644
index 0000000..7298687
--- /dev/null
+++ b/AutobusApi.Application/Companies/Queries/GetCompany/GetCompanyQueryValidator.cs
@@ -0,0 +1,11 @@
+using FluentValidation;
+
+namespace AutobusApi.Application.Companies.Queries.GetCompany;
+
+public class GetCompanyQueryValidator : AbstractValidator
+{
+ public GetCompanyQueryValidator()
+ {
+ RuleFor(v => v.Id).GreaterThan(0);
+ }
+}
diff --git a/AutobusApi.Application/Countries/Commands/CreateCountry/CreateCountryCommand.cs b/AutobusApi.Application/Countries/Commands/CreateCountry/CreateCountryCommand.cs
new file mode 100644
index 0000000..c251d7b
--- /dev/null
+++ b/AutobusApi.Application/Countries/Commands/CreateCountry/CreateCountryCommand.cs
@@ -0,0 +1,8 @@
+using MediatR;
+
+namespace AutobusApi.Application.Countries.Commands.CreateCountry;
+
+public record CreateCountryCommand : IRequest
+{
+ public string Name { get; set; } = null!;
+}
diff --git a/AutobusApi.Application/Countries/Commands/CreateCountry/CreateCountryCommandHandler.cs b/AutobusApi.Application/Countries/Commands/CreateCountry/CreateCountryCommandHandler.cs
new file mode 100644
index 0000000..ec700be
--- /dev/null
+++ b/AutobusApi.Application/Countries/Commands/CreateCountry/CreateCountryCommandHandler.cs
@@ -0,0 +1,30 @@
+using AutobusApi.Application.Common.Interfaces;
+using AutobusApi.Domain.Entities;
+using MediatR;
+
+namespace AutobusApi.Application.Countries.Commands.CreateCountry;
+
+public class CreateCountryCommandHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _dbContext;
+
+ public CreateCountryCommandHandler(IApplicationDbContext dbContext)
+ {
+ _dbContext = dbContext;
+ }
+
+ public async Task Handle(
+ CreateCountryCommand request,
+ CancellationToken cancellationToken)
+ {
+ var country = new Country();
+
+ country.Name = request.Name;
+
+ _dbContext.Countries.Add(country);
+
+ await _dbContext.SaveChangesAsync(cancellationToken);
+
+ return country.Id;
+ }
+}
diff --git a/AutobusApi.Application/Countries/Commands/CreateCountry/CreateCountryCommandValidator.cs b/AutobusApi.Application/Countries/Commands/CreateCountry/CreateCountryCommandValidator.cs
new file mode 100644
index 0000000..d2eaba1
--- /dev/null
+++ b/AutobusApi.Application/Countries/Commands/CreateCountry/CreateCountryCommandValidator.cs
@@ -0,0 +1,11 @@
+using FluentValidation;
+
+namespace AutobusApi.Application.Countries.Commands.CreateCountry;
+
+public class CreateCountryCommandValidator : AbstractValidator
+{
+ public CreateCountryCommandValidator()
+ {
+ RuleFor(v => v.Name).MinimumLength(2).MaximumLength(64);
+ }
+}
diff --git a/AutobusApi.Application/Countries/Commands/DeleteCountry/DeleteCountryCommand.cs b/AutobusApi.Application/Countries/Commands/DeleteCountry/DeleteCountryCommand.cs
new file mode 100644
index 0000000..4ac5030
--- /dev/null
+++ b/AutobusApi.Application/Countries/Commands/DeleteCountry/DeleteCountryCommand.cs
@@ -0,0 +1,8 @@
+using MediatR;
+
+namespace AutobusApi.Application.Countries.Commands.DeleteCountry;
+
+public record DeleteCountryCommand : IRequest
+{
+ public int Id { get; set; }
+}
diff --git a/AutobusApi.Application/Countries/Commands/DeleteCountry/DeleteCountryCommandHandler.cs b/AutobusApi.Application/Countries/Commands/DeleteCountry/DeleteCountryCommandHandler.cs
new file mode 100644
index 0000000..dacb0f4
--- /dev/null
+++ b/AutobusApi.Application/Countries/Commands/DeleteCountry/DeleteCountryCommandHandler.cs
@@ -0,0 +1,33 @@
+using AutobusApi.Application.Common.Exceptions;
+using AutobusApi.Application.Common.Interfaces;
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+
+namespace AutobusApi.Application.Countries.Commands.DeleteCountry;
+
+public class DeleteCountryCommandHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _dbContext;
+
+ public DeleteCountryCommandHandler(IApplicationDbContext dbContext)
+ {
+ _dbContext = dbContext;
+ }
+
+ public async Task Handle(
+ DeleteCountryCommand request,
+ CancellationToken cancellationToken)
+ {
+ var country = await _dbContext.Countries
+ .SingleOrDefaultAsync(c => c.Id == request.Id, cancellationToken);
+
+ if (country == null)
+ {
+ throw new NotFoundException();
+ }
+
+ _dbContext.Countries.Remove(country);
+
+ await _dbContext.SaveChangesAsync(cancellationToken);
+ }
+}
diff --git a/AutobusApi.Application/Countries/Commands/DeleteCountry/DeleteCountryCommandValidator.cs b/AutobusApi.Application/Countries/Commands/DeleteCountry/DeleteCountryCommandValidator.cs
new file mode 100644
index 0000000..d67fe46
--- /dev/null
+++ b/AutobusApi.Application/Countries/Commands/DeleteCountry/DeleteCountryCommandValidator.cs
@@ -0,0 +1,11 @@
+using FluentValidation;
+
+namespace AutobusApi.Application.Countries.Commands.DeleteCountry;
+
+public class DeleteCountryCommandValidator : AbstractValidator
+{
+ public DeleteCountryCommandValidator()
+ {
+ RuleFor(v => v.Id).GreaterThan(0);
+ }
+}
diff --git a/AutobusApi.Application/Countries/Commands/UpdateCountry/UpdateCountryCommand.cs b/AutobusApi.Application/Countries/Commands/UpdateCountry/UpdateCountryCommand.cs
new file mode 100644
index 0000000..5e5233c
--- /dev/null
+++ b/AutobusApi.Application/Countries/Commands/UpdateCountry/UpdateCountryCommand.cs
@@ -0,0 +1,10 @@
+using MediatR;
+
+namespace AutobusApi.Application.Countries.Commands.UpdateCountry;
+
+public record UpdateCountryCommand : IRequest
+{
+ public int Id { get; set; }
+
+ public string Name { get; set; } = null!;
+}
diff --git a/AutobusApi.Application/Countries/Commands/UpdateCountry/UpdateCountryCommandHandler.cs b/AutobusApi.Application/Countries/Commands/UpdateCountry/UpdateCountryCommandHandler.cs
new file mode 100644
index 0000000..35bcd4f
--- /dev/null
+++ b/AutobusApi.Application/Countries/Commands/UpdateCountry/UpdateCountryCommandHandler.cs
@@ -0,0 +1,32 @@
+using AutobusApi.Application.Common.Exceptions;
+using AutobusApi.Application.Common.Interfaces;
+using MediatR;
+
+namespace AutobusApi.Application.Countries.Commands.UpdateCountry;
+
+public class UpdateCountryCommandHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _dbContext;
+
+ public UpdateCountryCommandHandler(IApplicationDbContext dbContext)
+ {
+ _dbContext = dbContext;
+ }
+
+ public async Task Handle(
+ UpdateCountryCommand request,
+ CancellationToken cancellationToken)
+ {
+ var country = await _dbContext.Countries
+ .FindAsync(new object[] { request.Id }, cancellationToken);
+
+ if (country == null)
+ {
+ throw new NotFoundException();
+ }
+
+ country.Name = request.Name;
+
+ await _dbContext.SaveChangesAsync(cancellationToken);
+ }
+}
diff --git a/AutobusApi.Application/Countries/Commands/UpdateCountry/UpdateCountryCommandValidator.cs b/AutobusApi.Application/Countries/Commands/UpdateCountry/UpdateCountryCommandValidator.cs
new file mode 100644
index 0000000..04ad078
--- /dev/null
+++ b/AutobusApi.Application/Countries/Commands/UpdateCountry/UpdateCountryCommandValidator.cs
@@ -0,0 +1,13 @@
+using FluentValidation;
+
+namespace AutobusApi.Application.Countries.Commands.UpdateCountry;
+
+public class UpdateCountryCommandValidator : AbstractValidator
+{
+ public UpdateCountryCommandValidator()
+ {
+ RuleFor(v => v.Name).MinimumLength(2).MaximumLength(64);
+
+ RuleFor(v => v.Id).GreaterThan(0);
+ }
+}
diff --git a/AutobusApi.Application/Countries/Queries/CountryDto.cs b/AutobusApi.Application/Countries/Queries/CountryDto.cs
new file mode 100644
index 0000000..2027a47
--- /dev/null
+++ b/AutobusApi.Application/Countries/Queries/CountryDto.cs
@@ -0,0 +1,11 @@
+using AutobusApi.Application.Common.Mappings;
+using AutobusApi.Domain.Entities;
+
+namespace AutobusApi.Application.Countries.Queries;
+
+public class CountryDto : IMapFrom
+{
+ public int Id { get; set; }
+
+ public string Name { get; set; } = null!;
+}
diff --git a/AutobusApi.Application/Countries/Queries/GetCountriesWithPagination/GetCountriesWithPaginationQuery.cs b/AutobusApi.Application/Countries/Queries/GetCountriesWithPagination/GetCountriesWithPaginationQuery.cs
new file mode 100644
index 0000000..d7a8923
--- /dev/null
+++ b/AutobusApi.Application/Countries/Queries/GetCountriesWithPagination/GetCountriesWithPaginationQuery.cs
@@ -0,0 +1,13 @@
+using AutobusApi.Application.Common.Models;
+using MediatR;
+
+namespace AutobusApi.Application.Countries.Queries.GetCountriesWithPagination;
+
+public record GetCountriesWithPaginationQuery : IRequest>
+{
+ public string Sort { get; set; } = "";
+
+ public int PageNumber { get; set; } = 1;
+
+ public int PageSize { get; set; } = 10;
+}
diff --git a/AutobusApi.Application/Countries/Queries/GetCountriesWithPagination/GetCountriesWithPaginationQueryHandler.cs b/AutobusApi.Application/Countries/Queries/GetCountriesWithPagination/GetCountriesWithPaginationQueryHandler.cs
new file mode 100644
index 0000000..659ff51
--- /dev/null
+++ b/AutobusApi.Application/Countries/Queries/GetCountriesWithPagination/GetCountriesWithPaginationQueryHandler.cs
@@ -0,0 +1,32 @@
+using AutobusApi.Application.Common.Interfaces;
+using AutobusApi.Application.Common.Mappings;
+using AutobusApi.Application.Common.Models;
+using AutoMapper;
+using AutoMapper.QueryableExtensions;
+using MediatR;
+
+namespace AutobusApi.Application.Countries.Queries.GetCountriesWithPagination;
+
+public class GetCountriesWithPaginationQueryHandler : IRequestHandler>
+{
+ private readonly IApplicationDbContext _dbContext;
+ private readonly IMapper _mapper;
+
+ public GetCountriesWithPaginationQueryHandler(
+ IApplicationDbContext dbContext,
+ IMapper mapper)
+ {
+ _dbContext = dbContext;
+ _mapper = mapper;
+ }
+
+ public async Task> Handle(
+ GetCountriesWithPaginationQuery request,
+ CancellationToken cancellationToken)
+ {
+ return await _dbContext.Countries
+ .ProjectTo(_mapper.ConfigurationProvider)
+ .ApplySort(request.Sort)
+ .PaginatedListAsync(request.PageNumber, request.PageSize);
+ }
+}
diff --git a/AutobusApi.Application/Countries/Queries/GetCountriesWithPagination/GetCountriesWithPaginationQueryValidator.cs b/AutobusApi.Application/Countries/Queries/GetCountriesWithPagination/GetCountriesWithPaginationQueryValidator.cs
new file mode 100644
index 0000000..ed96df8
--- /dev/null
+++ b/AutobusApi.Application/Countries/Queries/GetCountriesWithPagination/GetCountriesWithPaginationQueryValidator.cs
@@ -0,0 +1,13 @@
+using FluentValidation;
+
+namespace AutobusApi.Application.Countries.Queries.GetCountriesWithPagination;
+
+public class GetCountriesWithPaginationQueryValidator : AbstractValidator
+{
+ public GetCountriesWithPaginationQueryValidator()
+ {
+ RuleFor(v => v.PageNumber).GreaterThanOrEqualTo(1);
+
+ RuleFor(v => v.PageSize).GreaterThanOrEqualTo(1).LessThanOrEqualTo(50);
+ }
+}
diff --git a/AutobusApi.Application/Countries/Queries/GetCountry/GetCountryQuery.cs b/AutobusApi.Application/Countries/Queries/GetCountry/GetCountryQuery.cs
new file mode 100644
index 0000000..fe1aae8
--- /dev/null
+++ b/AutobusApi.Application/Countries/Queries/GetCountry/GetCountryQuery.cs
@@ -0,0 +1,8 @@
+using MediatR;
+
+namespace AutobusApi.Application.Countries.Queries.GetCountry;
+
+public record GetCountryQuery : IRequest
+{
+ public int Id { get; set; }
+}
diff --git a/AutobusApi.Application/Countries/Queries/GetCountry/GetCountryQueryHandler.cs b/AutobusApi.Application/Countries/Queries/GetCountry/GetCountryQueryHandler.cs
new file mode 100644
index 0000000..b69cb76
--- /dev/null
+++ b/AutobusApi.Application/Countries/Queries/GetCountry/GetCountryQueryHandler.cs
@@ -0,0 +1,38 @@
+using AutobusApi.Application.Common.Exceptions;
+using AutobusApi.Application.Common.Interfaces;
+using AutoMapper;
+using AutoMapper.QueryableExtensions;
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+
+namespace AutobusApi.Application.Countries.Queries.GetCountry;
+
+public class GetCountryQueryHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _dbContext;
+ private readonly IMapper _mapper;
+
+ public GetCountryQueryHandler(
+ IApplicationDbContext dbContext,
+ IMapper mapper)
+ {
+ _dbContext = dbContext;
+ _mapper = mapper;
+ }
+
+ public async Task Handle(
+ GetCountryQuery request,
+ CancellationToken cancellationToken)
+ {
+ var country = await _dbContext.Countries
+ .ProjectTo(_mapper.ConfigurationProvider)
+ .SingleOrDefaultAsync(c => c.Id == request.Id);
+
+ if (country == null)
+ {
+ throw new NotFoundException();
+ }
+
+ return country;
+ }
+}
diff --git a/AutobusApi.Application/Countries/Queries/GetCountry/GetCountryQueryValidator.cs b/AutobusApi.Application/Countries/Queries/GetCountry/GetCountryQueryValidator.cs
new file mode 100644
index 0000000..2c84850
--- /dev/null
+++ b/AutobusApi.Application/Countries/Queries/GetCountry/GetCountryQueryValidator.cs
@@ -0,0 +1,11 @@
+using FluentValidation;
+
+namespace AutobusApi.Application.Countries.Queries.GetCountry;
+
+public class GetCountryQueryValidator : AbstractValidator
+{
+ public GetCountryQueryValidator()
+ {
+ RuleFor(v => v.Id).GreaterThan(0);
+ }
+}
diff --git a/AutobusApi.Application/DependencyInjection.cs b/AutobusApi.Application/DependencyInjection.cs
index 7896730..6e875f9 100644
--- a/AutobusApi.Application/DependencyInjection.cs
+++ b/AutobusApi.Application/DependencyInjection.cs
@@ -12,6 +12,8 @@ public static class DependencyInjection
{
services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
+ services.AddAutoMapper(Assembly.GetExecutingAssembly());
+
services.AddMediatR(configuration =>
{
configuration.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly());
diff --git a/AutobusApi.Application/Employees/Commands/CreateEmployee/CreateEmployeeCommand.cs b/AutobusApi.Application/Employees/Commands/CreateEmployee/CreateEmployeeCommand.cs
new file mode 100644
index 0000000..885c537
--- /dev/null
+++ b/AutobusApi.Application/Employees/Commands/CreateEmployee/CreateEmployeeCommand.cs
@@ -0,0 +1,31 @@
+using MediatR;
+
+namespace AutobusApi.Application.Employees.Commands.CreateEmployee;
+
+public record CreateEmployeeCommand : IRequest
+{
+ public int CompanyId { get; set; }
+
+ public string Email { get; set; } = null!;
+
+ public string Password { get; set; } = null!;
+
+ public string FirstName { get; set; } = null!;
+
+ public string LastName { get; set; } = null!;
+
+ public string Patronymic { get; set; } = null!;
+
+ public string Sex { get; set; } = null!;
+
+ public DateOnly BirthDate { get; set; }
+
+ public List Documents { get; set; } = null!;
+}
+
+public record CreateEmployeeDocumentCommand
+{
+ public string Type { get; set; } = null!;
+
+ public string Information { get; set; } = null!;
+}
diff --git a/AutobusApi.Application/Employees/Commands/CreateEmployee/CreateEmployeeCommandHandler.cs b/AutobusApi.Application/Employees/Commands/CreateEmployee/CreateEmployeeCommandHandler.cs
new file mode 100644
index 0000000..8afbce3
--- /dev/null
+++ b/AutobusApi.Application/Employees/Commands/CreateEmployee/CreateEmployeeCommandHandler.cs
@@ -0,0 +1,55 @@
+using AutobusApi.Application.Common.Interfaces;
+using AutobusApi.Domain.Entities;
+using AutobusApi.Domain.Enums;
+using MediatR;
+
+namespace AutobusApi.Application.Employees.Commands.CreateEmployee;
+
+public class CreateEmployeeCommandHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _dbContext;
+ private readonly IIdentityService _identityService;
+
+ public CreateEmployeeCommandHandler(
+ IApplicationDbContext dbContext,
+ IIdentityService identityService)
+ {
+ _dbContext = dbContext;
+ _identityService = identityService;
+ }
+
+ public async Task Handle(
+ CreateEmployeeCommand request,
+ CancellationToken cancellationToken)
+ {
+ var employee = new Employee();
+
+ employee.EmployerCompanyId = request.CompanyId;
+ employee.FirstName = request.FirstName;
+ employee.LastName = request.LastName;
+ employee.Patronymic = request.Patronymic;
+ employee.Sex = Enum.Parse(request.Sex);
+ employee.BirthDate = request.BirthDate;
+
+ var userId = await _identityService.RegisterAsync(request.Email, request.Password, cancellationToken);
+
+ employee.IdentityId = userId;
+
+ employee.Documents = new List();
+
+ foreach (var document in request.Documents)
+ {
+ employee.Documents.Add(new EmployeeDocument()
+ {
+ Type = Enum.Parse(document.Type),
+ Information = document.Information
+ });
+ }
+
+ _dbContext.Employees.Add(employee);
+
+ await _dbContext.SaveChangesAsync(cancellationToken);
+
+ return employee.Id;
+ }
+}
diff --git a/AutobusApi.Application/Employees/Commands/CreateEmployee/CreateEmployeeCommandValidator.cs b/AutobusApi.Application/Employees/Commands/CreateEmployee/CreateEmployeeCommandValidator.cs
new file mode 100644
index 0000000..d5a48dd
--- /dev/null
+++ b/AutobusApi.Application/Employees/Commands/CreateEmployee/CreateEmployeeCommandValidator.cs
@@ -0,0 +1,38 @@
+using AutobusApi.Domain.Enums;
+using FluentValidation;
+
+namespace AutobusApi.Application.Employees.Commands.CreateEmployee;
+
+public class CreateEmployeeCommandValidator : AbstractValidator
+{
+ public CreateEmployeeCommandValidator()
+ {
+ RuleFor(v => v.Email)
+ .NotEmpty().WithMessage("Email address is required.")
+ .Matches(@"\b[\w\.-]+@[\w\.-]+\.\w{2,4}\b").WithMessage("Email address is invalid.");
+
+ RuleFor(v => v.Password)
+ .NotEmpty().WithMessage("Password is required.")
+ .MinimumLength(8).WithMessage("Password must be at least 8 characters long.")
+ .MaximumLength(64).WithMessage("Password must be at most 64 characters long.")
+ .Matches(@"(?=.*[A-Z]).*").WithMessage("Password must contain at least one uppercase letter.")
+ .Matches(@"(?=.*[a-z]).*").WithMessage("Password must contain at least one lowercase letter.")
+ .Matches(@"(?=.*[\d]).*").WithMessage("Password must contain at least one digit.")
+ .Matches(@"(?=.*[!@#$%^&*()]).*").WithMessage("Password must contain at least one of the following special charactters: !@#$%^&*().");
+
+ RuleFor(v => v.FirstName).MinimumLength(2).MaximumLength(32);
+
+ RuleFor(v => v.LastName).MinimumLength(2).MaximumLength(32);
+
+ RuleFor(v => v.Patronymic).MinimumLength(2).MaximumLength(32);
+
+ RuleFor(v => v.Sex).Must(value => Enum.TryParse(value, true, out _));
+
+ RuleForEach(v => v.Documents).ChildRules(document =>
+ {
+ document.RuleFor(v => v.Type).Must(value => Enum.TryParse(value, true, out _));
+
+ document.RuleFor(v => v.Information).MinimumLength(2).MaximumLength(256);
+ });
+ }
+}
diff --git a/AutobusApi.Application/Employees/Commands/DeleteEmployee/DeleteEmployeeCommand.cs b/AutobusApi.Application/Employees/Commands/DeleteEmployee/DeleteEmployeeCommand.cs
new file mode 100644
index 0000000..f5567b6
--- /dev/null
+++ b/AutobusApi.Application/Employees/Commands/DeleteEmployee/DeleteEmployeeCommand.cs
@@ -0,0 +1,8 @@
+using MediatR;
+
+namespace AutobusApi.Application.Employees.Commands.DeleteEmployee;
+
+public record DeleteEmployeeCommand : IRequest
+{
+ public int Id { get; set; }
+}
diff --git a/AutobusApi.Application/Employees/Commands/DeleteEmployee/DeleteEmployeeCommandHandler.cs b/AutobusApi.Application/Employees/Commands/DeleteEmployee/DeleteEmployeeCommandHandler.cs
new file mode 100644
index 0000000..269e77b
--- /dev/null
+++ b/AutobusApi.Application/Employees/Commands/DeleteEmployee/DeleteEmployeeCommandHandler.cs
@@ -0,0 +1,33 @@
+using AutobusApi.Application.Common.Exceptions;
+using AutobusApi.Application.Common.Interfaces;
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+
+namespace AutobusApi.Application.Employees.Commands.DeleteEmployee;
+
+public class DeleteEmployeeCommandHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _dbContext;
+
+ public DeleteEmployeeCommandHandler(IApplicationDbContext dbContext)
+ {
+ _dbContext = dbContext;
+ }
+
+ public async Task Handle(
+ DeleteEmployeeCommand request,
+ CancellationToken cancellationToken)
+ {
+ var employee = await _dbContext.Employees
+ .SingleOrDefaultAsync(c => c.Id == request.Id, cancellationToken);
+
+ if (employee == null)
+ {
+ throw new NotFoundException();
+ }
+
+ _dbContext.Employees.Remove(employee);
+
+ await _dbContext.SaveChangesAsync(cancellationToken);
+ }
+}
diff --git a/AutobusApi.Application/Employees/Commands/DeleteEmployee/DeleteEmployeeCommandValidator.cs b/AutobusApi.Application/Employees/Commands/DeleteEmployee/DeleteEmployeeCommandValidator.cs
new file mode 100644
index 0000000..cb98268
--- /dev/null
+++ b/AutobusApi.Application/Employees/Commands/DeleteEmployee/DeleteEmployeeCommandValidator.cs
@@ -0,0 +1,11 @@
+using FluentValidation;
+
+namespace AutobusApi.Application.Employees.Commands.DeleteEmployee;
+
+public class DeleteEmployeeCommandValidator : AbstractValidator
+{
+ public DeleteEmployeeCommandValidator()
+ {
+ RuleFor(v => v.Id).GreaterThan(0);
+ }
+}
diff --git a/AutobusApi.Application/Employees/Commands/UpdateEmployee/UpdateEmployeeCommand.cs b/AutobusApi.Application/Employees/Commands/UpdateEmployee/UpdateEmployeeCommand.cs
new file mode 100644
index 0000000..d83d0c5
--- /dev/null
+++ b/AutobusApi.Application/Employees/Commands/UpdateEmployee/UpdateEmployeeCommand.cs
@@ -0,0 +1,29 @@
+using MediatR;
+
+namespace AutobusApi.Application.Employees.Commands.UpdateEmployee;
+
+public record UpdateEmployeeCommand : IRequest
+{
+ public int Id { get; set; }
+
+ public string FirstName { get; set; } = null!;
+
+ public string LastName { get; set; } = null!;
+
+ public string Patronymic { get; set; } = null!;
+
+ public string Sex { get; set; } = null!;
+
+ public DateOnly BirthDate { get; set; }
+
+ public List Documents { get; set; } = null!;
+}
+
+public record UpdateEmployeeDocumentCommand
+{
+ public int? Id { get; set; }
+
+ public string Type { get; set; } = null!;
+
+ public string Information { get; set; } = null!;
+}
diff --git a/AutobusApi.Application/Employees/Commands/UpdateEmployee/UpdateEmployeeCommandHandler.cs b/AutobusApi.Application/Employees/Commands/UpdateEmployee/UpdateEmployeeCommandHandler.cs
new file mode 100644
index 0000000..431cd89
--- /dev/null
+++ b/AutobusApi.Application/Employees/Commands/UpdateEmployee/UpdateEmployeeCommandHandler.cs
@@ -0,0 +1,114 @@
+using AutobusApi.Application.Common.Exceptions;
+using AutobusApi.Application.Common.Interfaces;
+using AutobusApi.Domain.Entities;
+using AutobusApi.Domain.Enums;
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+
+namespace AutobusApi.Application.Employees.Commands.UpdateEmployee;
+
+public class UpdateEmployeeCommandHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _dbContext;
+
+ public UpdateEmployeeCommandHandler(IApplicationDbContext dbContext)
+ {
+ _dbContext = dbContext;
+ }
+
+ public async Task Handle(
+ UpdateEmployeeCommand request,
+ CancellationToken cancellationToken)
+ {
+ var employee = await _dbContext.Employees
+ .Include(e => e.Documents)
+ .SingleAsync(e => e.Id == request.Id, cancellationToken);
+
+ if (employee == null)
+ {
+ throw new NotFoundException();
+ }
+
+ employee.FirstName = request.FirstName;
+ employee.LastName = request.LastName;
+ employee.Patronymic = request.Patronymic;
+ employee.Sex = Enum.Parse(request.Sex);
+ employee.BirthDate = request.BirthDate;
+
+ var compaper = new EmployeeDocumentEqualityComparer();
+
+ var routeAddressesToBeRemoved = employee.Documents
+ .ExceptBy(request.Documents.Select(
+ d => new EmployeeDocument
+ {
+ Id = d.Id ?? 0,
+ Information = d.Information,
+ Type = Enum.Parse(d.Type)
+ }),
+ d => new EmployeeDocument
+ {
+ Id = d.Id,
+ Information = d.Information,
+ Type = d.Type
+ },
+ compaper
+ ).ToList();
+
+ var remainingEmployeeDocumentes = employee.Documents
+ .ExceptBy(routeAddressesToBeRemoved.Select(
+ dtbr => new EmployeeDocument
+ {
+ Id = dtbr.Id,
+ Information = dtbr.Information,
+ Type = dtbr.Type
+ }),
+ d => new EmployeeDocument
+ {
+ Id = d.Id,
+ Information = d.Information,
+ Type = d.Type
+ },
+ compaper
+ ).ToList();
+
+ var newEmployeeDocumentes = remainingEmployeeDocumentes
+ .UnionBy(request.Documents.Select(
+ d => new EmployeeDocument
+ {
+ Id = d.Id ?? 0,
+ Information = d.Information,
+ Type = Enum.Parse(d.Type)
+ }),
+ rd => new EmployeeDocument
+ {
+ Id = rd.Id,
+ Information = rd.Information,
+ Type = rd.Type
+ },
+ compaper
+ ).ToList();
+
+ employee.Documents = newEmployeeDocumentes.ToList();
+
+ await _dbContext.SaveChangesAsync(cancellationToken);
+ }
+
+ private class EmployeeDocumentEqualityComparer : IEqualityComparer
+ {
+ public bool Equals(EmployeeDocument? x, EmployeeDocument? y)
+ {
+ return
+ x?.Id == y?.Id &&
+ x?.Type == y?.Type &&
+ x?.Information == y?.Information;
+ }
+
+ public int GetHashCode(EmployeeDocument obj)
+ {
+ return
+ obj.Id.GetHashCode() +
+ obj.Information.GetHashCode() +
+ obj.Type.GetHashCode();
+ }
+ }
+}
diff --git a/AutobusApi.Application/Employees/Commands/UpdateEmployee/UpdateEmployeeCommandValidator.cs b/AutobusApi.Application/Employees/Commands/UpdateEmployee/UpdateEmployeeCommandValidator.cs
new file mode 100644
index 0000000..30aeef2
--- /dev/null
+++ b/AutobusApi.Application/Employees/Commands/UpdateEmployee/UpdateEmployeeCommandValidator.cs
@@ -0,0 +1,27 @@
+using AutobusApi.Domain.Enums;
+using FluentValidation;
+
+namespace AutobusApi.Application.Employees.Commands.UpdateEmployee;
+
+public class UpdateEmployeeCommandValidator : AbstractValidator
+{
+ public UpdateEmployeeCommandValidator()
+ {
+ RuleFor(v => v.Id).GreaterThan(0);
+
+ RuleFor(v => v.FirstName).MinimumLength(2).MaximumLength(32);
+
+ RuleFor(v => v.LastName).MinimumLength(2).MaximumLength(32);
+
+ RuleFor(v => v.Patronymic).MinimumLength(2).MaximumLength(32);
+
+ RuleFor(v => v.Sex).Must(value => Enum.TryParse(value, true, out _));
+
+ RuleForEach(v => v.Documents).ChildRules(document =>
+ {
+ document.RuleFor(v => v.Type).Must(value => Enum.TryParse(value, true, out _));
+
+ document.RuleFor(v => v.Information).MinimumLength(2).MaximumLength(256);
+ });
+ }
+}
diff --git a/AutobusApi.Application/Employees/Queries/EmployeeDto.cs b/AutobusApi.Application/Employees/Queries/EmployeeDto.cs
new file mode 100644
index 0000000..08ff821
--- /dev/null
+++ b/AutobusApi.Application/Employees/Queries/EmployeeDto.cs
@@ -0,0 +1,30 @@
+using AutobusApi.Application.Common.Mappings;
+using AutobusApi.Domain.Entities;
+
+namespace AutobusApi.Application.Employees.Queries;
+
+public class EmployeeDto : IMapFrom
+{
+ public int Id { get; set; }
+
+ public string FirstName { get; set; } = null!;
+
+ public string LastName { get; set; } = null!;
+
+ public string Patronymic { get; set; } = null!;
+
+ public string Sex { get; set; } = null!;
+
+ public DateOnly BirthDate { get; set; }
+
+ public List Documents { get; set; } = null!;
+}
+
+public class EmployeeDocumentDto : IMapFrom
+{
+ public int Id { get; set; }
+
+ public string Type { get; set; } = null!;
+
+ public string Information { get; set; } = null!;
+}
diff --git a/AutobusApi.Application/Employees/Queries/GetEmployee/GetEmployeeQuery.cs b/AutobusApi.Application/Employees/Queries/GetEmployee/GetEmployeeQuery.cs
new file mode 100644
index 0000000..a469b51
--- /dev/null
+++ b/AutobusApi.Application/Employees/Queries/GetEmployee/GetEmployeeQuery.cs
@@ -0,0 +1,8 @@
+using MediatR;
+
+namespace AutobusApi.Application.Employees.Queries.GetEmployee;
+
+public record GetEmployeeQuery : IRequest
+{
+ public int Id { get; set; }
+}
diff --git a/AutobusApi.Application/Employees/Queries/GetEmployee/GetEmployeeQueryHandler.cs b/AutobusApi.Application/Employees/Queries/GetEmployee/GetEmployeeQueryHandler.cs
new file mode 100644
index 0000000..e3454bb
--- /dev/null
+++ b/AutobusApi.Application/Employees/Queries/GetEmployee/GetEmployeeQueryHandler.cs
@@ -0,0 +1,38 @@
+using AutobusApi.Application.Common.Exceptions;
+using AutobusApi.Application.Common.Interfaces;
+using AutoMapper;
+using AutoMapper.QueryableExtensions;
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+
+namespace AutobusApi.Application.Employees.Queries.GetEmployee;
+
+public class GetEmployeeQueryHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _dbContext;
+ private readonly IMapper _mapper;
+
+ public GetEmployeeQueryHandler(
+ IApplicationDbContext dbContext,
+ IMapper mapper)
+ {
+ _dbContext = dbContext;
+ _mapper = mapper;
+ }
+
+ public async Task Handle(
+ GetEmployeeQuery request,
+ CancellationToken cancellationToken)
+ {
+ var employee = await _dbContext.Employees
+ .ProjectTo(_mapper.ConfigurationProvider)
+ .SingleOrDefaultAsync(c => c.Id == request.Id);
+
+ if (employee == null)
+ {
+ throw new NotFoundException();
+ }
+
+ return employee;
+ }
+}
diff --git a/AutobusApi.Application/Employees/Queries/GetEmployee/GetEmployeeQueryValidator.cs b/AutobusApi.Application/Employees/Queries/GetEmployee/GetEmployeeQueryValidator.cs
new file mode 100644
index 0000000..04dad78
--- /dev/null
+++ b/AutobusApi.Application/Employees/Queries/GetEmployee/GetEmployeeQueryValidator.cs
@@ -0,0 +1,11 @@
+using FluentValidation;
+
+namespace AutobusApi.Application.Employees.Queries.GetEmployee;
+
+public class GetEmployeeQueryValidator : AbstractValidator
+{
+ public GetEmployeeQueryValidator()
+ {
+ RuleFor(v => v.Id).GreaterThan(0);
+ }
+}
diff --git a/AutobusApi.Application/Employees/Queries/GetEmployeesWithPagination/GetEmployeesWithPaginationQuery.cs b/AutobusApi.Application/Employees/Queries/GetEmployeesWithPagination/GetEmployeesWithPaginationQuery.cs
new file mode 100644
index 0000000..93ab915
--- /dev/null
+++ b/AutobusApi.Application/Employees/Queries/GetEmployeesWithPagination/GetEmployeesWithPaginationQuery.cs
@@ -0,0 +1,13 @@
+using AutobusApi.Application.Common.Models;
+using MediatR;
+
+namespace AutobusApi.Application.Employees.Queries.GetEmployeesWithPagination;
+
+public record GetEmployeesWithPaginationQuery : IRequest>
+{
+ public string Sort { get; set; } = "";
+
+ public int PageNumber { get; set; } = 1;
+
+ public int PageSize { get; set; } = 10;
+}
diff --git a/AutobusApi.Application/Employees/Queries/GetEmployeesWithPagination/GetEmployeesWithPaginationQueryHandler.cs b/AutobusApi.Application/Employees/Queries/GetEmployeesWithPagination/GetEmployeesWithPaginationQueryHandler.cs
new file mode 100644
index 0000000..078f56e
--- /dev/null
+++ b/AutobusApi.Application/Employees/Queries/GetEmployeesWithPagination/GetEmployeesWithPaginationQueryHandler.cs
@@ -0,0 +1,32 @@
+using AutobusApi.Application.Common.Interfaces;
+using AutobusApi.Application.Common.Mappings;
+using AutobusApi.Application.Common.Models;
+using AutoMapper;
+using AutoMapper.QueryableExtensions;
+using MediatR;
+
+namespace AutobusApi.Application.Employees.Queries.GetEmployeesWithPagination;
+
+public class GetEmployeesWithPaginationQueryHandler : IRequestHandler>
+{
+ private readonly IApplicationDbContext _dbContext;
+ private readonly IMapper _mapper;
+
+ public GetEmployeesWithPaginationQueryHandler(
+ IApplicationDbContext dbContext,
+ IMapper mapper)
+ {
+ _dbContext = dbContext;
+ _mapper = mapper;
+ }
+
+ public async Task> Handle(
+ GetEmployeesWithPaginationQuery request,
+ CancellationToken cancellationToken)
+ {
+ return await _dbContext.Employees
+ .ProjectTo(_mapper.ConfigurationProvider)
+ .ApplySort(request.Sort)
+ .PaginatedListAsync(request.PageNumber, request.PageSize);
+ }
+}
diff --git a/AutobusApi.Application/Employees/Queries/GetEmployeesWithPagination/GetEmployeesWithPaginationQueryValidator.cs b/AutobusApi.Application/Employees/Queries/GetEmployeesWithPagination/GetEmployeesWithPaginationQueryValidator.cs
new file mode 100644
index 0000000..8fd86fe
--- /dev/null
+++ b/AutobusApi.Application/Employees/Queries/GetEmployeesWithPagination/GetEmployeesWithPaginationQueryValidator.cs
@@ -0,0 +1,13 @@
+using FluentValidation;
+
+namespace AutobusApi.Application.Employees.Queries.GetEmployeesWithPagination;
+
+public class GetEmployeesWithPaginationQueryValidator : AbstractValidator
+{
+ public GetEmployeesWithPaginationQueryValidator()
+ {
+ RuleFor(v => v.PageNumber).GreaterThanOrEqualTo(1);
+
+ RuleFor(v => v.PageSize).GreaterThanOrEqualTo(1).LessThanOrEqualTo(50);
+ }
+}
diff --git a/AutobusApi.Application/Identity/Commands/Register/RegisterCommandValidator.cs b/AutobusApi.Application/Identity/Commands/Register/RegisterCommandValidator.cs
index ec3171a..9ff7df0 100644
--- a/AutobusApi.Application/Identity/Commands/Register/RegisterCommandValidator.cs
+++ b/AutobusApi.Application/Identity/Commands/Register/RegisterCommandValidator.cs
@@ -6,6 +6,7 @@ public class RegisterCommandValidator : AbstractValidator
{
public RegisterCommandValidator()
{
+ // https://regexr.com/2ri2c
RuleFor(v => v.Email)
.NotEmpty().WithMessage("Email address is required.")
.Matches(@"\b[\w\.-]+@[\w\.-]+\.\w{2,4}\b").WithMessage("Email address is invalid.");
diff --git a/AutobusApi.Application/Regions/Commands/CreateRegion/CreateRegionCommand.cs b/AutobusApi.Application/Regions/Commands/CreateRegion/CreateRegionCommand.cs
new file mode 100644
index 0000000..b2f28fd
--- /dev/null
+++ b/AutobusApi.Application/Regions/Commands/CreateRegion/CreateRegionCommand.cs
@@ -0,0 +1,10 @@
+using MediatR;
+
+namespace AutobusApi.Application.Regions.Commands.CreateRegion;
+
+public record CreateRegionCommand : IRequest
+{
+ public string Name { get; set; } = null!;
+
+ public int CountryId { get; set; }
+}
diff --git a/AutobusApi.Application/Regions/Commands/CreateRegion/CreateRegionCommandHandler.cs b/AutobusApi.Application/Regions/Commands/CreateRegion/CreateRegionCommandHandler.cs
new file mode 100644
index 0000000..8c09d1f
--- /dev/null
+++ b/AutobusApi.Application/Regions/Commands/CreateRegion/CreateRegionCommandHandler.cs
@@ -0,0 +1,31 @@
+using AutobusApi.Application.Common.Interfaces;
+using AutobusApi.Domain.Entities;
+using MediatR;
+
+namespace AutobusApi.Application.Regions.Commands.CreateRegion;
+
+public class CreateRegionCommandHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _dbContext;
+
+ public CreateRegionCommandHandler(IApplicationDbContext dbContext)
+ {
+ _dbContext = dbContext;
+ }
+
+ public async Task Handle(
+ CreateRegionCommand request,
+ CancellationToken cancellationToken)
+ {
+ var region = new Region();
+
+ region.Name = request.Name;
+ region.CountryId = request.CountryId;
+
+ _dbContext.Regions.Add(region);
+
+ await _dbContext.SaveChangesAsync(cancellationToken);
+
+ return region.Id;
+ }
+}
diff --git a/AutobusApi.Application/Regions/Commands/CreateRegion/CreateRegionCommandValidator.cs b/AutobusApi.Application/Regions/Commands/CreateRegion/CreateRegionCommandValidator.cs
new file mode 100644
index 0000000..c713f13
--- /dev/null
+++ b/AutobusApi.Application/Regions/Commands/CreateRegion/CreateRegionCommandValidator.cs
@@ -0,0 +1,11 @@
+using FluentValidation;
+
+namespace AutobusApi.Application.Regions.Commands.CreateRegion;
+
+public class CreateRegionCommandValidator : AbstractValidator
+{
+ public CreateRegionCommandValidator()
+ {
+ RuleFor(v => v.Name).MinimumLength(2).MaximumLength(64);
+ }
+}
diff --git a/AutobusApi.Application/Regions/Commands/DeleteRegion/DeleteRegionCommand.cs b/AutobusApi.Application/Regions/Commands/DeleteRegion/DeleteRegionCommand.cs
new file mode 100644
index 0000000..c68b05e
--- /dev/null
+++ b/AutobusApi.Application/Regions/Commands/DeleteRegion/DeleteRegionCommand.cs
@@ -0,0 +1,8 @@
+using MediatR;
+
+namespace AutobusApi.Application.Regions.Commands.DeleteRegion;
+
+public record DeleteRegionCommand : IRequest
+{
+ public int Id { get; set; }
+}
diff --git a/AutobusApi.Application/Regions/Commands/DeleteRegion/DeleteRegionCommandHandler.cs b/AutobusApi.Application/Regions/Commands/DeleteRegion/DeleteRegionCommandHandler.cs
new file mode 100644
index 0000000..3af22c2
--- /dev/null
+++ b/AutobusApi.Application/Regions/Commands/DeleteRegion/DeleteRegionCommandHandler.cs
@@ -0,0 +1,33 @@
+using AutobusApi.Application.Common.Exceptions;
+using AutobusApi.Application.Common.Interfaces;
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+
+namespace AutobusApi.Application.Regions.Commands.DeleteRegion;
+
+public class DeleteRegionCommandHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _dbContext;
+
+ public DeleteRegionCommandHandler(IApplicationDbContext dbContext)
+ {
+ _dbContext = dbContext;
+ }
+
+ public async Task Handle(
+ DeleteRegionCommand request,
+ CancellationToken cancellationToken)
+ {
+ var region = await _dbContext.Regions
+ .SingleOrDefaultAsync(c => c.Id == request.Id, cancellationToken);
+
+ if (region == null)
+ {
+ throw new NotFoundException();
+ }
+
+ _dbContext.Regions.Remove(region);
+
+ await _dbContext.SaveChangesAsync(cancellationToken);
+ }
+}
diff --git a/AutobusApi.Application/Regions/Commands/DeleteRegion/DeleteRegionCommandValidator.cs b/AutobusApi.Application/Regions/Commands/DeleteRegion/DeleteRegionCommandValidator.cs
new file mode 100644
index 0000000..ab094e4
--- /dev/null
+++ b/AutobusApi.Application/Regions/Commands/DeleteRegion/DeleteRegionCommandValidator.cs
@@ -0,0 +1,11 @@
+using FluentValidation;
+
+namespace AutobusApi.Application.Regions.Commands.DeleteRegion;
+
+public class DeleteRegionCommandValidator : AbstractValidator
+{
+ public DeleteRegionCommandValidator()
+ {
+ RuleFor(v => v.Id).NotNull();
+ }
+}
diff --git a/AutobusApi.Application/Regions/Commands/UpdateRegion/UpdateRegionCommand.cs b/AutobusApi.Application/Regions/Commands/UpdateRegion/UpdateRegionCommand.cs
new file mode 100644
index 0000000..aa4454a
--- /dev/null
+++ b/AutobusApi.Application/Regions/Commands/UpdateRegion/UpdateRegionCommand.cs
@@ -0,0 +1,12 @@
+using MediatR;
+
+namespace AutobusApi.Application.Regions.Commands.UpdateRegion;
+
+public record UpdateRegionCommand : IRequest
+{
+ public int Id { get; set; }
+
+ public string Name { get; set; } = null!;
+
+ public int CountryId { get; set; }
+}
diff --git a/AutobusApi.Application/Regions/Commands/UpdateRegion/UpdateRegionCommandHandler.cs b/AutobusApi.Application/Regions/Commands/UpdateRegion/UpdateRegionCommandHandler.cs
new file mode 100644
index 0000000..6ab9ad3
--- /dev/null
+++ b/AutobusApi.Application/Regions/Commands/UpdateRegion/UpdateRegionCommandHandler.cs
@@ -0,0 +1,33 @@
+using AutobusApi.Application.Common.Exceptions;
+using AutobusApi.Application.Common.Interfaces;
+using MediatR;
+
+namespace AutobusApi.Application.Regions.Commands.UpdateRegion;
+
+public class UpdateRegionCommandHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _dbContext;
+
+ public UpdateRegionCommandHandler(IApplicationDbContext dbContext)
+ {
+ _dbContext = dbContext;
+ }
+
+ public async Task Handle(
+ UpdateRegionCommand request,
+ CancellationToken cancellationToken)
+ {
+ var region = await _dbContext.Regions
+ .FindAsync(new object[] { request.Id }, cancellationToken);
+
+ if (region == null)
+ {
+ throw new NotFoundException();
+ }
+
+ region.Name = request.Name;
+ region.CountryId = request.CountryId;
+
+ await _dbContext.SaveChangesAsync(cancellationToken);
+ }
+}
diff --git a/AutobusApi.Application/Regions/Commands/UpdateRegion/UpdateRegionCommandValidator.cs b/AutobusApi.Application/Regions/Commands/UpdateRegion/UpdateRegionCommandValidator.cs
new file mode 100644
index 0000000..d1738c7
--- /dev/null
+++ b/AutobusApi.Application/Regions/Commands/UpdateRegion/UpdateRegionCommandValidator.cs
@@ -0,0 +1,11 @@
+using FluentValidation;
+
+namespace AutobusApi.Application.Regions.Commands.UpdateRegion;
+
+public class UpdateRegionCommandValidator : AbstractValidator
+{
+ public UpdateRegionCommandValidator()
+ {
+ RuleFor(v => v.Name).MinimumLength(2).MaximumLength(64);
+ }
+}
diff --git a/AutobusApi.Application/Regions/Queries/GetRegion/GetRegionQuery.cs b/AutobusApi.Application/Regions/Queries/GetRegion/GetRegionQuery.cs
new file mode 100644
index 0000000..8fe2681
--- /dev/null
+++ b/AutobusApi.Application/Regions/Queries/GetRegion/GetRegionQuery.cs
@@ -0,0 +1,8 @@
+using MediatR;
+
+namespace AutobusApi.Application.Regions.Queries.GetRegion;
+
+public record GetRegionQuery : IRequest
+{
+ public int Id { get; set; }
+}
diff --git a/AutobusApi.Application/Regions/Queries/GetRegion/GetRegionQueryHandler.cs b/AutobusApi.Application/Regions/Queries/GetRegion/GetRegionQueryHandler.cs
new file mode 100644
index 0000000..7b6d611
--- /dev/null
+++ b/AutobusApi.Application/Regions/Queries/GetRegion/GetRegionQueryHandler.cs
@@ -0,0 +1,36 @@
+using AutobusApi.Application.Common.Exceptions;
+using AutobusApi.Application.Common.Interfaces;
+using AutoMapper;
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+
+namespace AutobusApi.Application.Regions.Queries.GetRegion;
+
+public class GetRegionQueryHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _dbContext;
+ private readonly IMapper _mapper;
+
+ public GetRegionQueryHandler(
+ IApplicationDbContext dbContext,
+ IMapper mapper)
+ {
+ _dbContext = dbContext;
+ _mapper = mapper;
+ }
+
+ public async Task Handle(
+ GetRegionQuery request,
+ CancellationToken cancellationToken)
+ {
+ var region = await _dbContext.Regions
+ .SingleOrDefaultAsync(c => c.Id == request.Id);
+
+ if (region == null)
+ {
+ throw new NotFoundException();
+ }
+
+ return _mapper.Map(region);
+ }
+}
diff --git a/AutobusApi.Application/Regions/Queries/GetRegion/GetRegionQueryValidator.cs b/AutobusApi.Application/Regions/Queries/GetRegion/GetRegionQueryValidator.cs
new file mode 100644
index 0000000..15e8ed0
--- /dev/null
+++ b/AutobusApi.Application/Regions/Queries/GetRegion/GetRegionQueryValidator.cs
@@ -0,0 +1,11 @@
+using FluentValidation;
+
+namespace AutobusApi.Application.Regions.Queries.GetRegion;
+
+public class GetRegionQueryValidator : AbstractValidator
+{
+ public GetRegionQueryValidator()
+ {
+ RuleFor(v => v.Id).NotNull();
+ }
+}
diff --git a/AutobusApi.Application/Regions/Queries/GetRegionsWithPagination/GetRegionsWithPaginationQuery.cs b/AutobusApi.Application/Regions/Queries/GetRegionsWithPagination/GetRegionsWithPaginationQuery.cs
new file mode 100644
index 0000000..6ad93fa
--- /dev/null
+++ b/AutobusApi.Application/Regions/Queries/GetRegionsWithPagination/GetRegionsWithPaginationQuery.cs
@@ -0,0 +1,13 @@
+using AutobusApi.Application.Common.Models;
+using MediatR;
+
+namespace AutobusApi.Application.Regions.Queries.GetRegionsWithPagination;
+
+public record GetRegionsWithPaginationQuery : IRequest>
+{
+ public string Sort { get; set; } = "";
+
+ public int PageNumber { get; set; } = 1;
+
+ public int PageSize { get; set; } = 10;
+}
diff --git a/AutobusApi.Application/Regions/Queries/GetRegionsWithPagination/GetRegionsWithPaginationQueryHandler.cs b/AutobusApi.Application/Regions/Queries/GetRegionsWithPagination/GetRegionsWithPaginationQueryHandler.cs
new file mode 100644
index 0000000..fe78c37
--- /dev/null
+++ b/AutobusApi.Application/Regions/Queries/GetRegionsWithPagination/GetRegionsWithPaginationQueryHandler.cs
@@ -0,0 +1,32 @@
+using AutobusApi.Application.Common.Interfaces;
+using AutobusApi.Application.Common.Mappings;
+using AutobusApi.Application.Common.Models;
+using AutoMapper;
+using AutoMapper.QueryableExtensions;
+using MediatR;
+
+namespace AutobusApi.Application.Regions.Queries.GetRegionsWithPagination;
+
+public class GetRegionsWithPaginationQueryHandler : IRequestHandler>
+{
+ private readonly IApplicationDbContext _dbContext;
+ private readonly IMapper _mapper;
+
+ public GetRegionsWithPaginationQueryHandler(
+ IApplicationDbContext dbContext,
+ IMapper mapper)
+ {
+ _dbContext = dbContext;
+ _mapper = mapper;
+ }
+
+ public async Task> Handle(
+ GetRegionsWithPaginationQuery request,
+ CancellationToken cancellationToken)
+ {
+ return await _dbContext.Regions
+ .ProjectTo(_mapper.ConfigurationProvider)
+ .ApplySort(request.Sort)
+ .PaginatedListAsync(request.PageNumber, request.PageSize);
+ }
+}
diff --git a/AutobusApi.Application/Regions/Queries/GetRegionsWithPagination/GetRegionsWithPaginationQueryValidator.cs b/AutobusApi.Application/Regions/Queries/GetRegionsWithPagination/GetRegionsWithPaginationQueryValidator.cs
new file mode 100644
index 0000000..fe9146b
--- /dev/null
+++ b/AutobusApi.Application/Regions/Queries/GetRegionsWithPagination/GetRegionsWithPaginationQueryValidator.cs
@@ -0,0 +1,13 @@
+using FluentValidation;
+
+namespace AutobusApi.Application.Regions.Queries.GetRegionsWithPagination;
+
+public class GetRegionsWithPaginationQueryValidator : AbstractValidator
+{
+ public GetRegionsWithPaginationQueryValidator()
+ {
+ RuleFor(v => v.PageNumber).GreaterThanOrEqualTo(1);
+
+ RuleFor(v => v.PageSize).GreaterThanOrEqualTo(1).LessThanOrEqualTo(50);
+ }
+}
diff --git a/AutobusApi.Application/Regions/Queries/RegionDto.cs b/AutobusApi.Application/Regions/Queries/RegionDto.cs
new file mode 100644
index 0000000..bf81195
--- /dev/null
+++ b/AutobusApi.Application/Regions/Queries/RegionDto.cs
@@ -0,0 +1,15 @@
+using AutobusApi.Application.Common.Mappings;
+using AutobusApi.Domain.Entities;
+
+namespace AutobusApi.Application.Regions.Queries;
+
+public class RegionDto : IMapFrom
+{
+ public int Id { get; set; }
+
+ public string Name { get; set; } = null!;
+
+ public int CountryId { get; set; }
+
+ public string CountryName { get; set; } = null!;
+}
diff --git a/AutobusApi.Application/Routes/Commands/CreateRoute/CreateRouteCommand.cs b/AutobusApi.Application/Routes/Commands/CreateRoute/CreateRouteCommand.cs
new file mode 100644
index 0000000..b20df4b
--- /dev/null
+++ b/AutobusApi.Application/Routes/Commands/CreateRoute/CreateRouteCommand.cs
@@ -0,0 +1,15 @@
+using MediatR;
+
+namespace AutobusApi.Application.Routes.Commands.CreateRoute;
+
+public record CreateRouteCommand : IRequest
+{
+ public List Addresses { get; set; } = null!;
+}
+
+public record CreateRouteAddressCommand
+{
+ public int Id { get; set; }
+
+ public int Order { get; set; }
+}
diff --git a/AutobusApi.Application/Routes/Commands/CreateRoute/CreateRouteCommandHandler.cs b/AutobusApi.Application/Routes/Commands/CreateRoute/CreateRouteCommandHandler.cs
new file mode 100644
index 0000000..f85a6ad
--- /dev/null
+++ b/AutobusApi.Application/Routes/Commands/CreateRoute/CreateRouteCommandHandler.cs
@@ -0,0 +1,35 @@
+using AutobusApi.Application.Common.Interfaces;
+using AutobusApi.Domain.Entities;
+using MediatR;
+
+namespace AutobusApi.Application.Routes.Commands.CreateRoute;
+
+public class CreateRouteCommandHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _dbContext;
+
+ public CreateRouteCommandHandler(IApplicationDbContext dbContext)
+ {
+ _dbContext = dbContext;
+ }
+
+ public async Task Handle(
+ CreateRouteCommand request,
+ CancellationToken cancellationToken)
+ {
+ var route = new Route();
+
+ route.RouteAddresses = request.Addresses.Select(a =>
+ new RouteAddress
+ {
+ Order = a.Order,
+ AddressId = a.Id
+ }).ToList();
+
+ _dbContext.Routes.Add(route);
+
+ await _dbContext.SaveChangesAsync(cancellationToken);
+
+ return route.Id;
+ }
+}
diff --git a/AutobusApi.Application/Routes/Commands/CreateRoute/CreateRouteCommandValidator.cs b/AutobusApi.Application/Routes/Commands/CreateRoute/CreateRouteCommandValidator.cs
new file mode 100644
index 0000000..7991a5f
--- /dev/null
+++ b/AutobusApi.Application/Routes/Commands/CreateRoute/CreateRouteCommandValidator.cs
@@ -0,0 +1,16 @@
+using FluentValidation;
+
+namespace AutobusApi.Application.Routes.Commands.CreateRoute;
+
+public class CreateRouteCommandValidator : AbstractValidator
+{
+ public CreateRouteCommandValidator()
+ {
+ RuleFor(v => v.Addresses).Must(v => v.Count >= 2);
+
+ RuleForEach(v => v.Addresses).ChildRules(address =>
+ {
+ address.RuleFor(v => v.Id).GreaterThan(0);
+ });
+ }
+}
diff --git a/AutobusApi.Application/Routes/Commands/DeleteRoute/DeleteRouteCommand.cs b/AutobusApi.Application/Routes/Commands/DeleteRoute/DeleteRouteCommand.cs
new file mode 100644
index 0000000..c0223ee
--- /dev/null
+++ b/AutobusApi.Application/Routes/Commands/DeleteRoute/DeleteRouteCommand.cs
@@ -0,0 +1,8 @@
+using MediatR;
+
+namespace AutobusApi.Application.Routes.Commands.DeleteRoute;
+
+public record DeleteRouteCommand : IRequest
+{
+ public int Id { get; set; }
+}
diff --git a/AutobusApi.Application/Routes/Commands/DeleteRoute/DeleteRouteCommandHandler.cs b/AutobusApi.Application/Routes/Commands/DeleteRoute/DeleteRouteCommandHandler.cs
new file mode 100644
index 0000000..848bc77
--- /dev/null
+++ b/AutobusApi.Application/Routes/Commands/DeleteRoute/DeleteRouteCommandHandler.cs
@@ -0,0 +1,33 @@
+using AutobusApi.Application.Common.Exceptions;
+using AutobusApi.Application.Common.Interfaces;
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+
+namespace AutobusApi.Application.Routes.Commands.DeleteRoute;
+
+public class DeleteRouteCommandHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _dbContext;
+
+ public DeleteRouteCommandHandler(IApplicationDbContext dbContext)
+ {
+ _dbContext = dbContext;
+ }
+
+ public async Task Handle(
+ DeleteRouteCommand request,
+ CancellationToken cancellationToken)
+ {
+ var route = await _dbContext.Routes
+ .SingleOrDefaultAsync(c => c.Id == request.Id, cancellationToken);
+
+ if (route == null)
+ {
+ throw new NotFoundException();
+ }
+
+ _dbContext.Routes.Remove(route);
+
+ await _dbContext.SaveChangesAsync(cancellationToken);
+ }
+}
diff --git a/AutobusApi.Application/Routes/Commands/DeleteRoute/DeleteRouteCommandValidator.cs b/AutobusApi.Application/Routes/Commands/DeleteRoute/DeleteRouteCommandValidator.cs
new file mode 100644
index 0000000..8115c28
--- /dev/null
+++ b/AutobusApi.Application/Routes/Commands/DeleteRoute/DeleteRouteCommandValidator.cs
@@ -0,0 +1,11 @@
+using FluentValidation;
+
+namespace AutobusApi.Application.Routes.Commands.DeleteRoute;
+
+public class DeleteRouteCommandValidator : AbstractValidator
+{
+ public DeleteRouteCommandValidator()
+ {
+ RuleFor(v => v.Id).GreaterThan(0);
+ }
+}
diff --git a/AutobusApi.Application/Routes/Commands/UpdateRoute/UpdateRouteCommand.cs b/AutobusApi.Application/Routes/Commands/UpdateRoute/UpdateRouteCommand.cs
new file mode 100644
index 0000000..519974f
--- /dev/null
+++ b/AutobusApi.Application/Routes/Commands/UpdateRoute/UpdateRouteCommand.cs
@@ -0,0 +1,17 @@
+using MediatR;
+
+namespace AutobusApi.Application.Routes.Commands.UpdateRoute;
+
+public record UpdateRouteCommand : IRequest
+{
+ public int Id { get; set; }
+
+ public List Addresses { get; set; } = null!;
+}
+
+public record UpdateRouteAddressAddressCommand
+{
+ public int Id { get; set; }
+
+ public int Order { get; set; }
+}
diff --git a/AutobusApi.Application/Routes/Commands/UpdateRoute/UpdateRouteCommandHandler.cs b/AutobusApi.Application/Routes/Commands/UpdateRoute/UpdateRouteCommandHandler.cs
new file mode 100644
index 0000000..656eba0
--- /dev/null
+++ b/AutobusApi.Application/Routes/Commands/UpdateRoute/UpdateRouteCommandHandler.cs
@@ -0,0 +1,96 @@
+using AutobusApi.Application.Common.Exceptions;
+using AutobusApi.Application.Common.Interfaces;
+using AutobusApi.Domain.Entities;
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+
+namespace AutobusApi.Application.Routes.Commands.UpdateRoute;
+
+public class UpdateRouteCommandHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _dbContext;
+
+ public UpdateRouteCommandHandler(IApplicationDbContext dbContext)
+ {
+ _dbContext = dbContext;
+ }
+
+ public async Task Handle(
+ UpdateRouteCommand request,
+ CancellationToken cancellationToken)
+ {
+ var route = await _dbContext.Routes
+ .Include(r => r.RouteAddresses)
+ .SingleAsync(r => r.Id == request.Id, cancellationToken);
+
+ if (route == null)
+ {
+ throw new NotFoundException();
+ }
+
+ var compaper = new RouteAddressEqualityComparer();
+
+ var routeAddressesToBeRemoved = route.RouteAddresses
+ .ExceptBy(request.Addresses.Select(
+ a => new RouteAddress
+ {
+ AddressId = a.Id,
+ Order = a.Order
+ }),
+ ra => new RouteAddress
+ {
+ AddressId = ra.AddressId,
+ Order = ra.Order
+ },
+ compaper
+ ).ToList();
+
+ var remainingRouteAddresses = route.RouteAddresses
+ .ExceptBy(routeAddressesToBeRemoved.Select(
+ ratbr => new RouteAddress
+ {
+ AddressId = ratbr.AddressId,
+ Order = ratbr.Order
+ }),
+ ra => new RouteAddress
+ {
+ AddressId = ra.AddressId,
+ Order = ra.Order
+ },
+ compaper
+ ).ToList();
+
+ var newRouteAddresses = remainingRouteAddresses
+ .UnionBy(request.Addresses.Select(
+ a => new RouteAddress
+ {
+ AddressId = a.Id,
+ Order = a.Order
+ }),
+ rra => new RouteAddress
+ {
+ AddressId = rra.AddressId,
+ Order = rra.Order
+ },
+ compaper
+ ).ToList();
+
+ route.RouteAddresses = newRouteAddresses.ToList();
+
+ await _dbContext.SaveChangesAsync(cancellationToken);
+ }
+
+ private class RouteAddressEqualityComparer : IEqualityComparer
+ {
+ public bool Equals(RouteAddress? x, RouteAddress? y)
+ {
+ return x?.AddressId == y?.AddressId && x?.Order == y?.Order;
+ }
+
+ public int GetHashCode(RouteAddress obj)
+ {
+ return obj.AddressId.GetHashCode() + obj.Order.GetHashCode();
+ }
+ }
+}
+
diff --git a/AutobusApi.Application/Routes/Commands/UpdateRoute/UpdateRouteCommandValidator.cs b/AutobusApi.Application/Routes/Commands/UpdateRoute/UpdateRouteCommandValidator.cs
new file mode 100644
index 0000000..06e5d89
--- /dev/null
+++ b/AutobusApi.Application/Routes/Commands/UpdateRoute/UpdateRouteCommandValidator.cs
@@ -0,0 +1,17 @@
+using FluentValidation;
+
+namespace AutobusApi.Application.Routes.Commands.UpdateRoute;
+
+public class UpdateRouteCommandValidator : AbstractValidator
+{
+ public UpdateRouteCommandValidator()
+ {
+ RuleFor(v => v.Addresses).Must(v => v.Count >= 2);
+
+ RuleForEach(v => v.Addresses).ChildRules(address =>
+ {
+ address.RuleFor(v => v.Id).GreaterThan(0);
+ address.RuleFor(v => v.Order).GreaterThan(0);
+ });
+ }
+}
diff --git a/AutobusApi.Application/Routes/Queries/GetRoute/GetRouteQuery.cs b/AutobusApi.Application/Routes/Queries/GetRoute/GetRouteQuery.cs
new file mode 100644
index 0000000..cf5e8ed
--- /dev/null
+++ b/AutobusApi.Application/Routes/Queries/GetRoute/GetRouteQuery.cs
@@ -0,0 +1,8 @@
+using MediatR;
+
+namespace AutobusApi.Application.Routes.Queries.GetRoute;
+
+public record GetRouteQuery : IRequest
+{
+ public int Id { get; set; }
+}
diff --git a/AutobusApi.Application/Routes/Queries/GetRoute/GetRouteQueryHandler.cs b/AutobusApi.Application/Routes/Queries/GetRoute/GetRouteQueryHandler.cs
new file mode 100644
index 0000000..f63ab8d
--- /dev/null
+++ b/AutobusApi.Application/Routes/Queries/GetRoute/GetRouteQueryHandler.cs
@@ -0,0 +1,38 @@
+using AutobusApi.Application.Common.Exceptions;
+using AutobusApi.Application.Common.Interfaces;
+using AutoMapper;
+using AutoMapper.QueryableExtensions;
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+
+namespace AutobusApi.Application.Routes.Queries.GetRoute;
+
+public class GetRouteQueryHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _dbContext;
+ private readonly IMapper _mapper;
+
+ public GetRouteQueryHandler(
+ IApplicationDbContext dbContext,
+ IMapper mapper)
+ {
+ _dbContext = dbContext;
+ _mapper = mapper;
+ }
+
+ public async Task Handle(
+ GetRouteQuery request,
+ CancellationToken cancellationToken)
+ {
+ var route = await _dbContext.Routes
+ .ProjectTo(_mapper.ConfigurationProvider)
+ .SingleOrDefaultAsync(c => c.Id == request.Id);
+
+ if (route == null)
+ {
+ throw new NotFoundException();
+ }
+
+ return route;
+ }
+}
diff --git a/AutobusApi.Application/Routes/Queries/GetRoute/GetRouteQueryValidator.cs b/AutobusApi.Application/Routes/Queries/GetRoute/GetRouteQueryValidator.cs
new file mode 100644
index 0000000..8b7646a
--- /dev/null
+++ b/AutobusApi.Application/Routes/Queries/GetRoute/GetRouteQueryValidator.cs
@@ -0,0 +1,11 @@
+using FluentValidation;
+
+namespace AutobusApi.Application.Routes.Queries.GetRoute;
+
+public class GetRouteQueryValidator : AbstractValidator
+{
+ public GetRouteQueryValidator()
+ {
+ RuleFor(v => v.Id).GreaterThan(0);
+ }
+}
diff --git a/AutobusApi.Application/Routes/Queries/GetRoutesWithPagination/GetRoutesWithPaginationQuery.cs b/AutobusApi.Application/Routes/Queries/GetRoutesWithPagination/GetRoutesWithPaginationQuery.cs
new file mode 100644
index 0000000..5e3c69b
--- /dev/null
+++ b/AutobusApi.Application/Routes/Queries/GetRoutesWithPagination/GetRoutesWithPaginationQuery.cs
@@ -0,0 +1,13 @@
+using AutobusApi.Application.Common.Models;
+using MediatR;
+
+namespace AutobusApi.Application.Routes.Queries.GetRoutesWithPagination;
+
+public record GetRoutesWithPaginationQuery : IRequest>
+{
+ public string Sort { get; set; } = "";
+
+ public int PageNumber { get; set; } = 1;
+
+ public int PageSize { get; set; } = 10;
+}
diff --git a/AutobusApi.Application/Routes/Queries/GetRoutesWithPagination/GetRoutesWithPaginationQueryHandler.cs b/AutobusApi.Application/Routes/Queries/GetRoutesWithPagination/GetRoutesWithPaginationQueryHandler.cs
new file mode 100644
index 0000000..28be0bc
--- /dev/null
+++ b/AutobusApi.Application/Routes/Queries/GetRoutesWithPagination/GetRoutesWithPaginationQueryHandler.cs
@@ -0,0 +1,32 @@
+using AutobusApi.Application.Common.Interfaces;
+using AutobusApi.Application.Common.Mappings;
+using AutobusApi.Application.Common.Models;
+using AutoMapper;
+using AutoMapper.QueryableExtensions;
+using MediatR;
+
+namespace AutobusApi.Application.Routes.Queries.GetRoutesWithPagination;
+
+public class GetRoutesWithPaginationQueryHandler : IRequestHandler>
+{
+ private readonly IApplicationDbContext _dbContext;
+ private readonly IMapper _mapper;
+
+ public GetRoutesWithPaginationQueryHandler(
+ IApplicationDbContext dbContext,
+ IMapper mapper)
+ {
+ _dbContext = dbContext;
+ _mapper = mapper;
+ }
+
+ public async Task> Handle(
+ GetRoutesWithPaginationQuery request,
+ CancellationToken cancellationToken)
+ {
+ return await _dbContext.Routes
+ .ProjectTo(_mapper.ConfigurationProvider)
+ .ApplySort(request.Sort)
+ .PaginatedListAsync(request.PageNumber, request.PageSize);
+ }
+}
diff --git a/AutobusApi.Application/Routes/Queries/GetRoutesWithPagination/GetRoutesWithPaginationQueryValidator.cs b/AutobusApi.Application/Routes/Queries/GetRoutesWithPagination/GetRoutesWithPaginationQueryValidator.cs
new file mode 100644
index 0000000..544c7d6
--- /dev/null
+++ b/AutobusApi.Application/Routes/Queries/GetRoutesWithPagination/GetRoutesWithPaginationQueryValidator.cs
@@ -0,0 +1,13 @@
+using FluentValidation;
+
+namespace AutobusApi.Application.Routes.Queries.GetRoutesWithPagination;
+
+public class GetRoutesWithPaginationQueryValidator : AbstractValidator
+{
+ public GetRoutesWithPaginationQueryValidator()
+ {
+ RuleFor(v => v.PageNumber).GreaterThanOrEqualTo(1);
+
+ RuleFor(v => v.PageSize).GreaterThanOrEqualTo(1).LessThanOrEqualTo(50);
+ }
+}
diff --git a/AutobusApi.Application/Routes/Queries/RouteDto.cs b/AutobusApi.Application/Routes/Queries/RouteDto.cs
new file mode 100644
index 0000000..b9e6bad
--- /dev/null
+++ b/AutobusApi.Application/Routes/Queries/RouteDto.cs
@@ -0,0 +1,64 @@
+using AutobusApi.Application.Common.Mappings;
+using AutobusApi.Domain.Entities;
+using AutoMapper;
+
+namespace AutobusApi.Application.Routes.Queries;
+
+public class RouteDto : IMapFrom
+{
+ public int Id { get; set; }
+
+ public List Addresses { get; set; } = null!;
+
+ public void Mapping(Profile profile)
+ {
+ profile.CreateMap()
+ .ForMember(d => d.Addresses, opt => opt.MapFrom(s => s.RouteAddresses));
+ }
+}
+
+public class RouteAddressDto : IMapFrom
+{
+ public int Id { get; set; }
+
+ public string Name { get; set; } = null!;
+
+ public int Order { get; set; }
+
+ public double Latitude { get; set; }
+
+ public double Longitude { get; set; }
+
+ public string VehicleType { get; set; } = null!;
+
+ public int RouteAddressId { get; set; }
+
+ public int CityId { get; set; }
+
+ public string CityName { get; set; } = null!;
+
+ public int RegionId { get; set; }
+
+ public string RegionName { get; set; } = null!;
+
+ public int CountryId { get; set; }
+
+ public string CountryName { get; set; } = null!;
+
+ public void Mapping(Profile profile)
+ {
+ profile.CreateMap