├── PaymentService ├── core │ ├── Class1.cs │ ├── Application │ │ └── Application │ │ │ ├── MecadoPago │ │ │ ├── Exceptions │ │ │ │ └── InvalidPaymentIntentionException.cs │ │ │ └── MercadoPagoAdapter.cs │ │ │ ├── Payments.Application.csproj │ │ │ ├── PaymentProcessorFactory.cs │ │ │ └── NotImplementedPaymentProvider.cs │ └── Core.csproj └── Payments.UnitTests │ ├── Payments.UnitTests.csproj │ ├── PaymentProcessorFactoryTests.cs │ └── MercadoPagoTests.cs ├── BookingService ├── Core │ ├── Shared │ │ └── Shared │ │ │ ├── Utils.cs │ │ │ └── Shared.csproj │ ├── Domain │ │ ├── Guest │ │ │ ├── Exceptions │ │ │ │ ├── InvalidEmailException.cs │ │ │ │ ├── MissingRequiredInformation.cs │ │ │ │ ├── GuestHasInvalidInformation.cs │ │ │ │ └── InvalidPersonDocumentIdException.cs │ │ │ ├── Enums │ │ │ │ ├── DocumentType.cs │ │ │ │ ├── AcceptedCurrencies.cs │ │ │ │ ├── Status.cs │ │ │ │ └── Action.cs │ │ │ ├── Ports │ │ │ │ └── IGuestRepository.cs │ │ │ ├── ValueObjects │ │ │ │ ├── Price.cs │ │ │ │ └── PersonId.cs │ │ │ └── Entities │ │ │ │ ├── Guest.cs │ │ │ │ └── Booking.cs │ │ ├── Room │ │ │ ├── Exceptions │ │ │ │ ├── InvalidRoomDataException.cs │ │ │ │ └── InvalidRoomPriceException.cs │ │ │ ├── Ports │ │ │ │ └── IRoomRepository.cs │ │ │ └── Entities │ │ │ │ └── Room.cs │ │ ├── Booking │ │ │ ├── Exceptions │ │ │ │ ├── GuestIsRequiredException.cs │ │ │ │ ├── RoomIsRequiredException.cs │ │ │ │ ├── RoomCannotBeBookedException.cs │ │ │ │ ├── EndDateTimeIsRequiredException.cs │ │ │ │ ├── StartDateTimeIsRequiredException.cs │ │ │ │ └── PlacedAtIsARequiredInformationException.cs │ │ │ └── Ports │ │ │ │ └── IBookingRepository.cs │ │ ├── Domain.csproj │ │ └── Utils.cs │ └── Application │ │ ├── Guest │ │ ├── Requests │ │ │ └── CreateGuestRequest.cs │ │ ├── Responses │ │ │ └── GuestResponse.cs │ │ ├── Ports │ │ │ └── IGuestManager.cs │ │ ├── Dtos │ │ │ └── GuestDto.cs │ │ └── GuestManager.cs │ │ ├── Room │ │ ├── Request │ │ │ └── CreateRoomRequest.cs │ │ ├── Responses │ │ │ └── RoomResponse.cs │ │ ├── Queries │ │ │ ├── GetRoomQuery.cs │ │ │ └── GetRoomQueryHandler.cs │ │ ├── Commands │ │ │ ├── CreateRoomCommand.cs │ │ │ └── CreateRoomCommandHandler.cs │ │ ├── Ports │ │ │ └── IRoomManager.cs │ │ ├── Dtos │ │ │ └── RoomDto.cs │ │ └── RoomManager.cs │ │ ├── Booking │ │ ├── Queries │ │ │ ├── GetBookingQuery.cs │ │ │ └── GetBookingQueryHandler.cs │ │ ├── BookingResponse.cs │ │ ├── Ports │ │ │ └── IBookingManager.cs │ │ ├── Commands │ │ │ ├── CreateBookingCommand.cs │ │ │ └── CreateBookingCommandHandler.cs │ │ ├── Dtos │ │ │ ├── PaymentRequestDto.cs │ │ │ └── BookingDto.cs │ │ └── BookingManager.cs │ │ ├── Payment │ │ ├── Ports │ │ │ ├── IPaymentProcessor.cs │ │ │ └── IPaymentProcessorFactory.cs │ │ ├── Responses │ │ │ └── PaymentResponse.cs │ │ └── Dtos │ │ │ └── PaymentStateDto.cs │ │ ├── Application.csproj │ │ └── Response.cs ├── Consumers │ └── API │ │ ├── appsettings.Development.json │ │ ├── appsettings.json │ │ ├── Properties │ │ └── launchSettings.json │ │ ├── API.csproj │ │ ├── Program.cs │ │ └── Controllers │ │ ├── RoomController.cs │ │ ├── GuestController.cs │ │ └── BookingController.cs ├── Tests │ ├── Adapters │ │ └── AdaptersTests │ │ │ ├── UnitTest1.cs │ │ │ └── AdaptersTests.csproj │ ├── Domain │ │ └── DomainTests │ │ │ ├── DomainTests.csproj │ │ │ └── Booking │ │ │ └── StateMachineTests.cs │ └── Application │ │ └── ApplicationTests │ │ ├── ApplicationTests.csproj │ │ ├── GetRoomQueryHandlerTests.cs │ │ ├── BookingManagerTests.cs │ │ ├── CreateRoomCommandHandlerTests.cs │ │ ├── GuestManagerTests.cs │ │ └── CreateBookingCommandHandlerTests.cs └── Adapters │ └── Data │ ├── Room │ ├── RoomConfiguration.cs │ └── RoomRepository.cs │ ├── Guest │ ├── GuestConfiguration.cs │ └── GuestRepository.cs │ ├── HotelDbContext.cs │ ├── Migrations │ ├── 20220402211259_FixingStatus.cs │ ├── 20220320184229_AddingValueObjectToRoom.cs │ ├── 20220320183650_AddingValueObjectToGuest.cs │ ├── 20220320174334_InitialCreate.cs │ ├── 20220320174334_InitialCreate.Designer.cs │ ├── 20220320181710_RoomsAndBooking.cs │ ├── 20220320181842_AddingForeignKeyToBooking.cs │ ├── 20220320181710_RoomsAndBooking.Designer.cs │ ├── 20220320181842_AddingForeignKeyToBooking.Designer.cs │ ├── 20220320183650_AddingValueObjectToGuest.Designer.cs │ ├── 20220320184229_AddingValueObjectToRoom.Designer.cs │ ├── HotelDbContextModelSnapshot.cs │ └── 20220402211259_FixingStatus.Designer.cs │ ├── Booking │ └── BookingRepository.cs │ └── Data.csproj ├── READ.me ├── .gitignore └── HotelBooking.sln /PaymentService/core/Class1.cs: -------------------------------------------------------------------------------- 1 | namespace core 2 | { 3 | public class Class1 4 | { 5 | 6 | } 7 | } -------------------------------------------------------------------------------- /BookingService/Core/Shared/Shared/Utils.cs: -------------------------------------------------------------------------------- 1 | namespace Shared 2 | { 3 | public class Utils 4 | { 5 | 6 | } 7 | } -------------------------------------------------------------------------------- /BookingService/Core/Domain/Guest/Exceptions/InvalidEmailException.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Exceptions 2 | { 3 | public class InvalidEmailException : Exception 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /BookingService/Core/Domain/Guest/Exceptions/MissingRequiredInformation.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Exceptions 2 | { 3 | public class MissingRequiredInformation : Exception 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /BookingService/Core/Domain/Room/Exceptions/InvalidRoomDataException.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Room.Exceptions 2 | { 3 | public class InvalidRoomDataException : Exception 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /BookingService/Core/Domain/Booking/Exceptions/GuestIsRequiredException.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Booking.Exceptions 2 | { 3 | public class GuestIsRequiredException : Exception 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /BookingService/Core/Domain/Booking/Exceptions/RoomIsRequiredException.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Booking.Exceptions 2 | { 3 | public class RoomIsRequiredException : Exception 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /BookingService/Core/Domain/Guest/Enums/DocumentType.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Enums 2 | { 3 | public enum DocumentType 4 | { 5 | Passport = 1, 6 | DriveLicence = 2, 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /BookingService/Core/Domain/Guest/Exceptions/GuestHasInvalidInformation.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Guest.Exceptions 2 | { 3 | public class GuestHasInvalidInformation : Exception 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /BookingService/Core/Domain/Room/Exceptions/InvalidRoomPriceException.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Room.Exceptions 2 | { 3 | public class InvalidRoomPriceException : Exception 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /BookingService/Consumers/API/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /BookingService/Core/Domain/Booking/Exceptions/RoomCannotBeBookedException.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Booking.Exceptions 2 | { 3 | public class RoomCannotBeBookedException : Exception 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /BookingService/Core/Domain/Guest/Exceptions/InvalidPersonDocumentIdException.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Exceptions 2 | { 3 | public class InvalidPersonDocumentIdException : Exception 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /BookingService/Core/Domain/Booking/Exceptions/EndDateTimeIsRequiredException.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Booking.Exceptions 2 | { 3 | public class EndDateTimeIsRequiredException : Exception 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /BookingService/Core/Domain/Booking/Exceptions/StartDateTimeIsRequiredException.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Booking.Exceptions 2 | { 3 | public class StartDateTimeIsRequiredException : Exception 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /BookingService/Core/Domain/Booking/Exceptions/PlacedAtIsARequiredInformationException.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Booking.Exceptions 2 | { 3 | public class PlacedAtIsARequiredInformationException : Exception 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /BookingService/Core/Domain/Guest/Enums/AcceptedCurrencies.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Enums 2 | { 3 | public enum AcceptedCurrencies 4 | { 5 | Dollar = 0, 6 | Euro = 1, 7 | Bitcoin = 2, 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /PaymentService/core/Application/Application/MecadoPago/Exceptions/InvalidPaymentIntentionException.cs: -------------------------------------------------------------------------------- 1 | namespace Application.MecadoPago.Exceptions 2 | { 3 | public class InvalidPaymentIntentionException : Exception 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /READ.me: -------------------------------------------------------------------------------- 1 | Install .NET core 6 and ASP.NET on Linux ( Arch ) 2 | sudo pacman -S dotnet-sdk aspnet-runtime 3 | 4 | Docker connection string 5 | "Main": "Data Source=host.docker.internal,1433;Initial Catalog=TestDB;User ID=SA;Password=MyPassword123£$" 6 | -------------------------------------------------------------------------------- /BookingService/Core/Domain/Guest/Ports/IGuestRepository.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Ports 2 | { 3 | public interface IGuestRepository 4 | { 5 | Task Get(int Id); 6 | Task Create(Entities.Guest guest); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /BookingService/Core/Application/Guest/Requests/CreateGuestRequest.cs: -------------------------------------------------------------------------------- 1 | using Application.Guest.DTO; 2 | 3 | namespace Application.Guest.Requests 4 | { 5 | public class CreateGuestRequest 6 | { 7 | public GuestDto Data; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /BookingService/Core/Application/Guest/Responses/GuestResponse.cs: -------------------------------------------------------------------------------- 1 | using Application.Guest.DTO; 2 | 3 | namespace Application.Responses 4 | { 5 | public class GuestResponse : Response 6 | { 7 | public GuestDto Data; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /BookingService/Core/Application/Room/Request/CreateRoomRequest.cs: -------------------------------------------------------------------------------- 1 | using Application.Room.Dtos; 2 | 3 | namespace Application.Room.Request 4 | { 5 | public class CreateRoomRequest 6 | { 7 | public RoomDto Data { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /BookingService/Core/Domain/Guest/Enums/Status.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Enums 2 | { 3 | public enum Status 4 | { 5 | Created = 0, 6 | Paid = 1, 7 | Finished = 2, 8 | Canceled = 3, 9 | Refounded = 4, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /PaymentService/core/Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /BookingService/Core/Application/Booking/Queries/GetBookingQuery.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | 4 | namespace Application.Booking.Queries 5 | { 6 | public class GetBookingQuery : IRequest 7 | { 8 | public int Id { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /BookingService/Core/Application/Booking/BookingResponse.cs: -------------------------------------------------------------------------------- 1 | using Application.Booking.Dtos; 2 | using Application.Responses; 3 | 4 | namespace Application.Booking 5 | { 6 | public class BookingResponse : Response 7 | { 8 | public BookingDto Data; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /BookingService/Core/Domain/Domain.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /BookingService/Core/Application/Room/Responses/RoomResponse.cs: -------------------------------------------------------------------------------- 1 | using Application.Responses; 2 | using Application.Room.Dtos; 3 | 4 | namespace Application.Room.Responses 5 | { 6 | public class RoomResponse : Response 7 | { 8 | public RoomDto Data; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /BookingService/Core/Domain/Booking/Ports/IBookingRepository.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Booking.Ports 2 | { 3 | public interface IBookingRepository 4 | { 5 | Task Get(int id); 6 | Task CreateBooking(Entities.Booking booking); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /BookingService/Core/Domain/Guest/ValueObjects/Price.cs: -------------------------------------------------------------------------------- 1 | using Domain.Enums; 2 | 3 | namespace Domain.ValueObjects 4 | { 5 | public class Price 6 | { 7 | public decimal Value { get; set; } 8 | public AcceptedCurrencies Currency { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /BookingService/Core/Application/Room/Queries/GetRoomQuery.cs: -------------------------------------------------------------------------------- 1 | using Application.Room.Responses; 2 | using MediatR; 3 | 4 | namespace Application.Room.Queries 5 | { 6 | public class GetRoomQuery : IRequest 7 | { 8 | public int Id { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /BookingService/Core/Domain/Guest/ValueObjects/PersonId.cs: -------------------------------------------------------------------------------- 1 | using Domain.Enums; 2 | 3 | namespace Domain.ValueObjects 4 | { 5 | public class PersonId 6 | { 7 | public string IdNumber { get; set; } 8 | public DocumentType DocumentType { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /BookingService/Core/Shared/Shared/Shared.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /BookingService/Core/Application/Payment/Ports/IPaymentProcessor.cs: -------------------------------------------------------------------------------- 1 | using Application.Payment.Responses; 2 | 3 | namespace Application.Payment 4 | { 5 | public interface IPaymentProcessor 6 | { 7 | Task CapturePayment(string paymentIntention); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /BookingService/Core/Domain/Room/Ports/IRoomRepository.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Room.Ports 2 | { 3 | public interface IRoomRepository 4 | { 5 | Task Get(int Id); 6 | Task Create(Entities.Room room); 7 | Task GetAggregate(int Id); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /BookingService/Core/Domain/Utils.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.UtilsTools 2 | { 3 | public static class Utils 4 | { 5 | public static bool ValidateEmail(string email) 6 | { 7 | if (email == "b@b.com") return false; 8 | 9 | return true; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /BookingService/Core/Application/Payment/Responses/PaymentResponse.cs: -------------------------------------------------------------------------------- 1 | using Application.Payment.Dtos; 2 | using Application.Responses; 3 | 4 | namespace Application.Payment.Responses 5 | { 6 | public class PaymentResponse : Response 7 | { 8 | public PaymentStateDto Data { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /BookingService/Core/Application/Payment/Ports/IPaymentProcessorFactory.cs: -------------------------------------------------------------------------------- 1 | using Application.Booking.Dtos; 2 | 3 | namespace Application.Payment 4 | { 5 | public interface IPaymentProcessorFactory 6 | { 7 | IPaymentProcessor GetPaymentProcessor(SupportedPaymentProviders selectedPaymentProvider); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /BookingService/Core/Domain/Guest/Enums/Action.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Enums 2 | { 3 | public enum Action 4 | { 5 | Pay = 0, 6 | Finish = 1, // after paid and used 7 | Cancel = 2, // can never be paid 8 | Refound = 3, // Paid then refound 9 | Reopen = 4, // canceled 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /BookingService/Core/Application/Room/Commands/CreateRoomCommand.cs: -------------------------------------------------------------------------------- 1 | using Application.Room.Dtos; 2 | using Application.Room.Responses; 3 | using MediatR; 4 | 5 | namespace Application.Room.Commands 6 | { 7 | public class CreateRoomCommand : IRequest 8 | { 9 | public RoomDto RoomDto { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /BookingService/Consumers/API/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "ConnectionStrings": { 9 | "Main": "Data Source=(localdb)\\MSSQLLocalDB;Database=HotelManagemnt;Integrated Security=true" 10 | }, 11 | "AllowedHosts": "*" 12 | } -------------------------------------------------------------------------------- /BookingService/Core/Application/Room/Ports/IRoomManager.cs: -------------------------------------------------------------------------------- 1 | using Application.Room.Request; 2 | using Application.Room.Responses; 3 | 4 | namespace Application.Room.Ports 5 | { 6 | public interface IRoomManager 7 | { 8 | Task CreateRoom(CreateRoomRequest request); 9 | Task GetRoom(int roomId); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /BookingService/Core/Application/Guest/Ports/IGuestManager.cs: -------------------------------------------------------------------------------- 1 | using Application.Guest.Requests; 2 | using Application.Responses; 3 | 4 | namespace Application.Ports 5 | { 6 | public interface IGuestManager 7 | { 8 | Task CreateGuest(CreateGuestRequest request); 9 | Task GetGuest(int guestId); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /BookingService/Tests/Adapters/AdaptersTests/UnitTest1.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace AdaptersTests 4 | { 5 | public class Tests 6 | { 7 | [SetUp] 8 | public void Setup() 9 | { 10 | } 11 | 12 | [Test] 13 | public void Test1() 14 | { 15 | Assert.Pass(); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /BookingService/Core/Application/Booking/Ports/IBookingManager.cs: -------------------------------------------------------------------------------- 1 | using Application.Booking.Dtos; 2 | using Application.Payment.Responses; 3 | 4 | namespace Application.Booking.Ports 5 | { 6 | public interface IBookingManager 7 | { 8 | Task CreateBooking(BookingDto booking); 9 | Task PayForABooking(PaymentRequestDto paymentRequestDto); 10 | Task GetBooking(int id); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /BookingService/Core/Application/Booking/Commands/CreateBookingCommand.cs: -------------------------------------------------------------------------------- 1 | using Application.Booking.Dtos; 2 | using MediatR; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Application.Booking.Commands 10 | { 11 | public class CreateBookingCommand : IRequest 12 | { 13 | public BookingDto BookingDto { get; set; } 14 | 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /BookingService/Core/Application/Application.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /BookingService/Core/Application/Payment/Dtos/PaymentStateDto.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Payment.Dtos 2 | { 3 | public enum Status 4 | { 5 | Success = 0, 6 | Failed = 1, 7 | Error = 2, 8 | Undefined = 3, 9 | } 10 | public class PaymentStateDto 11 | { 12 | public Status Status { get; set; } 13 | public string PaymentId { get; set; } 14 | public DateTime CreatedDate { get; set; } = DateTime.Now; 15 | public string Message { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /PaymentService/core/Application/Application/Payments.Application.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /BookingService/Tests/Adapters/AdaptersTests/AdaptersTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /BookingService/Adapters/Data/Room/RoomConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | 4 | namespace Data 5 | { 6 | public class RoomConfiguration : IEntityTypeConfiguration 7 | { 8 | public void Configure(EntityTypeBuilder builder) 9 | { 10 | builder.HasKey(x => x.Id); 11 | builder.OwnsOne(x => x.Price) 12 | .Property(x => x.Currency); 13 | 14 | builder.OwnsOne(x => x.Price) 15 | .Property(x => x.Value); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /BookingService/Adapters/Data/Guest/GuestConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Entities = Domain.Entities; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 4 | 5 | namespace Data.Guest 6 | { 7 | public class GuestConfiguration : IEntityTypeConfiguration 8 | { 9 | public void Configure(EntityTypeBuilder builder) 10 | { 11 | builder.HasKey(x => x.Id); 12 | builder.OwnsOne(x => x.DocumentId) 13 | .Property(x => x.IdNumber); 14 | 15 | builder.OwnsOne(x => x.DocumentId) 16 | .Property(x => x.DocumentType); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /PaymentService/core/Application/Application/PaymentProcessorFactory.cs: -------------------------------------------------------------------------------- 1 | using Application.Booking.Dtos; 2 | using Payment.Application; 3 | using Payments.Application; 4 | 5 | namespace Application.Payment 6 | { 7 | public class PaymentProcessorFactory : IPaymentProcessorFactory 8 | { 9 | 10 | public IPaymentProcessor GetPaymentProcessor(SupportedPaymentProviders selectedPaymentProvider) 11 | { 12 | switch (selectedPaymentProvider) 13 | { 14 | case SupportedPaymentProviders.MercadoPago: 15 | return new MercadoPagoAdapter(); 16 | 17 | default: return new NotImplementedPaymentProvider(); 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /BookingService/Tests/Domain/DomainTests/DomainTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /BookingService/Core/Application/Booking/Dtos/PaymentRequestDto.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Booking.Dtos 2 | { 3 | public enum SupportedPaymentProviders 4 | { 5 | PayPal = 1, 6 | Stripe = 2, 7 | PagSeguro = 3, 8 | MercadoPago = 4, 9 | } 10 | 11 | public enum SupportedPaymentMethods 12 | { 13 | DebitCard = 1, 14 | CreditCard = 2, 15 | BankTransfer = 3, 16 | } 17 | 18 | public class PaymentRequestDto 19 | { 20 | public int BookingId { get; set; } 21 | public string PaymentIntention { get; set; } 22 | public SupportedPaymentProviders SelectedPaymentProvider { get; set; } 23 | public SupportedPaymentMethods SelectedPaymentMethod { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /PaymentService/core/Application/Application/NotImplementedPaymentProvider.cs: -------------------------------------------------------------------------------- 1 | using Application.Payment; 2 | using Application.Payment.Responses; 3 | using Application.Responses; 4 | 5 | namespace Payments.Application 6 | { 7 | public class NotImplementedPaymentProvider : IPaymentProcessor 8 | { 9 | public Task CapturePayment(string paymentIntention) 10 | { 11 | var paymentResponse = new PaymentResponse() 12 | { 13 | Success = false, 14 | ErrorCode = ErrorCodes.PAYMENT_PROVIDER_NOT_IMPLEMENTED, 15 | Message = "The selected payment provider is not available at the moment" 16 | }; 17 | 18 | return Task.FromResult(paymentResponse); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /BookingService/Adapters/Data/HotelDbContext.cs: -------------------------------------------------------------------------------- 1 | using Data.Guest; 2 | using Microsoft.EntityFrameworkCore; 3 | using Entities = Domain.Entities; 4 | 5 | namespace Data 6 | { 7 | public class HotelDbContext : DbContext 8 | { 9 | public HotelDbContext(DbContextOptions options) : base(options) { } 10 | 11 | public virtual DbSet Guests { get; set; } 12 | public virtual DbSet Rooms { get; set; } 13 | public virtual DbSet Bookings { get; set; } 14 | protected override void OnModelCreating(ModelBuilder modelBuilder) 15 | { 16 | modelBuilder.ApplyConfiguration(new GuestConfiguration()); 17 | modelBuilder.ApplyConfiguration(new RoomConfiguration()); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /BookingService/Tests/Application/ApplicationTests/ApplicationTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /BookingService/Adapters/Data/Migrations/20220402211259_FixingStatus.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Data.Migrations 6 | { 7 | public partial class FixingStatus : Migration 8 | { 9 | protected override void Up(MigrationBuilder migrationBuilder) 10 | { 11 | migrationBuilder.AddColumn( 12 | name: "Status", 13 | table: "Bookings", 14 | type: "int", 15 | nullable: false, 16 | defaultValue: 0); 17 | } 18 | 19 | protected override void Down(MigrationBuilder migrationBuilder) 20 | { 21 | migrationBuilder.DropColumn( 22 | name: "Status", 23 | table: "Bookings"); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /PaymentService/Payments.UnitTests/Payments.UnitTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /BookingService/Adapters/Data/Guest/GuestRepository.cs: -------------------------------------------------------------------------------- 1 | using Domain.Ports; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | namespace Data.Guest 5 | { 6 | public class GuestRepository : IGuestRepository 7 | { 8 | private readonly HotelDbContext _hotelDbContext; 9 | public GuestRepository(HotelDbContext hotelDbContext) 10 | { 11 | _hotelDbContext = hotelDbContext; 12 | } 13 | public async Task Create(Domain.Entities.Guest guest) 14 | { 15 | _hotelDbContext.Guests.Add(guest); 16 | await _hotelDbContext.SaveChangesAsync(); 17 | return guest.Id; 18 | } 19 | 20 | public Task Get(int Id) 21 | { 22 | return _hotelDbContext.Guests.Where(g => g.Id == Id).FirstOrDefaultAsync(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /BookingService/Adapters/Data/Booking/BookingRepository.cs: -------------------------------------------------------------------------------- 1 | using Domain.Booking.Ports; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | namespace Data.Booking 5 | { 6 | public class BookingRepository : IBookingRepository 7 | { 8 | private readonly HotelDbContext _dbContext; 9 | public BookingRepository(HotelDbContext hotelDbContext) 10 | { 11 | _dbContext = hotelDbContext; 12 | } 13 | public async Task CreateBooking(Domain.Entities.Booking booking) 14 | { 15 | _dbContext.Bookings.Add(booking); 16 | await _dbContext.SaveChangesAsync(); 17 | return booking; 18 | } 19 | 20 | public Task Get(int id) 21 | { 22 | return _dbContext.Bookings.Include(b => b.Guest).Include(b => b.Room).Where(x => x.Id == id).FirstAsync(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /BookingService/Consumers/API/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:48030", 8 | "sslPort": 44372 9 | } 10 | }, 11 | "profiles": { 12 | "API": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "launchUrl": "swagger", 17 | "applicationUrl": "https://localhost:7218;http://localhost:5218", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | }, 22 | "IIS Express": { 23 | "commandName": "IISExpress", 24 | "launchBrowser": true, 25 | "launchUrl": "swagger", 26 | "environmentVariables": { 27 | "ASPNETCORE_ENVIRONMENT": "Development" 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /BookingService/Core/Application/Booking/Queries/GetBookingQueryHandler.cs: -------------------------------------------------------------------------------- 1 | using Application.Booking.Dtos; 2 | using Domain.Booking.Ports; 3 | using MediatR; 4 | 5 | namespace Application.Booking.Queries 6 | { 7 | public class GetBookingQueryHandler : IRequestHandler 8 | { 9 | private readonly IBookingRepository _bookingRepository; 10 | public GetBookingQueryHandler(IBookingRepository bookingRepository) 11 | { 12 | _bookingRepository = bookingRepository; 13 | } 14 | public async Task Handle(GetBookingQuery request, CancellationToken cancellationToken) 15 | { 16 | var booking = await _bookingRepository.Get(request.Id); 17 | var bookingDto = BookingDto.MapToDto(booking); 18 | return new BookingResponse 19 | { 20 | Success = true, 21 | Data = bookingDto 22 | }; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /BookingService/Adapters/Data/Data.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | all 12 | runtime; build; native; contentfiles; analyzers; buildtransitive 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /BookingService/Adapters/Data/Room/RoomRepository.cs: -------------------------------------------------------------------------------- 1 | using Domain.Room.Ports; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | namespace Data.Room 5 | { 6 | public class RoomRepository : IRoomRepository 7 | { 8 | private readonly HotelDbContext _hotelDbContext; 9 | public RoomRepository(HotelDbContext hotelDbContext) 10 | { 11 | _hotelDbContext = hotelDbContext; 12 | } 13 | public async Task Create(Domain.Entities.Room room) 14 | { 15 | _hotelDbContext.Rooms.Add(room); 16 | await _hotelDbContext.SaveChangesAsync(); 17 | return room.Id; 18 | } 19 | 20 | public Task Get(int Id) 21 | { 22 | return _hotelDbContext.Rooms 23 | .Where(g => g.Id == Id).FirstOrDefaultAsync(); 24 | } 25 | 26 | public Task GetAggregate(int Id) 27 | { 28 | return _hotelDbContext.Rooms 29 | .Include(r => r.Bookings) 30 | .Where(g => g.Id == Id).FirstAsync(); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /BookingService/Consumers/API/API.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /BookingService/Adapters/Data/Migrations/20220320184229_AddingValueObjectToRoom.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Data.Migrations 6 | { 7 | public partial class AddingValueObjectToRoom : Migration 8 | { 9 | protected override void Up(MigrationBuilder migrationBuilder) 10 | { 11 | migrationBuilder.AddColumn( 12 | name: "Price_Currency", 13 | table: "Rooms", 14 | type: "int", 15 | nullable: false, 16 | defaultValue: 0); 17 | 18 | migrationBuilder.AddColumn( 19 | name: "Price_Value", 20 | table: "Rooms", 21 | type: "decimal(18,2)", 22 | nullable: false, 23 | defaultValue: 0m); 24 | } 25 | 26 | protected override void Down(MigrationBuilder migrationBuilder) 27 | { 28 | migrationBuilder.DropColumn( 29 | name: "Price_Currency", 30 | table: "Rooms"); 31 | 32 | migrationBuilder.DropColumn( 33 | name: "Price_Value", 34 | table: "Rooms"); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /BookingService/Adapters/Data/Migrations/20220320183650_AddingValueObjectToGuest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Data.Migrations 6 | { 7 | public partial class AddingValueObjectToGuest : Migration 8 | { 9 | protected override void Up(MigrationBuilder migrationBuilder) 10 | { 11 | migrationBuilder.AddColumn( 12 | name: "DocumentId_DocumentType", 13 | table: "Guests", 14 | type: "int", 15 | nullable: false, 16 | defaultValue: 0); 17 | 18 | migrationBuilder.AddColumn( 19 | name: "DocumentId_IdNumber", 20 | table: "Guests", 21 | type: "nvarchar(max)", 22 | nullable: false, 23 | defaultValue: ""); 24 | } 25 | 26 | protected override void Down(MigrationBuilder migrationBuilder) 27 | { 28 | migrationBuilder.DropColumn( 29 | name: "DocumentId_DocumentType", 30 | table: "Guests"); 31 | 32 | migrationBuilder.DropColumn( 33 | name: "DocumentId_IdNumber", 34 | table: "Guests"); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /BookingService/Core/Application/Room/Dtos/RoomDto.cs: -------------------------------------------------------------------------------- 1 | using Domain.Enums; 2 | using Domain.Entities; 3 | 4 | namespace Application.Room.Dtos 5 | { 6 | public class RoomDto 7 | { 8 | public int Id { get; set; } 9 | public string Name { get; set; } 10 | public int Level { get; set; } 11 | public bool InMaintenance { get; set; } 12 | public decimal Price { get; set; } 13 | public AcceptedCurrencies Currency { get; set; } 14 | 15 | public static Domain.Entities.Room MapToEntity(RoomDto dto) 16 | { 17 | return new Domain.Entities.Room 18 | { 19 | Id = dto.Id, 20 | Name = dto.Name, 21 | Level = dto.Level, 22 | InMaintenance = dto.InMaintenance, 23 | Price = new Domain.ValueObjects.Price { Currency = dto.Currency, Value = dto.Price } 24 | }; 25 | } 26 | 27 | public static RoomDto MapToDto(Domain.Entities.Room room) 28 | { 29 | return new RoomDto 30 | { 31 | Id = room.Id, 32 | Name = room.Name, 33 | Level = room.Level, 34 | InMaintenance = room.InMaintenance, 35 | }; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /BookingService/Core/Application/Room/Queries/GetRoomQueryHandler.cs: -------------------------------------------------------------------------------- 1 | using Application.Room.Dtos; 2 | using Application.Room.Responses; 3 | using Domain.Room.Ports; 4 | using MediatR; 5 | 6 | namespace Application.Room.Queries 7 | { 8 | public class GetRoomQueryHandler : IRequestHandler 9 | { 10 | private readonly IRoomRepository _roomRepository; 11 | public GetRoomQueryHandler(IRoomRepository roomRepository) 12 | { 13 | _roomRepository= roomRepository; 14 | } 15 | public async Task Handle(GetRoomQuery request, CancellationToken cancellationToken) 16 | { 17 | var room = await _roomRepository.Get(request.Id); 18 | 19 | if (room == null) 20 | { 21 | return new RoomResponse 22 | { 23 | Success = false, 24 | ErrorCode = Application.Responses.ErrorCodes.ROOM_NOT_FOUND, 25 | Message = "Could not find a Room with the given Id" 26 | }; 27 | } 28 | 29 | return new RoomResponse 30 | { 31 | Data = RoomDto.MapToDto(room), 32 | Success = true 33 | }; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /BookingService/Adapters/Data/Migrations/20220320174334_InitialCreate.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Data.Migrations 6 | { 7 | public partial class InitialCreate : Migration 8 | { 9 | protected override void Up(MigrationBuilder migrationBuilder) 10 | { 11 | migrationBuilder.CreateTable( 12 | name: "Guests", 13 | columns: table => new 14 | { 15 | Id = table.Column(type: "int", nullable: false) 16 | .Annotation("SqlServer:Identity", "1, 1"), 17 | Name = table.Column(type: "nvarchar(max)", nullable: false), 18 | Surname = table.Column(type: "nvarchar(max)", nullable: false), 19 | Email = table.Column(type: "nvarchar(max)", nullable: false) 20 | }, 21 | constraints: table => 22 | { 23 | table.PrimaryKey("PK_Guests", x => x.Id); 24 | }); 25 | } 26 | 27 | protected override void Down(MigrationBuilder migrationBuilder) 28 | { 29 | migrationBuilder.DropTable( 30 | name: "Guests"); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /BookingService/Core/Application/Response.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Responses 2 | { 3 | public enum ErrorCodes 4 | { 5 | // Guests related codes 1 to 99 6 | NOT_FOUND = 1, 7 | COULD_NOT_STORE_DATA = 2, 8 | INVALID_PERSON_ID = 3, 9 | MISSING_REQUIRED_INFORMATION = 4, 10 | INVALID_EMAIL = 5, 11 | GUEST_NOT_FOUND = 6, 12 | 13 | // Rooms related codes 100 199 14 | ROOM_NOT_FOUND = 100, 15 | ROOM_COULD_NOT_STORE_DATA = 101, 16 | ROOM_INVALID_PERSON_ID = 102, 17 | ROOM_MISSING_REQUIRED_INFORMATION = 103, 18 | ROOM_INVALID_EMAIL = 104, 19 | 20 | // Booking related codes 200 499 21 | BOOKING_NOT_FOUND = 200, 22 | BOOKING_COULD_NOT_STORE_DATA = 201, 23 | BOOKING_INVALID_PERSON_ID = 202, 24 | BOOKING_MISSING_REQUIRED_INFORMATION = 203, 25 | BOOKING_INVALID_EMAIL = 204, 26 | BOOKING_GUEST_NOT_FOUND = 205, 27 | BOOKING_ROOM_CANNOT_BE_BOOKED = 206, 28 | 29 | // Payment related codes 500 - 1500 30 | PAYMENT_INVALID_PAYMENT_INTENTION = 500, 31 | PAYMENT_PROVIDER_NOT_IMPLEMENTED = 501 32 | } 33 | 34 | public abstract class Response 35 | { 36 | public bool Success { get; set; } 37 | public string Message { get; set; } 38 | public ErrorCodes ErrorCode { get; set; } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /BookingService/Core/Application/Guest/Dtos/GuestDto.cs: -------------------------------------------------------------------------------- 1 | using Entities = Domain.Entities; 2 | using Domain.Enums; 3 | 4 | namespace Application.Guest.DTO 5 | { 6 | public class GuestDto 7 | { 8 | public int Id { get; set; } 9 | public string Name { get; set; } 10 | public string Surname { get; set; } 11 | public string Email { get; set; } 12 | public string IdNumber { get; set; } 13 | public int IdTypeCode { get; set; } 14 | public static Entities.Guest MapToEntity(GuestDto guestDTO) 15 | { 16 | return new Entities.Guest 17 | { 18 | Id = guestDTO.Id, 19 | Name = guestDTO.Name, 20 | Surname = guestDTO.Surname, 21 | Email = guestDTO.Email, 22 | DocumentId = new Domain.ValueObjects.PersonId 23 | { 24 | IdNumber = guestDTO.IdNumber, 25 | DocumentType = (DocumentType)guestDTO.IdTypeCode 26 | } 27 | }; 28 | } 29 | 30 | public static GuestDto MapToDto(Entities.Guest guest) 31 | { 32 | return new GuestDto 33 | { 34 | Id = guest.Id, 35 | Email = guest.Email, 36 | IdNumber = guest.DocumentId.IdNumber, 37 | IdTypeCode = (int)guest.DocumentId.DocumentType, 38 | Name = guest.Name, 39 | Surname = guest.Surname, 40 | }; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /BookingService/Core/Application/Booking/Dtos/BookingDto.cs: -------------------------------------------------------------------------------- 1 | using Domain.Enums; 2 | using Entities = Domain.Entities; 3 | 4 | namespace Application.Booking.Dtos 5 | { 6 | public class BookingDto 7 | { 8 | public BookingDto() 9 | { 10 | this.PlacedAt = DateTime.UtcNow; 11 | } 12 | 13 | public int Id { get; set; } 14 | public DateTime PlacedAt { get; set; } 15 | public DateTime Start { get; set; } 16 | public DateTime End { get; set; } 17 | public int RoomId { get; set; } 18 | public int GuestId { get; set; } 19 | private Status Status { get; set; } 20 | 21 | public static Entities.Booking MapToEntity(BookingDto bookingDto) 22 | { 23 | return new Entities.Booking 24 | { 25 | Id = bookingDto.Id, 26 | Start = bookingDto.Start, 27 | Guest = new Entities.Guest { Id = bookingDto.GuestId }, 28 | Room = new Entities.Room { Id = bookingDto.RoomId }, 29 | End = bookingDto.End, 30 | PlacedAt = bookingDto.PlacedAt, 31 | }; 32 | } 33 | 34 | public static BookingDto MapToDto(Entities.Booking booking) 35 | { 36 | return new BookingDto 37 | { 38 | Id = booking.Id, 39 | End = booking.End, 40 | GuestId = booking.Guest.Id, 41 | PlacedAt = booking.PlacedAt, 42 | RoomId = booking.Room.Id, 43 | Status = booking.Status, 44 | Start = booking.Start, 45 | }; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /BookingService/Tests/Application/ApplicationTests/GetRoomQueryHandlerTests.cs: -------------------------------------------------------------------------------- 1 | using Application.Room.Dtos; 2 | using Application.Room.Queries; 3 | using Domain.Entities; 4 | using Domain.Room.Ports; 5 | using Moq; 6 | using NUnit.Framework; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace ApplicationTests 11 | { 12 | public class GetRoomQueryHandlerTests 13 | { 14 | [Test] 15 | public async Task Should_Return_Room() 16 | { 17 | var query = new GetRoomQuery { Id = 1 }; 18 | 19 | var repoMock = new Mock(); 20 | var fakeRoom = new Room() { Id = 1 }; 21 | repoMock.Setup(x => x.Get(query.Id)).Returns(Task.FromResult(fakeRoom)); 22 | 23 | var handler = new GetRoomQueryHandler(repoMock.Object); 24 | var res = await handler.Handle(query, CancellationToken.None); 25 | 26 | Assert.True(res.Success); 27 | Assert.NotNull(res.Data); 28 | } 29 | 30 | [Test] 31 | public async Task Should_Return_ProperError_Message_WhenRoom_NotFound() 32 | { 33 | var query = new GetRoomQuery { Id = 1 }; 34 | 35 | var repoMock = new Mock(); 36 | 37 | var handler = new GetRoomQueryHandler(repoMock.Object); 38 | var res = await handler.Handle(query, CancellationToken.None); 39 | 40 | Assert.False(res.Success); 41 | Assert.AreEqual(res.ErrorCode, Application.Responses.ErrorCodes.ROOM_NOT_FOUND); 42 | Assert.AreEqual(res.Message, "Could not find a Room with the given Id"); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /PaymentService/Payments.UnitTests/PaymentProcessorFactoryTests.cs: -------------------------------------------------------------------------------- 1 | using Application.Booking.Dtos; 2 | using Application.Payment; 3 | using Application.Responses; 4 | using NUnit.Framework; 5 | using Payment.Application; 6 | using Payments.Application; 7 | using System.Threading.Tasks; 8 | 9 | namespace PaymentService.UnitTests 10 | { 11 | public class Tests 12 | { 13 | [Test] 14 | public void ShouldReturn_NotImplementedPaymentProvider_WhenAskingForStripeProvider() 15 | { 16 | var factory = new PaymentProcessorFactory(); 17 | 18 | var provider = factory.GetPaymentProcessor(SupportedPaymentProviders.Stripe); 19 | 20 | Assert.AreEqual(provider.GetType(), typeof(NotImplementedPaymentProvider)); 21 | } 22 | 23 | [Test] 24 | public void ShouldReturn_MercadoPagoAdapter_Provider() 25 | { 26 | var factory = new PaymentProcessorFactory(); 27 | 28 | var provider = factory.GetPaymentProcessor(SupportedPaymentProviders.MercadoPago); 29 | 30 | Assert.AreEqual(provider.GetType(), typeof(MercadoPagoAdapter)); 31 | } 32 | 33 | [Test] 34 | public async Task ShouldReturnFalse_WhenCapturingPaymentFor_NotImplementedPaymentProvider() 35 | { 36 | var factory = new PaymentProcessorFactory(); 37 | 38 | var provider = factory.GetPaymentProcessor(SupportedPaymentProviders.Stripe); 39 | 40 | var res = await provider.CapturePayment("https://myprovider.com/asdf"); 41 | 42 | Assert.False(res.Success); 43 | Assert.AreEqual(res.ErrorCode, ErrorCodes.PAYMENT_PROVIDER_NOT_IMPLEMENTED); 44 | Assert.AreEqual(res.Message, "The selected payment provider is not available at the moment"); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /BookingService/Core/Domain/Guest/Entities/Guest.cs: -------------------------------------------------------------------------------- 1 | using Domain.Exceptions; 2 | using Domain.ValueObjects; 3 | using Domain.UtilsTools; 4 | using Domain.Ports; 5 | 6 | namespace Domain.Entities 7 | { 8 | public class Guest 9 | { 10 | public int Id { get; set; } 11 | public string Name { get; set; } 12 | public string Surname { get; set; } 13 | public string Email { get; set; } 14 | public PersonId DocumentId { get; set; } 15 | 16 | private void ValidateState() 17 | { 18 | if (DocumentId == null || 19 | string.IsNullOrEmpty(DocumentId.IdNumber) || 20 | DocumentId.IdNumber.Length <= 3 || 21 | DocumentId.DocumentType == 0) 22 | { 23 | throw new InvalidPersonDocumentIdException(); 24 | } 25 | 26 | if (string.IsNullOrEmpty(Name) || 27 | string.IsNullOrEmpty(Surname) || 28 | string.IsNullOrEmpty(Email)) 29 | { 30 | throw new MissingRequiredInformation(); 31 | } 32 | 33 | if (Utils.ValidateEmail(this.Email) == false) 34 | { 35 | throw new InvalidEmailException(); 36 | } 37 | } 38 | 39 | public bool IsValid() 40 | { 41 | this.ValidateState(); 42 | return true; 43 | } 44 | 45 | public async Task Save(IGuestRepository guestRepository) 46 | { 47 | this.ValidateState(); 48 | 49 | if (this.Id == 0) 50 | { 51 | this.Id = await guestRepository.Create(this); 52 | } 53 | else 54 | { 55 | //await guestRepository.Update(this); 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /PaymentService/core/Application/Application/MecadoPago/MercadoPagoAdapter.cs: -------------------------------------------------------------------------------- 1 | using Application.MecadoPago.Exceptions; 2 | using Application.Payment; 3 | using Application.Payment.Dtos; 4 | using Application.Payment.Responses; 5 | using Application.Responses; 6 | 7 | namespace Payment.Application 8 | { 9 | public class MercadoPagoAdapter : IPaymentProcessor 10 | { 11 | public Task CapturePayment(string paymentIntention) 12 | { 13 | try 14 | { 15 | if (string.IsNullOrWhiteSpace(paymentIntention)) 16 | { 17 | throw new InvalidPaymentIntentionException(); 18 | } 19 | 20 | paymentIntention += "/success"; 21 | 22 | var dto = new PaymentStateDto 23 | { 24 | CreatedDate = DateTime.Now, 25 | Message = $"Successfully paid {paymentIntention}", 26 | PaymentId = "123", 27 | Status = Status.Success 28 | }; 29 | 30 | var response = new PaymentResponse 31 | { 32 | Data = dto, 33 | Success = true, 34 | Message = "Payment successfully processed" 35 | }; 36 | 37 | return Task.FromResult(response); 38 | } 39 | catch (InvalidPaymentIntentionException) 40 | { 41 | var resp = new PaymentResponse() 42 | { 43 | Success = false, 44 | ErrorCode = ErrorCodes.PAYMENT_INVALID_PAYMENT_INTENTION, 45 | Message = "The selected payment intention is invalid" 46 | }; 47 | return Task.FromResult(resp); 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /PaymentService/Payments.UnitTests/MercadoPagoTests.cs: -------------------------------------------------------------------------------- 1 | using Application.Booking.Dtos; 2 | using Application.Payment; 3 | using Application.Responses; 4 | using NUnit.Framework; 5 | using Payment.Application; 6 | using System.Threading.Tasks; 7 | 8 | namespace PaymentService.UnitTests 9 | { 10 | public class MercadoPagoTests 11 | { 12 | [Test] 13 | public void ShouldReturn_MercadoPagoAdapter_Provider() 14 | { 15 | var factory = new PaymentProcessorFactory(); 16 | 17 | var provider = factory.GetPaymentProcessor(SupportedPaymentProviders.MercadoPago); 18 | 19 | Assert.AreEqual(provider.GetType(), typeof(MercadoPagoAdapter)); 20 | } 21 | 22 | [Test] 23 | public async Task Should_FailWhenPaymentIntentionStringIsInvalid() 24 | { 25 | var factory = new PaymentProcessorFactory(); 26 | 27 | var provider = factory.GetPaymentProcessor(SupportedPaymentProviders.MercadoPago); 28 | 29 | var res = await provider.CapturePayment(""); 30 | 31 | Assert.False(res.Success); 32 | Assert.AreEqual(res.ErrorCode, ErrorCodes.PAYMENT_INVALID_PAYMENT_INTENTION); 33 | Assert.AreEqual(res.Message, "The selected payment intention is invalid"); 34 | } 35 | 36 | [Test] 37 | public async Task Should_SuccessfullyProcessPayment() 38 | { 39 | var factory = new PaymentProcessorFactory(); 40 | 41 | var provider = factory.GetPaymentProcessor(SupportedPaymentProviders.MercadoPago); 42 | 43 | var res = await provider.CapturePayment("https://mercadopago.com.br/asdf"); 44 | 45 | Assert.IsTrue(res.Success); 46 | Assert.AreEqual(res.Message, "Payment successfully processed"); 47 | Assert.NotNull(res.Data); 48 | Assert.NotNull(res.Data.CreatedDate); 49 | Assert.NotNull(res.Data.PaymentId); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /BookingService/Core/Application/Room/RoomManager.cs: -------------------------------------------------------------------------------- 1 | using Application.Responses; 2 | using Application.Room.Dtos; 3 | using Application.Room.Ports; 4 | using Application.Room.Request; 5 | using Application.Room.Responses; 6 | using Domain.Room.Exceptions; 7 | using Domain.Room.Ports; 8 | 9 | namespace Application.Room 10 | { 11 | public class RoomManager : IRoomManager 12 | { 13 | private readonly IRoomRepository _roomRepository; 14 | public RoomManager(IRoomRepository roomRepository) 15 | { 16 | _roomRepository = roomRepository; 17 | } 18 | 19 | public async Task CreateRoom(CreateRoomRequest request) 20 | { 21 | try 22 | { 23 | var room = RoomDto.MapToEntity(request.Data); 24 | 25 | await room.Save(_roomRepository); 26 | request.Data.Id = room.Id; 27 | 28 | return new RoomResponse 29 | { 30 | Success = true, 31 | Data = request.Data, 32 | }; 33 | } 34 | catch (InvalidRoomDataException) 35 | { 36 | 37 | return new RoomResponse 38 | { 39 | Success = false, 40 | ErrorCode = ErrorCodes.ROOM_MISSING_REQUIRED_INFORMATION, 41 | Message = "Missing required information passed" 42 | }; 43 | } 44 | catch (Exception) 45 | { 46 | return new RoomResponse 47 | { 48 | Success = false, 49 | ErrorCode = ErrorCodes.ROOM_COULD_NOT_STORE_DATA, 50 | Message = "There was an error when saving to DB" 51 | }; 52 | } 53 | } 54 | 55 | public Task GetRoom(int roomId) 56 | { 57 | throw new NotImplementedException(); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /BookingService/Adapters/Data/Migrations/20220320174334_InitialCreate.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using Data; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Metadata; 6 | using Microsoft.EntityFrameworkCore.Migrations; 7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 8 | 9 | #nullable disable 10 | 11 | namespace Data.Migrations 12 | { 13 | [DbContext(typeof(HotelDbContext))] 14 | [Migration("20220320174334_InitialCreate")] 15 | partial class InitialCreate 16 | { 17 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 18 | { 19 | #pragma warning disable 612, 618 20 | modelBuilder 21 | .HasAnnotation("ProductVersion", "6.0.3") 22 | .HasAnnotation("Relational:MaxIdentifierLength", 128); 23 | 24 | SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); 25 | 26 | modelBuilder.Entity("Domain.Entities.Guest", b => 27 | { 28 | b.Property("Id") 29 | .ValueGeneratedOnAdd() 30 | .HasColumnType("int"); 31 | 32 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); 33 | 34 | b.Property("Email") 35 | .IsRequired() 36 | .HasColumnType("nvarchar(max)"); 37 | 38 | b.Property("Name") 39 | .IsRequired() 40 | .HasColumnType("nvarchar(max)"); 41 | 42 | b.Property("Surname") 43 | .IsRequired() 44 | .HasColumnType("nvarchar(max)"); 45 | 46 | b.HasKey("Id"); 47 | 48 | b.ToTable("Guests"); 49 | }); 50 | #pragma warning restore 612, 618 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /BookingService/Adapters/Data/Migrations/20220320181710_RoomsAndBooking.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore.Migrations; 3 | 4 | #nullable disable 5 | 6 | namespace Data.Migrations 7 | { 8 | public partial class RoomsAndBooking : Migration 9 | { 10 | protected override void Up(MigrationBuilder migrationBuilder) 11 | { 12 | migrationBuilder.CreateTable( 13 | name: "Bookings", 14 | columns: table => new 15 | { 16 | Id = table.Column(type: "int", nullable: false) 17 | .Annotation("SqlServer:Identity", "1, 1"), 18 | PlacedAt = table.Column(type: "datetime2", nullable: false), 19 | Start = table.Column(type: "datetime2", nullable: false), 20 | End = table.Column(type: "datetime2", nullable: false) 21 | }, 22 | constraints: table => 23 | { 24 | table.PrimaryKey("PK_Bookings", x => x.Id); 25 | }); 26 | 27 | migrationBuilder.CreateTable( 28 | name: "Rooms", 29 | columns: table => new 30 | { 31 | Id = table.Column(type: "int", nullable: false) 32 | .Annotation("SqlServer:Identity", "1, 1"), 33 | Name = table.Column(type: "nvarchar(max)", nullable: false), 34 | Level = table.Column(type: "int", nullable: false), 35 | InMaintenance = table.Column(type: "bit", nullable: false) 36 | }, 37 | constraints: table => 38 | { 39 | table.PrimaryKey("PK_Rooms", x => x.Id); 40 | }); 41 | } 42 | 43 | protected override void Down(MigrationBuilder migrationBuilder) 44 | { 45 | migrationBuilder.DropTable( 46 | name: "Bookings"); 47 | 48 | migrationBuilder.DropTable( 49 | name: "Rooms"); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /BookingService/Consumers/API/Program.cs: -------------------------------------------------------------------------------- 1 | using Application; 2 | using Application.Booking; 3 | using Application.Booking.Ports; 4 | using Application.Payment; 5 | using Application.Ports; 6 | using Application.Room; 7 | using Application.Room.Ports; 8 | using Data; 9 | using Data.Booking; 10 | using Data.Guest; 11 | using Data.Room; 12 | using Domain.Booking.Ports; 13 | using Domain.Ports; 14 | using Domain.Room.Ports; 15 | using Microsoft.EntityFrameworkCore; 16 | using System.Text.Json.Serialization; 17 | using MediatR; 18 | 19 | var builder = WebApplication.CreateBuilder(args); 20 | 21 | // Add services to the container. 22 | 23 | builder.Services.AddControllers(); 24 | builder.Services.AddMediatR(typeof(BookingManager)); 25 | 26 | # region IoC 27 | builder.Services.AddScoped(); 28 | builder.Services.AddScoped(); 29 | builder.Services.AddScoped(); 30 | builder.Services.AddScoped(); 31 | builder.Services.AddScoped(); 32 | builder.Services.AddScoped(); 33 | builder.Services.AddScoped(); 34 | # endregion 35 | 36 | # region DB wiring up 37 | var connectionString = builder.Configuration.GetConnectionString("Main"); 38 | builder.Services.AddDbContext( 39 | options => options.UseSqlServer(connectionString)); 40 | # endregion 41 | 42 | 43 | // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle 44 | builder.Services.AddEndpointsApiExplorer(); 45 | builder.Services.AddSwaggerGen(); 46 | 47 | 48 | builder.Services.AddControllersWithViews() 49 | .AddJsonOptions(options => 50 | options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter())); 51 | 52 | var app = builder.Build(); 53 | 54 | // Configure the HTTP request pipeline. 55 | if (app.Environment.IsDevelopment()) 56 | { 57 | app.UseSwagger(); 58 | app.UseSwaggerUI(); 59 | } 60 | 61 | app.UseHttpsRedirection(); 62 | 63 | app.UseAuthorization(); 64 | 65 | app.MapControllers(); 66 | 67 | app.Run(); 68 | -------------------------------------------------------------------------------- /BookingService/Consumers/API/Controllers/RoomController.cs: -------------------------------------------------------------------------------- 1 | using Application.Responses; 2 | using Application.Room.Commands; 3 | using Application.Room.Dtos; 4 | using Application.Room.Ports; 5 | using Application.Room.Queries; 6 | using Application.Room.Request; 7 | using MediatR; 8 | using Microsoft.AspNetCore.Mvc; 9 | 10 | namespace API.Controllers 11 | { 12 | [Route("[controller]")] 13 | [ApiController] 14 | public class RoomController : ControllerBase 15 | { 16 | private readonly ILogger _logger; 17 | private readonly IRoomManager _roomManager; 18 | private readonly IMediator _mediator; 19 | 20 | public RoomController( 21 | ILogger logger, 22 | IRoomManager roomManager, 23 | IMediator mediator) 24 | { 25 | _logger = logger; 26 | _roomManager = roomManager; 27 | _mediator = mediator; 28 | } 29 | 30 | [HttpPost] 31 | public async Task> Post(RoomDto room) 32 | { 33 | var request = new CreateRoomCommand 34 | { 35 | RoomDto = room 36 | }; 37 | 38 | var res = await _mediator.Send(request); 39 | 40 | if (res.Success) return Created("", res.Data); 41 | 42 | else if (res.ErrorCode == ErrorCodes.ROOM_MISSING_REQUIRED_INFORMATION) 43 | { 44 | return BadRequest(res); 45 | } 46 | else if (res.ErrorCode == ErrorCodes.ROOM_COULD_NOT_STORE_DATA) 47 | { 48 | return BadRequest(res); 49 | } 50 | 51 | _logger.LogError("Response with unknown ErrorCode Returned", res); 52 | return BadRequest(500); 53 | } 54 | 55 | [HttpGet] 56 | public async Task> Get(int roomId) 57 | { 58 | var query = new GetRoomQuery 59 | { 60 | Id = roomId 61 | }; 62 | 63 | var res = await _mediator.Send(query); 64 | 65 | if (res.Success) return Ok(res.Data); 66 | 67 | return NotFound(res); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /BookingService/Core/Application/Room/Commands/CreateRoomCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using Application.Responses; 2 | using Application.Room.Dtos; 3 | using Application.Room.Responses; 4 | using Domain.Room.Exceptions; 5 | using Domain.Room.Ports; 6 | using MediatR; 7 | 8 | 9 | namespace Application.Room.Commands 10 | { 11 | public class CreateRoomCommandHandler : IRequestHandler 12 | { 13 | public readonly IRoomRepository _roomRepository; 14 | public CreateRoomCommandHandler(IRoomRepository roomRepository) 15 | { 16 | _roomRepository= roomRepository; 17 | } 18 | public async Task Handle(CreateRoomCommand request, CancellationToken cancellationToken) 19 | { 20 | try 21 | { 22 | var room = RoomDto.MapToEntity(request.RoomDto); 23 | 24 | await room.Save(_roomRepository); 25 | request.RoomDto.Id = room.Id; 26 | 27 | return new RoomResponse 28 | { 29 | Success = true, 30 | Data = request.RoomDto, 31 | }; 32 | } 33 | catch(InvalidRoomPriceException) 34 | { 35 | return new RoomResponse 36 | { 37 | Success = false, 38 | ErrorCode = ErrorCodes.ROOM_MISSING_REQUIRED_INFORMATION, 39 | Message = "Room price is invalid" 40 | }; 41 | } 42 | catch (InvalidRoomDataException) 43 | { 44 | return new RoomResponse 45 | { 46 | Success = false, 47 | ErrorCode = ErrorCodes.ROOM_MISSING_REQUIRED_INFORMATION, 48 | Message = "Missing required information passed" 49 | }; 50 | } 51 | catch (Exception) 52 | { 53 | return new RoomResponse 54 | { 55 | Success = false, 56 | ErrorCode = ErrorCodes.ROOM_COULD_NOT_STORE_DATA, 57 | Message = "There was an error when saving to DB" 58 | }; 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /BookingService/Consumers/API/Controllers/GuestController.cs: -------------------------------------------------------------------------------- 1 | using Application.Guest.DTO; 2 | using Application.Guest.Requests; 3 | using Application.Ports; 4 | using Application.Responses; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | namespace API.Controllers 8 | { 9 | [ApiController] 10 | [Route("[controller]")] 11 | public class GuestsController : ControllerBase 12 | { 13 | private readonly ILogger _logger; 14 | private readonly IGuestManager _guestManager; 15 | 16 | public GuestsController( 17 | ILogger logger, 18 | IGuestManager guestManager) 19 | { 20 | _logger = logger; 21 | _guestManager = guestManager; 22 | } 23 | 24 | [HttpPost] 25 | public async Task> Post(GuestDto guest) 26 | { 27 | var request = new CreateGuestRequest 28 | { 29 | Data = guest 30 | }; 31 | 32 | var res = await _guestManager.CreateGuest(request); 33 | 34 | if (res.Success) return Created("", res.Data); 35 | 36 | if (res.ErrorCode == ErrorCodes.NOT_FOUND) 37 | { 38 | return NotFound(res); 39 | } 40 | else if (res.ErrorCode == ErrorCodes.INVALID_PERSON_ID) 41 | { 42 | return BadRequest(res); 43 | } 44 | else if (res.ErrorCode == ErrorCodes.MISSING_REQUIRED_INFORMATION) 45 | { 46 | return BadRequest(res); 47 | } 48 | else if (res.ErrorCode == ErrorCodes.INVALID_EMAIL) 49 | { 50 | return BadRequest(res); 51 | } 52 | else if (res.ErrorCode == ErrorCodes.COULD_NOT_STORE_DATA) 53 | { 54 | return BadRequest(res); 55 | } 56 | 57 | _logger.LogError("Response with unknown ErrorCode Returned", res); 58 | return BadRequest(500); 59 | } 60 | 61 | [HttpGet] 62 | public async Task> Get(int guestId) 63 | { 64 | var res = await _guestManager.GetGuest(guestId); 65 | 66 | if (res.Success) return Ok(res.Data); 67 | 68 | return NotFound(res); 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /BookingService/Core/Domain/Room/Entities/Room.cs: -------------------------------------------------------------------------------- 1 | using Domain.Room.Exceptions; 2 | using Domain.Room.Ports; 3 | using Domain.ValueObjects; 4 | 5 | namespace Domain.Entities 6 | { 7 | public class Room 8 | { 9 | public int Id { get; set; } 10 | public string Name { get; set; } 11 | public int Level { get; set; } 12 | public bool InMaintenance { get; set; } 13 | public Price Price { get; set; } 14 | public ICollection Bookings { get; set; } 15 | 16 | public bool IsAvailable 17 | { 18 | get 19 | { 20 | if (this.InMaintenance || this.HasGuest) 21 | { 22 | return false; 23 | } 24 | 25 | return true; 26 | } 27 | } 28 | 29 | public bool HasGuest 30 | { 31 | get 32 | { 33 | var notAvailableStatuses = new List() 34 | { 35 | Enums.Status.Created, 36 | Enums.Status.Paid, 37 | }; 38 | 39 | return this.Bookings?.Where( 40 | b => b.Room.Id == this.Id && 41 | notAvailableStatuses.Contains(b.Status)).Count() > 0; 42 | } 43 | } 44 | 45 | private void ValidateState() 46 | { 47 | if (string.IsNullOrEmpty(this.Name)) 48 | { 49 | throw new InvalidRoomDataException(); 50 | } 51 | 52 | if (this.Price == null || this.Price.Value < 10) 53 | { 54 | throw new InvalidRoomPriceException(); 55 | } 56 | } 57 | 58 | public bool CanBeBooked() 59 | { 60 | try 61 | { 62 | this.ValidateState(); 63 | } 64 | catch (Exception) 65 | { 66 | 67 | return false; 68 | } 69 | 70 | if (!this.IsAvailable) 71 | { 72 | return false; 73 | } 74 | 75 | return true; 76 | } 77 | 78 | public async Task Save(IRoomRepository roomRepository) 79 | { 80 | this.ValidateState(); 81 | 82 | if (this.Id == 0) 83 | { 84 | this.Id = await roomRepository.Create(this); 85 | } 86 | else 87 | { 88 | } 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /BookingService/Tests/Application/ApplicationTests/BookingManagerTests.cs: -------------------------------------------------------------------------------- 1 | using Application.Booking; 2 | using Application.Booking.Dtos; 3 | using Application.Booking.Ports; 4 | using Application.Payment; 5 | using Application.Payment.Dtos; 6 | using Application.Payment.Responses; 7 | using Domain.Booking.Ports; 8 | using Domain.Ports; 9 | using Domain.Room.Ports; 10 | using Moq; 11 | using NUnit.Framework; 12 | using System; 13 | using System.Threading.Tasks; 14 | 15 | namespace ApplicationTests 16 | { 17 | public class BookingManagerTests 18 | { 19 | [Test] 20 | public async Task Should_PayForABooking() 21 | { 22 | var dto = new PaymentRequestDto 23 | { 24 | SelectedPaymentProvider = SupportedPaymentProviders.MercadoPago, 25 | PaymentIntention = "https://www.mercadopago.com.br/asdf", 26 | SelectedPaymentMethod = SupportedPaymentMethods.CreditCard 27 | }; 28 | 29 | var bookingRepository = new Mock(); 30 | var roomRepository = new Mock(); 31 | var guestRepository = new Mock(); 32 | var paymentProcessorFactory = new Mock(); 33 | var paymentProcessor = new Mock(); 34 | 35 | var responseDto = new PaymentStateDto 36 | { 37 | CreatedDate = DateTime.Now, 38 | Message = $"Successfully paid {dto.PaymentIntention}", 39 | PaymentId = "123", 40 | Status = Status.Success 41 | }; 42 | 43 | var response = new PaymentResponse 44 | { 45 | Data = responseDto, 46 | Success = true, 47 | Message = "Payment successfully processed" 48 | }; 49 | 50 | paymentProcessor. 51 | Setup(x => x.CapturePayment(dto.PaymentIntention)) 52 | .Returns(Task.FromResult(response)); 53 | 54 | paymentProcessorFactory 55 | .Setup(x => x.GetPaymentProcessor(dto.SelectedPaymentProvider)) 56 | .Returns(paymentProcessor.Object); 57 | 58 | var bookingManager = new BookingManager( 59 | bookingRepository.Object, 60 | roomRepository.Object, 61 | guestRepository.Object, 62 | paymentProcessorFactory.Object); 63 | 64 | var res = await bookingManager.PayForABooking(dto); 65 | 66 | Assert.NotNull(res); 67 | Assert.True(res.Success); 68 | Assert.AreEqual(res.Message, "Payment successfully processed"); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /BookingService/Adapters/Data/Migrations/20220320181842_AddingForeignKeyToBooking.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Data.Migrations 6 | { 7 | public partial class AddingForeignKeyToBooking : Migration 8 | { 9 | protected override void Up(MigrationBuilder migrationBuilder) 10 | { 11 | migrationBuilder.AddColumn( 12 | name: "GuestId", 13 | table: "Bookings", 14 | type: "int", 15 | nullable: false, 16 | defaultValue: 0); 17 | 18 | migrationBuilder.AddColumn( 19 | name: "RoomId", 20 | table: "Bookings", 21 | type: "int", 22 | nullable: false, 23 | defaultValue: 0); 24 | 25 | migrationBuilder.CreateIndex( 26 | name: "IX_Bookings_GuestId", 27 | table: "Bookings", 28 | column: "GuestId"); 29 | 30 | migrationBuilder.CreateIndex( 31 | name: "IX_Bookings_RoomId", 32 | table: "Bookings", 33 | column: "RoomId"); 34 | 35 | migrationBuilder.AddForeignKey( 36 | name: "FK_Bookings_Guests_GuestId", 37 | table: "Bookings", 38 | column: "GuestId", 39 | principalTable: "Guests", 40 | principalColumn: "Id", 41 | onDelete: ReferentialAction.Cascade); 42 | 43 | migrationBuilder.AddForeignKey( 44 | name: "FK_Bookings_Rooms_RoomId", 45 | table: "Bookings", 46 | column: "RoomId", 47 | principalTable: "Rooms", 48 | principalColumn: "Id", 49 | onDelete: ReferentialAction.Cascade); 50 | } 51 | 52 | protected override void Down(MigrationBuilder migrationBuilder) 53 | { 54 | migrationBuilder.DropForeignKey( 55 | name: "FK_Bookings_Guests_GuestId", 56 | table: "Bookings"); 57 | 58 | migrationBuilder.DropForeignKey( 59 | name: "FK_Bookings_Rooms_RoomId", 60 | table: "Bookings"); 61 | 62 | migrationBuilder.DropIndex( 63 | name: "IX_Bookings_GuestId", 64 | table: "Bookings"); 65 | 66 | migrationBuilder.DropIndex( 67 | name: "IX_Bookings_RoomId", 68 | table: "Bookings"); 69 | 70 | migrationBuilder.DropColumn( 71 | name: "GuestId", 72 | table: "Bookings"); 73 | 74 | migrationBuilder.DropColumn( 75 | name: "RoomId", 76 | table: "Bookings"); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /BookingService/Tests/Domain/DomainTests/Booking/StateMachineTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Domain.Entities; 3 | using Domain.Enums; 4 | 5 | namespace DomainTests.Bookings 6 | { 7 | public class Tests 8 | { 9 | [SetUp] 10 | public void Setup() 11 | { 12 | } 13 | 14 | [Test] 15 | public void ShouldAlwaysStartWithCreatedStatus() 16 | { 17 | var booking = new Booking(); 18 | 19 | Assert.AreEqual(booking.Status, Status.Created); 20 | } 21 | 22 | 23 | [Test] 24 | public void ShouldSetStatusToPaidWhenPayingForABookingWithCreatedStatus() 25 | { 26 | var booking = new Booking(); 27 | 28 | booking.ChangeState(Action.Pay); 29 | 30 | Assert.AreEqual(booking.Status, Status.Paid); 31 | } 32 | 33 | [Test] 34 | public void ShouldSetStatusToCanceldWhenCancelingABookingWithCreatedStatus() 35 | { 36 | var booking = new Booking(); 37 | 38 | booking.ChangeState(Action.Cancel); 39 | 40 | Assert.AreEqual(booking.Status, Status.Canceled); 41 | } 42 | 43 | [Test] 44 | public void ShouldSetStatusToFinishedWhenFinishingAPaidBooking() 45 | { 46 | var booking = new Booking(); 47 | 48 | booking.ChangeState(Action.Pay); 49 | booking.ChangeState(Action.Finish); 50 | 51 | Assert.AreEqual(booking.Status, Status.Finished); 52 | } 53 | 54 | [Test] 55 | public void ShouldSetStatusToRefoundedWhenRefoundingAPaidBooking() 56 | { 57 | var booking = new Booking(); 58 | 59 | booking.ChangeState(Action.Pay); 60 | booking.ChangeState(Action.Refound); 61 | 62 | Assert.AreEqual(booking.Status, Status.Refounded); 63 | } 64 | 65 | [Test] 66 | public void ShouldSetStatusToCreatedWhenReopeningACanceledBooking() 67 | { 68 | var booking = new Booking(); 69 | 70 | booking.ChangeState(Action.Cancel); 71 | booking.ChangeState(Action.Reopen); 72 | 73 | Assert.AreEqual(booking.Status, Status.Created); 74 | } 75 | 76 | [Test] 77 | public void ShouldNotChangeStatusWhenRefoundingABookingWithCreatedStatus() 78 | { 79 | var booking = new Booking(); 80 | 81 | booking.ChangeState(Action.Refound); 82 | 83 | Assert.AreEqual(booking.Status, Status.Created); 84 | } 85 | 86 | [Test] 87 | public void ShouldNotChangeStatusWhenRefoundingAFinishedBookin() 88 | { 89 | var booking = new Booking(); 90 | 91 | booking.ChangeState(Action.Pay); 92 | booking.ChangeState(Action.Finish); 93 | booking.ChangeState(Action.Refound); 94 | 95 | Assert.AreEqual(booking.Status, Status.Finished); 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /BookingService/Consumers/API/Controllers/BookingController.cs: -------------------------------------------------------------------------------- 1 | using Application.Booking; 2 | using Application.Booking.Commands; 3 | using Application.Booking.Dtos; 4 | using Application.Booking.Ports; 5 | using Application.Booking.Queries; 6 | using Application.Payment.Responses; 7 | using Application.Responses; 8 | using MediatR; 9 | using Microsoft.AspNetCore.Mvc; 10 | 11 | namespace API.Controllers 12 | { 13 | [ApiController] 14 | [Route("[controller]")] 15 | public class BookingController : ControllerBase 16 | { 17 | private readonly IBookingManager _bookingManager; 18 | private readonly ILogger _logger; 19 | private readonly IMediator _mediator; 20 | 21 | public BookingController( 22 | IBookingManager bookingManager, 23 | ILogger logger, 24 | IMediator mediator) 25 | { 26 | _bookingManager = bookingManager; 27 | _logger = logger; 28 | _mediator = mediator; 29 | } 30 | 31 | [HttpPost] 32 | [Route("{bookingId}/Pay")] 33 | public async Task> Pay( 34 | PaymentRequestDto paymentRequestDto, int bookingId) 35 | { 36 | paymentRequestDto.BookingId = bookingId; 37 | var res = await _bookingManager.PayForABooking(paymentRequestDto); 38 | 39 | if (res.Success) return Ok(res.Data); 40 | 41 | return BadRequest(res); 42 | } 43 | 44 | [HttpPost] 45 | public async Task> Post(BookingDto booking) 46 | { 47 | 48 | var command = new CreateBookingCommand 49 | { 50 | BookingDto = booking 51 | }; 52 | 53 | var res = await _mediator.Send(command); 54 | 55 | if (res.Success) return Created("", res.Data); 56 | 57 | else if (res.ErrorCode == ErrorCodes.BOOKING_MISSING_REQUIRED_INFORMATION) 58 | { 59 | return BadRequest(res); 60 | } 61 | else if (res.ErrorCode == ErrorCodes.BOOKING_COULD_NOT_STORE_DATA) 62 | { 63 | return BadRequest(res); 64 | } 65 | else if (res.ErrorCode == ErrorCodes.BOOKING_ROOM_CANNOT_BE_BOOKED) 66 | { 67 | return BadRequest(res); 68 | } 69 | 70 | _logger.LogError("Response with unknown ErrorCode Returned", res); 71 | return BadRequest(500); 72 | } 73 | 74 | [HttpGet] 75 | public async Task> Get(int id) 76 | { 77 | var query = new GetBookingQuery 78 | { 79 | Id = id 80 | }; 81 | 82 | var res = await _mediator.Send(query); 83 | 84 | if (res.Success) return Created("", res.Data); 85 | 86 | _logger.LogError("Could not process the request", res); 87 | return BadRequest(res); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /BookingService/Core/Domain/Guest/Entities/Booking.cs: -------------------------------------------------------------------------------- 1 | using Domain.Booking.Exceptions; 2 | using Domain.Booking.Ports; 3 | using Domain.Enums; 4 | using Action = Domain.Enums.Action; 5 | 6 | namespace Domain.Entities 7 | { 8 | public class Booking 9 | { 10 | public Booking() 11 | { 12 | this.Status = Status.Created; 13 | this.PlacedAt = DateTime.UtcNow; 14 | } 15 | public int Id { get; set; } 16 | public DateTime PlacedAt { get; set; } 17 | public DateTime Start { get; set; } 18 | public DateTime End { get; set; } 19 | public Room Room { get; set; } 20 | public Guest Guest { get; set; } 21 | public Status Status { get; set; } 22 | 23 | public void ChangeState(Action action) 24 | { 25 | this.Status = (this.Status, action) switch 26 | { 27 | (Status.Created, Action.Pay) => Status.Paid, 28 | (Status.Created, Action.Cancel) => Status.Canceled, 29 | (Status.Paid, Action.Finish) => Status.Finished, 30 | (Status.Paid, Action.Refound) => Status.Refounded, 31 | (Status.Canceled, Action.Reopen) => Status.Created, 32 | _ => this.Status 33 | }; 34 | } 35 | 36 | public bool IsValid() 37 | { 38 | try 39 | { 40 | this.ValidateState(); 41 | return true; 42 | } 43 | catch (Exception) 44 | { 45 | return false; 46 | } 47 | } 48 | 49 | private void ValidateState() 50 | { 51 | if (this.PlacedAt == default(DateTime)) 52 | { 53 | throw new PlacedAtIsARequiredInformationException(); 54 | } 55 | 56 | if (this.Start == default(DateTime)) 57 | { 58 | throw new StartDateTimeIsRequiredException(); 59 | } 60 | 61 | if (this.End == default(DateTime)) 62 | { 63 | throw new EndDateTimeIsRequiredException(); 64 | } 65 | 66 | if (this.Room == null) 67 | { 68 | throw new RoomIsRequiredException(); 69 | } 70 | 71 | if (this.Guest == null) 72 | { 73 | throw new GuestIsRequiredException(); 74 | } 75 | } 76 | 77 | public async Task Save(IBookingRepository bookingRepository) 78 | { 79 | this.ValidateState(); 80 | 81 | this.Guest.IsValid(); 82 | 83 | if (!this.Room.CanBeBooked()) 84 | { 85 | throw new RoomCannotBeBookedException(); 86 | } 87 | 88 | if (this.Id == 0) 89 | { 90 | var resp = await bookingRepository.CreateBooking(this); 91 | this.Id = resp.Id; 92 | } 93 | else 94 | { 95 | 96 | } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /BookingService/Core/Application/Guest/GuestManager.cs: -------------------------------------------------------------------------------- 1 | using Application.Guest.DTO; 2 | using Application.Guest.Requests; 3 | using Application.Ports; 4 | using Application.Responses; 5 | using Domain.Exceptions; 6 | using Domain.Ports; 7 | 8 | namespace Application 9 | { 10 | public class GuestManager : IGuestManager 11 | { 12 | private IGuestRepository _guestRepository; 13 | public GuestManager(IGuestRepository guestRepository) 14 | { 15 | _guestRepository = guestRepository; 16 | } 17 | public async Task CreateGuest(CreateGuestRequest request) 18 | { 19 | try 20 | { 21 | var guest = GuestDto.MapToEntity(request.Data); 22 | 23 | await guest.Save(_guestRepository); 24 | 25 | request.Data.Id = guest.Id; 26 | 27 | return new GuestResponse 28 | { 29 | Data = request.Data, 30 | Success = true, 31 | }; 32 | } 33 | catch (InvalidPersonDocumentIdException e) 34 | { 35 | return new GuestResponse 36 | { 37 | Success = false, 38 | ErrorCode = ErrorCodes.INVALID_PERSON_ID, 39 | Message = "The ID passed is not valid" 40 | }; 41 | } 42 | catch (MissingRequiredInformation e) 43 | { 44 | return new GuestResponse 45 | { 46 | Success = false, 47 | ErrorCode = ErrorCodes.MISSING_REQUIRED_INFORMATION, 48 | Message = "Missing required information passed" 49 | }; 50 | } 51 | catch (InvalidEmailException e) 52 | { 53 | return new GuestResponse 54 | { 55 | Success = false, 56 | ErrorCode = ErrorCodes.INVALID_EMAIL, 57 | Message = "The given email is not valid" 58 | }; 59 | } 60 | catch (Exception) 61 | { 62 | return new GuestResponse 63 | { 64 | Success = false, 65 | ErrorCode = ErrorCodes.COULD_NOT_STORE_DATA, 66 | Message = "There was an error when saving to DB" 67 | }; 68 | } 69 | } 70 | 71 | public async Task GetGuest(int guestId) 72 | { 73 | var guest = await _guestRepository.Get(guestId); 74 | 75 | if (guest == null) 76 | { 77 | return new GuestResponse 78 | { 79 | Success = false, 80 | ErrorCode = ErrorCodes.GUEST_NOT_FOUND, 81 | Message = "No Guest record was found with the given Id" 82 | }; 83 | } 84 | 85 | return new GuestResponse 86 | { 87 | Data = GuestDto.MapToDto(guest), 88 | Success = true, 89 | }; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /BookingService/Adapters/Data/Migrations/20220320181710_RoomsAndBooking.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Data; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.EntityFrameworkCore.Infrastructure; 6 | using Microsoft.EntityFrameworkCore.Metadata; 7 | using Microsoft.EntityFrameworkCore.Migrations; 8 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 9 | 10 | #nullable disable 11 | 12 | namespace Data.Migrations 13 | { 14 | [DbContext(typeof(HotelDbContext))] 15 | [Migration("20220320181710_RoomsAndBooking")] 16 | partial class RoomsAndBooking 17 | { 18 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 19 | { 20 | #pragma warning disable 612, 618 21 | modelBuilder 22 | .HasAnnotation("ProductVersion", "6.0.3") 23 | .HasAnnotation("Relational:MaxIdentifierLength", 128); 24 | 25 | SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); 26 | 27 | modelBuilder.Entity("Domain.Entities.Booking", b => 28 | { 29 | b.Property("Id") 30 | .ValueGeneratedOnAdd() 31 | .HasColumnType("int"); 32 | 33 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); 34 | 35 | b.Property("End") 36 | .HasColumnType("datetime2"); 37 | 38 | b.Property("PlacedAt") 39 | .HasColumnType("datetime2"); 40 | 41 | b.Property("Start") 42 | .HasColumnType("datetime2"); 43 | 44 | b.HasKey("Id"); 45 | 46 | b.ToTable("Bookings"); 47 | }); 48 | 49 | modelBuilder.Entity("Domain.Entities.Guest", b => 50 | { 51 | b.Property("Id") 52 | .ValueGeneratedOnAdd() 53 | .HasColumnType("int"); 54 | 55 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); 56 | 57 | b.Property("Email") 58 | .IsRequired() 59 | .HasColumnType("nvarchar(max)"); 60 | 61 | b.Property("Name") 62 | .IsRequired() 63 | .HasColumnType("nvarchar(max)"); 64 | 65 | b.Property("Surname") 66 | .IsRequired() 67 | .HasColumnType("nvarchar(max)"); 68 | 69 | b.HasKey("Id"); 70 | 71 | b.ToTable("Guests"); 72 | }); 73 | 74 | modelBuilder.Entity("Domain.Entities.Room", b => 75 | { 76 | b.Property("Id") 77 | .ValueGeneratedOnAdd() 78 | .HasColumnType("int"); 79 | 80 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); 81 | 82 | b.Property("InMaintenance") 83 | .HasColumnType("bit"); 84 | 85 | b.Property("Level") 86 | .HasColumnType("int"); 87 | 88 | b.Property("Name") 89 | .IsRequired() 90 | .HasColumnType("nvarchar(max)"); 91 | 92 | b.HasKey("Id"); 93 | 94 | b.ToTable("Rooms"); 95 | }); 96 | #pragma warning restore 612, 618 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /BookingService/Tests/Application/ApplicationTests/CreateRoomCommandHandlerTests.cs: -------------------------------------------------------------------------------- 1 | using Application.Room.Commands; 2 | using Domain.Entities; 3 | using Domain.Room.Ports; 4 | using Moq; 5 | using NUnit.Framework; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace ApplicationTests 10 | { 11 | public class CreateRoomCommandHandlerTests 12 | { 13 | [Test] 14 | public async Task Should_Not_CreateRoom_If_Name_IsNot_Provided() 15 | { 16 | var command = new CreateRoomCommand() 17 | { 18 | RoomDto = new Application.Room.Dtos.RoomDto() 19 | { 20 | InMaintenance = false, 21 | Level = 1, 22 | Currency = Domain.Enums.AcceptedCurrencies.Dollar, 23 | Name = "", 24 | Price = 100 25 | } 26 | }; 27 | 28 | var repoMock = new Mock(); 29 | repoMock.Setup(x => x.Create(It.IsAny())) 30 | .Returns(Task.FromResult(1)); 31 | var handler = new CreateRoomCommandHandler(repoMock.Object); 32 | 33 | var res = await handler.Handle(command, CancellationToken.None); 34 | 35 | Assert.NotNull(res); 36 | Assert.False(res.Success); 37 | Assert.AreEqual(res.ErrorCode, Application.Responses.ErrorCodes.ROOM_MISSING_REQUIRED_INFORMATION); 38 | Assert.AreEqual(res.Message, "Missing required information passed"); 39 | } 40 | 41 | [Test] 42 | public async Task Should_Not_CreateRoom_If_Price_Is_Invalid() 43 | { 44 | var command = new CreateRoomCommand() 45 | { 46 | RoomDto = new Application.Room.Dtos.RoomDto() 47 | { 48 | InMaintenance = false, 49 | Level = 1, 50 | Currency = Domain.Enums.AcceptedCurrencies.Dollar, 51 | Name = "Room test", 52 | Price = 9 53 | } 54 | }; 55 | 56 | var repoMock = new Mock(); 57 | repoMock.Setup(x => x.Create(It.IsAny())) 58 | .Returns(Task.FromResult(1)); 59 | var handler = new CreateRoomCommandHandler(repoMock.Object); 60 | 61 | var res = await handler.Handle(command, CancellationToken.None); 62 | 63 | Assert.NotNull(res); 64 | Assert.False(res.Success); 65 | Assert.AreEqual(res.ErrorCode, Application.Responses.ErrorCodes.ROOM_MISSING_REQUIRED_INFORMATION); 66 | Assert.AreEqual(res.Message, "Room price is invalid"); 67 | } 68 | 69 | 70 | [Test] 71 | public async Task Should_CreateRoom() 72 | { 73 | var command = new CreateRoomCommand() 74 | { 75 | RoomDto = new Application.Room.Dtos.RoomDto() 76 | { 77 | InMaintenance = false, 78 | Level = 1, 79 | Currency = Domain.Enums.AcceptedCurrencies.Dollar, 80 | Name = "Room test", 81 | Price = 100 82 | } 83 | }; 84 | 85 | var repoMock = new Mock(); 86 | repoMock.Setup(x => x.Create(It.IsAny())) 87 | .Returns(Task.FromResult(1)); 88 | var handler = new CreateRoomCommandHandler(repoMock.Object); 89 | 90 | var res = await handler.Handle(command, CancellationToken.None); 91 | 92 | Assert.NotNull(res); 93 | Assert.True(res.Success); 94 | Assert.NotNull(res.Data); 95 | Assert.AreEqual(res.Data.Id, 1); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /BookingService/Core/Application/Booking/Commands/CreateBookingCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using Application.Booking.Dtos; 2 | using Application.Payment; 3 | using Application.Responses; 4 | using Domain.Booking.Exceptions; 5 | using Domain.Booking.Ports; 6 | using Domain.Ports; 7 | using Domain.Room.Ports; 8 | using MediatR; 9 | 10 | namespace Application.Booking.Commands 11 | { 12 | public class CreateBookingCommandHandler : IRequestHandler 13 | { 14 | private readonly IBookingRepository _bookingRepository; 15 | private readonly IRoomRepository _roomRepository; 16 | private readonly IGuestRepository _guestRepository; 17 | private readonly IPaymentProcessorFactory _paymentProcessorFactory; 18 | public CreateBookingCommandHandler( 19 | IBookingRepository bookingRepository, 20 | IRoomRepository roomRepository, 21 | IGuestRepository guestRepository) 22 | { 23 | _roomRepository = roomRepository; 24 | _guestRepository = guestRepository; 25 | _bookingRepository = bookingRepository; 26 | } 27 | 28 | public async Task Handle(CreateBookingCommand request, CancellationToken cancellationToken) 29 | { 30 | try 31 | { 32 | var bookingDto = request.BookingDto; 33 | var booking = BookingDto.MapToEntity(bookingDto); 34 | booking.Guest = await _guestRepository.Get(bookingDto.GuestId); 35 | booking.Room = await _roomRepository.GetAggregate(bookingDto.RoomId); 36 | 37 | await booking.Save(_bookingRepository); 38 | 39 | bookingDto.Id = booking.Id; 40 | 41 | return new BookingResponse 42 | { 43 | Success = true, 44 | Data = bookingDto, 45 | }; 46 | } 47 | catch (PlacedAtIsARequiredInformationException) 48 | { 49 | return new BookingResponse 50 | { 51 | Success = false, 52 | ErrorCode = ErrorCodes.BOOKING_MISSING_REQUIRED_INFORMATION, 53 | Message = "PlacedAt is a required information" 54 | }; 55 | } 56 | catch (StartDateTimeIsRequiredException) 57 | { 58 | return new BookingResponse 59 | { 60 | Success = false, 61 | ErrorCode = ErrorCodes.BOOKING_MISSING_REQUIRED_INFORMATION, 62 | Message = "Start is a required information" 63 | }; 64 | } 65 | catch (RoomIsRequiredException) 66 | { 67 | return new BookingResponse 68 | { 69 | Success = false, 70 | ErrorCode = ErrorCodes.BOOKING_MISSING_REQUIRED_INFORMATION, 71 | Message = "Room is a required information" 72 | }; 73 | } 74 | catch (GuestIsRequiredException) 75 | { 76 | return new BookingResponse 77 | { 78 | Success = false, 79 | ErrorCode = ErrorCodes.BOOKING_MISSING_REQUIRED_INFORMATION, 80 | Message = "Guest is a required information" 81 | }; 82 | } 83 | catch (RoomCannotBeBookedException) 84 | { 85 | return new BookingResponse 86 | { 87 | Success = false, 88 | ErrorCode = ErrorCodes.BOOKING_ROOM_CANNOT_BE_BOOKED, 89 | Message = "The selected Room is not available" 90 | }; 91 | } 92 | catch (Exception) 93 | { 94 | return new BookingResponse 95 | { 96 | Success = false, 97 | ErrorCode = ErrorCodes.BOOKING_COULD_NOT_STORE_DATA, 98 | Message = "There was an error when saving to DB" 99 | }; 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /BookingService/Adapters/Data/Migrations/20220320181842_AddingForeignKeyToBooking.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Data; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.EntityFrameworkCore.Infrastructure; 6 | using Microsoft.EntityFrameworkCore.Metadata; 7 | using Microsoft.EntityFrameworkCore.Migrations; 8 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 9 | 10 | #nullable disable 11 | 12 | namespace Data.Migrations 13 | { 14 | [DbContext(typeof(HotelDbContext))] 15 | [Migration("20220320181842_AddingForeignKeyToBooking")] 16 | partial class AddingForeignKeyToBooking 17 | { 18 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 19 | { 20 | #pragma warning disable 612, 618 21 | modelBuilder 22 | .HasAnnotation("ProductVersion", "6.0.3") 23 | .HasAnnotation("Relational:MaxIdentifierLength", 128); 24 | 25 | SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); 26 | 27 | modelBuilder.Entity("Domain.Entities.Booking", b => 28 | { 29 | b.Property("Id") 30 | .ValueGeneratedOnAdd() 31 | .HasColumnType("int"); 32 | 33 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); 34 | 35 | b.Property("End") 36 | .HasColumnType("datetime2"); 37 | 38 | b.Property("GuestId") 39 | .HasColumnType("int"); 40 | 41 | b.Property("PlacedAt") 42 | .HasColumnType("datetime2"); 43 | 44 | b.Property("RoomId") 45 | .HasColumnType("int"); 46 | 47 | b.Property("Start") 48 | .HasColumnType("datetime2"); 49 | 50 | b.HasKey("Id"); 51 | 52 | b.HasIndex("GuestId"); 53 | 54 | b.HasIndex("RoomId"); 55 | 56 | b.ToTable("Bookings"); 57 | }); 58 | 59 | modelBuilder.Entity("Domain.Entities.Guest", b => 60 | { 61 | b.Property("Id") 62 | .ValueGeneratedOnAdd() 63 | .HasColumnType("int"); 64 | 65 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); 66 | 67 | b.Property("Email") 68 | .IsRequired() 69 | .HasColumnType("nvarchar(max)"); 70 | 71 | b.Property("Name") 72 | .IsRequired() 73 | .HasColumnType("nvarchar(max)"); 74 | 75 | b.Property("Surname") 76 | .IsRequired() 77 | .HasColumnType("nvarchar(max)"); 78 | 79 | b.HasKey("Id"); 80 | 81 | b.ToTable("Guests"); 82 | }); 83 | 84 | modelBuilder.Entity("Domain.Entities.Room", b => 85 | { 86 | b.Property("Id") 87 | .ValueGeneratedOnAdd() 88 | .HasColumnType("int"); 89 | 90 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); 91 | 92 | b.Property("InMaintenance") 93 | .HasColumnType("bit"); 94 | 95 | b.Property("Level") 96 | .HasColumnType("int"); 97 | 98 | b.Property("Name") 99 | .IsRequired() 100 | .HasColumnType("nvarchar(max)"); 101 | 102 | b.HasKey("Id"); 103 | 104 | b.ToTable("Rooms"); 105 | }); 106 | 107 | modelBuilder.Entity("Domain.Entities.Booking", b => 108 | { 109 | b.HasOne("Domain.Entities.Guest", "Guest") 110 | .WithMany() 111 | .HasForeignKey("GuestId") 112 | .OnDelete(DeleteBehavior.Cascade) 113 | .IsRequired(); 114 | 115 | b.HasOne("Domain.Entities.Room", "Room") 116 | .WithMany() 117 | .HasForeignKey("RoomId") 118 | .OnDelete(DeleteBehavior.Cascade) 119 | .IsRequired(); 120 | 121 | b.Navigation("Guest"); 122 | 123 | b.Navigation("Room"); 124 | }); 125 | #pragma warning restore 612, 618 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /BookingService/Core/Application/Booking/BookingManager.cs: -------------------------------------------------------------------------------- 1 | using Application.Booking.Dtos; 2 | using Application.Booking.Ports; 3 | using Application.Payment; 4 | using Application.Payment.Responses; 5 | using Application.Responses; 6 | using Domain.Booking.Exceptions; 7 | using Domain.Booking.Ports; 8 | using Domain.Ports; 9 | using Domain.Room.Ports; 10 | 11 | namespace Application.Booking 12 | { 13 | public class BookingManager : IBookingManager 14 | { 15 | private readonly IBookingRepository _bookingRepository; 16 | private readonly IRoomRepository _roomRepository; 17 | private readonly IGuestRepository _guestRepository; 18 | private readonly IPaymentProcessorFactory _paymentProcessorFactory; 19 | public BookingManager(IBookingRepository bookingRepository, 20 | IRoomRepository roomRepository, 21 | IGuestRepository guestRepository, 22 | IPaymentProcessorFactory paymentProcessorFactory) 23 | { 24 | _roomRepository = roomRepository; 25 | _guestRepository = guestRepository; 26 | _bookingRepository = bookingRepository; 27 | _paymentProcessorFactory = paymentProcessorFactory; 28 | } 29 | public async Task CreateBooking(BookingDto bookingDto) 30 | { 31 | try 32 | { 33 | var booking = BookingDto.MapToEntity(bookingDto); 34 | booking.Guest = await _guestRepository.Get(bookingDto.GuestId); 35 | booking.Room = await _roomRepository.GetAggregate(bookingDto.RoomId); 36 | 37 | await booking.Save(_bookingRepository); 38 | 39 | bookingDto.Id = booking.Id; 40 | 41 | return new BookingResponse 42 | { 43 | Success = true, 44 | Data = bookingDto, 45 | }; 46 | } 47 | catch (PlacedAtIsARequiredInformationException) 48 | { 49 | return new BookingResponse 50 | { 51 | Success = false, 52 | ErrorCode = ErrorCodes.BOOKING_MISSING_REQUIRED_INFORMATION, 53 | Message = "PlacedAt is a required information" 54 | }; 55 | } 56 | catch (StartDateTimeIsRequiredException) 57 | { 58 | return new BookingResponse 59 | { 60 | Success = false, 61 | ErrorCode = ErrorCodes.BOOKING_MISSING_REQUIRED_INFORMATION, 62 | Message = "Start is a required information" 63 | }; 64 | } 65 | catch (RoomIsRequiredException) 66 | { 67 | return new BookingResponse 68 | { 69 | Success = false, 70 | ErrorCode = ErrorCodes.BOOKING_MISSING_REQUIRED_INFORMATION, 71 | Message = "Room is a required information" 72 | }; 73 | } 74 | catch (GuestIsRequiredException) 75 | { 76 | return new BookingResponse 77 | { 78 | Success = false, 79 | ErrorCode = ErrorCodes.BOOKING_MISSING_REQUIRED_INFORMATION, 80 | Message = "Guest is a required information" 81 | }; 82 | } 83 | catch (RoomCannotBeBookedException) 84 | { 85 | return new BookingResponse 86 | { 87 | Success = false, 88 | ErrorCode = ErrorCodes.BOOKING_ROOM_CANNOT_BE_BOOKED, 89 | Message = "The selected Room is not available" 90 | }; 91 | } 92 | catch (Exception) 93 | { 94 | return new BookingResponse 95 | { 96 | Success = false, 97 | ErrorCode = ErrorCodes.BOOKING_COULD_NOT_STORE_DATA, 98 | Message = "There was an error when saving to DB" 99 | }; 100 | } 101 | } 102 | 103 | public async Task PayForABooking(PaymentRequestDto paymentRequestDto) 104 | { 105 | var paymentProcessor = _paymentProcessorFactory.GetPaymentProcessor(paymentRequestDto.SelectedPaymentProvider); 106 | 107 | var response = await paymentProcessor.CapturePayment(paymentRequestDto.PaymentIntention); 108 | 109 | if (response.Success) 110 | { 111 | return new PaymentResponse 112 | { 113 | Success = true, 114 | Data = response.Data, 115 | Message = "Payment successfully processed" 116 | }; 117 | } 118 | 119 | return response; 120 | } 121 | 122 | public Task GetBooking(int id) 123 | { 124 | throw new NotImplementedException(); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /BookingService/Adapters/Data/Migrations/20220320183650_AddingValueObjectToGuest.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Data; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.EntityFrameworkCore.Infrastructure; 6 | using Microsoft.EntityFrameworkCore.Metadata; 7 | using Microsoft.EntityFrameworkCore.Migrations; 8 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 9 | 10 | #nullable disable 11 | 12 | namespace Data.Migrations 13 | { 14 | [DbContext(typeof(HotelDbContext))] 15 | [Migration("20220320183650_AddingValueObjectToGuest")] 16 | partial class AddingValueObjectToGuest 17 | { 18 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 19 | { 20 | #pragma warning disable 612, 618 21 | modelBuilder 22 | .HasAnnotation("ProductVersion", "6.0.3") 23 | .HasAnnotation("Relational:MaxIdentifierLength", 128); 24 | 25 | SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); 26 | 27 | modelBuilder.Entity("Domain.Entities.Booking", b => 28 | { 29 | b.Property("Id") 30 | .ValueGeneratedOnAdd() 31 | .HasColumnType("int"); 32 | 33 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); 34 | 35 | b.Property("End") 36 | .HasColumnType("datetime2"); 37 | 38 | b.Property("GuestId") 39 | .HasColumnType("int"); 40 | 41 | b.Property("PlacedAt") 42 | .HasColumnType("datetime2"); 43 | 44 | b.Property("RoomId") 45 | .HasColumnType("int"); 46 | 47 | b.Property("Start") 48 | .HasColumnType("datetime2"); 49 | 50 | b.HasKey("Id"); 51 | 52 | b.HasIndex("GuestId"); 53 | 54 | b.HasIndex("RoomId"); 55 | 56 | b.ToTable("Bookings"); 57 | }); 58 | 59 | modelBuilder.Entity("Domain.Entities.Guest", b => 60 | { 61 | b.Property("Id") 62 | .ValueGeneratedOnAdd() 63 | .HasColumnType("int"); 64 | 65 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); 66 | 67 | b.Property("Email") 68 | .IsRequired() 69 | .HasColumnType("nvarchar(max)"); 70 | 71 | b.Property("Name") 72 | .IsRequired() 73 | .HasColumnType("nvarchar(max)"); 74 | 75 | b.Property("Surname") 76 | .IsRequired() 77 | .HasColumnType("nvarchar(max)"); 78 | 79 | b.HasKey("Id"); 80 | 81 | b.ToTable("Guests"); 82 | }); 83 | 84 | modelBuilder.Entity("Domain.Entities.Room", b => 85 | { 86 | b.Property("Id") 87 | .ValueGeneratedOnAdd() 88 | .HasColumnType("int"); 89 | 90 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); 91 | 92 | b.Property("InMaintenance") 93 | .HasColumnType("bit"); 94 | 95 | b.Property("Level") 96 | .HasColumnType("int"); 97 | 98 | b.Property("Name") 99 | .IsRequired() 100 | .HasColumnType("nvarchar(max)"); 101 | 102 | b.HasKey("Id"); 103 | 104 | b.ToTable("Rooms"); 105 | }); 106 | 107 | modelBuilder.Entity("Domain.Entities.Booking", b => 108 | { 109 | b.HasOne("Domain.Entities.Guest", "Guest") 110 | .WithMany() 111 | .HasForeignKey("GuestId") 112 | .OnDelete(DeleteBehavior.Cascade) 113 | .IsRequired(); 114 | 115 | b.HasOne("Domain.Entities.Room", "Room") 116 | .WithMany() 117 | .HasForeignKey("RoomId") 118 | .OnDelete(DeleteBehavior.Cascade) 119 | .IsRequired(); 120 | 121 | b.Navigation("Guest"); 122 | 123 | b.Navigation("Room"); 124 | }); 125 | 126 | modelBuilder.Entity("Domain.Entities.Guest", b => 127 | { 128 | b.OwnsOne("Domain.ValueObjects.PersonId", "DocumentId", b1 => 129 | { 130 | b1.Property("GuestId") 131 | .HasColumnType("int"); 132 | 133 | b1.Property("DocumentType") 134 | .HasColumnType("int"); 135 | 136 | b1.Property("IdNumber") 137 | .IsRequired() 138 | .HasColumnType("nvarchar(max)"); 139 | 140 | b1.HasKey("GuestId"); 141 | 142 | b1.ToTable("Guests"); 143 | 144 | b1.WithOwner() 145 | .HasForeignKey("GuestId"); 146 | }); 147 | 148 | b.Navigation("DocumentId") 149 | .IsRequired(); 150 | }); 151 | #pragma warning restore 612, 618 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /BookingService/Tests/Application/ApplicationTests/GuestManagerTests.cs: -------------------------------------------------------------------------------- 1 | using Application; 2 | using Application.Guest.DTO; 3 | using Application.Guest.Requests; 4 | using Application.Responses; 5 | using Domain.Entities; 6 | using Domain.Ports; 7 | using Moq; 8 | using NUnit.Framework; 9 | using System.Threading.Tasks; 10 | 11 | namespace ApplicationTests 12 | { 13 | public class Tests 14 | { 15 | GuestManager guestManager; 16 | 17 | [SetUp] 18 | public void Setup() 19 | { 20 | } 21 | 22 | [Test] 23 | public async Task HappyPath() 24 | { 25 | var guestDto = new GuestDto 26 | { 27 | Name = "Fulano", 28 | Surname = "Ciclano", 29 | Email = "abc@gmail.com", 30 | IdNumber = "abca", 31 | IdTypeCode = 1 32 | }; 33 | 34 | int expectedId = 222; 35 | 36 | var request = new CreateGuestRequest() 37 | { 38 | Data = guestDto, 39 | }; 40 | 41 | var fakeRepo = new Mock(); 42 | 43 | fakeRepo.Setup(x => x.Create( 44 | It.IsAny())).Returns(Task.FromResult(expectedId)); 45 | 46 | guestManager = new GuestManager(fakeRepo.Object); 47 | 48 | var res = await guestManager.CreateGuest(request); 49 | Assert.IsNotNull(res); 50 | Assert.True(res.Success); 51 | Assert.AreEqual(res.Data.Id, expectedId); 52 | Assert.AreEqual(res.Data.Name, guestDto.Name); 53 | } 54 | 55 | [TestCase("")] 56 | [TestCase(null)] 57 | [TestCase("a")] 58 | [TestCase("ab")] 59 | [TestCase("abc")] 60 | public async Task Should_Return_InvalidPersonDocumentIdException_WhenDocsAreInvalid(string docNumber) 61 | { 62 | var guestDto = new GuestDto 63 | { 64 | Name = "Fulano", 65 | Surname = "Ciclano", 66 | Email = "abc@gmail.com", 67 | IdNumber = docNumber, 68 | IdTypeCode = 1 69 | }; 70 | 71 | var request = new CreateGuestRequest() 72 | { 73 | Data = guestDto, 74 | }; 75 | 76 | var fakeRepo = new Mock(); 77 | 78 | fakeRepo.Setup(x => x.Create( 79 | It.IsAny())).Returns(Task.FromResult(222)); 80 | 81 | guestManager = new GuestManager(fakeRepo.Object); 82 | 83 | var res = await guestManager.CreateGuest(request); 84 | 85 | Assert.IsNotNull(res); 86 | Assert.False(res.Success); 87 | Assert.AreEqual(res.ErrorCode, ErrorCodes.INVALID_PERSON_ID); 88 | Assert.AreEqual(res.Message, "The ID passed is not valid"); 89 | } 90 | 91 | 92 | [TestCase("", "surnametest", "asdf@gmail.com")] 93 | [TestCase(null, "surnametest", "asdf@gmail.com")] 94 | [TestCase("Fulano", "", "asdf@gmail.com")] 95 | [TestCase("Fulano", null, "asdf@gmail.com")] 96 | [TestCase("Fulano", "surnametest", "")] 97 | [TestCase("Fulano", "surnametest", null)] 98 | public async Task Should_Return_MissingRequiredInformation_WhenDocsAreInvalid(string name, string surname, string email) 99 | { 100 | var guestDto = new GuestDto 101 | { 102 | Name = name, 103 | Surname = surname, 104 | Email = email, 105 | IdNumber = "abcd", 106 | IdTypeCode = 1 107 | }; 108 | 109 | var request = new CreateGuestRequest() 110 | { 111 | Data = guestDto, 112 | }; 113 | 114 | var fakeRepo = new Mock(); 115 | 116 | fakeRepo.Setup(x => x.Create( 117 | It.IsAny())).Returns(Task.FromResult(222)); 118 | 119 | guestManager = new GuestManager(fakeRepo.Object); 120 | 121 | var res = await guestManager.CreateGuest(request); 122 | 123 | Assert.IsNotNull(res); 124 | Assert.False(res.Success); 125 | Assert.AreEqual(res.ErrorCode, ErrorCodes.MISSING_REQUIRED_INFORMATION); 126 | Assert.AreEqual(res.Message, "Missing required information passed"); 127 | } 128 | 129 | [Test] 130 | public async Task Should_Return_GuestNotFound_When_GuestDoesntExist() 131 | { 132 | var fakeRepo = new Mock(); 133 | 134 | fakeRepo.Setup(x => x.Get(333)).Returns(Task.FromResult(null)); 135 | 136 | guestManager = new GuestManager(fakeRepo.Object); 137 | 138 | var res = await guestManager.GetGuest(333); 139 | 140 | Assert.IsNotNull(res); 141 | Assert.False(res.Success); 142 | Assert.AreEqual(res.ErrorCode, ErrorCodes.GUEST_NOT_FOUND); 143 | Assert.AreEqual(res.Message, "No Guest record was found with the given Id"); 144 | } 145 | 146 | [Test] 147 | public async Task Should_Return_Guest_Success() 148 | { 149 | var fakeRepo = new Mock(); 150 | 151 | var fakeGuest = new Guest 152 | { 153 | Id = 333, 154 | Name = "Test", 155 | DocumentId = new Domain.ValueObjects.PersonId 156 | { 157 | DocumentType = Domain.Enums.DocumentType.DriveLicence, 158 | IdNumber = "123" 159 | } 160 | }; 161 | 162 | fakeRepo.Setup(x => x.Get(333)).Returns(Task.FromResult((Guest?)fakeGuest)); 163 | 164 | guestManager = new GuestManager(fakeRepo.Object); 165 | 166 | var res = await guestManager.GetGuest(333); 167 | 168 | Assert.IsNotNull(res); 169 | Assert.True(res.Success); 170 | Assert.AreEqual(res.Data.Id, fakeGuest.Id); 171 | Assert.AreEqual(res.Data.Name, fakeGuest.Name); 172 | } 173 | } 174 | } -------------------------------------------------------------------------------- /BookingService/Adapters/Data/Migrations/20220320184229_AddingValueObjectToRoom.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Data; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.EntityFrameworkCore.Infrastructure; 6 | using Microsoft.EntityFrameworkCore.Metadata; 7 | using Microsoft.EntityFrameworkCore.Migrations; 8 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 9 | 10 | #nullable disable 11 | 12 | namespace Data.Migrations 13 | { 14 | [DbContext(typeof(HotelDbContext))] 15 | [Migration("20220320184229_AddingValueObjectToRoom")] 16 | partial class AddingValueObjectToRoom 17 | { 18 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 19 | { 20 | #pragma warning disable 612, 618 21 | modelBuilder 22 | .HasAnnotation("ProductVersion", "6.0.3") 23 | .HasAnnotation("Relational:MaxIdentifierLength", 128); 24 | 25 | SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); 26 | 27 | modelBuilder.Entity("Domain.Entities.Booking", b => 28 | { 29 | b.Property("Id") 30 | .ValueGeneratedOnAdd() 31 | .HasColumnType("int"); 32 | 33 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); 34 | 35 | b.Property("End") 36 | .HasColumnType("datetime2"); 37 | 38 | b.Property("GuestId") 39 | .HasColumnType("int"); 40 | 41 | b.Property("PlacedAt") 42 | .HasColumnType("datetime2"); 43 | 44 | b.Property("RoomId") 45 | .HasColumnType("int"); 46 | 47 | b.Property("Start") 48 | .HasColumnType("datetime2"); 49 | 50 | b.HasKey("Id"); 51 | 52 | b.HasIndex("GuestId"); 53 | 54 | b.HasIndex("RoomId"); 55 | 56 | b.ToTable("Bookings"); 57 | }); 58 | 59 | modelBuilder.Entity("Domain.Entities.Guest", b => 60 | { 61 | b.Property("Id") 62 | .ValueGeneratedOnAdd() 63 | .HasColumnType("int"); 64 | 65 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); 66 | 67 | b.Property("Email") 68 | .IsRequired() 69 | .HasColumnType("nvarchar(max)"); 70 | 71 | b.Property("Name") 72 | .IsRequired() 73 | .HasColumnType("nvarchar(max)"); 74 | 75 | b.Property("Surname") 76 | .IsRequired() 77 | .HasColumnType("nvarchar(max)"); 78 | 79 | b.HasKey("Id"); 80 | 81 | b.ToTable("Guests"); 82 | }); 83 | 84 | modelBuilder.Entity("Domain.Entities.Room", b => 85 | { 86 | b.Property("Id") 87 | .ValueGeneratedOnAdd() 88 | .HasColumnType("int"); 89 | 90 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); 91 | 92 | b.Property("InMaintenance") 93 | .HasColumnType("bit"); 94 | 95 | b.Property("Level") 96 | .HasColumnType("int"); 97 | 98 | b.Property("Name") 99 | .IsRequired() 100 | .HasColumnType("nvarchar(max)"); 101 | 102 | b.HasKey("Id"); 103 | 104 | b.ToTable("Rooms"); 105 | }); 106 | 107 | modelBuilder.Entity("Domain.Entities.Booking", b => 108 | { 109 | b.HasOne("Domain.Entities.Guest", "Guest") 110 | .WithMany() 111 | .HasForeignKey("GuestId") 112 | .OnDelete(DeleteBehavior.Cascade) 113 | .IsRequired(); 114 | 115 | b.HasOne("Domain.Entities.Room", "Room") 116 | .WithMany() 117 | .HasForeignKey("RoomId") 118 | .OnDelete(DeleteBehavior.Cascade) 119 | .IsRequired(); 120 | 121 | b.Navigation("Guest"); 122 | 123 | b.Navigation("Room"); 124 | }); 125 | 126 | modelBuilder.Entity("Domain.Entities.Guest", b => 127 | { 128 | b.OwnsOne("Domain.ValueObjects.PersonId", "DocumentId", b1 => 129 | { 130 | b1.Property("GuestId") 131 | .HasColumnType("int"); 132 | 133 | b1.Property("DocumentType") 134 | .HasColumnType("int"); 135 | 136 | b1.Property("IdNumber") 137 | .IsRequired() 138 | .HasColumnType("nvarchar(max)"); 139 | 140 | b1.HasKey("GuestId"); 141 | 142 | b1.ToTable("Guests"); 143 | 144 | b1.WithOwner() 145 | .HasForeignKey("GuestId"); 146 | }); 147 | 148 | b.Navigation("DocumentId") 149 | .IsRequired(); 150 | }); 151 | 152 | modelBuilder.Entity("Domain.Entities.Room", b => 153 | { 154 | b.OwnsOne("Domain.ValueObjects.Price", "Price", b1 => 155 | { 156 | b1.Property("RoomId") 157 | .HasColumnType("int"); 158 | 159 | b1.Property("Currency") 160 | .HasColumnType("int"); 161 | 162 | b1.Property("Value") 163 | .HasColumnType("decimal(18,2)"); 164 | 165 | b1.HasKey("RoomId"); 166 | 167 | b1.ToTable("Rooms"); 168 | 169 | b1.WithOwner() 170 | .HasForeignKey("RoomId"); 171 | }); 172 | 173 | b.Navigation("Price") 174 | .IsRequired(); 175 | }); 176 | #pragma warning restore 612, 618 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /BookingService/Adapters/Data/Migrations/HotelDbContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Data; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.EntityFrameworkCore.Infrastructure; 6 | using Microsoft.EntityFrameworkCore.Metadata; 7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 8 | 9 | #nullable disable 10 | 11 | namespace Data.Migrations 12 | { 13 | [DbContext(typeof(HotelDbContext))] 14 | partial class HotelDbContextModelSnapshot : ModelSnapshot 15 | { 16 | protected override void BuildModel(ModelBuilder modelBuilder) 17 | { 18 | #pragma warning disable 612, 618 19 | modelBuilder 20 | .HasAnnotation("ProductVersion", "6.0.3") 21 | .HasAnnotation("Relational:MaxIdentifierLength", 128); 22 | 23 | SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); 24 | 25 | modelBuilder.Entity("Domain.Entities.Booking", b => 26 | { 27 | b.Property("Id") 28 | .ValueGeneratedOnAdd() 29 | .HasColumnType("int"); 30 | 31 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); 32 | 33 | b.Property("End") 34 | .HasColumnType("datetime2"); 35 | 36 | b.Property("GuestId") 37 | .HasColumnType("int"); 38 | 39 | b.Property("PlacedAt") 40 | .HasColumnType("datetime2"); 41 | 42 | b.Property("RoomId") 43 | .HasColumnType("int"); 44 | 45 | b.Property("Start") 46 | .HasColumnType("datetime2"); 47 | 48 | b.Property("Status") 49 | .HasColumnType("int"); 50 | 51 | b.HasKey("Id"); 52 | 53 | b.HasIndex("GuestId"); 54 | 55 | b.HasIndex("RoomId"); 56 | 57 | b.ToTable("Bookings"); 58 | }); 59 | 60 | modelBuilder.Entity("Domain.Entities.Guest", b => 61 | { 62 | b.Property("Id") 63 | .ValueGeneratedOnAdd() 64 | .HasColumnType("int"); 65 | 66 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); 67 | 68 | b.Property("Email") 69 | .IsRequired() 70 | .HasColumnType("nvarchar(max)"); 71 | 72 | b.Property("Name") 73 | .IsRequired() 74 | .HasColumnType("nvarchar(max)"); 75 | 76 | b.Property("Surname") 77 | .IsRequired() 78 | .HasColumnType("nvarchar(max)"); 79 | 80 | b.HasKey("Id"); 81 | 82 | b.ToTable("Guests"); 83 | }); 84 | 85 | modelBuilder.Entity("Domain.Entities.Room", b => 86 | { 87 | b.Property("Id") 88 | .ValueGeneratedOnAdd() 89 | .HasColumnType("int"); 90 | 91 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); 92 | 93 | b.Property("InMaintenance") 94 | .HasColumnType("bit"); 95 | 96 | b.Property("Level") 97 | .HasColumnType("int"); 98 | 99 | b.Property("Name") 100 | .IsRequired() 101 | .HasColumnType("nvarchar(max)"); 102 | 103 | b.HasKey("Id"); 104 | 105 | b.ToTable("Rooms"); 106 | }); 107 | 108 | modelBuilder.Entity("Domain.Entities.Booking", b => 109 | { 110 | b.HasOne("Domain.Entities.Guest", "Guest") 111 | .WithMany() 112 | .HasForeignKey("GuestId") 113 | .OnDelete(DeleteBehavior.Cascade) 114 | .IsRequired(); 115 | 116 | b.HasOne("Domain.Entities.Room", "Room") 117 | .WithMany("Bookings") 118 | .HasForeignKey("RoomId") 119 | .OnDelete(DeleteBehavior.Cascade) 120 | .IsRequired(); 121 | 122 | b.Navigation("Guest"); 123 | 124 | b.Navigation("Room"); 125 | }); 126 | 127 | modelBuilder.Entity("Domain.Entities.Guest", b => 128 | { 129 | b.OwnsOne("Domain.ValueObjects.PersonId", "DocumentId", b1 => 130 | { 131 | b1.Property("GuestId") 132 | .HasColumnType("int"); 133 | 134 | b1.Property("DocumentType") 135 | .HasColumnType("int"); 136 | 137 | b1.Property("IdNumber") 138 | .IsRequired() 139 | .HasColumnType("nvarchar(max)"); 140 | 141 | b1.HasKey("GuestId"); 142 | 143 | b1.ToTable("Guests"); 144 | 145 | b1.WithOwner() 146 | .HasForeignKey("GuestId"); 147 | }); 148 | 149 | b.Navigation("DocumentId") 150 | .IsRequired(); 151 | }); 152 | 153 | modelBuilder.Entity("Domain.Entities.Room", b => 154 | { 155 | b.OwnsOne("Domain.ValueObjects.Price", "Price", b1 => 156 | { 157 | b1.Property("RoomId") 158 | .HasColumnType("int"); 159 | 160 | b1.Property("Currency") 161 | .HasColumnType("int"); 162 | 163 | b1.Property("Value") 164 | .HasColumnType("decimal(18,2)"); 165 | 166 | b1.HasKey("RoomId"); 167 | 168 | b1.ToTable("Rooms"); 169 | 170 | b1.WithOwner() 171 | .HasForeignKey("RoomId"); 172 | }); 173 | 174 | b.Navigation("Price") 175 | .IsRequired(); 176 | }); 177 | 178 | modelBuilder.Entity("Domain.Entities.Room", b => 179 | { 180 | b.Navigation("Bookings"); 181 | }); 182 | #pragma warning restore 612, 618 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /BookingService/Adapters/Data/Migrations/20220402211259_FixingStatus.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using Data; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.EntityFrameworkCore.Infrastructure; 6 | using Microsoft.EntityFrameworkCore.Metadata; 7 | using Microsoft.EntityFrameworkCore.Migrations; 8 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 9 | 10 | #nullable disable 11 | 12 | namespace Data.Migrations 13 | { 14 | [DbContext(typeof(HotelDbContext))] 15 | [Migration("20220402211259_FixingStatus")] 16 | partial class FixingStatus 17 | { 18 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 19 | { 20 | #pragma warning disable 612, 618 21 | modelBuilder 22 | .HasAnnotation("ProductVersion", "6.0.3") 23 | .HasAnnotation("Relational:MaxIdentifierLength", 128); 24 | 25 | SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); 26 | 27 | modelBuilder.Entity("Domain.Entities.Booking", b => 28 | { 29 | b.Property("Id") 30 | .ValueGeneratedOnAdd() 31 | .HasColumnType("int"); 32 | 33 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); 34 | 35 | b.Property("End") 36 | .HasColumnType("datetime2"); 37 | 38 | b.Property("GuestId") 39 | .HasColumnType("int"); 40 | 41 | b.Property("PlacedAt") 42 | .HasColumnType("datetime2"); 43 | 44 | b.Property("RoomId") 45 | .HasColumnType("int"); 46 | 47 | b.Property("Start") 48 | .HasColumnType("datetime2"); 49 | 50 | b.Property("Status") 51 | .HasColumnType("int"); 52 | 53 | b.HasKey("Id"); 54 | 55 | b.HasIndex("GuestId"); 56 | 57 | b.HasIndex("RoomId"); 58 | 59 | b.ToTable("Bookings"); 60 | }); 61 | 62 | modelBuilder.Entity("Domain.Entities.Guest", b => 63 | { 64 | b.Property("Id") 65 | .ValueGeneratedOnAdd() 66 | .HasColumnType("int"); 67 | 68 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); 69 | 70 | b.Property("Email") 71 | .IsRequired() 72 | .HasColumnType("nvarchar(max)"); 73 | 74 | b.Property("Name") 75 | .IsRequired() 76 | .HasColumnType("nvarchar(max)"); 77 | 78 | b.Property("Surname") 79 | .IsRequired() 80 | .HasColumnType("nvarchar(max)"); 81 | 82 | b.HasKey("Id"); 83 | 84 | b.ToTable("Guests"); 85 | }); 86 | 87 | modelBuilder.Entity("Domain.Entities.Room", b => 88 | { 89 | b.Property("Id") 90 | .ValueGeneratedOnAdd() 91 | .HasColumnType("int"); 92 | 93 | SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); 94 | 95 | b.Property("InMaintenance") 96 | .HasColumnType("bit"); 97 | 98 | b.Property("Level") 99 | .HasColumnType("int"); 100 | 101 | b.Property("Name") 102 | .IsRequired() 103 | .HasColumnType("nvarchar(max)"); 104 | 105 | b.HasKey("Id"); 106 | 107 | b.ToTable("Rooms"); 108 | }); 109 | 110 | modelBuilder.Entity("Domain.Entities.Booking", b => 111 | { 112 | b.HasOne("Domain.Entities.Guest", "Guest") 113 | .WithMany() 114 | .HasForeignKey("GuestId") 115 | .OnDelete(DeleteBehavior.Cascade) 116 | .IsRequired(); 117 | 118 | b.HasOne("Domain.Entities.Room", "Room") 119 | .WithMany("Bookings") 120 | .HasForeignKey("RoomId") 121 | .OnDelete(DeleteBehavior.Cascade) 122 | .IsRequired(); 123 | 124 | b.Navigation("Guest"); 125 | 126 | b.Navigation("Room"); 127 | }); 128 | 129 | modelBuilder.Entity("Domain.Entities.Guest", b => 130 | { 131 | b.OwnsOne("Domain.ValueObjects.PersonId", "DocumentId", b1 => 132 | { 133 | b1.Property("GuestId") 134 | .HasColumnType("int"); 135 | 136 | b1.Property("DocumentType") 137 | .HasColumnType("int"); 138 | 139 | b1.Property("IdNumber") 140 | .IsRequired() 141 | .HasColumnType("nvarchar(max)"); 142 | 143 | b1.HasKey("GuestId"); 144 | 145 | b1.ToTable("Guests"); 146 | 147 | b1.WithOwner() 148 | .HasForeignKey("GuestId"); 149 | }); 150 | 151 | b.Navigation("DocumentId") 152 | .IsRequired(); 153 | }); 154 | 155 | modelBuilder.Entity("Domain.Entities.Room", b => 156 | { 157 | b.OwnsOne("Domain.ValueObjects.Price", "Price", b1 => 158 | { 159 | b1.Property("RoomId") 160 | .HasColumnType("int"); 161 | 162 | b1.Property("Currency") 163 | .HasColumnType("int"); 164 | 165 | b1.Property("Value") 166 | .HasColumnType("decimal(18,2)"); 167 | 168 | b1.HasKey("RoomId"); 169 | 170 | b1.ToTable("Rooms"); 171 | 172 | b1.WithOwner() 173 | .HasForeignKey("RoomId"); 174 | }); 175 | 176 | b.Navigation("Price") 177 | .IsRequired(); 178 | }); 179 | 180 | modelBuilder.Entity("Domain.Entities.Room", b => 181 | { 182 | b.Navigation("Bookings"); 183 | }); 184 | #pragma warning restore 612, 618 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # ASP.NET Scaffolding 66 | ScaffoldingReadMe.txt 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *_wpftmp.csproj 92 | *.log 93 | *.tlog 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 298 | *.vbp 299 | 300 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 301 | *.dsw 302 | *.dsp 303 | 304 | # Visual Studio 6 technical files 305 | *.ncb 306 | *.aps 307 | 308 | # Visual Studio LightSwitch build output 309 | **/*.HTMLClient/GeneratedArtifacts 310 | **/*.DesktopClient/GeneratedArtifacts 311 | **/*.DesktopClient/ModelManifest.xml 312 | **/*.Server/GeneratedArtifacts 313 | **/*.Server/ModelManifest.xml 314 | _Pvt_Extensions 315 | 316 | # Paket dependency manager 317 | .paket/paket.exe 318 | paket-files/ 319 | 320 | # FAKE - F# Make 321 | .fake/ 322 | 323 | # CodeRush personal settings 324 | .cr/personal 325 | 326 | # Python Tools for Visual Studio (PTVS) 327 | __pycache__/ 328 | *.pyc 329 | 330 | # Cake - Uncomment if you are using it 331 | # tools/** 332 | # !tools/packages.config 333 | 334 | # Tabs Studio 335 | *.tss 336 | 337 | # Telerik's JustMock configuration file 338 | *.jmconfig 339 | 340 | # BizTalk build output 341 | *.btp.cs 342 | *.btm.cs 343 | *.odx.cs 344 | *.xsd.cs 345 | 346 | # OpenCover UI analysis results 347 | OpenCover/ 348 | 349 | # Azure Stream Analytics local run output 350 | ASALocalRun/ 351 | 352 | # MSBuild Binary and Structured Log 353 | *.binlog 354 | 355 | # NVidia Nsight GPU debugger configuration file 356 | *.nvuser 357 | 358 | # MFractors (Xamarin productivity tool) working folder 359 | .mfractor/ 360 | 361 | # Local History for Visual Studio 362 | .localhistory/ 363 | 364 | # Visual Studio History (VSHistory) files 365 | .vshistory/ 366 | 367 | # BeatPulse healthcheck temp database 368 | healthchecksdb 369 | 370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 371 | MigrationBackup/ 372 | 373 | # Ionide (cross platform F# VS Code tools) working folder 374 | .ionide/ 375 | 376 | # Fody - auto-generated XML schema 377 | FodyWeavers.xsd 378 | 379 | # VS Code files for those working on multiple tools 380 | .vscode/* 381 | !.vscode/settings.json 382 | !.vscode/tasks.json 383 | !.vscode/launch.json 384 | !.vscode/extensions.json 385 | *.code-workspace 386 | 387 | # Local History for Visual Studio Code 388 | .history/ 389 | 390 | # Windows Installer files from build outputs 391 | *.cab 392 | *.msi 393 | *.msix 394 | *.msm 395 | *.msp 396 | 397 | # JetBrains Rider 398 | *.sln.iml -------------------------------------------------------------------------------- /BookingService/Tests/Application/ApplicationTests/CreateBookingCommandHandlerTests.cs: -------------------------------------------------------------------------------- 1 | using Application.Booking.Commands; 2 | using Application.Ports; 3 | using Domain.Booking.Ports; 4 | using Domain.Entities; 5 | using Domain.Ports; 6 | using Domain.Room.Ports; 7 | using Moq; 8 | using NUnit.Framework; 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Threading; 12 | using System.Threading.Tasks; 13 | 14 | namespace ApplicationTests 15 | { 16 | public class CreateBookingCommandHandlerTests 17 | { 18 | private CreateBookingCommandHandler GetCommandMock( 19 | Mock roomRepository = null, 20 | Mock guestRepository = null, 21 | Mock bookingRepository = null 22 | ) 23 | { 24 | var _bookingRepository = bookingRepository ?? new Mock(); 25 | var _guestRepository = guestRepository ?? new Mock(); 26 | var _roomRepository = roomRepository ?? new Mock(); 27 | 28 | return new CreateBookingCommandHandler( 29 | _bookingRepository.Object, 30 | _roomRepository.Object, 31 | _guestRepository.Object 32 | ); 33 | } 34 | [Test] 35 | public async Task Should_Not_CreateBooking_If_Room_Is_Missing() 36 | { 37 | var command = new CreateBookingCommand 38 | { 39 | BookingDto = new Application.Booking.Dtos.BookingDto 40 | { 41 | // RoomId= 1, 42 | GuestId = 1, 43 | Start = DateTime.Now, 44 | End = DateTime.Now.AddDays(2), 45 | } 46 | }; 47 | 48 | var fakeGuest = new Guest 49 | { 50 | Id = command.BookingDto.GuestId, 51 | DocumentId = new Domain.ValueObjects.PersonId 52 | { 53 | DocumentType = Domain.Enums.DocumentType.Passport, 54 | IdNumber = "abc1234" 55 | }, 56 | Email = "a@a.com", 57 | Name = "Fake Guest", 58 | Surname = "Fake Guest Surname" 59 | }; 60 | 61 | var guestRepository = new Mock(); 62 | guestRepository.Setup(x => x.Get(command.BookingDto.GuestId)) 63 | .Returns(Task.FromResult(fakeGuest)); 64 | 65 | var fakeRoom = new Room 66 | { 67 | Id = command.BookingDto.RoomId, 68 | InMaintenance = false, 69 | Price = new Domain.ValueObjects.Price 70 | { 71 | Currency = Domain.Enums.AcceptedCurrencies.Dollar, 72 | Value = 100 73 | }, 74 | Name = "Fake Room 01", 75 | Level = 1, 76 | }; 77 | 78 | var fakeBooking = new Booking 79 | { 80 | Id = 1 81 | }; 82 | 83 | var bookingRepository = new Mock(); 84 | bookingRepository.Setup(x => x.CreateBooking(It.IsAny())) 85 | .Returns(Task.FromResult(fakeBooking)); 86 | 87 | var handler = GetCommandMock(null, guestRepository, bookingRepository); 88 | var resp = await handler.Handle(command, CancellationToken.None); 89 | 90 | Assert.NotNull(resp); 91 | Assert.False(resp.Success); 92 | Assert.AreEqual(resp.ErrorCode, Application.Responses.ErrorCodes.BOOKING_MISSING_REQUIRED_INFORMATION); 93 | Assert.AreEqual(resp.Message, "Room is a required information"); 94 | } 95 | 96 | [Test] 97 | public async Task Should_Not_CreateBooking_If_StartDateIsMissing() 98 | { 99 | var command = new CreateBookingCommand 100 | { 101 | BookingDto = new Application.Booking.Dtos.BookingDto 102 | { 103 | RoomId= 1, 104 | GuestId = 1, 105 | //Start = DateTime.Now, 106 | End = DateTime.Now.AddDays(2), 107 | } 108 | }; 109 | 110 | var fakeGuest = new Guest 111 | { 112 | Id = command.BookingDto.GuestId, 113 | DocumentId = new Domain.ValueObjects.PersonId 114 | { 115 | DocumentType = Domain.Enums.DocumentType.Passport, 116 | IdNumber = "abc1234" 117 | }, 118 | Email = "a@a.com", 119 | Name = "Fake Guest", 120 | Surname = "Fake Guest Surname" 121 | }; 122 | 123 | var guestRepository = new Mock(); 124 | guestRepository.Setup(x => x.Get(command.BookingDto.GuestId)) 125 | .Returns(Task.FromResult(fakeGuest)); 126 | 127 | var fakeRoom = new Room 128 | { 129 | Id = command.BookingDto.RoomId, 130 | InMaintenance = false, 131 | Price = new Domain.ValueObjects.Price 132 | { 133 | Currency = Domain.Enums.AcceptedCurrencies.Dollar, 134 | Value = 100 135 | }, 136 | Name = "Fake Room 01", 137 | Level = 1, 138 | }; 139 | 140 | var fakeBooking = new Booking 141 | { 142 | Id = 1 143 | }; 144 | 145 | var bookingRepository = new Mock(); 146 | bookingRepository.Setup(x => x.CreateBooking(It.IsAny())) 147 | .Returns(Task.FromResult(fakeBooking)); 148 | 149 | var handler = GetCommandMock(null, guestRepository, bookingRepository); 150 | var resp = await handler.Handle(command, CancellationToken.None); 151 | 152 | Assert.NotNull(resp); 153 | Assert.False(resp.Success); 154 | Assert.AreEqual(resp.ErrorCode, Application.Responses.ErrorCodes.BOOKING_MISSING_REQUIRED_INFORMATION); 155 | Assert.AreEqual(resp.Message, "Start is a required information"); 156 | } 157 | 158 | [Test] 159 | public async Task Should_CreateBooking() 160 | { 161 | var command = new CreateBookingCommand 162 | { 163 | BookingDto = new Application.Booking.Dtos.BookingDto 164 | { 165 | RoomId = 1, 166 | GuestId = 1, 167 | Start = DateTime.Now, 168 | End = DateTime.Now.AddDays(2), 169 | } 170 | }; 171 | 172 | var fakeGuest = new Guest 173 | { 174 | Id = command.BookingDto.GuestId, 175 | DocumentId = new Domain.ValueObjects.PersonId 176 | { 177 | DocumentType = Domain.Enums.DocumentType.Passport, 178 | IdNumber = "abc1234" 179 | }, 180 | Email = "a@a.com", 181 | Name = "Fake Guest", 182 | Surname = "Fake Guest Surname" 183 | }; 184 | 185 | var guestRepository = new Mock(); 186 | guestRepository.Setup(x => x.Get(command.BookingDto.GuestId)) 187 | .Returns(Task.FromResult(fakeGuest)); 188 | 189 | var fakeRoom = new Room 190 | { 191 | Id = command.BookingDto.RoomId, 192 | InMaintenance = false, 193 | Price = new Domain.ValueObjects.Price 194 | { 195 | Currency = Domain.Enums.AcceptedCurrencies.Dollar, 196 | Value = 100 197 | }, 198 | Name = "Fake Room 01", 199 | Level = 1, 200 | }; 201 | 202 | var roomRepository = new Mock(); 203 | roomRepository.Setup(x => x.GetAggregate(command.BookingDto.RoomId)) 204 | .Returns(Task.FromResult(fakeRoom)); 205 | 206 | var fakeBooking = new Booking 207 | { 208 | Id = 1, 209 | Room = fakeRoom, 210 | Guest= fakeGuest, 211 | 212 | }; 213 | 214 | var bookingRepoMock = new Mock(); 215 | bookingRepoMock.Setup(x => x.CreateBooking(It.IsAny())) 216 | .Returns(Task.FromResult(fakeBooking)); 217 | //bookingRepository.Setup(x => x.Save) 218 | 219 | var handler = GetCommandMock(roomRepository, guestRepository, bookingRepoMock); 220 | var resp = await handler.Handle(command, CancellationToken.None); 221 | 222 | Assert.NotNull(resp); 223 | Assert.True(resp.Success); 224 | Assert.NotNull(resp.Data); 225 | Assert.AreEqual(resp.Data.Id, command.BookingDto.Id); 226 | } 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /HotelBooking.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31903.59 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BookingService", "BookingService", "{3E46B3F5-74EE-4A65-BD15-05F0BF8EF7F6}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PaymentService", "PaymentService", "{3E499E7F-5F4E-45F5-9F5E-A55252E7D224}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{739383A2-2699-4C9C-8ACF-48189875D7C6}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Domain", "Domain", "{F76E8014-954B-404A-AD07-57FC3E408BB8}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Application", "Application", "{463052A1-2530-4B9F-B9B4-0480350B688D}" 15 | EndProject 16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Adapters", "Adapters", "{D3270139-197D-4CB2-AF33-43C73E19209F}" 17 | EndProject 18 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Consumers", "Consumers", "{E4EAC603-120B-4C6B-97D8-69900FA64770}" 19 | EndProject 20 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{228B5F1E-D18E-44CF-A728-24AD4C528BE3}" 21 | EndProject 22 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Domain", "Domain", "{8301CCCE-44D9-4D78-824F-ADDF03C8FE49}" 23 | EndProject 24 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Application", "Application", "{066AE35D-22BF-4623-B9FE-10BC58A53755}" 25 | EndProject 26 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Adapters", "Adapters", "{469796D9-6841-40C7-B07C-5787C390CE72}" 27 | EndProject 28 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DomainTests", "BookingService\Tests\Domain\DomainTests\DomainTests.csproj", "{675D688C-2C4E-4452-9A9C-605ABC38736C}" 29 | EndProject 30 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationTests", "BookingService\Tests\Application\ApplicationTests\ApplicationTests.csproj", "{336A2FFD-CB91-4932-9CA9-FA405527787A}" 31 | EndProject 32 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AdaptersTests", "BookingService\Tests\Adapters\AdaptersTests\AdaptersTests.csproj", "{C994CD75-D5DC-417B-B84E-EE44565B2FC5}" 33 | EndProject 34 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "API", "BookingService\Consumers\API\API.csproj", "{72A1835B-0618-48D4-A8CF-619639BEFA8E}" 35 | EndProject 36 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Data", "BookingService\Adapters\Data\Data.csproj", "{9E63BC40-ACBE-4C56-A90C-C0EEE0D4E7EB}" 37 | EndProject 38 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Application", "BookingService\Core\Application\Application.csproj", "{F2BF86D8-2FD5-403F-9C19-637D7FE7D4F6}" 39 | EndProject 40 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Domain", "BookingService\Core\Domain\Domain.csproj", "{C28C270D-C955-42D8-B5B8-FE52600362F8}" 41 | EndProject 42 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{1B1058F9-E64A-418B-8107-C556B3055BFA}" 43 | EndProject 44 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Application", "Application", "{EDA72FD8-BE04-4A58-BE62-4A2A20508ADD}" 45 | EndProject 46 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Domain", "Domain", "{0A3B7789-8729-4163-9639-D871AEB95FFA}" 47 | EndProject 48 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Payments.Application", "PaymentService\core\Application\Application\Payments.Application.csproj", "{7CE8A4D1-C548-4F26-ACD8-7A9A262B68A1}" 49 | EndProject 50 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Payments.UnitTests", "PaymentService\Payments.UnitTests\Payments.UnitTests.csproj", "{2F3B56DA-A37E-434C-906D-05C1387F20AC}" 51 | EndProject 52 | Global 53 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 54 | Debug|Any CPU = Debug|Any CPU 55 | Release|Any CPU = Release|Any CPU 56 | EndGlobalSection 57 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 58 | {675D688C-2C4E-4452-9A9C-605ABC38736C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 59 | {675D688C-2C4E-4452-9A9C-605ABC38736C}.Debug|Any CPU.Build.0 = Debug|Any CPU 60 | {675D688C-2C4E-4452-9A9C-605ABC38736C}.Release|Any CPU.ActiveCfg = Release|Any CPU 61 | {675D688C-2C4E-4452-9A9C-605ABC38736C}.Release|Any CPU.Build.0 = Release|Any CPU 62 | {336A2FFD-CB91-4932-9CA9-FA405527787A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 63 | {336A2FFD-CB91-4932-9CA9-FA405527787A}.Debug|Any CPU.Build.0 = Debug|Any CPU 64 | {336A2FFD-CB91-4932-9CA9-FA405527787A}.Release|Any CPU.ActiveCfg = Release|Any CPU 65 | {336A2FFD-CB91-4932-9CA9-FA405527787A}.Release|Any CPU.Build.0 = Release|Any CPU 66 | {C994CD75-D5DC-417B-B84E-EE44565B2FC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 67 | {C994CD75-D5DC-417B-B84E-EE44565B2FC5}.Debug|Any CPU.Build.0 = Debug|Any CPU 68 | {C994CD75-D5DC-417B-B84E-EE44565B2FC5}.Release|Any CPU.ActiveCfg = Release|Any CPU 69 | {C994CD75-D5DC-417B-B84E-EE44565B2FC5}.Release|Any CPU.Build.0 = Release|Any CPU 70 | {72A1835B-0618-48D4-A8CF-619639BEFA8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 71 | {72A1835B-0618-48D4-A8CF-619639BEFA8E}.Debug|Any CPU.Build.0 = Debug|Any CPU 72 | {72A1835B-0618-48D4-A8CF-619639BEFA8E}.Release|Any CPU.ActiveCfg = Release|Any CPU 73 | {72A1835B-0618-48D4-A8CF-619639BEFA8E}.Release|Any CPU.Build.0 = Release|Any CPU 74 | {9E63BC40-ACBE-4C56-A90C-C0EEE0D4E7EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 75 | {9E63BC40-ACBE-4C56-A90C-C0EEE0D4E7EB}.Debug|Any CPU.Build.0 = Debug|Any CPU 76 | {9E63BC40-ACBE-4C56-A90C-C0EEE0D4E7EB}.Release|Any CPU.ActiveCfg = Release|Any CPU 77 | {9E63BC40-ACBE-4C56-A90C-C0EEE0D4E7EB}.Release|Any CPU.Build.0 = Release|Any CPU 78 | {F2BF86D8-2FD5-403F-9C19-637D7FE7D4F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 79 | {F2BF86D8-2FD5-403F-9C19-637D7FE7D4F6}.Debug|Any CPU.Build.0 = Debug|Any CPU 80 | {F2BF86D8-2FD5-403F-9C19-637D7FE7D4F6}.Release|Any CPU.ActiveCfg = Release|Any CPU 81 | {F2BF86D8-2FD5-403F-9C19-637D7FE7D4F6}.Release|Any CPU.Build.0 = Release|Any CPU 82 | {C28C270D-C955-42D8-B5B8-FE52600362F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 83 | {C28C270D-C955-42D8-B5B8-FE52600362F8}.Debug|Any CPU.Build.0 = Debug|Any CPU 84 | {C28C270D-C955-42D8-B5B8-FE52600362F8}.Release|Any CPU.ActiveCfg = Release|Any CPU 85 | {C28C270D-C955-42D8-B5B8-FE52600362F8}.Release|Any CPU.Build.0 = Release|Any CPU 86 | {7CE8A4D1-C548-4F26-ACD8-7A9A262B68A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 87 | {7CE8A4D1-C548-4F26-ACD8-7A9A262B68A1}.Debug|Any CPU.Build.0 = Debug|Any CPU 88 | {7CE8A4D1-C548-4F26-ACD8-7A9A262B68A1}.Release|Any CPU.ActiveCfg = Release|Any CPU 89 | {7CE8A4D1-C548-4F26-ACD8-7A9A262B68A1}.Release|Any CPU.Build.0 = Release|Any CPU 90 | {2F3B56DA-A37E-434C-906D-05C1387F20AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 91 | {2F3B56DA-A37E-434C-906D-05C1387F20AC}.Debug|Any CPU.Build.0 = Debug|Any CPU 92 | {2F3B56DA-A37E-434C-906D-05C1387F20AC}.Release|Any CPU.ActiveCfg = Release|Any CPU 93 | {2F3B56DA-A37E-434C-906D-05C1387F20AC}.Release|Any CPU.Build.0 = Release|Any CPU 94 | EndGlobalSection 95 | GlobalSection(SolutionProperties) = preSolution 96 | HideSolutionNode = FALSE 97 | EndGlobalSection 98 | GlobalSection(NestedProjects) = preSolution 99 | {739383A2-2699-4C9C-8ACF-48189875D7C6} = {3E46B3F5-74EE-4A65-BD15-05F0BF8EF7F6} 100 | {F76E8014-954B-404A-AD07-57FC3E408BB8} = {739383A2-2699-4C9C-8ACF-48189875D7C6} 101 | {463052A1-2530-4B9F-B9B4-0480350B688D} = {739383A2-2699-4C9C-8ACF-48189875D7C6} 102 | {D3270139-197D-4CB2-AF33-43C73E19209F} = {3E46B3F5-74EE-4A65-BD15-05F0BF8EF7F6} 103 | {E4EAC603-120B-4C6B-97D8-69900FA64770} = {3E46B3F5-74EE-4A65-BD15-05F0BF8EF7F6} 104 | {228B5F1E-D18E-44CF-A728-24AD4C528BE3} = {3E46B3F5-74EE-4A65-BD15-05F0BF8EF7F6} 105 | {8301CCCE-44D9-4D78-824F-ADDF03C8FE49} = {228B5F1E-D18E-44CF-A728-24AD4C528BE3} 106 | {066AE35D-22BF-4623-B9FE-10BC58A53755} = {228B5F1E-D18E-44CF-A728-24AD4C528BE3} 107 | {469796D9-6841-40C7-B07C-5787C390CE72} = {228B5F1E-D18E-44CF-A728-24AD4C528BE3} 108 | {675D688C-2C4E-4452-9A9C-605ABC38736C} = {8301CCCE-44D9-4D78-824F-ADDF03C8FE49} 109 | {336A2FFD-CB91-4932-9CA9-FA405527787A} = {066AE35D-22BF-4623-B9FE-10BC58A53755} 110 | {C994CD75-D5DC-417B-B84E-EE44565B2FC5} = {469796D9-6841-40C7-B07C-5787C390CE72} 111 | {72A1835B-0618-48D4-A8CF-619639BEFA8E} = {E4EAC603-120B-4C6B-97D8-69900FA64770} 112 | {9E63BC40-ACBE-4C56-A90C-C0EEE0D4E7EB} = {D3270139-197D-4CB2-AF33-43C73E19209F} 113 | {F2BF86D8-2FD5-403F-9C19-637D7FE7D4F6} = {463052A1-2530-4B9F-B9B4-0480350B688D} 114 | {C28C270D-C955-42D8-B5B8-FE52600362F8} = {F76E8014-954B-404A-AD07-57FC3E408BB8} 115 | {1B1058F9-E64A-418B-8107-C556B3055BFA} = {3E499E7F-5F4E-45F5-9F5E-A55252E7D224} 116 | {EDA72FD8-BE04-4A58-BE62-4A2A20508ADD} = {1B1058F9-E64A-418B-8107-C556B3055BFA} 117 | {0A3B7789-8729-4163-9639-D871AEB95FFA} = {1B1058F9-E64A-418B-8107-C556B3055BFA} 118 | {7CE8A4D1-C548-4F26-ACD8-7A9A262B68A1} = {EDA72FD8-BE04-4A58-BE62-4A2A20508ADD} 119 | {2F3B56DA-A37E-434C-906D-05C1387F20AC} = {3E499E7F-5F4E-45F5-9F5E-A55252E7D224} 120 | EndGlobalSection 121 | GlobalSection(ExtensibilityGlobals) = postSolution 122 | SolutionGuid = {0EA22528-B7A6-4957-9A57-D2BA8D40E259} 123 | EndGlobalSection 124 | EndGlobal 125 | --------------------------------------------------------------------------------