add city entity management

This commit is contained in:
cuqmbr 2025-04-30 17:29:40 +03:00
parent 16457fc2cc
commit 0345f58f7b
Signed by: cuqmbr
GPG Key ID: 0AA446880C766199
44 changed files with 2544 additions and 535 deletions

View File

@ -0,0 +1,39 @@
using cuqmbr.TravelGuide.Application.Common.Mappings;
using cuqmbr.TravelGuide.Domain.Entities;
namespace cuqmbr.TravelGuide.Application.Cities;
public sealed class CityDto : IMapFrom<City>
{
public Guid Uuid { get; set; }
public string Name { get; set; }
public Guid CountryUuid { get; set; }
public string CountryName { get; set; }
public Guid RegionUuid { get; set; }
public string RegionName { get; set; }
public void Mapping(MappingProfile profile)
{
profile.CreateMap<City, CityDto>()
.ForMember(
d => d.Uuid,
opt => opt.MapFrom(s => s.Guid))
.ForMember(
d => d.CountryUuid,
opt => opt.MapFrom(s => s.Region.Country.Guid))
.ForMember(
d => d.CountryName,
opt => opt.MapFrom(s => s.Region.Country.Name))
.ForMember(
d => d.RegionUuid,
opt => opt.MapFrom(s => s.Region.Guid))
.ForMember(
d => d.RegionName,
opt => opt.MapFrom(s => s.Region.Name));
}
}

View File

@ -0,0 +1,10 @@
using MediatR;
namespace cuqmbr.TravelGuide.Application.Cities.Commands.AddCity;
public record AddCityCommand : IRequest<CityDto>
{
public string Name { get; set; }
public Guid RegionUuid { get; set; }
}

View File

@ -0,0 +1,31 @@
using cuqmbr.TravelGuide.Application.Common.Authorization;
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
using cuqmbr.TravelGuide.Application.Common.Models;
using MediatR.Behaviors.Authorization;
namespace cuqmbr.TravelGuide.Application.Cities.Commands.AddCity;
public class AddCityCommandAuthorizer :
AbstractRequestAuthorizer<AddCityCommand>
{
private readonly SessionUserService _sessionUserService;
public AddCityCommandAuthorizer(SessionUserService sessionUserService)
{
_sessionUserService = sessionUserService;
}
public override void BuildPolicy(AddCityCommand request)
{
UseRequirement(new MustBeAuthenticatedRequirement
{
IsAuthenticated= _sessionUserService.IsAuthenticated
});
UseRequirement(new MustBeInRolesRequirement
{
RequiredRoles = [IdentityRole.Administrator],
UserRoles = _sessionUserService.Roles
});
}
}

View File

@ -0,0 +1,61 @@
using MediatR;
using cuqmbr.TravelGuide.Application.Common.Interfaces.Persistence;
using cuqmbr.TravelGuide.Domain.Entities;
using AutoMapper;
using cuqmbr.TravelGuide.Application.Common.Exceptions;
namespace cuqmbr.TravelGuide.Application.Cities.Commands.AddCity;
public class AddCityCommandHandler :
IRequestHandler<AddCityCommand, CityDto>
{
private readonly UnitOfWork _unitOfWork;
private readonly IMapper _mapper;
public AddCityCommandHandler(
UnitOfWork unitOfWork,
IMapper mapper)
{
_unitOfWork = unitOfWork;
_mapper = mapper;
}
public async Task<CityDto> Handle(
AddCityCommand request,
CancellationToken cancellationToken)
{
var entity = await _unitOfWork.CityRepository.GetOneAsync(
e => e.Name == request.Name && e.Region.Guid == request.RegionUuid,
cancellationToken);
if (entity != null)
{
throw new DuplicateEntityException(
"City with given name already exists.");
}
var parentEntity = await _unitOfWork.RegionRepository.GetOneAsync(
e => e.Guid == request.RegionUuid, e => e.Country,
cancellationToken);
if (parentEntity == null)
{
throw new NotFoundException(
$"Parent entity with Guid: {request.RegionUuid} not found.");
}
entity = new City()
{
Name = request.Name,
RegionId = parentEntity.Id
};
entity = await _unitOfWork.CityRepository.AddOneAsync(
entity, cancellationToken);
await _unitOfWork.SaveAsync(cancellationToken);
_unitOfWork.Dispose();
return _mapper.Map<CityDto>(entity);
}
}

View File

@ -0,0 +1,27 @@
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
using FluentValidation;
using Microsoft.Extensions.Localization;
namespace cuqmbr.TravelGuide.Application.Cities.Commands.AddCity;
public class AddCityCommandValidator : AbstractValidator<AddCityCommand>
{
public AddCityCommandValidator(
IStringLocalizer localizer,
CultureService cultureService)
{
RuleFor(v => v.Name)
.NotEmpty()
.WithMessage(localizer["FluentValidation.NotEmpty"])
.MaximumLength(64)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.MaximumLength"],
64));
RuleFor(v => v.RegionUuid)
.NotEmpty()
.WithMessage(localizer["FluentValidation.NotEmpty"]);
}
}

View File

@ -0,0 +1,8 @@
using MediatR;
namespace cuqmbr.TravelGuide.Application.Cities.Commands.DeleteCity;
public record DeleteCityCommand : IRequest
{
public Guid Uuid { get; set; }
}

View File

@ -0,0 +1,31 @@
using cuqmbr.TravelGuide.Application.Common.Authorization;
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
using cuqmbr.TravelGuide.Application.Common.Models;
using MediatR.Behaviors.Authorization;
namespace cuqmbr.TravelGuide.Application.Cities.Commands.DeleteCity;
public class DeleteCityCommandAuthorizer :
AbstractRequestAuthorizer<DeleteCityCommand>
{
private readonly SessionUserService _sessionUserService;
public DeleteCityCommandAuthorizer(SessionUserService sessionUserService)
{
_sessionUserService = sessionUserService;
}
public override void BuildPolicy(DeleteCityCommand request)
{
UseRequirement(new MustBeAuthenticatedRequirement
{
IsAuthenticated= _sessionUserService.IsAuthenticated
});
UseRequirement(new MustBeInRolesRequirement
{
RequiredRoles = [IdentityRole.Administrator],
UserRoles = _sessionUserService.Roles
});
}
}

View File

@ -0,0 +1,34 @@
using MediatR;
using cuqmbr.TravelGuide.Application.Common.Interfaces.Persistence;
using cuqmbr.TravelGuide.Application.Common.Exceptions;
namespace cuqmbr.TravelGuide.Application.Cities.Commands.DeleteCity;
public class DeleteCityCommandHandler : IRequestHandler<DeleteCityCommand>
{
private readonly UnitOfWork _unitOfWork;
public DeleteCityCommandHandler(UnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public async Task Handle(
DeleteCityCommand request,
CancellationToken cancellationToken)
{
var entity = await _unitOfWork.CityRepository.GetOneAsync(
e => e.Guid == request.Uuid, cancellationToken);
if (entity == null)
{
throw new NotFoundException();
}
await _unitOfWork.CityRepository.DeleteOneAsync(
entity, cancellationToken);
await _unitOfWork.SaveAsync(cancellationToken);
_unitOfWork.Dispose();
}
}

View File

@ -0,0 +1,14 @@
using FluentValidation;
using Microsoft.Extensions.Localization;
namespace cuqmbr.TravelGuide.Application.Cities.Commands.DeleteCity;
public class DeleteCityCommandValidator : AbstractValidator<DeleteCityCommand>
{
public DeleteCityCommandValidator(IStringLocalizer localizer)
{
RuleFor(v => v.Uuid)
.NotEmpty()
.WithMessage(localizer["FluentValidation.NotEmpty"]);
}
}

View File

@ -0,0 +1,12 @@
using MediatR;
namespace cuqmbr.TravelGuide.Application.Cities.Commands.UpdateCity;
public record UpdateCityCommand : IRequest<CityDto>
{
public Guid Uuid { get; set; }
public string Name { get; set; }
public Guid RegionUuid { get; set; }
}

View File

@ -0,0 +1,31 @@
using cuqmbr.TravelGuide.Application.Common.Authorization;
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
using cuqmbr.TravelGuide.Application.Common.Models;
using MediatR.Behaviors.Authorization;
namespace cuqmbr.TravelGuide.Application.Cities.Commands.UpdateCity;
public class UpdateCityCommandAuthorizer :
AbstractRequestAuthorizer<UpdateCityCommand>
{
private readonly SessionUserService _sessionUserService;
public UpdateCityCommandAuthorizer(SessionUserService sessionUserService)
{
_sessionUserService = sessionUserService;
}
public override void BuildPolicy(UpdateCityCommand request)
{
UseRequirement(new MustBeAuthenticatedRequirement
{
IsAuthenticated= _sessionUserService.IsAuthenticated
});
UseRequirement(new MustBeInRolesRequirement
{
RequiredRoles = [IdentityRole.Administrator],
UserRoles = _sessionUserService.Roles
});
}
}

View File

@ -0,0 +1,55 @@
using MediatR;
using cuqmbr.TravelGuide.Application.Common.Interfaces.Persistence;
using AutoMapper;
using cuqmbr.TravelGuide.Application.Common.Exceptions;
namespace cuqmbr.TravelGuide.Application.Cities.Commands.UpdateCity;
public class UpdateCityCommandHandler :
IRequestHandler<UpdateCityCommand, CityDto>
{
private readonly UnitOfWork _unitOfWork;
private readonly IMapper _mapper;
public UpdateCityCommandHandler(
UnitOfWork unitOfWork,
IMapper mapper)
{
_unitOfWork = unitOfWork;
_mapper = mapper;
}
public async Task<CityDto> Handle(
UpdateCityCommand request,
CancellationToken cancellationToken)
{
var entity = await _unitOfWork.CityRepository.GetOneAsync(
e => e.Guid == request.Uuid, e => e.Region.Country,
cancellationToken);
if (entity == null)
{
throw new NotFoundException();
}
var parentEntity = await _unitOfWork.RegionRepository.GetOneAsync(
e => e.Guid == request.RegionUuid, cancellationToken);
if (parentEntity == null)
{
throw new NotFoundException(
$"Parent entity with Guid: {request.RegionUuid} not found.");
}
entity.Name = request.Name;
entity.RegionId = parentEntity.Id;
entity = await _unitOfWork.CityRepository.UpdateOneAsync(
entity, cancellationToken);
await _unitOfWork.SaveAsync(cancellationToken);
_unitOfWork.Dispose();
return _mapper.Map<CityDto>(entity);
}
}

View File

@ -0,0 +1,31 @@
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
using FluentValidation;
using Microsoft.Extensions.Localization;
namespace cuqmbr.TravelGuide.Application.Cities.Commands.UpdateCity;
public class UpdateCityCommandValidator : AbstractValidator<UpdateCityCommand>
{
public UpdateCityCommandValidator(
IStringLocalizer localizer,
CultureService cultureService)
{
RuleFor(v => v.Uuid)
.NotEmpty()
.WithMessage(localizer["FluentValidation.NotEmpty"]);
RuleFor(v => v.Name)
.NotEmpty()
.WithMessage(localizer["FluentValidation.NotEmpty"])
.MaximumLength(64)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.MaximumLength"],
64));
RuleFor(v => v.RegionUuid)
.NotEmpty()
.WithMessage(localizer["FluentValidation.NotEmpty"]);
}
}

View File

@ -0,0 +1,19 @@
using cuqmbr.TravelGuide.Application.Common.Models;
using MediatR;
namespace cuqmbr.TravelGuide.Application.Cities.Queries.GetCitiesPage;
public record GetCitiesPageQuery : IRequest<PaginatedList<CityDto>>
{
public int PageNumber { get; set; } = 1;
public int PageSize { get; set; } = 10;
public string Search { get; set; } = String.Empty;
public string Sort { get; set; } = String.Empty;
public Guid? CountryUuid { get; set; }
public Guid? RegionUuid { get; set; }
}

View File

@ -0,0 +1,31 @@
using cuqmbr.TravelGuide.Application.Common.Authorization;
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
using cuqmbr.TravelGuide.Application.Common.Models;
using MediatR.Behaviors.Authorization;
namespace cuqmbr.TravelGuide.Application.Cities.Queries.GetCitiesPage;
public class GetCitiesPageQueryAuthorizer :
AbstractRequestAuthorizer<GetCitiesPageQuery>
{
private readonly SessionUserService _sessionUserService;
public GetCitiesPageQueryAuthorizer(SessionUserService sessionUserService)
{
_sessionUserService = sessionUserService;
}
public override void BuildPolicy(GetCitiesPageQuery request)
{
UseRequirement(new MustBeAuthenticatedRequirement
{
IsAuthenticated= _sessionUserService.IsAuthenticated
});
UseRequirement(new MustBeInRolesRequirement
{
RequiredRoles = [IdentityRole.Administrator],
UserRoles = _sessionUserService.Roles
});
}
}

View File

@ -0,0 +1,55 @@
using MediatR;
using cuqmbr.TravelGuide.Application.Common.Interfaces.Persistence;
using AutoMapper;
using cuqmbr.TravelGuide.Application.Common.Models;
using cuqmbr.TravelGuide.Application.Common.Extensions;
namespace cuqmbr.TravelGuide.Application.Cities.Queries.GetCitiesPage;
public class GetCitiesPageQueryHandler :
IRequestHandler<GetCitiesPageQuery, PaginatedList<CityDto>>
{
private readonly UnitOfWork _unitOfWork;
private readonly IMapper _mapper;
public GetCitiesPageQueryHandler(
UnitOfWork unitOfWork,
IMapper mapper)
{
_unitOfWork = unitOfWork;
_mapper = mapper;
}
public async Task<PaginatedList<CityDto>> Handle(
GetCitiesPageQuery request,
CancellationToken cancellationToken)
{
var paginatedList = await _unitOfWork.CityRepository.GetPageAsync(
e =>
(e.Name.ToLower().Contains(request.Search.ToLower()) ||
e.Region.Name.ToLower().Contains(request.Search.ToLower()) ||
e.Region.Country.Name.ToLower().Contains(request.Search.ToLower())) &&
(request.RegionUuid != null
? e.Region.Guid == request.RegionUuid
: true) &&
(request.CountryUuid != null
? e.Region.Country.Guid == request.CountryUuid
: true),
e => e.Region.Country,
request.PageNumber, request.PageSize,
cancellationToken);
var mappedItems = _mapper
.ProjectTo<CityDto>(paginatedList.Items.AsQueryable());
mappedItems = QueryableExtension<CityDto>
.ApplySort(mappedItems, request.Sort);
_unitOfWork.Dispose();
return new PaginatedList<CityDto>(
mappedItems.ToList(),
paginatedList.TotalCount, request.PageNumber,
request.PageSize);
}
}

View File

@ -0,0 +1,43 @@
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
using FluentValidation;
using Microsoft.Extensions.Localization;
namespace cuqmbr.TravelGuide.Application.Cities.Queries.GetCitiesPage;
public class GetCitiesPageQueryValidator : AbstractValidator<GetCitiesPageQuery>
{
public GetCitiesPageQueryValidator(
IStringLocalizer localizer,
CultureService cultureService)
{
RuleFor(v => v.PageNumber)
.GreaterThanOrEqualTo(1)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.GreaterThanOrEqualTo"],
1));
RuleFor(v => v.PageSize)
.GreaterThanOrEqualTo(1)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.GreaterThanOrEqualTo"],
1))
.LessThanOrEqualTo(50)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.LessThanOrEqualTo"],
50));
RuleFor(v => v.Search)
.MaximumLength(64)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.MaximumLength"],
64));
}
}

View File

@ -0,0 +1,8 @@
using MediatR;
namespace cuqmbr.TravelGuide.Application.Cities.Queries.GetCity;
public record GetCityQuery : IRequest<CityDto>
{
public Guid Uuid { get; set; }
}

View File

@ -0,0 +1,31 @@
using cuqmbr.TravelGuide.Application.Common.Authorization;
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
using cuqmbr.TravelGuide.Application.Common.Models;
using MediatR.Behaviors.Authorization;
namespace cuqmbr.TravelGuide.Application.Cities.Queries.GetCity;
public class GetCityQueryAuthorizer :
AbstractRequestAuthorizer<GetCityQuery>
{
private readonly SessionUserService _sessionUserService;
public GetCityQueryAuthorizer(SessionUserService sessionUserService)
{
_sessionUserService = sessionUserService;
}
public override void BuildPolicy(GetCityQuery request)
{
UseRequirement(new MustBeAuthenticatedRequirement
{
IsAuthenticated= _sessionUserService.IsAuthenticated
});
UseRequirement(new MustBeInRolesRequirement
{
RequiredRoles = [IdentityRole.Administrator],
UserRoles = _sessionUserService.Roles
});
}
}

View File

@ -0,0 +1,39 @@
using MediatR;
using cuqmbr.TravelGuide.Application.Common.Interfaces.Persistence;
using cuqmbr.TravelGuide.Application.Common.Exceptions;
using AutoMapper;
namespace cuqmbr.TravelGuide.Application.Cities.Queries.GetCity;
public class GetCityQueryHandler :
IRequestHandler<GetCityQuery, CityDto>
{
private readonly UnitOfWork _unitOfWork;
private readonly IMapper _mapper;
public GetCityQueryHandler(
UnitOfWork unitOfWork,
IMapper mapper)
{
_unitOfWork = unitOfWork;
_mapper = mapper;
}
public async Task<CityDto> Handle(
GetCityQuery request,
CancellationToken cancellationToken)
{
var entity = await _unitOfWork.CityRepository.GetOneAsync(
e => e.Guid == request.Uuid, e => e.Region.Country,
cancellationToken);
_unitOfWork.Dispose();
if (entity == null)
{
throw new NotFoundException();
}
return _mapper.Map<CityDto>(entity);
}
}

View File

@ -0,0 +1,14 @@
using FluentValidation;
using Microsoft.Extensions.Localization;
namespace cuqmbr.TravelGuide.Application.Cities.Queries.GetCity;
public class GetCityQueryValidator : AbstractValidator<GetCityQuery>
{
public GetCityQueryValidator(IStringLocalizer localizer)
{
RuleFor(v => v.Uuid)
.NotEmpty()
.WithMessage(localizer["FluentValidation.NotEmpty"]);
}
}

View File

@ -0,0 +1,8 @@
namespace cuqmbr.TravelGuide.Application.Cities.ViewModels;
public sealed class GetCitiesPageFilterViewModel
{
public Guid? CountryUuid { get; set; }
public Guid? RegionUuid { get; set; }
}

View File

@ -0,0 +1,6 @@
using cuqmbr.TravelGuide.Domain.Entities;
namespace cuqmbr.TravelGuide.Application.Common.Interfaces
.Persistence.Repositories;
public interface CityRepository : BaseRepository<City> { }

View File

@ -5,8 +5,12 @@ namespace cuqmbr.TravelGuide.Application.Common.Interfaces.Persistence;
public interface UnitOfWork : IDisposable
{
CountryRepository CountryRepository { get; }
RegionRepository RegionRepository { get; }
CityRepository CityRepository { get; }
int Save();
Task<int> SaveAsync(CancellationToken cancellationToken);
}

View File

@ -72,9 +72,12 @@ public static class Configuration
$"{configuration.Type} datastore is not supported.");
}
// using var serviceProvider = services.BuildServiceProvider();
// var unitOfWork = serviceProvider.GetService<UnitOfWork>();
// DbSeeder.Seed(unitOfWork);
if (configuration.Seed)
{
using var serviceProvider = services.BuildServiceProvider();
var unitOfWork = serviceProvider.GetService<UnitOfWork>();
DbSeeder.Seed(unitOfWork);
}
return services;
}

View File

@ -0,0 +1,176 @@
using Microsoft.AspNetCore.Mvc;
using Swashbuckle.AspNetCore.Annotations;
using cuqmbr.TravelGuide.Application.Common.Models;
using cuqmbr.TravelGuide.Application.Common.ViewModels;
using cuqmbr.TravelGuide.Application.Cities;
using cuqmbr.TravelGuide.Application.Cities.Commands.AddCity;
using cuqmbr.TravelGuide.Application.Cities.Queries.GetCitiesPage;
using cuqmbr.TravelGuide.Application.Cities.Queries.GetCity;
using cuqmbr.TravelGuide.Application.Cities.Commands.UpdateCity;
using cuqmbr.TravelGuide.Application.Cities.Commands.DeleteCity;
using cuqmbr.TravelGuide.Application.Cities.ViewModels;
namespace cuqmbr.TravelGuide.HttpApi.Controllers;
[Route("cities")]
public class CitiesController : ControllerBase
{
[HttpPost]
[SwaggerOperation("Create a city")]
[SwaggerResponse(
StatusCodes.Status201Created, "Object successfuly created",
typeof(CityDto))]
[SwaggerResponse(
StatusCodes.Status400BadRequest, "Object already exists",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status400BadRequest, "Input data validation error",
typeof(HttpValidationProblemDetails))]
[SwaggerResponse(
StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status403Forbidden,
"Not enough privileges to perform an action",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status404NotFound, "Parent object not found",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status500InternalServerError, "Internal server error",
typeof(ProblemDetails))]
public async Task<ActionResult<CityDto>> Add(
[FromBody] AddCityCommand command,
CancellationToken cancellationToken)
{
return StatusCode(
StatusCodes.Status201Created,
await Mediator.Send(command, cancellationToken));
}
[HttpGet]
[SwaggerOperation("Get a list of all cities")]
[SwaggerResponse(
StatusCodes.Status200OK, "Request successful",
typeof(PaginatedList<CityDto>))]
[SwaggerResponse(
StatusCodes.Status400BadRequest, "Input data validation error",
typeof(HttpValidationProblemDetails))]
[SwaggerResponse(
StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status403Forbidden,
"Not enough privileges to perform an action",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status500InternalServerError, "Internal server error",
typeof(ProblemDetails))]
public async Task<PaginatedList<CityDto>> GetPage(
[FromQuery] PageQuery pageQuery, [FromQuery] SearchQuery searchQuery,
[FromQuery] SortQuery sortQuery,
[FromQuery] GetCitiesPageFilterViewModel filterQuery,
CancellationToken cancellationToken)
{
return await Mediator.Send(
new GetCitiesPageQuery()
{
PageNumber = pageQuery.PageNumber,
PageSize = pageQuery.PageSize,
Search = searchQuery.Search,
Sort = sortQuery.Sort,
CountryUuid = filterQuery.CountryUuid,
RegionUuid = filterQuery.RegionUuid
},
cancellationToken);
}
[HttpGet("{uuid:guid}")]
[SwaggerOperation("Get city by uuid")]
[SwaggerResponse(
StatusCodes.Status200OK, "Request successful", typeof(CityDto))]
[SwaggerResponse(
StatusCodes.Status400BadRequest, "Input data validation error",
typeof(HttpValidationProblemDetails))]
[SwaggerResponse(
StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status403Forbidden,
"Not enough privileges to perform an action",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status404NotFound, "Object not found", typeof(CityDto))]
[SwaggerResponse(
StatusCodes.Status500InternalServerError, "Internal server error",
typeof(ProblemDetails))]
public async Task<CityDto> Get(
[FromRoute] Guid uuid,
CancellationToken cancellationToken)
{
return await Mediator.Send(new GetCityQuery() { Uuid = uuid },
cancellationToken);
}
[HttpPut("{uuid:guid}")]
[SwaggerOperation("Update city")]
[SwaggerResponse(
StatusCodes.Status200OK, "Request successful", typeof(CityDto))]
[SwaggerResponse(
StatusCodes.Status400BadRequest, "Object already exists",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status400BadRequest, "Input data validation error",
typeof(HttpValidationProblemDetails))]
[SwaggerResponse(
StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status403Forbidden,
"Not enough privileges to perform an action",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status404NotFound, "Object not found", typeof(CityDto))]
[SwaggerResponse(
StatusCodes.Status404NotFound, "Parent object not found",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status500InternalServerError, "Internal server error",
typeof(ProblemDetails))]
public async Task<CityDto> Update(
[FromRoute] Guid uuid,
[FromBody] UpdateCityCommand command,
CancellationToken cancellationToken)
{
command.Uuid = uuid;
return await Mediator.Send(command, cancellationToken);
}
[HttpDelete("{uuid:guid}")]
[SwaggerOperation("Delete city")]
[SwaggerResponse(StatusCodes.Status204NoContent, "Request successful")]
[SwaggerResponse(
StatusCodes.Status400BadRequest, "Input data validation error",
typeof(HttpValidationProblemDetails))]
[SwaggerResponse(
StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status403Forbidden,
"Not enough privileges to perform an action",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status404NotFound, "Object not found", typeof(CityDto))]
[SwaggerResponse(
StatusCodes.Status500InternalServerError, "Internal server error",
typeof(ProblemDetails))]
public async Task<IActionResult> Delete(
[FromRoute] Guid uuid,
CancellationToken cancellationToken)
{
await Mediator.Send(
new DeleteCityCommand() { Uuid = uuid },
cancellationToken);
return StatusCode(StatusCodes.Status204NoContent);
}
}

View File

@ -53,9 +53,6 @@ public class RegionsController : ControllerBase
[SwaggerResponse(
StatusCodes.Status200OK, "Request successful",
typeof(PaginatedList<RegionDto>))]
[SwaggerResponse(
StatusCodes.Status400BadRequest, "Object already exists",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status400BadRequest, "Input data validation error",
typeof(HttpValidationProblemDetails))]
@ -91,9 +88,6 @@ public class RegionsController : ControllerBase
[SwaggerOperation("Get region by uuid")]
[SwaggerResponse(
StatusCodes.Status200OK, "Request successful", typeof(RegionDto))]
[SwaggerResponse(
StatusCodes.Status400BadRequest, "Object already exists",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status400BadRequest, "Input data validation error",
typeof(HttpValidationProblemDetails))]

View File

@ -1,5 +1,6 @@
using System.Reflection;
// using System.Reflection;
// using cuqmbr.TravelGuide.Domain.Enums;
using cuqmbr.TravelGuide.Domain.Entities;
using Microsoft.EntityFrameworkCore;
// using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
@ -10,6 +11,10 @@ public class InMemoryDbContext : DbContext
public InMemoryDbContext(DbContextOptions<InMemoryDbContext> options)
: base(options) { }
public DbSet<Country> Countries { get => Set<Country>(); }
public DbSet<Region> Regions { get => Set<Region>(); }
public DbSet<City> Cities { get => Set<City>(); }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
@ -18,11 +23,6 @@ public class InMemoryDbContext : DbContext
// "vehicle_type",
// VehicleType.Enumerations.Select(e => e.Value.Name).ToArray());
//
builder
.ApplyConfigurationsFromAssembly(
Assembly.GetExecutingAssembly(),
t => t.Namespace ==
"cuqmbr.TravelGuide.Persistence.InMemory.Configurations");
}
protected override void ConfigureConventions(

View File

@ -15,11 +15,15 @@ public sealed class InMemoryUnitOfWork : UnitOfWork
CountryRepository = new InMemoryCountryRepository(_dbContext);
RegionRepository = new InMemoryRegionRepository(_dbContext);
CityRepository = new InMemoryCityRepository(_dbContext);
}
public CountryRepository CountryRepository { get; init; }
public RegionRepository RegionRepository { get; init; }
public CityRepository CityRepository { get; init; }
public int Save()
{
return _dbContext.SaveChanges();

View File

@ -0,0 +1,11 @@
using cuqmbr.TravelGuide.Application.Common.Interfaces.Persistence.Repositories;
using cuqmbr.TravelGuide.Domain.Entities;
namespace cuqmbr.TravelGuide.Persistence.InMemory.Repositories;
public sealed class InMemoryCityRepository :
InMemoryBaseRepository<City>, CityRepository
{
public InMemoryCityRepository(InMemoryDbContext dbContext)
: base(dbContext) { }
}

View File

@ -1,55 +0,0 @@
using cuqmbr.TravelGuide.Domain.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace cuqmbr.TravelGuide.Persistence.PostgreSql.Configurations;
public class AddressConfiguration : BaseConfiguration<Address>
{
public override void Configure(EntityTypeBuilder<Address> builder)
{
builder
.ToTable("addresses");
base.Configure(builder);
builder
.Property(a => a.Name)
.HasColumnName("name")
.HasColumnType("varchar(128)")
.IsRequired(true);
// builder
// .Property(a => a.VehicleType)
// .HasColumnName("vehicle_type")
// .HasColumnType($"{PostgreSqlDbContext.DefaultSchema}.vehicle_type")
// .HasConversion<VehicleTypeConverter>()
// .IsRequired(true);
builder
.Property(a => a.CityId)
.HasColumnName("city_id")
.HasColumnType("bigint")
.IsRequired(true);
builder
.HasOne(a => a.City)
.WithMany(c => c.Addresses)
.HasForeignKey(a => a.CityId)
.HasConstraintName(
"fk_" +
$"{builder.Metadata.GetTableName()}_" +
$"{builder.Property(a => a.CityId).Metadata.GetColumnName()}")
.OnDelete(DeleteBehavior.Cascade);
builder
.HasIndex(a => a.CityId)
.HasDatabaseName(
"ix_" +
$"{builder.Metadata.GetTableName()}_" +
$"{builder.Property(a => a.CityId).Metadata.GetColumnName()}")
.IsUnique();
}
}

View File

@ -36,13 +36,5 @@ public class CityConfiguration : BaseConfiguration<City>
$"{builder.Metadata.GetTableName()}_" +
$"{builder.Property(c => c.RegionId).Metadata.GetColumnName()}")
.OnDelete(DeleteBehavior.Cascade);
builder
.HasIndex(c => c.RegionId)
.HasDatabaseName(
"ix_" +
$"{builder.Metadata.GetTableName()}_" +
$"{builder.Property(c => c.RegionId).Metadata.GetColumnName()}")
.IsUnique();
}
}

View File

@ -1,83 +0,0 @@
using cuqmbr.TravelGuide.Domain.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace cuqmbr.TravelGuide.Persistence.PostgreSql.Configurations;
public class RouteAddressConfiguration : BaseConfiguration<RouteAddress>
{
public override void Configure(EntityTypeBuilder<RouteAddress> builder)
{
builder
.ToTable("route_addresses");
base.Configure(builder);
builder
.Property(ra => ra.Order)
.HasColumnName("order")
.HasColumnType("smallint")
.IsRequired(true);
builder
.Property(ra => ra.AddressId)
.HasColumnName("address_id")
.HasColumnType("bigint")
.IsRequired(true);
builder
.HasOne(ra => ra.Address)
.WithMany(a => a.AddressRoutes)
.HasForeignKey(ra => ra.AddressId)
.HasConstraintName(
"fk_" +
$"{builder.Metadata.GetTableName()}_" +
$"{builder.Property(ra => ra.AddressId).Metadata.GetColumnName()}")
.OnDelete(DeleteBehavior.Cascade);
builder
.HasIndex(ra => ra.AddressId)
.HasDatabaseName(
"ix_" +
$"{builder.Metadata.GetTableName()}_" +
$"{builder.Property(ra => ra.AddressId).Metadata.GetColumnName()}")
.IsUnique();
builder
.Property(ra => ra.RouteId)
.HasColumnName("route_id")
.HasColumnType("bigint")
.IsRequired(true);
builder
.HasOne(ra => ra.Route)
.WithMany(a => a.RouteAddresses)
.HasForeignKey(ra => ra.RouteId)
.HasConstraintName(
"fk_" +
$"{builder.Metadata.GetTableName()}_" +
$"{builder.Property(ra => ra.RouteId).Metadata.GetColumnName()}")
.OnDelete(DeleteBehavior.Cascade);
builder
.HasIndex(ra => ra.RouteId)
.HasDatabaseName(
"ix_" +
$"{builder.Metadata.GetTableName()}_" +
$"{builder.Property(ra => ra.RouteId).Metadata.GetColumnName()}")
.IsUnique();
builder
.HasAlternateKey(ra => new { ra.AddressId, ra.RouteId, ra.Order })
.HasName(
"altk_" +
$"{builder.Metadata.GetTableName()}_" +
$"{builder.Property(ra => ra.AddressId).Metadata.GetColumnName()}_" +
$"{builder.Property(ra => ra.RouteId).Metadata.GetColumnName()}_" +
$"{builder.Property(ra => ra.Order).Metadata.GetColumnName()}");
}
}

View File

@ -1,30 +0,0 @@
using cuqmbr.TravelGuide.Domain.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace cuqmbr.TravelGuide.Persistence.PostgreSql.Configurations;
public class RouteConfiguration : BaseConfiguration<Route>
{
public override void Configure(EntityTypeBuilder<Route> builder)
{
builder
.ToTable("routes");
base.Configure(builder);
builder
.Property(r => r.Name)
.HasColumnName("name")
.HasColumnType("varchar(64)")
.IsRequired(true);
// builder
// .Property(r => r.VehicleType)
// .HasColumnName("vehicle_type")
// .HasColumnType($"{PostgreSqlDbContext.DefaultSchema}.vehicle_type")
// .HasConversion<VehicleTypeConverter>()
// .IsRequired(true);
}
}

View File

@ -12,8 +12,8 @@ using cuqmbr.TravelGuide.Persistence.PostgreSql;
namespace Persistence.PostgreSql.Migrations
{
[DbContext(typeof(PostgreSqlDbContext))]
[Migration("20250427160059_Initial_migration")]
partial class Initial_migration
[Migration("20250430113338_Countries_Regions_Cities")]
partial class Countries_Regions_Cities
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
@ -27,65 +27,35 @@ namespace Persistence.PostgreSql.Migrations
NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "vehicle_type", new[] { "bus", "train", "aircraft" });
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.HasSequence("addresses_id_sequence");
modelBuilder.HasSequence("cities_id_sequence");
modelBuilder.HasSequence("countries_id_sequence");
modelBuilder.HasSequence("regions_id_sequence");
modelBuilder.HasSequence("route_addresses_id_sequence");
modelBuilder.HasSequence("routes_id_sequence");
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Address", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasColumnName("id")
.HasDefaultValueSql("nextval('application.addresses_id_sequence')");
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "addresses_id_sequence");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<long>("CityId")
.HasColumnType("bigint")
.HasColumnName("city_id");
.HasColumnType("bigint");
b.Property<Guid>("Guid")
.HasColumnType("uuid")
.HasColumnName("uuid");
.HasColumnType("uuid");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("varchar(128)")
.HasColumnName("name");
.HasColumnType("text");
b.Property<string>("VehicleType")
.IsRequired()
.HasColumnType("application.vehicle_type")
.HasColumnName("vehicle_type");
b.HasKey("Id");
b.HasKey("Id")
.HasName("pk_addresses");
b.HasIndex("CityId");
b.HasAlternateKey("Guid")
.HasName("altk_addresses_Guid");
b.HasIndex("CityId")
.IsUnique()
.HasDatabaseName("ix_addresses_city_id");
b.HasIndex("Guid")
.IsUnique()
.HasDatabaseName("ix_addresses_uuid");
b.HasIndex("Id")
.IsUnique()
.HasDatabaseName("ix_addresses_id");
b.ToTable("addresses", "application");
b.ToTable("Address", "application");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.City", b =>
@ -125,9 +95,7 @@ namespace Persistence.PostgreSql.Migrations
.IsUnique()
.HasDatabaseName("ix_cities_id");
b.HasIndex("RegionId")
.IsUnique()
.HasDatabaseName("ix_cities_region_id");
b.HasIndex("RegionId");
b.ToTable("cities", "application");
});
@ -214,95 +182,49 @@ namespace Persistence.PostgreSql.Migrations
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasColumnName("id")
.HasDefaultValueSql("nextval('application.routes_id_sequence')");
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "routes_id_sequence");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<Guid>("Guid")
.HasColumnType("uuid")
.HasColumnName("uuid");
.HasColumnType("uuid");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("varchar(64)")
.HasColumnName("name");
.HasColumnType("text");
b.Property<string>("VehicleType")
.IsRequired()
.HasColumnType("application.vehicle_type")
.HasColumnName("vehicle_type");
b.HasKey("Id");
b.HasKey("Id")
.HasName("pk_routes");
b.HasAlternateKey("Guid")
.HasName("altk_routes_Guid");
b.HasIndex("Guid")
.IsUnique()
.HasDatabaseName("ix_routes_uuid");
b.HasIndex("Id")
.IsUnique()
.HasDatabaseName("ix_routes_id");
b.ToTable("routes", "application");
b.ToTable("Route", "application");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.RouteAddress", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasColumnName("id")
.HasDefaultValueSql("nextval('application.route_addresses_id_sequence')");
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "route_addresses_id_sequence");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<long>("AddressId")
.HasColumnType("bigint")
.HasColumnName("address_id");
.HasColumnType("bigint");
b.Property<Guid>("Guid")
.HasColumnType("uuid")
.HasColumnName("uuid");
.HasColumnType("uuid");
b.Property<short>("Order")
.HasColumnType("smallint")
.HasColumnName("order");
.HasColumnType("smallint");
b.Property<long>("RouteId")
.HasColumnType("bigint")
.HasColumnName("route_id");
.HasColumnType("bigint");
b.HasKey("Id")
.HasName("pk_route_addresses");
b.HasKey("Id");
b.HasAlternateKey("Guid")
.HasName("altk_route_addresses_Guid");
b.HasIndex("AddressId");
b.HasAlternateKey("AddressId", "RouteId", "Order")
.HasName("altk_route_addresses_address_id_route_id_order");
b.HasIndex("RouteId");
b.HasIndex("AddressId")
.IsUnique()
.HasDatabaseName("ix_route_addresses_address_id");
b.HasIndex("Guid")
.IsUnique()
.HasDatabaseName("ix_route_addresses_uuid");
b.HasIndex("Id")
.IsUnique()
.HasDatabaseName("ix_route_addresses_id");
b.HasIndex("RouteId")
.IsUnique()
.HasDatabaseName("ix_route_addresses_route_id");
b.ToTable("route_addresses", "application");
b.ToTable("RouteAddress", "application");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Address", b =>
@ -311,8 +233,7 @@ namespace Persistence.PostgreSql.Migrations
.WithMany("Addresses")
.HasForeignKey("CityId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_addresses_city_id");
.IsRequired();
b.Navigation("City");
});
@ -347,15 +268,13 @@ namespace Persistence.PostgreSql.Migrations
.WithMany("AddressRoutes")
.HasForeignKey("AddressId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_route_addresses_address_id");
.IsRequired();
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Route", "Route")
.WithMany("RouteAddresses")
.HasForeignKey("RouteId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_route_addresses_route_id");
.IsRequired();
b.Navigation("Address");

View File

@ -1,12 +1,13 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Persistence.PostgreSql.Migrations
{
/// <inheritdoc />
public partial class Initial_migration : Migration
public partial class Countries_Regions_Cities : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
@ -17,10 +18,6 @@ namespace Persistence.PostgreSql.Migrations
migrationBuilder.AlterDatabase()
.Annotation("Npgsql:Enum:vehicle_type", "bus,train,aircraft");
migrationBuilder.CreateSequence(
name: "addresses_id_sequence",
schema: "application");
migrationBuilder.CreateSequence(
name: "cities_id_sequence",
schema: "application");
@ -33,14 +30,6 @@ namespace Persistence.PostgreSql.Migrations
name: "regions_id_sequence",
schema: "application");
migrationBuilder.CreateSequence(
name: "route_addresses_id_sequence",
schema: "application");
migrationBuilder.CreateSequence(
name: "routes_id_sequence",
schema: "application");
migrationBuilder.CreateTable(
name: "countries",
schema: "application",
@ -57,19 +46,18 @@ namespace Persistence.PostgreSql.Migrations
});
migrationBuilder.CreateTable(
name: "routes",
name: "Route",
schema: "application",
columns: table => new
{
id = table.Column<long>(type: "bigint", nullable: false, defaultValueSql: "nextval('application.routes_id_sequence')"),
name = table.Column<string>(type: "varchar(64)", nullable: false),
vehicle_type = table.Column<string>(type: "application.vehicle_type", nullable: false),
uuid = table.Column<Guid>(type: "uuid", nullable: false)
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
Name = table.Column<string>(type: "text", nullable: false),
Guid = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_routes", x => x.id);
table.UniqueConstraint("altk_routes_Guid", x => x.uuid);
table.PrimaryKey("PK_Route", x => x.Id);
});
migrationBuilder.CreateTable(
@ -119,23 +107,22 @@ namespace Persistence.PostgreSql.Migrations
});
migrationBuilder.CreateTable(
name: "addresses",
name: "Address",
schema: "application",
columns: table => new
{
id = table.Column<long>(type: "bigint", nullable: false, defaultValueSql: "nextval('application.addresses_id_sequence')"),
name = table.Column<string>(type: "varchar(128)", nullable: false),
vehicle_type = table.Column<string>(type: "application.vehicle_type", nullable: false),
city_id = table.Column<long>(type: "bigint", nullable: false),
uuid = table.Column<Guid>(type: "uuid", nullable: false)
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
Name = table.Column<string>(type: "text", nullable: false),
CityId = table.Column<long>(type: "bigint", nullable: false),
Guid = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_addresses", x => x.id);
table.UniqueConstraint("altk_addresses_Guid", x => x.uuid);
table.PrimaryKey("PK_Address", x => x.Id);
table.ForeignKey(
name: "fk_addresses_city_id",
column: x => x.city_id,
name: "FK_Address_cities_CityId",
column: x => x.CityId,
principalSchema: "application",
principalTable: "cities",
principalColumn: "id",
@ -143,57 +130,41 @@ namespace Persistence.PostgreSql.Migrations
});
migrationBuilder.CreateTable(
name: "route_addresses",
name: "RouteAddress",
schema: "application",
columns: table => new
{
id = table.Column<long>(type: "bigint", nullable: false, defaultValueSql: "nextval('application.route_addresses_id_sequence')"),
order = table.Column<short>(type: "smallint", nullable: false),
address_id = table.Column<long>(type: "bigint", nullable: false),
route_id = table.Column<long>(type: "bigint", nullable: false),
uuid = table.Column<Guid>(type: "uuid", nullable: false)
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
Order = table.Column<short>(type: "smallint", nullable: false),
AddressId = table.Column<long>(type: "bigint", nullable: false),
RouteId = table.Column<long>(type: "bigint", nullable: false),
Guid = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_route_addresses", x => x.id);
table.UniqueConstraint("altk_route_addresses_address_id_route_id_order", x => new { x.address_id, x.route_id, x.order });
table.UniqueConstraint("altk_route_addresses_Guid", x => x.uuid);
table.PrimaryKey("PK_RouteAddress", x => x.Id);
table.ForeignKey(
name: "fk_route_addresses_address_id",
column: x => x.address_id,
name: "FK_RouteAddress_Address_AddressId",
column: x => x.AddressId,
principalSchema: "application",
principalTable: "addresses",
principalColumn: "id",
principalTable: "Address",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_route_addresses_route_id",
column: x => x.route_id,
name: "FK_RouteAddress_Route_RouteId",
column: x => x.RouteId,
principalSchema: "application",
principalTable: "routes",
principalColumn: "id",
principalTable: "Route",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "ix_addresses_city_id",
name: "IX_Address_CityId",
schema: "application",
table: "addresses",
column: "city_id",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_addresses_id",
schema: "application",
table: "addresses",
column: "id",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_addresses_uuid",
schema: "application",
table: "addresses",
column: "uuid",
unique: true);
table: "Address",
column: "CityId");
migrationBuilder.CreateIndex(
name: "ix_cities_id",
@ -203,11 +174,10 @@ namespace Persistence.PostgreSql.Migrations
unique: true);
migrationBuilder.CreateIndex(
name: "ix_cities_region_id",
name: "IX_cities_region_id",
schema: "application",
table: "cities",
column: "region_id",
unique: true);
column: "region_id");
migrationBuilder.CreateIndex(
name: "ix_cities_uuid",
@ -251,61 +221,31 @@ namespace Persistence.PostgreSql.Migrations
unique: true);
migrationBuilder.CreateIndex(
name: "ix_route_addresses_address_id",
name: "IX_RouteAddress_AddressId",
schema: "application",
table: "route_addresses",
column: "address_id",
unique: true);
table: "RouteAddress",
column: "AddressId");
migrationBuilder.CreateIndex(
name: "ix_route_addresses_id",
name: "IX_RouteAddress_RouteId",
schema: "application",
table: "route_addresses",
column: "id",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_route_addresses_route_id",
schema: "application",
table: "route_addresses",
column: "route_id",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_route_addresses_uuid",
schema: "application",
table: "route_addresses",
column: "uuid",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_routes_id",
schema: "application",
table: "routes",
column: "id",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_routes_uuid",
schema: "application",
table: "routes",
column: "uuid",
unique: true);
table: "RouteAddress",
column: "RouteId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "route_addresses",
name: "RouteAddress",
schema: "application");
migrationBuilder.DropTable(
name: "addresses",
name: "Address",
schema: "application");
migrationBuilder.DropTable(
name: "routes",
name: "Route",
schema: "application");
migrationBuilder.DropTable(
@ -320,10 +260,6 @@ namespace Persistence.PostgreSql.Migrations
name: "countries",
schema: "application");
migrationBuilder.DropSequence(
name: "addresses_id_sequence",
schema: "application");
migrationBuilder.DropSequence(
name: "cities_id_sequence",
schema: "application");
@ -335,14 +271,6 @@ namespace Persistence.PostgreSql.Migrations
migrationBuilder.DropSequence(
name: "regions_id_sequence",
schema: "application");
migrationBuilder.DropSequence(
name: "route_addresses_id_sequence",
schema: "application");
migrationBuilder.DropSequence(
name: "routes_id_sequence",
schema: "application");
}
}
}

View File

@ -24,65 +24,35 @@ namespace Persistence.PostgreSql.Migrations
NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "vehicle_type", new[] { "bus", "train", "aircraft" });
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.HasSequence("addresses_id_sequence");
modelBuilder.HasSequence("cities_id_sequence");
modelBuilder.HasSequence("countries_id_sequence");
modelBuilder.HasSequence("regions_id_sequence");
modelBuilder.HasSequence("route_addresses_id_sequence");
modelBuilder.HasSequence("routes_id_sequence");
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Address", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasColumnName("id")
.HasDefaultValueSql("nextval('application.addresses_id_sequence')");
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "addresses_id_sequence");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<long>("CityId")
.HasColumnType("bigint")
.HasColumnName("city_id");
.HasColumnType("bigint");
b.Property<Guid>("Guid")
.HasColumnType("uuid")
.HasColumnName("uuid");
.HasColumnType("uuid");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("varchar(128)")
.HasColumnName("name");
.HasColumnType("text");
b.Property<string>("VehicleType")
.IsRequired()
.HasColumnType("application.vehicle_type")
.HasColumnName("vehicle_type");
b.HasKey("Id");
b.HasKey("Id")
.HasName("pk_addresses");
b.HasIndex("CityId");
b.HasAlternateKey("Guid")
.HasName("altk_addresses_Guid");
b.HasIndex("CityId")
.IsUnique()
.HasDatabaseName("ix_addresses_city_id");
b.HasIndex("Guid")
.IsUnique()
.HasDatabaseName("ix_addresses_uuid");
b.HasIndex("Id")
.IsUnique()
.HasDatabaseName("ix_addresses_id");
b.ToTable("addresses", "application");
b.ToTable("Address", "application");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.City", b =>
@ -122,9 +92,7 @@ namespace Persistence.PostgreSql.Migrations
.IsUnique()
.HasDatabaseName("ix_cities_id");
b.HasIndex("RegionId")
.IsUnique()
.HasDatabaseName("ix_cities_region_id");
b.HasIndex("RegionId");
b.ToTable("cities", "application");
});
@ -211,95 +179,49 @@ namespace Persistence.PostgreSql.Migrations
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasColumnName("id")
.HasDefaultValueSql("nextval('application.routes_id_sequence')");
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "routes_id_sequence");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<Guid>("Guid")
.HasColumnType("uuid")
.HasColumnName("uuid");
.HasColumnType("uuid");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("varchar(64)")
.HasColumnName("name");
.HasColumnType("text");
b.Property<string>("VehicleType")
.IsRequired()
.HasColumnType("application.vehicle_type")
.HasColumnName("vehicle_type");
b.HasKey("Id");
b.HasKey("Id")
.HasName("pk_routes");
b.HasAlternateKey("Guid")
.HasName("altk_routes_Guid");
b.HasIndex("Guid")
.IsUnique()
.HasDatabaseName("ix_routes_uuid");
b.HasIndex("Id")
.IsUnique()
.HasDatabaseName("ix_routes_id");
b.ToTable("routes", "application");
b.ToTable("Route", "application");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.RouteAddress", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasColumnName("id")
.HasDefaultValueSql("nextval('application.route_addresses_id_sequence')");
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "route_addresses_id_sequence");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<long>("AddressId")
.HasColumnType("bigint")
.HasColumnName("address_id");
.HasColumnType("bigint");
b.Property<Guid>("Guid")
.HasColumnType("uuid")
.HasColumnName("uuid");
.HasColumnType("uuid");
b.Property<short>("Order")
.HasColumnType("smallint")
.HasColumnName("order");
.HasColumnType("smallint");
b.Property<long>("RouteId")
.HasColumnType("bigint")
.HasColumnName("route_id");
.HasColumnType("bigint");
b.HasKey("Id")
.HasName("pk_route_addresses");
b.HasKey("Id");
b.HasAlternateKey("Guid")
.HasName("altk_route_addresses_Guid");
b.HasIndex("AddressId");
b.HasAlternateKey("AddressId", "RouteId", "Order")
.HasName("altk_route_addresses_address_id_route_id_order");
b.HasIndex("RouteId");
b.HasIndex("AddressId")
.IsUnique()
.HasDatabaseName("ix_route_addresses_address_id");
b.HasIndex("Guid")
.IsUnique()
.HasDatabaseName("ix_route_addresses_uuid");
b.HasIndex("Id")
.IsUnique()
.HasDatabaseName("ix_route_addresses_id");
b.HasIndex("RouteId")
.IsUnique()
.HasDatabaseName("ix_route_addresses_route_id");
b.ToTable("route_addresses", "application");
b.ToTable("RouteAddress", "application");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Address", b =>
@ -308,8 +230,7 @@ namespace Persistence.PostgreSql.Migrations
.WithMany("Addresses")
.HasForeignKey("CityId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_addresses_city_id");
.IsRequired();
b.Navigation("City");
});
@ -344,15 +265,13 @@ namespace Persistence.PostgreSql.Migrations
.WithMany("AddressRoutes")
.HasForeignKey("AddressId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_route_addresses_address_id");
.IsRequired();
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Route", "Route")
.WithMany("RouteAddresses")
.HasForeignKey("RouteId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_route_addresses_route_id");
.IsRequired();
b.Navigation("Address");

View File

@ -15,11 +15,15 @@ public sealed class PostgreSqlUnitOfWork : UnitOfWork
CountryRepository = new PostgreSqlCountryRepository(_dbContext);
RegionRepository = new PostgreSqlRegionRepository(_dbContext);
CityRepository = new PostgreSqlCityRepository(_dbContext);
}
public CountryRepository CountryRepository { get; init; }
public RegionRepository RegionRepository { get; init; }
public CityRepository CityRepository { get; init; }
public int Save()
{
return _dbContext.SaveChanges();

View File

@ -0,0 +1,11 @@
using cuqmbr.TravelGuide.Application.Common.Interfaces.Persistence.Repositories;
using cuqmbr.TravelGuide.Domain.Entities;
namespace cuqmbr.TravelGuide.Persistence.PostgreSql.Repositories;
public sealed class PostgreSqlCityRepository :
PostgreSqlBaseRepository<City>, CityRepository
{
public PostgreSqlCityRepository(PostgreSqlDbContext dbContext)
: base(dbContext) { }
}

File diff suppressed because it is too large Load Diff

View File

@ -28,7 +28,7 @@ public class RegionsTests : TestBase
Name = countryName
}, TestContext.Current.CancellationToken);
string regionName = "Regin Name";
string regionName = "Region Name";
var createRegionResult = await mediator.Send(
new AddRegionCommand()
@ -65,7 +65,7 @@ public class RegionsTests : TestBase
Name = countryName
}, TestContext.Current.CancellationToken);
string regionName = "Regin Name";
string regionName = "Region Name";
var createRegionResult = await mediator.Send(
new AddRegionCommand()
@ -106,7 +106,7 @@ public class RegionsTests : TestBase
Name = countryName2
}, TestContext.Current.CancellationToken);
string regionName = "Regin Name";
string regionName = "Region Name";
var createRegionResult1 = await mediator.Send(
new AddRegionCommand()
@ -517,8 +517,6 @@ public class RegionsTests : TestBase
// TODO: Add more tests with unauthenticated user
// (copy tests with admin role)
// TODO: Add test for GetRegion and GetRegionPage
[Fact]
public async Task GetRegion_WithAdminRole_RegionReturned()
{