From eef983c50ef9290609aca8354c0c9b9e93ecf3f8 Mon Sep 17 00:00:00 2001 From: Mouaz Abdelsamad <ma03081@surrey.ac.uk> Date: Tue, 9 Apr 2024 03:37:36 +0100 Subject: [PATCH] remove these...... --- BookingService_Old/.gitignore | 263 ------------ .../FlightBooking.Service.Tests.csproj | 37 -- .../FlightBooking.Service.Tests/UnitTest1.cs | 10 - BookingService_Old/FlightBooking.Service.sln | 31 -- .../Controllers/BookingsController.cs | 88 ---- .../Controllers/SeatsController.cs | 42 -- .../Controllers/StripeController.cs | 69 ---- .../Data/Configs/ConfigSettings.cs | 15 - .../Data/Configs/ConfigSettingsModule.cs | 10 - .../FlightBooking.Service/Data/Constants.cs | 38 -- .../Data/DTO/BookingDTO.cs | 56 --- .../Data/DTO/BookingOrderDTO.cs | 25 -- .../Data/DTO/FlightFareDTO.cs | 16 - .../Data/DTO/FlightInformationDTO.cs | 19 - .../Data/DTO/ReservedSeatDTO.cs | 17 - .../Data/DTO/StripeDataDTO.cs | 32 -- .../Data/DatabaseSeeding.cs | 158 ------- .../FlightBooking.Service/Data/Enums.cs | 40 -- .../Data/FlightBookingContext.cs | 70 ---- .../BookingConfiguration.cs | 27 -- .../BookingOrderConfiguration.cs | 13 - .../FlightFareConfiguration.cs | 17 - .../FlightInformationConfiguration.cs | 13 - .../PaymentConfiguration.cs | 17 - .../ReservedSeatConfiguration.cs | 17 - .../Data/Models/Booking.cs | 36 -- .../Data/Models/BookingOrder.cs | 26 -- .../Data/Models/FlightFare.cs | 28 -- .../Data/Models/FlightInformation.cs | 25 -- .../Data/Models/PassengerInformation.cs | 20 - .../Data/Models/Payment.cs | 30 -- .../Data/Models/ReservedSeat.cs | 22 - .../Data/Repository/GenericRepository.cs | 257 ------------ .../Data/Repository/IGenericRepository.cs | 36 -- .../Data/Repository/RepositoryModule.cs | 17 - .../FlightBooking.Service.csproj | 45 -- .../FlightBooking.Service.http | 6 - .../Middleware/ErrorHandlingMiddleware.cs | 6 - .../Middleware/MiddlewareExtensions.cs | 10 - .../FlightBooking.Service/Program.cs | 63 --- .../Properties/launchSettings.json | 41 -- .../Services/BookingOrderService.cs | 391 ------------------ .../Services/BookingService.cs | 101 ----- .../Services/FlightBookingProfile.cs | 29 -- .../Services/FlightFareService.cs | 44 -- .../Services/FlightService.cs | 47 --- .../Interfaces/IBookingOrderService.cs | 21 - .../Services/Interfaces/IBookingService.cs | 35 -- .../Services/Interfaces/IFlightFareService.cs | 21 - .../Services/Interfaces/IFlightService.cs | 22 - .../Interfaces/IReservedSeatService.cs | 29 -- .../Services/Interfaces/IStripeService.cs | 22 - .../Services/PaymentService.cs | 6 - .../Services/ReservedSeatService.cs | 127 ------ .../Services/ResponseFormatter.cs | 176 -------- .../Services/ServicesModule.cs | 17 - .../Services/StripeService.cs | 147 ------- .../FlightBooking.Service/Startup.cs | 162 -------- .../appsettings.Development.json | 21 - .../FlightBooking.Service/appsettings.json | 22 - .../FlightBooking.Service/nlog.config | 40 -- .../FlightBooking.Service/readme.md | 385 ----------------- Templates/login-register/login.html | 31 -- .../login-register/login_register_style.css | 15 - Templates/login-register/register.html | 36 -- Templates/login-register/register.js | 15 - Templates/user_profile/avatar.jpg | Bin 6031 -> 0 bytes Templates/user_profile/profile.css | 53 --- Templates/user_profile/profile.html | 158 ------- Templates/user_profile/profile_script.js | 19 - Templates/user_profile/user.png | Bin 19382 -> 0 bytes 71 files changed, 4000 deletions(-) delete mode 100644 BookingService_Old/.gitignore delete mode 100644 BookingService_Old/FlightBooking.Service.Tests/FlightBooking.Service.Tests.csproj delete mode 100644 BookingService_Old/FlightBooking.Service.Tests/UnitTest1.cs delete mode 100644 BookingService_Old/FlightBooking.Service.sln delete mode 100644 BookingService_Old/FlightBooking.Service/Controllers/BookingsController.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Controllers/SeatsController.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Controllers/StripeController.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Data/Configs/ConfigSettings.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Data/Configs/ConfigSettingsModule.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Data/Constants.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Data/DTO/BookingDTO.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Data/DTO/BookingOrderDTO.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Data/DTO/FlightFareDTO.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Data/DTO/FlightInformationDTO.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Data/DTO/ReservedSeatDTO.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Data/DTO/StripeDataDTO.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Data/DatabaseSeeding.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Data/Enums.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Data/FlightBookingContext.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Data/ModelConfigurations/BookingConfiguration.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Data/ModelConfigurations/BookingOrderConfiguration.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Data/ModelConfigurations/FlightFareConfiguration.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Data/ModelConfigurations/FlightInformationConfiguration.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Data/ModelConfigurations/PaymentConfiguration.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Data/ModelConfigurations/ReservedSeatConfiguration.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Data/Models/Booking.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Data/Models/BookingOrder.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Data/Models/FlightFare.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Data/Models/FlightInformation.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Data/Models/PassengerInformation.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Data/Models/Payment.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Data/Models/ReservedSeat.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Data/Repository/GenericRepository.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Data/Repository/IGenericRepository.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Data/Repository/RepositoryModule.cs delete mode 100644 BookingService_Old/FlightBooking.Service/FlightBooking.Service.csproj delete mode 100644 BookingService_Old/FlightBooking.Service/FlightBooking.Service.http delete mode 100644 BookingService_Old/FlightBooking.Service/Middleware/ErrorHandlingMiddleware.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Middleware/MiddlewareExtensions.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Program.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Properties/launchSettings.json delete mode 100644 BookingService_Old/FlightBooking.Service/Services/BookingOrderService.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Services/BookingService.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Services/FlightBookingProfile.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Services/FlightFareService.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Services/FlightService.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Services/Interfaces/IBookingOrderService.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Services/Interfaces/IBookingService.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Services/Interfaces/IFlightFareService.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Services/Interfaces/IFlightService.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Services/Interfaces/IReservedSeatService.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Services/Interfaces/IStripeService.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Services/PaymentService.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Services/ReservedSeatService.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Services/ResponseFormatter.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Services/ServicesModule.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Services/StripeService.cs delete mode 100644 BookingService_Old/FlightBooking.Service/Startup.cs delete mode 100644 BookingService_Old/FlightBooking.Service/appsettings.Development.json delete mode 100644 BookingService_Old/FlightBooking.Service/appsettings.json delete mode 100644 BookingService_Old/FlightBooking.Service/nlog.config delete mode 100644 BookingService_Old/FlightBooking.Service/readme.md delete mode 100644 Templates/login-register/login.html delete mode 100644 Templates/login-register/login_register_style.css delete mode 100644 Templates/login-register/register.html delete mode 100644 Templates/login-register/register.js delete mode 100644 Templates/user_profile/avatar.jpg delete mode 100644 Templates/user_profile/profile.css delete mode 100644 Templates/user_profile/profile.html delete mode 100644 Templates/user_profile/profile_script.js delete mode 100644 Templates/user_profile/user.png diff --git a/BookingService_Old/.gitignore b/BookingService_Old/.gitignore deleted file mode 100644 index 1242aad..0000000 --- a/BookingService_Old/.gitignore +++ /dev/null @@ -1,263 +0,0 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. - -# User-specific files -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -bld/ -[Bb]in/ -[Oo]bj/ -[Ll]og/ - -# Visual Studio 2015 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUNIT -*.VisualState.xml -TestResult.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# DNX -project.lock.json -project.fragment.lock.json -artifacts/ - -*_i.c -*_p.c -*_i.h -*.ilk -*.meta -*.obj -*.pch -*.pdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# JustCode is a .NET coding add-in -.JustCode - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# TODO: Comment the next line if you want to checkin your web deploy settings -# but database connection strings (with potential passwords) will be unencrypted -#*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# The packages folder can be ignored because of Package Restore -**/packages/* -# except build/, which is used as an MSBuild target. -!**/packages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/packages/repositories.config -# NuGet v3's project.json files produces more ignoreable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -node_modules/ -orleans.codegen.cs - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm - -# SQL Server files -*.mdf -*.ldf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# JetBrains Rider -.idea/ -*.sln.iml - -# CodeRush -.cr/ - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc -/WellaHealthApiCore/Generated -/WellahealthCore.AzureFunctions diff --git a/BookingService_Old/FlightBooking.Service.Tests/FlightBooking.Service.Tests.csproj b/BookingService_Old/FlightBooking.Service.Tests/FlightBooking.Service.Tests.csproj deleted file mode 100644 index 78b328a..0000000 --- a/BookingService_Old/FlightBooking.Service.Tests/FlightBooking.Service.Tests.csproj +++ /dev/null @@ -1,37 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk"> - - <PropertyGroup> - <TargetFramework>net8.0</TargetFramework> - <ImplicitUsings>enable</ImplicitUsings> - <Nullable>enable</Nullable> - - <IsPackable>false</IsPackable> - <IsTestProject>true</IsTestProject> - </PropertyGroup> - <PropertyGroup> - <EnableNETAnalyzers>true</EnableNETAnalyzers> - </PropertyGroup> - <ItemGroup> - <PackageReference Include="coverlet.collector" Version="6.0.1"> - <PrivateAssets>all</PrivateAssets> - <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> - </PackageReference> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" /> - <PackageReference Include="xunit" Version="2.5.3" /> - <PackageReference Include="xunit.runner.visualstudio" Version="2.5.7"> - <PrivateAssets>all</PrivateAssets> - <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> - </PackageReference> - <PackageReference Include="MockQueryable.Moq" Version="7.0.0" /> - <PackageReference Include="Moq" Version="4.18.4" /> - </ItemGroup> - - <ItemGroup> - <ProjectReference Include="..\FlightBooking.Service\FlightBooking.Service.csproj" /> - </ItemGroup> - - <ItemGroup> - <Using Include="Xunit" /> - </ItemGroup> - -</Project> diff --git a/BookingService_Old/FlightBooking.Service.Tests/UnitTest1.cs b/BookingService_Old/FlightBooking.Service.Tests/UnitTest1.cs deleted file mode 100644 index 127caef..0000000 --- a/BookingService_Old/FlightBooking.Service.Tests/UnitTest1.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace FlightBooking.Service.Tests -{ - public class UnitTest1 - { - [Fact] - public void Test1() - { - } - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service.sln b/BookingService_Old/FlightBooking.Service.sln deleted file mode 100644 index 2a079f3..0000000 --- a/BookingService_Old/FlightBooking.Service.sln +++ /dev/null @@ -1,31 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.9.34622.214 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FlightBooking.Service", "FlightBooking.Service\FlightBooking.Service.csproj", "{0043031F-8453-4554-90A8-DA1921F95235}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FlightBooking.Service.Tests", "FlightBooking.Service.Tests\FlightBooking.Service.Tests.csproj", "{C7FCD62C-5D7D-44BB-8A38-821BEBE2BBC9}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {0043031F-8453-4554-90A8-DA1921F95235}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0043031F-8453-4554-90A8-DA1921F95235}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0043031F-8453-4554-90A8-DA1921F95235}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0043031F-8453-4554-90A8-DA1921F95235}.Release|Any CPU.Build.0 = Release|Any CPU - {C7FCD62C-5D7D-44BB-8A38-821BEBE2BBC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C7FCD62C-5D7D-44BB-8A38-821BEBE2BBC9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C7FCD62C-5D7D-44BB-8A38-821BEBE2BBC9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C7FCD62C-5D7D-44BB-8A38-821BEBE2BBC9}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {1CCB1631-8F04-4C14-AC4D-9CFC54B609B7} - EndGlobalSection -EndGlobal diff --git a/BookingService_Old/FlightBooking.Service/Controllers/BookingsController.cs b/BookingService_Old/FlightBooking.Service/Controllers/BookingsController.cs deleted file mode 100644 index d9d2de4..0000000 --- a/BookingService_Old/FlightBooking.Service/Controllers/BookingsController.cs +++ /dev/null @@ -1,88 +0,0 @@ -using FlightBooking.Service.Data.DTO; -using FlightBooking.Service.Services; -using FlightBooking.Service.Services.Interfaces; -using Microsoft.AspNetCore.Mvc; -using System.Net.Mime; - -namespace FlightBooking.Service.Controllers -{ - [Route("api/[controller]")] - [ApiController] - public class BookingsController : ControllerBase - { - private readonly IBookingService _bookingService; - private readonly IBookingOrderService _bookingOrderService; - - public BookingsController(IBookingService bookingService, IBookingOrderService bookingOrderService) - { - _bookingService = bookingService; - _bookingOrderService = bookingOrderService; - } - - [HttpGet("bookingNumber/{bookingNumber}")] - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(BookingDTO))] - [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(ProblemDetails))] - [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ProblemDetails))] - public async Task<IActionResult> GetBookingsByBookingNumber([FromRoute] string bookingNumber) - { - ServiceResponse<BookingDTO?> result = await _bookingService.GetBookingByBookingNumberAsync(bookingNumber); - - return result.FormatResponse(); - } - - [HttpGet("email/{email}")] - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<BookingDTO>))] - [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ProblemDetails))] - public IActionResult GetBookingsByEmail([FromRoute] string email) - { - ServiceResponse<IEnumerable<BookingDTO>?> result = _bookingService.GetBookingsByEmail(email); - - return result.FormatResponse(); - } - - [HttpGet("orderNumber/{orderNumber}")] - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<BookingDTO>))] - [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ProblemDetails))] - public IActionResult GetBookingsByOrderNumber([FromRoute] string orderNumber) - { - ServiceResponse<IEnumerable<BookingDTO>?> result = _bookingService.GetBookingsByOrderNumber(orderNumber); - - return result.FormatResponse(); - } - - [HttpGet("id/{bookingId}")] - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(BookingDTO))] - [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(ProblemDetails))] - public async Task<IActionResult> GetBookingById([FromRoute] int bookingId) - { - ServiceResponse<BookingDTO?> result = await _bookingService.GetBookingByBookingId(bookingId); - - return result.FormatResponse(); - } - - [HttpPost] - [Consumes(MediaTypeNames.Application.Json)] - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(BookingResponseDTO))] - [ProducesResponseType(StatusCodes.Status422UnprocessableEntity, Type = typeof(ProblemDetails))] - [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ProblemDetails))] - [ProducesResponseType(StatusCodes.Status500InternalServerError, Type = typeof(ProblemDetails))] - public async Task<IActionResult> PostBooking([FromBody] BookingOrderDTO bookingOrderDTO) - { - ServiceResponse<BookingResponseDTO?> result = await _bookingOrderService.CreateBookingOrderAsync(bookingOrderDTO); - - return result.FormatResponse(); - } - - [HttpGet("payment/{orderNumber}")] - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(BookingResponseDTO))] - [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(ProblemDetails))] - [ProducesResponseType(StatusCodes.Status422UnprocessableEntity, Type = typeof(ProblemDetails))] - [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ProblemDetails))] - public async Task<IActionResult> GetBookingPayment([FromRoute] string orderNumber) - { - ServiceResponse<BookingResponseDTO?> result = await _bookingOrderService.GetCheckoutUrlAsync(orderNumber); - - return result.FormatResponse(); - } - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Controllers/SeatsController.cs b/BookingService_Old/FlightBooking.Service/Controllers/SeatsController.cs deleted file mode 100644 index a6ca8bb..0000000 --- a/BookingService_Old/FlightBooking.Service/Controllers/SeatsController.cs +++ /dev/null @@ -1,42 +0,0 @@ -using FlightBooking.Service.Data.DTO; -using FlightBooking.Service.Services; -using FlightBooking.Service.Services.Interfaces; -using Microsoft.AspNetCore.Mvc; -using System.Net.Mime; - -namespace FlightBooking.Service.Controllers -{ - [Route("api/[controller]")] - [ApiController] - public class SeatsController : ControllerBase - { - private readonly IReservedSeatService _service; - - public SeatsController(IReservedSeatService service) - { - _service = service; - } - - [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<ReservedSeatDTO>))] - [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ProblemDetails))] - public IActionResult GetAvailableSeats([FromQuery] string flightNumber) - { - ServiceResponse<IEnumerable<ReservedSeatDTO>> result = _service.GetAvailableSeatsByFlightNumber(flightNumber); - - return result.FormatResponse(); - } - - [HttpPost] - [Consumes(MediaTypeNames.Application.Json)] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status422UnprocessableEntity, Type = typeof(ProblemDetails))] - [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ProblemDetails))] - public async Task<IActionResult> ReserveSeat([FromBody] ReservedSeatRequestDTO requestDTO) - { - ServiceResponse<string> result = await _service.ReserveSeatAsync(requestDTO); - - return result.FormatResponse(); - } - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Controllers/StripeController.cs b/BookingService_Old/FlightBooking.Service/Controllers/StripeController.cs deleted file mode 100644 index 0f9bf55..0000000 --- a/BookingService_Old/FlightBooking.Service/Controllers/StripeController.cs +++ /dev/null @@ -1,69 +0,0 @@ -using FlightBooking.Service.Data.Configs; -using FlightBooking.Service.Services; -using FlightBooking.Service.Services.Interfaces; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; -using Stripe; -using System.Text; - -namespace FlightBooking.Service.Controllers -{ - [Route("api/[controller]")] - [ApiController] - public class StripeController : ControllerBase - { - private readonly StripeConfig _stripeConfig; - private readonly IStripeService _stripeService; - private readonly ILogger<StripeController> _logger; - - public StripeController(IOptionsMonitor<StripeConfig> options, IStripeService stripeService, ILogger<StripeController> logger) - { - _stripeConfig = options.CurrentValue; - _stripeService = stripeService; - _logger = logger; - } - - //webhook for stripe to verify payment - [HttpPost] - public async Task<IActionResult> NotificationWebhookAsync() - { - //var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync(); - - string requestBody; - using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8)) - { - requestBody = await reader.ReadToEndAsync(); - } - - if (string.IsNullOrEmpty(requestBody)) - { - //_logger.LogInformation("Event object is empty", eventObject); - return BadRequest(); - } - - string stripeSigningKey = _stripeConfig.SigningSecret; - - Event stripeEvent; - - try - { - stripeEvent = EventUtility.ConstructEvent(requestBody, Request.Headers["Stripe-Signature"], stripeSigningKey, throwOnApiVersionMismatch: false); - } - catch (StripeException exception) - { - _logger.LogError(exception.ToString()); - return BadRequest(); - } - - //Since this is the only event we are handling. - if (stripeEvent.Type == Events.CheckoutSessionCompleted) - { - var response = await _stripeService.ProcessPayment(stripeEvent); - - return response.FormatResponse(); - } - - return Ok(); - } - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Data/Configs/ConfigSettings.cs b/BookingService_Old/FlightBooking.Service/Data/Configs/ConfigSettings.cs deleted file mode 100644 index 72ff348..0000000 --- a/BookingService_Old/FlightBooking.Service/Data/Configs/ConfigSettings.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace FlightBooking.Service.Data.Configs -{ - public class ConfigSettings - { - } - - public class StripeConfig - { - public const string ConfigName = nameof(StripeConfig); - - public string SecretKey { get; set; } = null!; - public string PublicKey { get; set; } = null!; - public string SigningSecret { get; set; } = null!; - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Data/Configs/ConfigSettingsModule.cs b/BookingService_Old/FlightBooking.Service/Data/Configs/ConfigSettingsModule.cs deleted file mode 100644 index 21c0d09..0000000 --- a/BookingService_Old/FlightBooking.Service/Data/Configs/ConfigSettingsModule.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace FlightBooking.Service.Data.Configs -{ - public static class ConfigSettingsModule - { - public static void AddConfigSettings(this IServiceCollection services, IConfiguration configuration) - { - services.Configure<StripeConfig>(configuration.GetSection(StripeConfig.ConfigName)); - } - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Data/Constants.cs b/BookingService_Old/FlightBooking.Service/Data/Constants.cs deleted file mode 100644 index ff8ff81..0000000 --- a/BookingService_Old/FlightBooking.Service/Data/Constants.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace FlightBooking.Service.Data -{ - public class Constants - { - } - - internal static class RepositoryConstants - { - public const string LoggingStarted = "Started logging"; - public const string CreateNullError = "Attempt to insert empty entity. Type of Entity : {0}"; - public const string DeleteNullError = "Could not find entity for deleting. type of Entity : {0}"; - public const string BulkDeleteNullError = "Attempt to Delete empty list of entities. Type of Entity : {0}"; - public const string BulkCreateNullError = "Attempt to insert empty list of entities. Type of Entity : {0}"; - public const string EmptySaveInfo = "No changes was written to underlying database."; - public const string UpdateException = "Update Exception"; - public const string UpdateConcurrencyException = "Update Concurrency Exception"; - public const string SaveChangesException = "Generic Error in Generic Repo Update method"; - } - - internal static class ServiceErrorMessages - { - public const string Success = "The operation was successful"; - public const string Failed = "An unhandled errror has occured while processing your request"; - public const string UpdateError = "There was an error carrying out operation"; - public const string MisMatch = "The entity Id does not match the supplied Id"; - public const string EntityIsNull = "Supplied entity is null or supplied list of entities is empty. Check our docs"; - public const string EntityNotFound = "The requested resource was not found. Verify that the supplied Id is correct"; - public const string Incompleted = "Some actions may not have been successfully processed"; - public const string EntityExist = "An entity of the same name or id exists"; - public const string InvalidParam = "A supplied parameter or model is invalid. Check the docs"; - public const string UnprocessableEntity = "The action cannot be processed"; - public const string InternalServerError = "An internal server error and request could not processed"; - public const string OperationFailed = "Operation failed"; - - public const string ParameterEmptyOrNull = "The parameter list is null or empty"; - public const string RequestIdRequired = "Request Id is required"; - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Data/DTO/BookingDTO.cs b/BookingService_Old/FlightBooking.Service/Data/DTO/BookingDTO.cs deleted file mode 100644 index 8a93333..0000000 --- a/BookingService_Old/FlightBooking.Service/Data/DTO/BookingDTO.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace FlightBooking.Service.Data.DTO -{ - public class BookingDTO - { - public int Id { get; set; } - public string FirstName { get; set; } = null!; - public string LastName { get; set; } = null!; - public string PhoneNumber { get; set; } = null!; - public string Email { get; set; } = null!; - public string Address { get; set; } = null!; - public DateOnly DateOfBirth { get; set; } - - [EnumDataType(typeof(Gender))] - public Gender Gender { get; set; } - public DateTime CreatedAt { get; set; } - - public string BookingNumber { get; set; } = null!; - public int BookingOrderId { get; set; } - - [EnumDataType(typeof(BookingStatus))] - public BookingStatus BookingStatus { get; set; } = BookingStatus.Pending; - public string? SeatNumber { get; set; } - - public BookingFlightInformationDTO FlightInformation { get; set; } = null!; - public BookingFlightFareDTO FlightFare { get; set; } = null!; - } - - public class BookingRequestDTO - { - [Required] - public string FirstName { get; set; } = null!; - - [Required] - public string LastName { get; set; } = null!; - - public string PhoneNumber { get; set; } = null!; - public string? Email { get; set; } - - [Required] - public string Address { get; set; } = null!; - - [Required] - public DateOnly DateOfBirth { get; set; } - - [EnumDataType(typeof(Gender))] - public Gender Gender { get; set; } = Gender.PreferNotToSay; - - [Required] - [Range(1, int.MaxValue)] - public int OutboundFareId { get; set; } //We are keeping Fare per booking to allow flexibility e.g Adult in Economy and Child in Business class - - public int? ReturnFareId { get; set; } - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Data/DTO/BookingOrderDTO.cs b/BookingService_Old/FlightBooking.Service/Data/DTO/BookingOrderDTO.cs deleted file mode 100644 index 80a6ee1..0000000 --- a/BookingService_Old/FlightBooking.Service/Data/DTO/BookingOrderDTO.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace FlightBooking.Service.Data.DTO -{ - public class BookingOrderDTO - { - [Required] - public string Email { get; set; } = null!; - - [Required] - public string OutboundFlightNumber { get; set; } = null!; - - public string? ReturnFlightNumber { get; set; } - - [Required] - public List<BookingRequestDTO> Bookings { get; set; } = new List<BookingRequestDTO>(); - } - - public class BookingResponseDTO - { - public string OrderNumber { get; set; } = null!; - public string PaymentLink { get; set; } = null!; - public DateTime OrderExpiration { get; set; } = DateTime.UtcNow.AddHours(1); - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Data/DTO/FlightFareDTO.cs b/BookingService_Old/FlightBooking.Service/Data/DTO/FlightFareDTO.cs deleted file mode 100644 index ae7db01..0000000 --- a/BookingService_Old/FlightBooking.Service/Data/DTO/FlightFareDTO.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace FlightBooking.Service.Data.DTO -{ - public class FlightFareDTO : BookingFlightFareDTO - { - public int Id { get; set; } - public decimal AvailableSeats { get; set; } - } - - public class BookingFlightFareDTO - { - public string FareCode { get; set; } = null!; - public string FareName { get; set; } = null!; - public decimal Price { get; set; } - public string FlightNumber { get; set; } = null!; - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Data/DTO/FlightInformationDTO.cs b/BookingService_Old/FlightBooking.Service/Data/DTO/FlightInformationDTO.cs deleted file mode 100644 index eb8acda..0000000 --- a/BookingService_Old/FlightBooking.Service/Data/DTO/FlightInformationDTO.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace FlightBooking.Service.Data.DTO -{ - public class FlightInformationDTO : BookingFlightInformationDTO - { - public int Id { get; set; } - public int SeatCapacity { get; set; } - public int AvailableSeats { get; set; } - } - - public class BookingFlightInformationDTO - { - public string FlightNumber { get; set; } = null!; - public string Origin { get; set; } = null!; - public string Destination { get; set; } = null!; - public DateTime DepartureDate { get; set; } - public DateTime ArrivalDate { get; set; } - public string Airline { get; set; } = null!; - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Data/DTO/ReservedSeatDTO.cs b/BookingService_Old/FlightBooking.Service/Data/DTO/ReservedSeatDTO.cs deleted file mode 100644 index 862b363..0000000 --- a/BookingService_Old/FlightBooking.Service/Data/DTO/ReservedSeatDTO.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace FlightBooking.Service.Data.DTO -{ - public class ReservedSeatDTO - { - public string SeatNumber { get; set; } = null!; // e.g 1A, 33B - - public string FlightNumber { get; set; } = null!; - - public bool IsReserved { get; set; } - } - - public class ReservedSeatRequestDTO - { - public string SeatNumber { get; set; } = null!; // e.g 1A, 33B - public string BookingNumber { get; set; } = null!; - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Data/DTO/StripeDataDTO.cs b/BookingService_Old/FlightBooking.Service/Data/DTO/StripeDataDTO.cs deleted file mode 100644 index b6ca868..0000000 --- a/BookingService_Old/FlightBooking.Service/Data/DTO/StripeDataDTO.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace FlightBooking.Service.Data.DTO -{ - public class StripeDataDTO - { - [Required] - public string SuccessUrl { get; set; } = null!; - - [Required] - public string CancelUrl { get; set; } = null!; - - [Required] - public string ProductName { get; set; } = null!; - - [Required] - public string ProductDescription { get; set; } = null!; - - [Required] - [Range(0.5, double.MaxValue)] //minimum of 50 cents - public decimal Amount { get; set; } - - [Required] - public string CustomerEmail { get; set; } = null!; - - [Required] - public string CurrencyCode { get; set; } = "USD"; - - [Required] - public string OrderNumber { get; set; } = null!; - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Data/DatabaseSeeding.cs b/BookingService_Old/FlightBooking.Service/Data/DatabaseSeeding.cs deleted file mode 100644 index bd27596..0000000 --- a/BookingService_Old/FlightBooking.Service/Data/DatabaseSeeding.cs +++ /dev/null @@ -1,158 +0,0 @@ -using FlightBooking.Service.Data.Models; -using Microsoft.EntityFrameworkCore; - -namespace FlightBooking.Service.Data -{ - public static class DatabaseSeeding - { - public static void Initialize(IServiceProvider serviceProvider) - { - using (var context = new FlightBookingContext(serviceProvider.GetRequiredService<DbContextOptions<FlightBookingContext>>())) - { - if (context.FlightInformation.Any()) - { - return; - } - - string flightA = Guid.NewGuid().ToString("N")[..4].ToUpper(); - string flightB = Guid.NewGuid().ToString("N")[..4].ToUpper(); - List<FlightFare> flightFaresA = new List<FlightFare> - { - new FlightFare - { - FareName = "Economy class", - FareCode = "Eco-1", - Price = 4000, - SeatCapacity = 30, - SeatReserved = 0, - CreatedAt = DateTime.UtcNow, - UpdatedAt = DateTime.UtcNow, - }, - new FlightFare - { - FareName = "Business class", - FareCode = "Biz-1", - Price = 4000, - SeatCapacity = 30, - SeatReserved = 0, - CreatedAt = DateTime.UtcNow, - UpdatedAt = DateTime.UtcNow, - }, - }; - - List<FlightFare> flightFaresB = new List<FlightFare> - { - new FlightFare - { - FareName = "Economy class", - FareCode = "Eco-11", - Price = 5000, - SeatCapacity = 20, - SeatReserved = 0, - CreatedAt = DateTime.UtcNow, - UpdatedAt = DateTime.UtcNow, - }, - new FlightFare - { - FareName = "Business class", - FareCode = "Biz-11", - Price = 4000, - SeatCapacity = 10, - SeatReserved = 0, - CreatedAt = DateTime.UtcNow, - UpdatedAt = DateTime.UtcNow, - }, - }; - - //Create available seats - var reservedSeatsA = GenerateSeats(flightA, 60); - var reservedSeatsB = GenerateSeats(flightB, 60); - - List<FlightInformation> flights = new List<FlightInformation> - { - new FlightInformation - { - SeatCapacity = 60, - DepartureDate = DateTime.UtcNow.AddMonths(3), - ArrivalDate = DateTime.UtcNow.AddMonths(3).AddHours(4), - Airline = "Emirates", - SeatReserved = 0, - Destination = "London", - Origin = "Lagos", - FlightNumber = flightA, - CreatedAt = DateTime.UtcNow, - UpdatedAt = DateTime.UtcNow, - FlightFares = flightFaresA, - ReservedSeats = reservedSeatsA, - }, - new FlightInformation - { - SeatCapacity = 40, - DepartureDate = DateTime.UtcNow.AddMonths(3).AddDays(7), - ArrivalDate = DateTime.UtcNow.AddMonths(3).AddDays(7).AddHours(4), - Airline = "Emirates", - SeatReserved = 0, - Destination = "Lagos", - Origin = "London", - FlightNumber = flightB, - CreatedAt = DateTime.UtcNow, - UpdatedAt = DateTime.UtcNow, - FlightFares = flightFaresB, - ReservedSeats = reservedSeatsB - } - }; - - context.FlightInformation.AddRange(flights); - - context.SaveChanges(); - - context.Database.EnsureCreated(); - } - } - - private static List<ReservedSeat> GenerateSeats(string flightNumber, int flightCapacity) - { - //assume seats are in group of 4 Alphabets e.g 1A, 1B, 1C, 1D - - Dictionary<int, string> SeatMaps = new Dictionary<int, string> - { - {1, "A" }, - {2, "B" }, - {3, "C" }, - {4, "D" } - }; - - int seatId = 1; - int seatCount = 1; - - List<string> seatNumbers = new List<string>(); - - for (int i = 1; i < flightCapacity + 1; i++) - { - if (seatCount > 4) - { - seatId++; - seatCount = 1; - } - - seatNumbers.Add(seatId + SeatMaps[seatCount]); - seatCount++; - } - - List<ReservedSeat> reservedSeats = new List<ReservedSeat>(); - - foreach (var seatNumber in seatNumbers) - { - reservedSeats.Add(new ReservedSeat - { - BookingNumber = null, - FlightNumber = flightNumber, - IsReserved = false, - SeatNumber = seatNumber - }); - } - - return reservedSeats; - } - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Data/Enums.cs b/BookingService_Old/FlightBooking.Service/Data/Enums.cs deleted file mode 100644 index 15a8a62..0000000 --- a/BookingService_Old/FlightBooking.Service/Data/Enums.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace FlightBooking.Service.Data -{ - public enum Gender - { - Female = 1, - Male, - Transgender, - Fluid, - PreferNotToSay - } - - public enum InternalCode - { - UpdateError = -1, - Failed, - Success, - EntityIsNull, - EntityNotFound, - Mismatch, - InvalidParam, - Incompleted, - ListEmpty, - EntityExist, - Unprocessable, - Unauthorized, - } - - public enum SortOrder - { - ASC = 1, - DESC = 2 - } - - public enum BookingStatus - { - Pending = 1, - Confirmed, - Paid - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Data/FlightBookingContext.cs b/BookingService_Old/FlightBooking.Service/Data/FlightBookingContext.cs deleted file mode 100644 index ecdf63e..0000000 --- a/BookingService_Old/FlightBooking.Service/Data/FlightBookingContext.cs +++ /dev/null @@ -1,70 +0,0 @@ -using FlightBooking.Service.Data.ModelConfigurations; -using FlightBooking.Service.Data.Models; -using Microsoft.EntityFrameworkCore; - -namespace FlightBooking.Service.Data -{ - public class FlightBookingContext : DbContext - { - public FlightBookingContext(DbContextOptions<FlightBookingContext> options) : base(options) - { - } - - public DbSet<Booking> Bookings { get; set; } - public DbSet<BookingOrder> BookingOrders { get; set; } - public DbSet<ReservedSeat> ReservedSeats { get; set; } - public DbSet<Payment> Payments { get; set; } - public DbSet<FlightFare> FlightFares { get; set; } - public DbSet<FlightInformation> FlightInformation { get; set; } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - //configure each model - - modelBuilder.ApplyConfiguration(new FlightInformationConfiguration()); - modelBuilder.ApplyConfiguration(new FlightFareConfiguration()); - modelBuilder.ApplyConfiguration(new BookingConfiguration()); - modelBuilder.ApplyConfiguration(new BookingOrderConfiguration()); - modelBuilder.ApplyConfiguration(new PaymentConfiguration()); - modelBuilder.ApplyConfiguration(new ReservedSeatConfiguration()); - - ////Ensure all dates are saved as UTC and read as UTC: - ////https://github.com/dotnet/efcore/issues/4711#issuecomment-481215673 - - foreach (var entityType in modelBuilder.Model.GetEntityTypes()) - { - foreach (var property in entityType.GetProperties()) - { - if (property.ClrType == typeof(DateTime)) - { - modelBuilder.Entity(entityType.ClrType) - .Property<DateTime>(property.Name) - .HasConversion( - v => v.ToUniversalTime(), - v => DateTime.SpecifyKind(v, DateTimeKind.Utc)); - } - else if (property.ClrType == typeof(DateTime?)) - { - modelBuilder.Entity(entityType.ClrType) - .Property<DateTime?>(property.Name) - .HasConversion( - v => v.HasValue ? v.Value.ToUniversalTime() : v, - v => v.HasValue ? DateTime.SpecifyKind(v.Value, DateTimeKind.Utc) : v); - } - } - } - } - - public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default) - { - return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken); - } - - //protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - //{ - // base.OnConfiguring(optionsBuilder); - //} - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Data/ModelConfigurations/BookingConfiguration.cs b/BookingService_Old/FlightBooking.Service/Data/ModelConfigurations/BookingConfiguration.cs deleted file mode 100644 index a07fc63..0000000 --- a/BookingService_Old/FlightBooking.Service/Data/ModelConfigurations/BookingConfiguration.cs +++ /dev/null @@ -1,27 +0,0 @@ -using FlightBooking.Service.Data.Models; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; - -namespace FlightBooking.Service.Data.ModelConfigurations -{ - public class BookingConfiguration : IEntityTypeConfiguration<Booking> - { - public void Configure(EntityTypeBuilder<Booking> entity) - { - entity.HasOne(d => d.BookingOrder).WithMany(p => p.Bookings) - .HasPrincipalKey(p => p.Id) - .HasForeignKey(d => d.BookingOrderId) - .OnDelete(DeleteBehavior.ClientSetNull); - - entity.HasOne(d => d.FlightInformation).WithMany(p => p.Bookings) - .HasPrincipalKey(p => p.Id) - .HasForeignKey(d => d.FlightId) - .OnDelete(DeleteBehavior.ClientSetNull); - - entity.HasOne(d => d.FlightFare).WithMany(p => p.Bookings) - .HasPrincipalKey(p => p.Id) - .HasForeignKey(d => d.FlightFareId) - .OnDelete(DeleteBehavior.ClientSetNull); - } - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Data/ModelConfigurations/BookingOrderConfiguration.cs b/BookingService_Old/FlightBooking.Service/Data/ModelConfigurations/BookingOrderConfiguration.cs deleted file mode 100644 index 7ab6170..0000000 --- a/BookingService_Old/FlightBooking.Service/Data/ModelConfigurations/BookingOrderConfiguration.cs +++ /dev/null @@ -1,13 +0,0 @@ -using FlightBooking.Service.Data.Models; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; - -namespace FlightBooking.Service.Data.ModelConfigurations -{ - public class BookingOrderConfiguration : IEntityTypeConfiguration<BookingOrder> - { - public void Configure(EntityTypeBuilder<BookingOrder> entity) - { - } - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Data/ModelConfigurations/FlightFareConfiguration.cs b/BookingService_Old/FlightBooking.Service/Data/ModelConfigurations/FlightFareConfiguration.cs deleted file mode 100644 index 298a02f..0000000 --- a/BookingService_Old/FlightBooking.Service/Data/ModelConfigurations/FlightFareConfiguration.cs +++ /dev/null @@ -1,17 +0,0 @@ -using FlightBooking.Service.Data.Models; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; - -namespace FlightBooking.Service.Data.ModelConfigurations -{ - public class FlightFareConfiguration : IEntityTypeConfiguration<FlightFare> - { - public void Configure(EntityTypeBuilder<FlightFare> entity) - { - entity.HasOne(d => d.FlightInformation).WithMany(p => p.FlightFares) - .HasPrincipalKey(p => p.Id) - .HasForeignKey(d => d.FlightInformationId) - .OnDelete(DeleteBehavior.ClientSetNull); - } - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Data/ModelConfigurations/FlightInformationConfiguration.cs b/BookingService_Old/FlightBooking.Service/Data/ModelConfigurations/FlightInformationConfiguration.cs deleted file mode 100644 index 2af41e5..0000000 --- a/BookingService_Old/FlightBooking.Service/Data/ModelConfigurations/FlightInformationConfiguration.cs +++ /dev/null @@ -1,13 +0,0 @@ -using FlightBooking.Service.Data.Models; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; - -namespace FlightBooking.Service.Data.ModelConfigurations -{ - public class FlightInformationConfiguration : IEntityTypeConfiguration<FlightInformation> - { - public void Configure(EntityTypeBuilder<FlightInformation> entity) - { - } - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Data/ModelConfigurations/PaymentConfiguration.cs b/BookingService_Old/FlightBooking.Service/Data/ModelConfigurations/PaymentConfiguration.cs deleted file mode 100644 index 5b13234..0000000 --- a/BookingService_Old/FlightBooking.Service/Data/ModelConfigurations/PaymentConfiguration.cs +++ /dev/null @@ -1,17 +0,0 @@ -using FlightBooking.Service.Data.Models; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; - -namespace FlightBooking.Service.Data.ModelConfigurations -{ - public class PaymentConfiguration : IEntityTypeConfiguration<Payment> - { - public void Configure(EntityTypeBuilder<Payment> entity) - { - entity.HasOne(d => d.BookingOrder).WithMany(p => p.Payments) - .HasPrincipalKey(p => p.Id) - .HasForeignKey(d => d.BookingOrderId) - .OnDelete(DeleteBehavior.ClientSetNull); - } - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Data/ModelConfigurations/ReservedSeatConfiguration.cs b/BookingService_Old/FlightBooking.Service/Data/ModelConfigurations/ReservedSeatConfiguration.cs deleted file mode 100644 index 9ca62f1..0000000 --- a/BookingService_Old/FlightBooking.Service/Data/ModelConfigurations/ReservedSeatConfiguration.cs +++ /dev/null @@ -1,17 +0,0 @@ -using FlightBooking.Service.Data.Models; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; - -namespace FlightBooking.Service.Data.ModelConfigurations -{ - public class ReservedSeatConfiguration : IEntityTypeConfiguration<ReservedSeat> - { - public void Configure(EntityTypeBuilder<ReservedSeat> entity) - { - entity.HasOne(d => d.FlightInformation).WithMany(p => p.ReservedSeats) - .HasPrincipalKey(p => p.Id) - .HasForeignKey(d => d.FlightInformationId) - .OnDelete(DeleteBehavior.ClientSetNull); - } - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Data/Models/Booking.cs b/BookingService_Old/FlightBooking.Service/Data/Models/Booking.cs deleted file mode 100644 index 315ea18..0000000 --- a/BookingService_Old/FlightBooking.Service/Data/Models/Booking.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace FlightBooking.Service.Data.Models -{ - public class Booking - { - [Key] - public int Id { get; set; } - - public string FirstName { get; set; } = null!; - public string LastName { get; set; } = null!; - public string PhoneNumber { get; set; } = null!; - public string? Email { get; set; } - public string Address { get; set; } = null!; - public DateOnly? DateOfBirth { get; set; } - public Gender Gender { get; set; } - - public string BookingNumber { get; set; } = null!; - public int BookingOrderId { get; set; } - - public BookingStatus BookingStatus { get; set; } = BookingStatus.Pending; - - public int FlightId { get; set; } - public int FlightFareId { get; set; } - public DateTime CreatedAt { get; set; } = DateTime.UtcNow; - public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; - - public virtual FlightInformation FlightInformation { get; set; } = null!; - public virtual FlightFare FlightFare { get; set; } = null!; - public virtual BookingOrder BookingOrder { get; set; } = null!; - - //one to one to for seats. one booking at most one seat - - public ReservedSeat? ReservedSeat { get; set; } - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Data/Models/BookingOrder.cs b/BookingService_Old/FlightBooking.Service/Data/Models/BookingOrder.cs deleted file mode 100644 index 5dce348..0000000 --- a/BookingService_Old/FlightBooking.Service/Data/Models/BookingOrder.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using System.ComponentModel.DataAnnotations; - -namespace FlightBooking.Service.Data.Models -{ - public class BookingOrder - { - [Key] - public int Id { get; set; } - - public string OrderNumber { get; set; } = null!; - public string Email { get; set; } = null!; - - [Precision(19, 4)] - public decimal TotalAmount { get; set; } - - public BookingStatus OrderStatus { get; set; } = BookingStatus.Pending; - public int NumberOfAdults { get; set; } - public int NumberOfChildren { get; set; } - public DateTime CreatedAt { get; set; } = DateTime.UtcNow; - public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; - - public ICollection<Booking> Bookings { get; set; } = new List<Booking>(); - public ICollection<Payment> Payments { get; set; } = new List<Payment>(); - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Data/Models/FlightFare.cs b/BookingService_Old/FlightBooking.Service/Data/Models/FlightFare.cs deleted file mode 100644 index 9dfca35..0000000 --- a/BookingService_Old/FlightBooking.Service/Data/Models/FlightFare.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using System.ComponentModel.DataAnnotations; - -namespace FlightBooking.Service.Data.Models -{ - public class FlightFare - { - [Key] - public int Id { get; set; } - - public int FlightInformationId { get; set; } - public string FareCode { get; set; } = null!; - public string FareName { get; set; } = null!; - - [Precision(19, 4)] - public decimal Price { get; set; } - - public int SeatCapacity { get; set; } - public int SeatReserved { get; set; } - - public DateTime CreatedAt { get; set; } = DateTime.UtcNow; - public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; - - public ICollection<Booking> Bookings { get; set; } = new List<Booking>(); - - public virtual FlightInformation FlightInformation { get; set; } = null!; - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Data/Models/FlightInformation.cs b/BookingService_Old/FlightBooking.Service/Data/Models/FlightInformation.cs deleted file mode 100644 index ed01d2e..0000000 --- a/BookingService_Old/FlightBooking.Service/Data/Models/FlightInformation.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace FlightBooking.Service.Data.Models -{ - public class FlightInformation - { - [Key] - public int Id { get; set; } - - public string FlightNumber { get; set; } = null!; - public string Origin { get; set; } = null!; - public string Destination { get; set; } = null!; - public DateTime DepartureDate { get; set; } - public DateTime ArrivalDate { get; set; } - public string Airline { get; set; } = null!; - public int SeatCapacity { get; set; } - public int SeatReserved { get; set; } - public DateTime CreatedAt { get; set; } = DateTime.UtcNow; - public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; - - public ICollection<Booking> Bookings { get; set; } = new List<Booking>(); - public ICollection<FlightFare> FlightFares { get; set; } = new List<FlightFare>(); - public ICollection<ReservedSeat> ReservedSeats { get; set; } = new List<ReservedSeat>(); - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Data/Models/PassengerInformation.cs b/BookingService_Old/FlightBooking.Service/Data/Models/PassengerInformation.cs deleted file mode 100644 index 1bcb851..0000000 --- a/BookingService_Old/FlightBooking.Service/Data/Models/PassengerInformation.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace FlightBooking.Service.Data.Models -{ - public class PassengerInformation - { - [Key] - public int Id { get; set; } - - public string FirstName { get; set; } = null!; - public string LastName { get; set; } = null!; - public string? PhoneNumber { get; set; } - public string? Email { get; set; } - public string Address { get; set; } = null!; - public DateOnly DateOfBirth { get; set; } - public Gender Gender { get; set; } - public DateTime CreatedAt { get; set; } = DateTime.UtcNow; - public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Data/Models/Payment.cs b/BookingService_Old/FlightBooking.Service/Data/Models/Payment.cs deleted file mode 100644 index 4e9e2cb..0000000 --- a/BookingService_Old/FlightBooking.Service/Data/Models/Payment.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using System.ComponentModel.DataAnnotations; - -namespace FlightBooking.Service.Data.Models -{ - public class Payment - { - [Key] - public int Id { get; set; } - - public string CustomerEmail { get; set; } = null!; - - [Precision(19, 4)] - public decimal TransactionAmount { get; set; } - - public string PaymentReference { get; set; } = null!; - public string OrderNumber { get; set; } = null!; - public string CurrencyCode { get; set; } = null!; - public string PaymentChannel { get; set; } = null!; - public string PaymentStatus { get; set; } = null!; - public int BookingOrderId { get; set; } - public DateTime TransactionDate { get; set; } - public string? MetaData { get; set; } - - public DateTime CreatedAt { get; set; } = DateTime.UtcNow; - public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; - - public virtual BookingOrder BookingOrder { get; set; } = null!; - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Data/Models/ReservedSeat.cs b/BookingService_Old/FlightBooking.Service/Data/Models/ReservedSeat.cs deleted file mode 100644 index 6f6fb69..0000000 --- a/BookingService_Old/FlightBooking.Service/Data/Models/ReservedSeat.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace FlightBooking.Service.Data.Models -{ - public class ReservedSeat - { - [Key] - public int Id { get; set; } - - public string SeatNumber { get; set; } = null!; // e.g 1A, 33B - public string? BookingNumber { get; set; } - public int? BookingId { get; set; } //FK to Booking - public string FlightNumber { get; set; } = null!; - public int FlightInformationId { get; set; } - public bool IsReserved { get; set; } = false; - public DateTime CreatedAt { get; set; } = DateTime.UtcNow; - public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; - - public virtual FlightInformation FlightInformation { get; set; } = null!; - public virtual Booking? Booking { get; set; } - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Data/Repository/GenericRepository.cs b/BookingService_Old/FlightBooking.Service/Data/Repository/GenericRepository.cs deleted file mode 100644 index 584354a..0000000 --- a/BookingService_Old/FlightBooking.Service/Data/Repository/GenericRepository.cs +++ /dev/null @@ -1,257 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using System.Linq.Expressions; - -namespace FlightBooking.Service.Data.Repository -{ - public class GenericRepository<T> : IGenericRepository<T> where T : class, new() - { - //Responses: failed=0, success=1 - - //IEnumerable iterates over an in-memory collection while IQueryable does so on the DB - // call to .ToList to enable instant query against DB - - protected FlightBookingContext _db; - protected ILogger _logger; - - public GenericRepository(FlightBookingContext db, ILogger<GenericRepository<T>> logger) - { - _logger = logger; - _db = db; - } - - public IQueryable<T> GetAll() - { - return _db.Set<T>(); - } - - public DbSet<T> GetDbSet() - { - return _db.Set<T>(); - } - - public IQueryable<T> Query() - { - return _db.Set<T>().AsQueryable(); - } - - #region Async Methods - - public async Task<T?> GetByIdAsync(int id) - { - var entity = await _db.Set<T>().FindAsync(id); - - return entity; - } - - public async Task<T?> GetByGuidAsync(Guid id) - { - var entity = await _db.Set<T>().FindAsync(id); - - return entity; - } - - public async Task<int> CreateAsync(T entity, bool isSave = true) - { - if (entity == null) - { - _logger.LogError(RepositoryConstants.CreateNullError, typeof(T).Name); - return (int)InternalCode.EntityIsNull; - } - - _db.Set<T>().Add(entity); - - if (isSave) - { - return await SaveChangesToDbAsync(); - } - - return (int)InternalCode.Success; - } - - public async Task<int> UpdateAsync(T entity, bool isSave = true) - { - //Check for this in each overriding implementation or services - //var prev = await GetById(id); - - //if (prev == null) - //{ - // return 0; - //} - - _db.Set<T>().Update(entity); - - if (isSave) - { - return await SaveChangesToDbAsync(); - } - - return (int)InternalCode.Success; - } - - public async Task<int> DeleteAsync(int id, bool isSave = true) - { - T? entity = await GetByIdAsync(id); - - if (entity == null) - { - _logger.LogError(RepositoryConstants.DeleteNullError, typeof(T).Name); - return (int)InternalCode.EntityNotFound; - } - - _db.Set<T>().Remove(entity); - - if (isSave) - { - return await SaveChangesToDbAsync(); - } - - return (int)InternalCode.Success; - } - - public async Task<int> BulkDeleteAsync(IEnumerable<int> entityId, bool isSave = true) - { - if (entityId == null || !entityId.Any()) - { - _logger.LogError(RepositoryConstants.BulkDeleteNullError, typeof(T).Name); - return (int)InternalCode.EntityIsNull; - } - - DbSet<T> table = _db.Set<T>(); - - foreach (int id in entityId) - { - T? entity = await GetByIdAsync(id); - if (entity != null) - { - table.Remove(entity); - } - } - - if (isSave) - { - return await SaveChangesToDbAsync(); - } - - return (int)InternalCode.Success; - } - - public async Task<int> BulkCreateAsync(IEnumerable<T> entities, bool isSave = true) - { - if (entities == null || !entities.Any()) - { - _logger.LogError(RepositoryConstants.BulkCreateNullError, typeof(T).Name); - return (int)InternalCode.EntityIsNull; - } - - DbSet<T> table = _db.Set<T>(); - - table.AddRange(entities); - - if (isSave) - { - return await SaveChangesToDbAsync(); - } - - return (int)InternalCode.Success; - } - - //calling this once works since we are using just one DbContext - //TODO: returning 0 should not lead to 500 error. 0 means no entries were added which may be because all entries have been added already - //fix this after tests have been writing for projects - public async Task<int> SaveChangesToDbAsync() - { - _logger.LogInformation(RepositoryConstants.LoggingStarted); - int saveResult; - - try - { - int tempResult = await _db.SaveChangesAsync(); //give numbers of entries updated in db. in some cases e.g Update, when no data changes, this method returns 0 - if (tempResult == 0) - { - _logger.LogInformation(RepositoryConstants.EmptySaveInfo); - } - saveResult = (int)InternalCode.Success; //means atleast one entry was made. 1 is InternalCode.Success. - //saveResult = tempResult > 0 ? 1 : 0; //means atleast one entry was made. 1 is InternalCode.Success - } - catch (DbUpdateConcurrencyException ex) - { - _logger.LogError(ex, RepositoryConstants.UpdateConcurrencyException); - saveResult = (int)InternalCode.UpdateError; - throw; - } - catch (DbUpdateException ex) - { - _logger.LogError(ex, RepositoryConstants.UpdateException); - saveResult = (int)InternalCode.UpdateError; - throw; - } - catch (Exception ex) - { - _logger.LogError(ex, RepositoryConstants.SaveChangesException); - saveResult = (int)InternalCode.UpdateError; - throw; - } - return saveResult; - } - - public async Task<bool> EntityExistsAsync(int id) - { - T? entityFound = await _db.Set<T>().FindAsync(id); - if (entityFound == null) - { - return false; - } - - return true; - } - - #endregion Async Methods - - public IQueryable<T> OrderByText(IQueryable<T> data, SortOrder order, Expression<Func<T, string>> expression) - { - IQueryable<T> orderedData; - if (order == SortOrder.ASC) - { - orderedData = data.OrderBy(expression); - } - else - { - orderedData = data.OrderByDescending(expression); - } - - return orderedData; - } - - public IQueryable<T> OrderByDate(IQueryable<T> data, SortOrder order, Expression<Func<T, DateTime>> expression) - { - IQueryable<T> orderedData; - if (order == SortOrder.ASC) - { - orderedData = data.OrderBy(expression); - } - else - { - orderedData = data.OrderByDescending(expression); - } - - return orderedData; - } - - public async Task<List<T>> TakeAndSkipAsync(IQueryable<T> data, int pageSize, int pageIndex) - { - //List<T> paginatedList = new List<T>(); - - //if (data == null || data.Count() <= 0) - // return paginatedList; - - //if (pageSize == 0 && pageIndex == 0) - // return paginatedList; - - int numRowSkipped = pageSize * (pageIndex - 1); - - List<T> paginated = await data.Skip(numRowSkipped).Take(pageSize).ToListAsync(); - - return paginated; - } - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Data/Repository/IGenericRepository.cs b/BookingService_Old/FlightBooking.Service/Data/Repository/IGenericRepository.cs deleted file mode 100644 index 74e90c8..0000000 --- a/BookingService_Old/FlightBooking.Service/Data/Repository/IGenericRepository.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using System.Linq.Expressions; - -namespace FlightBooking.Service.Data.Repository -{ - public interface IGenericRepository<T> where T : class - { - Task<int> BulkCreateAsync(IEnumerable<T> entities, bool isSave = true); - - Task<int> BulkDeleteAsync(IEnumerable<int> ids, bool isSave = true); - - Task<int> CreateAsync(T entity, bool isSave = true); - - Task<int> DeleteAsync(int id, bool isSave = true); - - Task<bool> EntityExistsAsync(int id); - - Task<T?> GetByIdAsync(int id); - - Task<T?> GetByGuidAsync(Guid id); - - Task<int> UpdateAsync(T entity, bool isSave = true); - - Task<int> SaveChangesToDbAsync(); - - DbSet<T> GetDbSet(); - - IQueryable<T> Query(); - - Task<List<T>> TakeAndSkipAsync(IQueryable<T> data, int pageSize, int pageIndex); - - IQueryable<T> OrderByText(IQueryable<T> data, SortOrder order, Expression<Func<T, string>> expression); - - IQueryable<T> OrderByDate(IQueryable<T> data, SortOrder order, Expression<Func<T, DateTime>> expression); - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Data/Repository/RepositoryModule.cs b/BookingService_Old/FlightBooking.Service/Data/Repository/RepositoryModule.cs deleted file mode 100644 index 90b6c0e..0000000 --- a/BookingService_Old/FlightBooking.Service/Data/Repository/RepositoryModule.cs +++ /dev/null @@ -1,17 +0,0 @@ -using FlightBooking.Service.Data.Models; - -namespace FlightBooking.Service.Data.Repository -{ - public static class RepositoryModule - { - public static void AddRepository(this IServiceCollection services) - { - services.AddScoped<IGenericRepository<Booking>, GenericRepository<Booking>>(); - services.AddScoped<IGenericRepository<FlightInformation>, GenericRepository<FlightInformation>>(); - services.AddScoped<IGenericRepository<FlightFare>, GenericRepository<FlightFare>>(); - services.AddScoped<IGenericRepository<Payment>, GenericRepository<Payment>>(); - services.AddScoped<IGenericRepository<BookingOrder>, GenericRepository<BookingOrder>>(); - services.AddScoped<IGenericRepository<ReservedSeat>, GenericRepository<ReservedSeat>>(); - } - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/FlightBooking.Service.csproj b/BookingService_Old/FlightBooking.Service/FlightBooking.Service.csproj deleted file mode 100644 index 217d79c..0000000 --- a/BookingService_Old/FlightBooking.Service/FlightBooking.Service.csproj +++ /dev/null @@ -1,45 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk.Web"> - - <PropertyGroup> - <TargetFramework>net8.0</TargetFramework> - <Nullable>enable</Nullable> - <ImplicitUsings>enable</ImplicitUsings> - </PropertyGroup> - <PropertyGroup> - <EnableNETAnalyzers>true</EnableNETAnalyzers> - <UserSecretsId>b1b4ccfd-9fcb-4702-b4e5-c4e88d0bc805</UserSecretsId> - </PropertyGroup> - - <ItemGroup> - <PackageReference Include="AutoMapper" Version="13.0.1" /> - <PackageReference Include="Azure.Identity" Version="1.10.4" /> - <PackageReference Include="libphonenumber-csharp" Version="8.13.30" /> - <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.2" /> - <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.2" /> - <PackageReference Include="Microsoft.Data.SqlClient" Version="5.1.5" /> - <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.2"> - <PrivateAssets>all</PrivateAssets> - <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> - </PackageReference> - <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.2"> - <PrivateAssets>all</PrivateAssets> - <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> - </PackageReference> - <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.1" /> - <PackageReference Include="MySqlConnector" Version="2.3.5" /> - <PackageReference Include="NLog.Web.AspNetCore" Version="5.3.8" /> - <PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="7.3.1" /> - - <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.2" /> - <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.2" /> - <PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="8.0.0" /> - <PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.3.1" /> - <PackageReference Include="NuGet.Common" Version="6.9.1" /> - <PackageReference Include="NuGet.Protocol" Version="6.9.1" /> - <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.1" /> - <PackageReference Include="Stripe.net" Version="43.18.0" /> - <PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" /> - <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.3.1" /> - </ItemGroup> - -</Project> diff --git a/BookingService_Old/FlightBooking.Service/FlightBooking.Service.http b/BookingService_Old/FlightBooking.Service/FlightBooking.Service.http deleted file mode 100644 index 045f70b..0000000 --- a/BookingService_Old/FlightBooking.Service/FlightBooking.Service.http +++ /dev/null @@ -1,6 +0,0 @@ -@FlightBooking.Service_HostAddress = http://localhost:5000 - -GET {{FlightBooking.Service_HostAddress}}/weatherforecast/ -Accept: application/json - -### diff --git a/BookingService_Old/FlightBooking.Service/Middleware/ErrorHandlingMiddleware.cs b/BookingService_Old/FlightBooking.Service/Middleware/ErrorHandlingMiddleware.cs deleted file mode 100644 index 1510d26..0000000 --- a/BookingService_Old/FlightBooking.Service/Middleware/ErrorHandlingMiddleware.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace FlightBooking.Service.Middleware -{ - public class ErrorHandlingMiddleware - { - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Middleware/MiddlewareExtensions.cs b/BookingService_Old/FlightBooking.Service/Middleware/MiddlewareExtensions.cs deleted file mode 100644 index 6b50b93..0000000 --- a/BookingService_Old/FlightBooking.Service/Middleware/MiddlewareExtensions.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace FlightBooking.Service.Middleware -{ - public static class MiddlewareExtensions - { - public static IApplicationBuilder UseErrorHandlingMiddleware(this IApplicationBuilder builder) - { - return builder.UseMiddleware<ErrorHandlingMiddleware>(); - } - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Program.cs b/BookingService_Old/FlightBooking.Service/Program.cs deleted file mode 100644 index 041f1b1..0000000 --- a/BookingService_Old/FlightBooking.Service/Program.cs +++ /dev/null @@ -1,63 +0,0 @@ -using FlightBooking.Service.Data; -using NLog; -using NLog.Web; - -namespace FlightBooking.Service -{ - public class Program - { - public static void Main(string[] args) - { - var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger(); - - try - { - logger.Debug("init main"); - - var host = CreateHostBuilder(args).Build(); - - using (var scope = host.Services.CreateScope()) - { - var services = scope.ServiceProvider; - - try - { - DatabaseSeeding.Initialize(services); - } - catch (Exception ex) - { - logger.Error(ex, "An error occurred seeding the DB."); - } - } - - host.Run(); - } - catch (Exception exception) - { - //NLog: catch setup errors - logger.Error(exception, "Stopped program because of exception"); - throw; - } - finally - { - // Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux) - LogManager.Shutdown(); - } - } - - public static IHostBuilder CreateHostBuilder(string[] args) - { - return Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup<Startup>(); - }) - .ConfigureLogging(logging => - { - logging.ClearProviders(); - logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Information); - }) - .UseNLog(); // NLog: Setup NLog for Dependency injection - } - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Properties/launchSettings.json b/BookingService_Old/FlightBooking.Service/Properties/launchSettings.json deleted file mode 100644 index 434256c..0000000 --- a/BookingService_Old/FlightBooking.Service/Properties/launchSettings.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "$schema": "http://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:37680", - "sslPort": 44321 - } - }, - "profiles": { - "http": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "launchUrl": "swagger", - "applicationUrl": "http://localhost:5000", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "https": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "launchUrl": "swagger", - "applicationUrl": "https://localhost:7287;http://localhost:5000", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "swagger", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Services/BookingOrderService.cs b/BookingService_Old/FlightBooking.Service/Services/BookingOrderService.cs deleted file mode 100644 index f1d70bd..0000000 --- a/BookingService_Old/FlightBooking.Service/Services/BookingOrderService.cs +++ /dev/null @@ -1,391 +0,0 @@ -using FlightBooking.Service.Data; -using FlightBooking.Service.Data.DTO; -using FlightBooking.Service.Data.Models; -using FlightBooking.Service.Data.Repository; -using FlightBooking.Service.Services.Interfaces; -using Microsoft.EntityFrameworkCore; - -namespace FlightBooking.Service.Services -{ - ///<inheritdoc /> - public class BookingOrderService : IBookingOrderService - { - private readonly IGenericRepository<BookingOrder> _orderRepo; - private readonly IGenericRepository<FlightInformation> _flightRepo; - private readonly IGenericRepository<FlightFare> _flightFareRepo; - private readonly IStripeService _stripeService; - - private readonly ILogger<BookingOrderService> _logger; - - private List<FlightFare> allFlightFares = new List<FlightFare>(); //declare once so we can use it throughout - - public BookingOrderService(IGenericRepository<BookingOrder> orderRepo, IGenericRepository<FlightInformation> flightRepo, - IGenericRepository<FlightFare> flightFareRepo, IStripeService stripeService, ILogger<BookingOrderService> logger) - { - _orderRepo = orderRepo; - _flightRepo = flightRepo; - _flightFareRepo = flightFareRepo; - _stripeService = stripeService; - _logger = logger; - } - - public async Task<ServiceResponse<BookingResponseDTO?>> CreateBookingOrderAsync(BookingOrderDTO order) - { - if (order == null) - { - return new ServiceResponse<BookingResponseDTO?>(null, InternalCode.InvalidParam); - } - - /* - Checks: - - outbound and return flights cannot be same - - Check if all the flights are available - - Check if the fares have available seats - - If Checks are not passed, return 422 - */ - - bool hasReturnFlight = !string.IsNullOrWhiteSpace(order.ReturnFlightNumber); - - //Outbound and Return flights cannot be the same - if (hasReturnFlight && order.ReturnFlightNumber == order.OutboundFlightNumber) - { - return new ServiceResponse<BookingResponseDTO?>(null, InternalCode.Unprocessable, "Outbound and return flights cannot be the same"); - } - - int totalFlights = order.Bookings.Count; - - FlightInformation outboundFlightInfo = new FlightInformation(); - FlightInformation returnFlightInfo = new FlightInformation(); - - //Check if the outbound flight is valid and if seats are available - var outboundFlight = CheckAvailableFlight(order.OutboundFlightNumber, totalFlights); - - //If flight not valid, return false - if (outboundFlight.FlightInformation == null) - { - return new ServiceResponse<BookingResponseDTO?>(null, InternalCode.Unprocessable, "The selected flight is not valid"); - } - - //if it's booked to Max, return error - if (outboundFlight.IsBookedToMax) - { - return new ServiceResponse<BookingResponseDTO?>(null, InternalCode.Unprocessable, "The number of flights exceed number of available seats"); - } - - outboundFlightInfo = outboundFlight.FlightInformation; - - //Check if Return flight available and not booked to max - - if (hasReturnFlight) - { - //If none of the Booking - - var returnFlight = CheckAvailableFlight(order.ReturnFlightNumber!, totalFlights); - - if (returnFlight.FlightInformation == null) - { - return new ServiceResponse<BookingResponseDTO?>(null, InternalCode.Unprocessable, "The selected flight is not valid"); - } - - if (returnFlight.IsBookedToMax) - { - return new ServiceResponse<BookingResponseDTO?>(null, InternalCode.Unprocessable, "The number of flights exceed number of available seats"); - } - - returnFlightInfo = returnFlight.FlightInformation; - } - - //Check if the selected Fares for each flight is valid and seats are available - var (IsAllFareExists, IsAnyFareMaxedOut, FareCodes) = CheckIfAnyFareIsMaxedOut(order, hasReturnFlight); - - if (!IsAllFareExists) - { - return new ServiceResponse<BookingResponseDTO?>(null, InternalCode.Unprocessable, "Some selected fare do not exist. Please check that all fare exists"); - } - - if (IsAnyFareMaxedOut) - { - return new ServiceResponse<BookingResponseDTO?>(null, InternalCode.Unprocessable, "The number of flights exceed number of available seats"); - } - - //if all checks passed, lets reduce number of available seats. - //If Checks are passed, set status as InProgress and reduce available seats to avoid overbooking the flight - //We hold a seat for N mins max(N is set in config). Use a background job to return UnbookedSeats back into the Pool - - await UpdateAvailableSeats(order, FareCodes); - - string orderReference = Guid.NewGuid().ToString("N")[..10].ToUpper(); - - //Create all the bookings - List<Booking> bookings = new List<Booking>(); - - decimal totalAmount = 0; - foreach (var booking in order.Bookings) - { - //TODO: Use AutoMapper - bookings.Add(new Booking - { - FirstName = booking.FirstName, - LastName = booking.LastName, - PhoneNumber = booking.PhoneNumber, - Email = booking.Email, - DateOfBirth = booking.DateOfBirth, - Address = booking.Address, - Gender = booking.Gender, - BookingNumber = Guid.NewGuid().ToString("N")[..10].ToUpper(), - BookingStatus = BookingStatus.Confirmed, - FlightId = outboundFlightInfo.Id, - FlightFareId = booking.OutboundFareId, - CreatedAt = DateTime.UtcNow - }); - - totalAmount += allFlightFares.FirstOrDefault(x => x.Id == booking.OutboundFareId)!.Price; - - //If return flight, add a seperate booking - if (hasReturnFlight) - { - bookings.Add(new Booking - { - FirstName = booking.FirstName, - LastName = booking.LastName, - PhoneNumber = booking.PhoneNumber, - Email = booking.Email, - DateOfBirth = booking.DateOfBirth, - Address = booking.Address, - Gender = booking.Gender, - BookingNumber = Guid.NewGuid().ToString("N")[..10].ToUpper(), - BookingStatus = BookingStatus.Confirmed, - FlightId = returnFlightInfo.Id, - FlightFareId = (int)booking.ReturnFareId!, - CreatedAt = DateTime.UtcNow - }); - - totalAmount += allFlightFares.FirstOrDefault(x => x.Id == booking.ReturnFareId)!.Price; - } - } - - //Sum all cost as part of Order - - BookingOrder bookingOrder = new BookingOrder - { - Bookings = bookings, - Email = order.Email, - OrderStatus = BookingStatus.Confirmed, - TotalAmount = totalAmount, - OrderNumber = orderReference, - CreatedAt = DateTime.UtcNow, - NumberOfAdults = 1, - NumberOfChildren = 1, - }; - - int result = await _orderRepo.CreateAsync(bookingOrder); - - if (result != 1) - { - return new ServiceResponse<BookingResponseDTO?>(null, (InternalCode)result); - } - - //Generate payment link with the cost if payment successful - - /* - Return Order Number, Payment Link, Payment Expiry - */ - StripeDataDTO stripeData = new StripeDataDTO - { - SuccessUrl = "https://localhost:44321/success", - CancelUrl = "https://localhost:44321/cancel", - ProductDescription = $"Booking for {orderReference}", - Amount = totalAmount, - CurrencyCode = "USD", - CustomerEmail = order.Email, - ProductName = "Flight Booking Service", - OrderNumber = orderReference, - }; - - var stripeResponse = _stripeService.GetStripeCheckoutUrl(stripeData); - - BookingResponseDTO bookingResponse = new BookingResponseDTO - { - OrderNumber = orderReference, - PaymentLink = stripeResponse.Data - }; - - return new ServiceResponse<BookingResponseDTO?>(bookingResponse, InternalCode.Success); - } - - public async Task<ServiceResponse<BookingResponseDTO?>> GetCheckoutUrlAsync(string orderNumber) - { - if (string.IsNullOrWhiteSpace(orderNumber)) - { - return new ServiceResponse<BookingResponseDTO?>(null, InternalCode.InvalidParam, "Order reference not supplied"); - } - - //gett the order details - var orderDetails = await _orderRepo.Query() - .FirstOrDefaultAsync(x => x.OrderNumber == orderNumber); - - if (orderDetails == null) - { - return new ServiceResponse<BookingResponseDTO?>(null, InternalCode.EntityNotFound, "The order with the supplied order number was not found"); - } - - if (orderDetails.OrderStatus == BookingStatus.Paid) - { - return new ServiceResponse<BookingResponseDTO?>(null, InternalCode.Unprocessable, "This order has already been paid for"); - } - - StripeDataDTO stripeData = new StripeDataDTO - { - SuccessUrl = "https://localhost:44321/success", - CancelUrl = "https://localhost:44321/cancel", - ProductDescription = $"Booking for Order : {orderDetails.OrderNumber}", - Amount = orderDetails.TotalAmount, - CurrencyCode = "USD", - CustomerEmail = orderDetails.Email, - ProductName = "Flight Booking Service", - OrderNumber = orderNumber - }; - - var stripeResponse = _stripeService.GetStripeCheckoutUrl(stripeData); - - BookingResponseDTO bookingResponse = new BookingResponseDTO - { - OrderNumber = orderNumber, - PaymentLink = stripeResponse.Data - }; - - return new ServiceResponse<BookingResponseDTO?>(bookingResponse, InternalCode.Success); - } - - private (FlightInformation? FlightInformation, bool IsBookedToMax) CheckAvailableFlight(string flightNumber, int totalFlights) - { - FlightInformation? flightInformation = _flightRepo.Query() - .FirstOrDefault(x => x.FlightNumber == flightNumber); - - //If flight not valid, return false - if (flightInformation == null) - { - return (flightInformation, false); - } - - int availableFlightCapacity = flightInformation.SeatCapacity - flightInformation.SeatReserved; - - return (flightInformation, totalFlights > availableFlightCapacity); - } - - private (bool IsAllFareExists, bool IsAnyFareMaxedOut, List<int> FareCodes) CheckIfAnyFareIsMaxedOut(BookingOrderDTO order, bool hasReturnFlight) - { - //get all flight fares for all the list ID. We can then use throughout the booking process - var validFlightFares = _flightFareRepo.Query() - .Include(x => x.FlightInformation) - .Where(x => x.FlightInformation.FlightNumber == order.OutboundFlightNumber); - - //if return flight, then add the fares - if (hasReturnFlight) - { - validFlightFares = _flightFareRepo.Query() - .Include(x => x.FlightInformation) - .Where(x => x.FlightInformation.FlightNumber == order.OutboundFlightNumber - || x.FlightInformation.FlightNumber == order.ReturnFlightNumber); - } - - //we get all the flight fares and save to the variable so we can reuse - allFlightFares = validFlightFares.ToList(); - - List<int> fareIds = new List<int>(); - - //get all fare Id - var outboundFares = order.Bookings - .Select(x => x.OutboundFareId) - .ToList(); - - //Add to a list - fareIds.AddRange(outboundFares); - - //we check if all fares are valid for that flight - var isAllFareValid = allFlightFares.Any(x => outboundFares.Contains(x.Id) - && x.FlightInformation.FlightNumber == order.OutboundFlightNumber); - - //if atleast one of the fare in the outbound flight is invalid, return false - if (!isAllFareValid) - { - return (false, true, new List<int>()); - } - - //check the codes for the return flights are also valid - if (hasReturnFlight) - { - var returnFares = order.Bookings - .Select(x => (int)x.ReturnFareId!) - .ToList(); - - fareIds.AddRange(returnFares); - - isAllFareValid = allFlightFares.Any(x => returnFares.Contains(x.Id) - && x.FlightInformation.FlightNumber == order.ReturnFlightNumber); - - if (!isAllFareValid) - { - return (false, true, new List<int>()); - } - } - - var flightFares = allFlightFares - .Select(y => new - { - y.Id, - y.FareCode, - AvailableSeats = y.SeatCapacity - y.SeatReserved - }).ToList(); - - //group the fares by Id - var fareGroup = fareIds.GroupBy(x => x).ToList(); - - bool isAnyFareMaxedOut = true; - - //for each fare, check if the seats are available - foreach (var group in fareGroup) - { - isAnyFareMaxedOut = flightFares.Any(x => x.Id == group.Key && group.Count() > x.AvailableSeats); - - //if any fare is maxed out, terminate the loop - if (isAnyFareMaxedOut) - { - break; - } - } - - return (true, isAnyFareMaxedOut, fareIds); - } - - private async Task UpdateAvailableSeats(BookingOrderDTO order, List<int> fareCodes) - { - int totalFlights = order.Bookings.Count; - - int result = await _flightRepo.Query() - .Where(x => x.FlightNumber == order.OutboundFlightNumber) - .ExecuteUpdateAsync(x => x.SetProperty(y => y.SeatReserved, y => y.SeatReserved + totalFlights)); - - //If a return is booked, reduce the seats too - if (!string.IsNullOrWhiteSpace(order.ReturnFlightNumber)) - { - result = await _flightRepo.Query() - .Where(x => x.FlightNumber == order.ReturnFlightNumber) - .ExecuteUpdateAsync(x => x.SetProperty(y => y.SeatReserved, y => y.SeatReserved + totalFlights)); - } - - //update fare capacity. includes all fares, initial and return - var grouped = fareCodes - .GroupBy(x => x).ToList(); - - foreach (var fare in grouped) - { - result = await _flightFareRepo.Query() - .Where(x => x.Id == fare.Key) - .ExecuteUpdateAsync(x => x.SetProperty(y => y.SeatReserved, y => y.SeatReserved + fare.Count())); - } - - return; - } - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Services/BookingService.cs b/BookingService_Old/FlightBooking.Service/Services/BookingService.cs deleted file mode 100644 index 9dd41fe..0000000 --- a/BookingService_Old/FlightBooking.Service/Services/BookingService.cs +++ /dev/null @@ -1,101 +0,0 @@ -using AutoMapper; -using AutoMapper.QueryableExtensions; -using FlightBooking.Service.Data; -using FlightBooking.Service.Data.DTO; -using FlightBooking.Service.Data.Models; -using FlightBooking.Service.Data.Repository; -using FlightBooking.Service.Services.Interfaces; -using Microsoft.EntityFrameworkCore; - -namespace FlightBooking.Service.Services -{ - ///<inheritdoc /> - public class BookingService : IBookingService - { - private readonly IGenericRepository<Booking> _bookingRepo; - private readonly IMapper _mapper; - - public BookingService(IGenericRepository<Booking> bookingRepo, IMapper mapper) - { - _bookingRepo = bookingRepo; - _mapper = mapper; - } - - public async Task<ServiceResponse<BookingDTO?>> GetBookingByBookingNumberAsync(string bookingNumber) - { - if (string.IsNullOrWhiteSpace(bookingNumber)) - { - return new ServiceResponse<BookingDTO?>(null, InternalCode.InvalidParam, "No booking number supplied"); - } - - var booking = await _bookingRepo.Query() - .Include(x => x.FlightFare) - .Include(x => x.FlightInformation) - .Include(x => x.ReservedSeat) - .FirstOrDefaultAsync(x => x.BookingNumber == bookingNumber); - - if (booking == null) - { - return new ServiceResponse<BookingDTO?>(null, InternalCode.EntityNotFound, "No booking with that booking number exists"); - } - - var bookingDTO = _mapper.Map<Booking, BookingDTO>(booking); - - return new ServiceResponse<BookingDTO?>(bookingDTO, InternalCode.Success); - } - - public async Task<ServiceResponse<BookingDTO?>> GetBookingByBookingId(int bookingId) - { - var booking = await _bookingRepo.Query() - .Include(x => x.FlightFare) - .Include(x => x.FlightInformation) - .Include(x => x.ReservedSeat) - .FirstOrDefaultAsync(x => x.Id == bookingId); - - if (booking == null) - { - return new ServiceResponse<BookingDTO?>(null, InternalCode.EntityNotFound, "No booking with that booking ID exists"); - } - - var bookingDTO = _mapper.Map<Booking, BookingDTO>(booking); - - return new ServiceResponse<BookingDTO?>(bookingDTO, InternalCode.Success); - } - - public ServiceResponse<IEnumerable<BookingDTO>?> GetBookingsByEmail(string email) - { - if (string.IsNullOrWhiteSpace(email)) - { - return new ServiceResponse<IEnumerable<BookingDTO>?>(null, InternalCode.InvalidParam, "No email supplied"); - } - - var bookings = _bookingRepo.Query() - .Include(x => x.FlightFare) - .Include(x => x.FlightInformation) - .Include(x => x.ReservedSeat) - .Where(x => x.Email == email) - .ProjectTo<BookingDTO>(_mapper.ConfigurationProvider) - .ToList(); - - return new ServiceResponse<IEnumerable<BookingDTO>?>(bookings, InternalCode.Success); - } - - public ServiceResponse<IEnumerable<BookingDTO>?> GetBookingsByOrderNumber(string orderNumber) - { - if (string.IsNullOrWhiteSpace(orderNumber)) - { - return new ServiceResponse<IEnumerable<BookingDTO>?>(null, InternalCode.InvalidParam, "No order number supplied"); - } - - var bookings = _bookingRepo.Query() - .Include(x => x.FlightFare) - .Include(x => x.FlightInformation) - .Include(x => x.ReservedSeat) - .Where(x => x.BookingOrder.OrderNumber == orderNumber) - .ProjectTo<BookingDTO>(_mapper.ConfigurationProvider) - .ToList(); - - return new ServiceResponse<IEnumerable<BookingDTO>?>(bookings, InternalCode.Success); - } - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Services/FlightBookingProfile.cs b/BookingService_Old/FlightBooking.Service/Services/FlightBookingProfile.cs deleted file mode 100644 index 4a7d890..0000000 --- a/BookingService_Old/FlightBooking.Service/Services/FlightBookingProfile.cs +++ /dev/null @@ -1,29 +0,0 @@ -using AutoMapper; -using FlightBooking.Service.Data.DTO; -using FlightBooking.Service.Data.Models; - -namespace FlightBooking.Service.Services -{ - public class FlightBookingProfile : Profile - { - public FlightBookingProfile() - { - CreateMap<FlightFare, FlightFareDTO>() - .ForMember(dest => dest.AvailableSeats, opt => opt.MapFrom(src => src.SeatCapacity - src.SeatReserved)) - .ForMember(dest => dest.FlightNumber, opt => opt.MapFrom(src => src.FlightInformation.FlightNumber)); - - CreateMap<ReservedSeat, ReservedSeatDTO>(); - - CreateMap<FlightInformation, FlightInformationDTO>() - .ForMember(dest => dest.AvailableSeats, opt => opt.MapFrom(src => src.SeatCapacity - src.SeatReserved)); - - CreateMap<FlightInformation, BookingFlightInformationDTO>(); - - CreateMap<FlightFare, BookingFlightFareDTO>() - .ForMember(dest => dest.FlightNumber, opt => opt.MapFrom(src => src.FlightInformation.FlightNumber)); - - CreateMap<Booking, BookingDTO>() - .ForMember(dest => dest.SeatNumber, opt => opt.MapFrom(src => src.ReservedSeat != null ? src.ReservedSeat.SeatNumber : null)); - } - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Services/FlightFareService.cs b/BookingService_Old/FlightBooking.Service/Services/FlightFareService.cs deleted file mode 100644 index 33bb18c..0000000 --- a/BookingService_Old/FlightBooking.Service/Services/FlightFareService.cs +++ /dev/null @@ -1,44 +0,0 @@ -using AutoMapper; -using AutoMapper.QueryableExtensions; -using FlightBooking.Service.Data; -using FlightBooking.Service.Data.DTO; -using FlightBooking.Service.Data.Models; -using FlightBooking.Service.Data.Repository; -using FlightBooking.Service.Services.Interfaces; -using Microsoft.EntityFrameworkCore; - -namespace FlightBooking.Service.Services -{ - ///<inheritdoc /> - public class FlightFareService : IFlightFareService - { - private readonly IGenericRepository<FlightFare> _fareRepository; - private readonly IMapper _mapper; - - public FlightFareService(IMapper mapper, IGenericRepository<FlightFare> fareRepository) - { - _mapper = mapper; - _fareRepository = fareRepository; - } - - public ServiceResponse<IEnumerable<FlightFareDTO>?> GetFaresByFlightNumber(string flightNumber) - { - var fares = _fareRepository.Query() - .Include(x => x.FlightInformation) - .Where(x => x.FlightInformation.FlightNumber == flightNumber) - .ProjectTo<FlightFareDTO>(_mapper.ConfigurationProvider) - .ToList(); - - return new ServiceResponse<IEnumerable<FlightFareDTO>?>(fares, InternalCode.Success); - } - - public async Task<ServiceResponse<string>> UpdateFlightFareCapacityAsync(int fareId) - { - int result = await _fareRepository.Query() - .Where(x => x.Id == fareId) - .ExecuteUpdateAsync(x => x.SetProperty(y => y.SeatReserved, y => y.SeatReserved + 1)); - - return new ServiceResponse<string>(string.Empty, (InternalCode)result); - } - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Services/FlightService.cs b/BookingService_Old/FlightBooking.Service/Services/FlightService.cs deleted file mode 100644 index f7f4e13..0000000 --- a/BookingService_Old/FlightBooking.Service/Services/FlightService.cs +++ /dev/null @@ -1,47 +0,0 @@ -using AutoMapper; -using FlightBooking.Service.Data; -using FlightBooking.Service.Data.DTO; -using FlightBooking.Service.Data.Models; -using FlightBooking.Service.Data.Repository; -using FlightBooking.Service.Services.Interfaces; -using Microsoft.EntityFrameworkCore; - -namespace FlightBooking.Service.Services -{ - ///<inheritdoc /> - public class FlightService : IFlightService - { - private readonly IGenericRepository<FlightInformation> _flightRepo; - private readonly IMapper _mapper; - - public FlightService(IGenericRepository<FlightInformation> flightRepo, IMapper mapper) - { - _mapper = mapper; - _flightRepo = flightRepo; - } - - public async Task<ServiceResponse<FlightInformationDTO?>> GetFlightInformationAsync(string flightNumber) - { - FlightInformation? flight = await _flightRepo.Query() - .FirstOrDefaultAsync(x => x.FlightNumber == flightNumber); - - if (flight == null) - { - return new ServiceResponse<FlightInformationDTO?>(null, InternalCode.EntityNotFound, "flight not found"); - } - - FlightInformationDTO flightDto = _mapper.Map<FlightInformation, FlightInformationDTO>(flight); - - return new ServiceResponse<FlightInformationDTO?>(flightDto, InternalCode.Success); - } - - public async Task<ServiceResponse<string>> UpdateFlightCapacityAsync(string flightNumber, int bookedSeats) - { - int result = await _flightRepo.Query() - .Where(x => x.FlightNumber == flightNumber) - .ExecuteUpdateAsync(x => x.SetProperty(y => y.SeatReserved, y => y.SeatReserved + bookedSeats)); - - return new ServiceResponse<string>(string.Empty, (InternalCode)result); - } - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Services/Interfaces/IBookingOrderService.cs b/BookingService_Old/FlightBooking.Service/Services/Interfaces/IBookingOrderService.cs deleted file mode 100644 index 7e1b124..0000000 --- a/BookingService_Old/FlightBooking.Service/Services/Interfaces/IBookingOrderService.cs +++ /dev/null @@ -1,21 +0,0 @@ -using FlightBooking.Service.Data.DTO; - -namespace FlightBooking.Service.Services.Interfaces -{ - public interface IBookingOrderService - { - /// <summary> - /// Create a new order with a list of booking. Accepts one-way, two and multiple bookings per order - /// </summary> - /// <param name="order"></param> - /// <returns>Returns an object containing payment reference and order number</returns> - Task<ServiceResponse<BookingResponseDTO?>> CreateBookingOrderAsync(BookingOrderDTO order); - - /// <summary> - /// Creates a Stripe payment link using the order number to search for the particular order - /// </summary> - /// <param name="orderNumber"></param> - /// <returns>Returns an object containing payment reference and order number</returns> - Task<ServiceResponse<BookingResponseDTO?>> GetCheckoutUrlAsync(string orderNumber); - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Services/Interfaces/IBookingService.cs b/BookingService_Old/FlightBooking.Service/Services/Interfaces/IBookingService.cs deleted file mode 100644 index 47c5aae..0000000 --- a/BookingService_Old/FlightBooking.Service/Services/Interfaces/IBookingService.cs +++ /dev/null @@ -1,35 +0,0 @@ -using FlightBooking.Service.Data.DTO; - -namespace FlightBooking.Service.Services.Interfaces -{ - public interface IBookingService - { - /// <summary> - /// Get booking information using booking ID - /// </summary> - /// <param name="bookingId"></param> - /// <returns></returns> - Task<ServiceResponse<BookingDTO?>> GetBookingByBookingId(int bookingId); - - /// <summary> - /// Get booking information using booking number - /// </summary> - /// <param name="bookingNumber"></param> - /// <returns></returns> - Task<ServiceResponse<BookingDTO?>> GetBookingByBookingNumberAsync(string bookingNumber); - - /// <summary> - /// Get all bookings for an email address - /// </summary> - /// <param name="email"></param> - /// <returns></returns> - ServiceResponse<IEnumerable<BookingDTO>?> GetBookingsByEmail(string email); - - /// <summary> - /// Get all booking for an order - /// </summary> - /// <param name="orderNumber"></param> - /// <returns></returns> - ServiceResponse<IEnumerable<BookingDTO>?> GetBookingsByOrderNumber(string orderNumber); - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Services/Interfaces/IFlightFareService.cs b/BookingService_Old/FlightBooking.Service/Services/Interfaces/IFlightFareService.cs deleted file mode 100644 index 34e4545..0000000 --- a/BookingService_Old/FlightBooking.Service/Services/Interfaces/IFlightFareService.cs +++ /dev/null @@ -1,21 +0,0 @@ -using FlightBooking.Service.Data.DTO; - -namespace FlightBooking.Service.Services.Interfaces -{ - public interface IFlightFareService - { - /// <summary> - /// Get all the fares for a flight using flight number - /// </summary> - /// <param name="flightNumber"></param> - /// <returns></returns> - ServiceResponse<IEnumerable<FlightFareDTO>?> GetFaresByFlightNumber(string flightNumber); - - /// <summary> - /// Update the flight capacity - /// </summary> - /// <param name="fareId"></param> - /// <returns></returns> - Task<ServiceResponse<string>> UpdateFlightFareCapacityAsync(int fareId); - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Services/Interfaces/IFlightService.cs b/BookingService_Old/FlightBooking.Service/Services/Interfaces/IFlightService.cs deleted file mode 100644 index 290f5d2..0000000 --- a/BookingService_Old/FlightBooking.Service/Services/Interfaces/IFlightService.cs +++ /dev/null @@ -1,22 +0,0 @@ -using FlightBooking.Service.Data.DTO; - -namespace FlightBooking.Service.Services.Interfaces -{ - public interface IFlightService - { - /// <summary> - /// Gets the flight information for a flight - /// </summary> - /// <param name="flightNumber"></param> - /// <returns></returns> - Task<ServiceResponse<FlightInformationDTO?>> GetFlightInformationAsync(string flightNumber); - - /// <summary> - /// Updates the flight capacity - /// </summary> - /// <param name="flightNumber"></param> - /// <param name="bookedSeats"></param> - /// <returns></returns> - Task<ServiceResponse<string>> UpdateFlightCapacityAsync(string flightNumber, int bookedSeats); - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Services/Interfaces/IReservedSeatService.cs b/BookingService_Old/FlightBooking.Service/Services/Interfaces/IReservedSeatService.cs deleted file mode 100644 index c0f7055..0000000 --- a/BookingService_Old/FlightBooking.Service/Services/Interfaces/IReservedSeatService.cs +++ /dev/null @@ -1,29 +0,0 @@ -using FlightBooking.Service.Data.DTO; - -namespace FlightBooking.Service.Services.Interfaces -{ - public interface IReservedSeatService - { - /// <summary> - /// Gets all available seats for a flight - /// </summary> - /// <param name="flightNumber"></param> - /// <returns></returns> - ServiceResponse<IEnumerable<ReservedSeatDTO>> GetAvailableSeatsByFlightNumber(string flightNumber); - - /// <summary> - /// Creates a seat reservation for a valid booking - /// </summary> - /// <param name="requestDTO"></param> - /// <returns></returns> - Task<ServiceResponse<string>> ReserveSeatAsync(ReservedSeatRequestDTO requestDTO); - - /// <summary> - /// Generates seat numbers for a flight based on flight capacity - /// </summary> - /// <param name="flightNumber"></param> - /// <param name="flightCapacity"></param> - /// <returns></returns> - Task<ServiceResponse<string>> GenerateSeatNumbersAsync(string flightNumber, int flightCapacity); - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Services/Interfaces/IStripeService.cs b/BookingService_Old/FlightBooking.Service/Services/Interfaces/IStripeService.cs deleted file mode 100644 index abfb0f0..0000000 --- a/BookingService_Old/FlightBooking.Service/Services/Interfaces/IStripeService.cs +++ /dev/null @@ -1,22 +0,0 @@ -using FlightBooking.Service.Data.DTO; -using Stripe; - -namespace FlightBooking.Service.Services.Interfaces -{ - public interface IStripeService - { - /// <summary> - /// Creates a Stripe payment link - /// </summary> - /// <param name="stripeDataDTO"></param> - /// <returns>Payment link</returns> - ServiceResponse<string> GetStripeCheckoutUrl(StripeDataDTO stripeDataDTO); - - /// <summary> - /// Processes Stripe events when a checkout (payment) is completed - /// </summary> - /// <param name="stripeEvent"></param> - /// <returns></returns> - Task<ServiceResponse<string>> ProcessPayment(Event stripeEvent); - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Services/PaymentService.cs b/BookingService_Old/FlightBooking.Service/Services/PaymentService.cs deleted file mode 100644 index b66d125..0000000 --- a/BookingService_Old/FlightBooking.Service/Services/PaymentService.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace FlightBooking.Service.Services -{ - public class PaymentService - { - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Services/ReservedSeatService.cs b/BookingService_Old/FlightBooking.Service/Services/ReservedSeatService.cs deleted file mode 100644 index 16126d1..0000000 --- a/BookingService_Old/FlightBooking.Service/Services/ReservedSeatService.cs +++ /dev/null @@ -1,127 +0,0 @@ -using AutoMapper; -using AutoMapper.QueryableExtensions; -using FlightBooking.Service.Data; -using FlightBooking.Service.Data.DTO; -using FlightBooking.Service.Data.Models; -using FlightBooking.Service.Data.Repository; -using FlightBooking.Service.Services.Interfaces; -using Microsoft.EntityFrameworkCore; - -namespace FlightBooking.Service.Services -{ - ///<inheritdoc /> - public class ReservedSeatService : IReservedSeatService - { - private readonly IGenericRepository<ReservedSeat> _seatRepo; - private readonly IGenericRepository<Booking> _bookingRepo; - private readonly IMapper _mapper; - - public ReservedSeatService(IGenericRepository<ReservedSeat> seatRepository, IGenericRepository<Booking> bookingRepo, - IMapper mapper) - { - _seatRepo = seatRepository; - _bookingRepo = bookingRepo; - _mapper = mapper; - } - - public async Task<ServiceResponse<string>> ReserveSeatAsync(ReservedSeatRequestDTO requestDTO) - { - if (requestDTO == null) - { - return new ServiceResponse<string>(string.Empty, InternalCode.InvalidParam); - } - - //check if booking is valid - Booking? booking = await _bookingRepo.Query() - .Include(x => x.FlightInformation) - .FirstOrDefaultAsync(x => x.BookingNumber == requestDTO.BookingNumber); - - if (booking == null) - { - return new ServiceResponse<string>(string.Empty, InternalCode.EntityNotFound, "Booking not found for the supplied booking number"); - } - - //check if seat is available - ReservedSeat? existingSeat = await _seatRepo.Query() - .Include(x => x.FlightInformation) - .FirstOrDefaultAsync(x => x.FlightNumber == booking.FlightInformation.FlightNumber - && x.SeatNumber == requestDTO.SeatNumber); - - if (existingSeat == null) - { - return new ServiceResponse<string>(string.Empty, InternalCode.Unprocessable, "The selected seat number does not exist"); - } - - if (existingSeat.IsReserved) - { - return new ServiceResponse<string>(string.Empty, InternalCode.Unprocessable, "The selected seat has already been reserved"); - } - - //reserve the seat - existingSeat.IsReserved = true; - existingSeat.BookingNumber = booking.BookingNumber; - existingSeat.BookingId = booking.Id; - - int result = await _seatRepo.SaveChangesToDbAsync(); - - return new ServiceResponse<string>(string.Empty, (InternalCode)result); - } - - public ServiceResponse<IEnumerable<ReservedSeatDTO>> GetAvailableSeatsByFlightNumber(string flightNumber) - { - List<ReservedSeatDTO> seats = _seatRepo.Query() - .Where(x => x.FlightNumber == flightNumber && !x.IsReserved) - .ProjectTo<ReservedSeatDTO>(_mapper.ConfigurationProvider) - .ToList(); - - return new ServiceResponse<IEnumerable<ReservedSeatDTO>>(seats, InternalCode.Success); - } - - public async Task<ServiceResponse<string>> GenerateSeatNumbersAsync(string flightNumber, int flightCapacity) - { - //assume seats are in group of 4 Alphabets e.g 1A, 1B, 1C, 1D - - Dictionary<int, string> SeatMaps = new Dictionary<int, string> - { - {1, "A" }, - {2, "B" }, - {3, "C" }, - {4, "D" } - }; - - int seatId = 1; - int seatCount = 1; - - List<string> seatNumbers = new List<string>(); - - for (int i = 1; i < flightCapacity + 1; i++) - { - if (seatCount > 4) - { - seatId++; - seatCount = 1; - } - - seatNumbers.Add(seatId + SeatMaps[seatCount]); - seatCount++; - } - - List<ReservedSeat> reservedSeats = new List<ReservedSeat>(); - - foreach (var seatNumber in seatNumbers) - { - reservedSeats.Add(new ReservedSeat - { - BookingNumber = null, - FlightNumber = flightNumber, - IsReserved = false, - SeatNumber = seatNumber - }); - } - - int result = await _seatRepo.BulkCreateAsync(reservedSeats); - - return new ServiceResponse<string>(string.Empty, (InternalCode)result); - } - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Services/ResponseFormatter.cs b/BookingService_Old/FlightBooking.Service/Services/ResponseFormatter.cs deleted file mode 100644 index 504c418..0000000 --- a/BookingService_Old/FlightBooking.Service/Services/ResponseFormatter.cs +++ /dev/null @@ -1,176 +0,0 @@ -using FlightBooking.Service.Data; -using Microsoft.AspNetCore.Mvc; - -namespace FlightBooking.Service.Services -{ - public static class FormatResponseExtension - { - public static ActionResult FormatResponse<T>(this ServiceResponse<T> serviceResponse) - { - ObjectResult response; - ProblemDetails problemDetails; - - if (serviceResponse == null) - { - problemDetails = new ProblemDetails - { - Status = 500, - Title = ServiceErrorMessages.OperationFailed, - Type = "https://tools.ietf.org/html/rfc7231#section-6.6" - }; - return new ObjectResult(problemDetails) - { - StatusCode = 500 - }; - } - - switch (serviceResponse.ServiceCode) - { - case InternalCode.Failed: - problemDetails = new ProblemDetails - { - Status = 500, - Title = ServiceErrorMessages.OperationFailed, - Detail = serviceResponse.Message, - Type = "https://tools.ietf.org/html/rfc7231#section-6.6" - }; - response = new ObjectResult(problemDetails) - { - StatusCode = 500 - }; - - return response; - - case InternalCode.Success: - - if (serviceResponse.Data != null) - { - Type dataType = serviceResponse.Data.GetType(); - - if (dataType == typeof(string)) - { - string? data = serviceResponse!.Data as string; - if (string.IsNullOrEmpty(data)) - { - return new OkResult(); - } - } - } - - return new OkObjectResult(serviceResponse.Data); - - case InternalCode.UpdateError: - problemDetails = new ProblemDetails - { - Status = 500, - Title = ServiceErrorMessages.InternalServerError, - Detail = serviceResponse.Message, - Type = "https://tools.ietf.org/html/rfc7231#section-6.6" - }; - response = new ObjectResult(problemDetails) - { - StatusCode = 500, - }; - return response; - - case InternalCode.Mismatch: - problemDetails = new ProblemDetails - { - Status = 400, - Title = ServiceErrorMessages.MisMatch, - Detail = serviceResponse.Message, - Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1" - }; - return new BadRequestObjectResult(problemDetails); - - case InternalCode.EntityIsNull: - problemDetails = new ProblemDetails - { - Status = 400, - Title = ServiceErrorMessages.EntityIsNull, - Detail = serviceResponse.Message, - Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1" - }; - return new BadRequestObjectResult(problemDetails); - - case InternalCode.InvalidParam: - problemDetails = new ProblemDetails - { - Status = 400, - Title = ServiceErrorMessages.InvalidParam, - Detail = serviceResponse.Message, - Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1" - }; - return new BadRequestObjectResult(problemDetails); - - case InternalCode.EntityNotFound: - problemDetails = new ProblemDetails - { - Status = 404, - Title = ServiceErrorMessages.EntityNotFound, - Detail = string.IsNullOrEmpty(serviceResponse.Message) ? "The requested resource was not found" : serviceResponse.Message, - Type = "https://tools.ietf.org/html/rfc7231#section-6.5.4" - }; - return new NotFoundObjectResult(problemDetails); - - case InternalCode.Incompleted: - return new AcceptedResult("", serviceResponse.Data); - - case InternalCode.ListEmpty: - problemDetails = new ProblemDetails - { - Status = 400, - Title = ServiceErrorMessages.EntityIsNull, - Detail = serviceResponse.Message, - Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1" - }; - return new BadRequestObjectResult(problemDetails); - - case InternalCode.EntityExist: - problemDetails = new ProblemDetails - { - Status = 409, - Title = ServiceErrorMessages.EntityExist, - Detail = string.IsNullOrEmpty(serviceResponse.Message) ? $"An entity of the type exists" : serviceResponse.Message, - Type = "https://tools.ietf.org/html/rfc7231#section-6.5.8" - }; - return new ConflictObjectResult(problemDetails); - - case InternalCode.Unprocessable: - problemDetails = new ProblemDetails - { - Status = 422, - Title = ServiceErrorMessages.UnprocessableEntity, - Detail = string.IsNullOrEmpty(serviceResponse.Message) ? "The request cannot be processed" : serviceResponse.Message - }; - return new UnprocessableEntityObjectResult(problemDetails); - - case InternalCode.Unauthorized: - problemDetails = new ProblemDetails - { - Status = 401, - Title = "Unathorized request", - Detail = "The supplied credentials is invalid." - }; - return new UnauthorizedObjectResult(problemDetails); - - default: - return new OkObjectResult(serviceResponse.Data); - } - } - } - - public class ServiceResponse<T> - { - public InternalCode ServiceCode { get; set; } = InternalCode.Failed; - public T Data { get; set; } - public string Message { get; set; } - - public ServiceResponse(T data, InternalCode serviceCode = InternalCode.Failed, string message = "") - { - Message = message; - ServiceCode = serviceCode; - Data = data; - } - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Services/ServicesModule.cs b/BookingService_Old/FlightBooking.Service/Services/ServicesModule.cs deleted file mode 100644 index 30710dc..0000000 --- a/BookingService_Old/FlightBooking.Service/Services/ServicesModule.cs +++ /dev/null @@ -1,17 +0,0 @@ -using FlightBooking.Service.Services.Interfaces; - -namespace FlightBooking.Service.Services -{ - public static class ServicesModule - { - public static void AddServices(this IServiceCollection services) - { - services.AddScoped<IBookingService, BookingService>(); - services.AddScoped<IBookingOrderService, BookingOrderService>(); - services.AddScoped<IReservedSeatService, ReservedSeatService>(); - services.AddScoped<IFlightFareService, FlightFareService>(); - services.AddScoped<IFlightService, FlightService>(); - services.AddScoped<IStripeService, StripeService>(); - } - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Services/StripeService.cs b/BookingService_Old/FlightBooking.Service/Services/StripeService.cs deleted file mode 100644 index ab75571..0000000 --- a/BookingService_Old/FlightBooking.Service/Services/StripeService.cs +++ /dev/null @@ -1,147 +0,0 @@ -using FlightBooking.Service.Data; -using FlightBooking.Service.Data.Configs; -using FlightBooking.Service.Data.DTO; -using FlightBooking.Service.Data.Models; -using FlightBooking.Service.Data.Repository; -using FlightBooking.Service.Services.Interfaces; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Options; -using Newtonsoft.Json; -using Stripe; -using Stripe.Checkout; - -namespace FlightBooking.Service.Services -{ - ///<inheritdoc /> - public class StripeService : IStripeService - { - private readonly StripeConfig _stripeConfig; - private readonly IGenericRepository<Payment> _paymentRepo; - private readonly IGenericRepository<BookingOrder> _orderRepo; - - private readonly ILogger<StripeService> _logger; - - public StripeService(IOptionsMonitor<StripeConfig> options, IGenericRepository<Payment> paymentRepo, - IGenericRepository<BookingOrder> orderRepo, ILogger<StripeService> logger) - { - _stripeConfig = options.CurrentValue; - _paymentRepo = paymentRepo; - _orderRepo = orderRepo; - _logger = logger; - } - - public ServiceResponse<string> GetStripeCheckoutUrl(StripeDataDTO stripeDataDTO) - { - if (stripeDataDTO == null) - { - return new ServiceResponse<string>(string.Empty, InternalCode.InvalidParam, "Invalid Data"); - } - - string checkoutUrl = string.Empty; - - try - { - StripeConfiguration.ApiKey = _stripeConfig.SecretKey; - - var amountInCents = stripeDataDTO.Amount * 100; - - var options = new SessionCreateOptions - { - LineItems = new List<SessionLineItemOptions> - { - new SessionLineItemOptions - { - PriceData = new SessionLineItemPriceDataOptions - { - Currency = stripeDataDTO.CurrencyCode, - UnitAmountDecimal = amountInCents, //in cents - ProductData = new SessionLineItemPriceDataProductDataOptions - { - Name = stripeDataDTO.ProductName, - Description = stripeDataDTO.ProductDescription - }, - }, - Quantity = 1, - }, - }, - Mode = "payment", - SuccessUrl = stripeDataDTO.SuccessUrl, - CancelUrl = stripeDataDTO.CancelUrl, - ClientReferenceId = stripeDataDTO.OrderNumber, - CustomerEmail = stripeDataDTO.CustomerEmail, - }; - var service = new SessionService(); - Session session = service.Create(options); - checkoutUrl = session.Url; - } - catch (Exception ex) - { - _logger.LogCritical(ex.ToString()); - } - - return new ServiceResponse<string>(checkoutUrl, InternalCode.Success); - } - - public async Task<ServiceResponse<string>> ProcessPayment(Event stripeEvent) - { - if (stripeEvent == null) - { - return new ServiceResponse<string>(string.Empty, InternalCode.InvalidParam); - } - - var session = stripeEvent.Data.Object as Session; - - //check if payment already saved, if yes, return - bool isPaymentSaved = _paymentRepo.Query() - .Any(x => x.OrderNumber == session!.ClientReferenceId); - - if (isPaymentSaved) - { - return new ServiceResponse<string>(string.Empty, InternalCode.Success); - } - - string orderNumber = session!.ClientReferenceId; - - var bookingOrder = await _orderRepo.Query() - .Include(x => x.Bookings) - .FirstOrDefaultAsync(x => x.OrderNumber == orderNumber); - - if (bookingOrder == null) - { - _logger.LogCritical("Payment made for a booking that doesn't exist"); - return new ServiceResponse<string>(string.Empty, InternalCode.Success); - } - - //save payment - Payment payment = new Payment - { - TransactionDate = session!.Created, - OrderNumber = orderNumber, - MetaData = JsonConvert.SerializeObject(session), - BookingOrderId = bookingOrder.Id, - CurrencyCode = session.Currency, - CustomerEmail = session.CustomerEmail, - PaymentReference = Guid.NewGuid().ToString("N").ToUpper(), - PaymentStatus = session.PaymentStatus, - CreatedAt = DateTime.UtcNow, - TransactionAmount = (decimal)session.AmountTotal!, - PaymentChannel = "Stripe", - }; - - await _paymentRepo.CreateAsync(payment); - - //update flight and booking information - - bookingOrder.OrderStatus = BookingStatus.Paid; - - foreach (var booking in bookingOrder.Bookings) - { - booking.BookingStatus = BookingStatus.Paid; - } - - await _orderRepo.SaveChangesToDbAsync(); - - return new ServiceResponse<string>(string.Empty, InternalCode.Success); - } - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/Startup.cs b/BookingService_Old/FlightBooking.Service/Startup.cs deleted file mode 100644 index 540cfff..0000000 --- a/BookingService_Old/FlightBooking.Service/Startup.cs +++ /dev/null @@ -1,162 +0,0 @@ -using FlightBooking.Service.Data; -using FlightBooking.Service.Data.Configs; -using FlightBooking.Service.Data.Repository; -using FlightBooking.Service.Middleware; -using FlightBooking.Service.Services; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.EntityFrameworkCore; -using Microsoft.IdentityModel.Tokens; -using Microsoft.OpenApi.Models; -using Newtonsoft.Json.Converters; -using System.Text; - -namespace FlightBooking.Service -{ - public class Startup - { - public static readonly LoggerFactory _myLoggerFactory = - new LoggerFactory(new[] - { - new Microsoft.Extensions.Logging.Debug.DebugLoggerProvider() - }); - - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - //we keep using NewtonSoft so that serialization of reference loop can be ignored, especially because of EFCore - services.AddControllers() - .AddNewtonsoftJson(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore) - .AddNewtonsoftJson(x => x.SerializerSettings.Converters.Add(new StringEnumConverter())); - - services.AddAutoMapper(typeof(Startup)); - - //Configuration for SQL Servr and MySql - string mysqlConnectionString = Configuration.GetConnectionString("FlightBookingServiceDb_Mysql")!; - var mySqlServerVersion = new MySqlServerVersion(new Version(8, 0, 36)); - - services.AddDbContext<FlightBookingContext>(options => - { - //options.UseLoggerFactory(_myLoggerFactory).EnableSensitiveDataLogging(); //DEV: ENABLE TO SEE SQL Queries - - //To Use Sql Server - //options.UseSqlServer(Configuration.GetConnectionString("FlightBookingServiceDb")); - - //To Use MySql - options.UseMySql(mysqlConnectionString, mySqlServerVersion, opt => opt.EnableRetryOnFailure()) - .LogTo(Console.WriteLine, LogLevel.Warning) - .EnableSensitiveDataLogging() - .EnableDetailedErrors(); - }); - - services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options => - { - //For OpenId Connect tokens - options.Authority = Configuration["JWTConfig:Issuer"]; - options.Audience = Configuration["JWTConfig:Issuer"]; - options.SaveToken = true; - - options.TokenValidationParameters = new TokenValidationParameters - { - ValidateIssuer = true, - ValidateAudience = false, - ValidateLifetime = true, - ValidateIssuerSigningKey = true, - ValidIssuers = [Configuration["JWTConfig:Issuer"], Configuration["JWTConfig:Issuer"]], - IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JWTConfig:Key"]!)), - }; - }); - - services.AddCors(option => - { - option.AddDefaultPolicy( - builder => - { - builder - .AllowAnyOrigin() - .SetIsOriginAllowedToAllowWildcardSubdomains() - .AllowAnyHeader() - .AllowAnyMethod(); - }); - }); - - services.AddSignalR(); - services.AddHttpClient(); - services.AddHttpContextAccessor(); - - //Add our custom services - services.AddRepository(); - services.AddServices(); - - services.AddEndpointsApiExplorer(); - services.AddSwaggerGen(options => - { - options.SwaggerDoc("v1", new OpenApiInfo { Title = "Flight Booking API", Version = "v1" }); - }); - - //Add our configs - services.AddConfigSettings(Configuration); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - //https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-8.0 - //The following Startup.Configure method adds middleware components for common app scenarios: - - //1. Exception / error handling (HTTP Strict Transport Security Protocol in prod) - //2. HTTPS redirection - //3. Static File Middleware - //4. Cookie Policy Middleware - //5. Routing Middleware (UseRouting) to route requests. - //5. Cors - //5. Custom route - //6. Authentication Middleware - //7. Authorization Middleware - //8. Session Middleware - //9. Endpoint Routing Middleware (UseEndpoints - - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - //app.UseDatabaseErrorPage(); - } - else - { - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - app.UseHsts(); - app.UseErrorHandlingMiddleware(); //custom error handler - //app.UseExceptionHandler(); - } - - app.UseHttpsRedirection(); - - app.UseRouting(); - - app.UseCors();//this was moved here since the BasicAuthMiddleware below is also authentication and cors must come before authentication. - - if (env.IsDevelopment() || env.IsStaging()) - { - app.UseSwagger(); - app.UseSwaggerUI(opt => - { - opt.SwaggerEndpoint("/swagger/v1/swagger.json", "Flight Booking API"); - }); - } - - app.UseAuthentication(); - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapControllers(); - }); - } - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/appsettings.Development.json b/BookingService_Old/FlightBooking.Service/appsettings.Development.json deleted file mode 100644 index 14d2ada..0000000 --- a/BookingService_Old/FlightBooking.Service/appsettings.Development.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "ConnectionStrings": { - "FlightBookingServiceDb": "Server=.;Database=FlightBooking_Db;User ID=sa;Password=beforwardj!;Trusted_Connection=False;Encrypt=False;Connection Timeout=180;", - "FlightBookingServiceDb_Mysql": "Server=localhost;Database=FlightBooking_Db;User=root;Password=P@ss1ord" - }, - "JWTConfig": { - "Key": "28298659-1c10-4f2e-b045-42698ab4b02b", - "Issuer": "https://localhost:44321" - }, - "StripeConfig": { - "PublicKey": "", - "SecretKey": "", - "SigningSecret": "" - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/appsettings.json b/BookingService_Old/FlightBooking.Service/appsettings.json deleted file mode 100644 index 31a3e8d..0000000 --- a/BookingService_Old/FlightBooking.Service/appsettings.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*", - "ConnectionStrings": { - "FlightBookingServiceDb": "Server=.;Database=FlightBooking_Db;User ID=sa;Password=beforwardj!;Trusted_Connection=False;Encrypt=False;Connection Timeout=180;", - "FlightBookingServiceDb_Mysql": "Server=localhost;Database=FlightBooking_Db;User=root;Password=P@ss1ord" - }, - "JWTConfig": { - "Key": "28298659-1c10-4f2e-b045-42698ab4b02b", - "Issuer": "https://localhost:44321" - }, - "StripeConfig": { - "PublicKey": "", - "SecretKey": "", - "SigningSecret": "" - } -} \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/nlog.config b/BookingService_Old/FlightBooking.Service/nlog.config deleted file mode 100644 index 1967321..0000000 --- a/BookingService_Old/FlightBooking.Service/nlog.config +++ /dev/null @@ -1,40 +0,0 @@ -<?xml version="1.0" encoding="utf-8" ?> -<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - autoReload="true" - internalLogLevel="Info" - internalLogFile=".\log\internal-nlog.txt"> - - <!-- enable asp.net core layout renderers --> - <extensions> - <add assembly="NLog.Web.AspNetCore" /> - </extensions> - - <!-- the targets to write to --> - <targets> - <!-- write logs to file --> - <target xsi:type="File" name="allfile" fileName=".\log\nlog-all-${shortdate}.log" - layout="${longdate}|${event-properties:item=EventId_Id:whenEmpty=0}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}" /> - <!-- another file log, only own logs. Uses some ASP.NET core renderers --> - <target xsi:type="File" name="ownFile-web" fileName=".\log\nlog-own-${shortdate}.log" - layout="${longdate}|${event-properties:item=EventId_Id:whenEmpty=0}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}|${callsite}| body: ${aspnet-request-posted-body}" /> - - <!--Console Target for hosting lifetime messages to improve Docker / Visual Studio startup detection --> - <target xsi:type="Console" name="lifetimeConsole" layout="${level:truncate=4:lowercase=true}: ${logger}[0]${newline} ${message}${exception:format=tostring}" /> - </targets> - - <!-- rules to map from logger name to target --> - <rules> - <!--All logs, including from Microsoft--> - <logger name="*" minlevel="Trace" writeTo="allfile" /> - - <!--Output hosting lifetime messages to console target for faster startup detection --> - <logger name="Microsoft.Hosting.Lifetime" minlevel="Info" writeTo="lifetimeConsole, ownFile-web" final="true" /> - - <!--Skip non-critical Microsoft logs and so log only own logs (BlackHole) --> - <logger name="Microsoft.*" maxlevel="Info" final="true" /> - <logger name="System.Net.Http.*" maxlevel="Info" final="true" /> - - <logger name="*" minlevel="Trace" writeTo="ownFile-web" /> - </rules> -</nlog> \ No newline at end of file diff --git a/BookingService_Old/FlightBooking.Service/readme.md b/BookingService_Old/FlightBooking.Service/readme.md deleted file mode 100644 index 77617ad..0000000 --- a/BookingService_Old/FlightBooking.Service/readme.md +++ /dev/null @@ -1,385 +0,0 @@ -Libraries Used: - -1. NLog. We used NLog for logging. By default all logs with level Informational are saved. This setting can be changed in the appsettings.json - -nlog.config contains the file that is used to configure our NLog. - -2. Automapper: This is a great utility tool that allows mapping one model or DTO (Data Transfer Object) to another. It helps to us avoid repetitve code. - To use, we first add a line in the `ConfigureServices` method of our `Startup.cs`: - - `services.AddAutoMapper(typeof(Startup));` - - The we declare a class that contains our mappings: - ``` - public class FlightBookingProfile : Profile - { - public FlightBookingProfile() - { - CreateMap<FlightFare, FlightFareDTO>() - .ForMember(dest => dest.AvailableSeats, opt => opt.MapFrom(src => src.SeatCapacity - src.SeatReserved)) - .ForMember(dest => dest.FlightNumber, opt => opt.MapFrom(src => src.FlightInformation.FlightNumber)); - } - } - ``` - - -3. We also installed NewtonSoftJson library for working with Json input and output. - -4. Swashbuckle was also installed so we can generate Swagger documentation from our controllers. - -5. Some libraries for using Jwt for authentication were also added. - - -## Program Flow. - -Controller -> Services -> Repository -> EntityFramework -> Data - - -### Controllers - -They represent our endpoints based on the MVC pattern. -Each controller has been decorated with attributes that makes it easy to read what input and output to expect. -For example, our `ReservedSeatController`: - -``` - [Route("api/[controller]")] - [ApiController] - public class SeatsController : ControllerBase - { - private readonly IReservedSeatService _service; - - public SeatsController(IReservedSeatService service) - { - _service = service; - } - - [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<ReservedSeatDTO>))] - [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ProblemDetails))] - public IActionResult GetAvailableSeats([FromQuery] string flightNumber) - { - ServiceResponse<IEnumerable<ReservedSeatDTO>> result = _service.GetAvailableSeatsByFlightNumber(flightNumber); - - return result.FormatResponse(); - } - - [HttpPost] - [Consumes(MediaTypeNames.Application.Json)] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status422UnprocessableEntity, Type = typeof(ProblemDetails))] - [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ProblemDetails))] - public async Task<IActionResult> ReserveSeat([FromBody] ReservedSeatRequestDTO requestDTO) - { - ServiceResponse<string> result = await _service.ReserveSeatAsync(requestDTO); - - return result.FormatResponse(); - } - } -``` - -In the above, we have to methods in the controller which corresponds to 2 endpoints. - -First, we inject our `IReservedSeatService` into the controller. - -The first endpoint is a GET endpoint: -And the attributes show that it can return a 200, 400 and 422 response. The 400 and 422 responses are in types of ProblemDetails which is a specification for returning API responses. - -This part: `([FromQuery] string flightNumber)` indicates that a query string named ``flightNumber`` is passed to the endpoint generated by this method. - -The `_service` returns a type of `ServiceResponse` which is then formatted to return the appropriate response. More on this later - - -The second endpoint is a POST endpoint: -The attributes show it accepts a body (`[FromBody]`) of type application/json (`MediaTypeNames.Application.Json`) and response produced are 200, 422, 400. - - - -### Services -They represent the logic for our app. We this pattern to make it easy to test the services. -By abstracting the services to use Interfaces, we can easily write tests that can be flexible. - -We rely on the built-in Dependency Injection framework to resolve service dependencies. - -In each service, we inject Repository classes and other Services. - -The services also have to be registered and we do this by declaring a static class that will do the registration: - -``` -public static class ServicesModule -{ - public static void AddServices(this IServiceCollection services) - { - services.AddScoped<IBookingService, BookingService>(); - services.AddScoped<IBookingOrderService, BookingOrderService>(); - services.AddScoped<IReservedSeatService, ReservedSeatService>(); - services.AddScoped<IFlightFareService, FlightFareService>(); - services.AddScoped<IFlightService, FlightService>(); - services.AddScoped<IStripeService, StripeService>(); - } -} -``` -Here: We register each service as a Scoped dependency. Scoped means that the service will only be active for the duration of a request (i.e Http Request). - -Then we call the method in our Startup.cs: -` services.AddServices();` - -Each service also returns a type `ServiceResponse<T>` where T is a class - -The `ServiceResponse<T>` is declared in the `ResponseFormatter.cs` - -``` -public class ServiceResponse<T> -{ - public InternalCode ServiceCode { get; set; } = InternalCode.Failed; - public T Data { get; set; }// = null; - public string Message { get; set; } - - public ServiceResponse(T data, InternalCode serviceCode = InternalCode.Failed, string message = "") - { - Message = message; - ServiceCode = serviceCode; - Data = data; - } -} -``` -We declare the `ServiceResponse` to be a generic class. It's properties are: -`InternalCode` that indicates the status. InternalCode is an enum -`Data`: which is a type of `T` -`Message`: optional message - -The `FormatResponse` is an extension method that accepts a `ServiceResponse<T>` and then checks the Internal Code and uses that to -return an appropriate response. - -This makes it easy for us to return a uniform type of response. -2xx responses return a simple 2xx and an optional data -4xx and 5xx responses return a type of `ProblemDetails`. -The helps to give more context to the nature of the response. - - -### Repository - -We use a Repository pattern that wraps EntityFramework unit of work pattern. The class is declared as a Generic class so that we can pass any model to it. -We then declare helper methods that in turn call EntityFramework methods: - -``` - public class GenericRepository<T> : IGenericRepository<T> where T : class, new() - { - //Responses: failed=0, success=1 - - //IEnumerable iterates over an in-memory collection while IQueryable does so on the DB - // call to .ToList to enable instant query against DB - - protected FlightBookingContext _db; - protected ILogger _logger; - - //...omitted for brevity - - public async Task<T?> GetByGuidAsync(Guid id) - { - var entity = await _db.Set<T>().FindAsync(id); - return entity; - } - -} -``` - -In the aboved, we pass a type of `DbContext` and a `Logger`. -In the `GetByGuidAsync()` method, we use the `_db` of that model to find the data. - -We must also inject the Repository of each model so we can use anywhere in our project: -``` -public static class RepositoryModule -{ - public static void AddRepository(this IServiceCollection services) - { - services.AddScoped<IGenericRepository<Booking>, GenericRepository<Booking>>(); - services.AddScoped<IGenericRepository<FlightInformation>, GenericRepository<FlightInformation>>(); - services.AddScoped<IGenericRepository<FlightFare>, GenericRepository<FlightFare>>(); - services.AddScoped<IGenericRepository<Payment>, GenericRepository<Payment>>(); - services.AddScoped<IGenericRepository<BookingOrder>, GenericRepository<BookingOrder>>(); - services.AddScoped<IGenericRepository<ReservedSeat>, GenericRepository<ReservedSeat>>(); - } -} -``` -Just like in the Services, we also add the dependencies as a Scoped service. - -This is important because we want to quickly use a DbContext which in turn holds a connection to the database. If we use it and quickly return it to the connection pool, we can avoid issues with resource exhaustion. - - -### Data - -We are using MySql. So we must install the following MySqlConnector and the Pomelo.EntityFramework.MySql libraries. -Let's add these 2 lines to our Package reference. - -1. `<PackageReference Include="MySqlConnector" Version="2.3.5" />` -2. `<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.1" />` - - -To use our model, we use classes to represent our data. Each class in the model is used to create tables in the database. - -We declare properties of the class and also configure relationships. - -The models are in `Model` folder. - -We also wish to do some relations that are not automatically done by the framework. - -These configurations are applied to the model and is used to configure how the tables should be created during table creation: -e.g: -``` -public class BookingConfiguration : IEntityTypeConfiguration<Booking> - { - public void Configure(EntityTypeBuilder<Booking> entity) - { - entity.HasOne(d => d.BookingOrder).WithMany(p => p.Bookings) - .HasPrincipalKey(p => p.Id) - .HasForeignKey(d => d.BookingOrderId) - .OnDelete(DeleteBehavior.ClientSetNull); - - entity.HasOne(d => d.FlightInformation).WithMany(p => p.Bookings) - .HasPrincipalKey(p => p.Id) - .HasForeignKey(d => d.FlightId) - .OnDelete(DeleteBehavior.ClientSetNull); - - entity.HasOne(d => d.FlightFare).WithMany(p => p.Bookings) - .HasPrincipalKey(p => p.Id) - .HasForeignKey(d => d.FlightFareId) - .OnDelete(DeleteBehavior.ClientSetNull); - } - } -``` - The above code for the `Booking.cs` model configures the foreign key relationship. - - -To make use of our models, we create a `FlightBookingDbContext.cs` where we declare all our models and also apply the configurations: - -We also added a change in the `OnModelCreating()` method of the `FlightDbContext` to make sure that when a model (data in the database) is updated, the `UpdatedAt` and `CreatedAt` is saved as `UTC`. - -``` -foreach (var entityType in modelBuilder.Model.GetEntityTypes()) - { - foreach (var property in entityType.GetProperties()) - { - if (property.ClrType == typeof(DateTime)) - { - modelBuilder.Entity(entityType.ClrType) - .Property<DateTime>(property.Name) - .HasConversion( - v => v.ToUniversalTime(), - v => DateTime.SpecifyKind(v, DateTimeKind.Utc)); - } - else if (property.ClrType == typeof(DateTime?)) - { - modelBuilder.Entity(entityType.ClrType) - .Property<DateTime?>(property.Name) - .HasConversion( - v => v.HasValue ? v.Value.ToUniversalTime() : v, - v => v.HasValue ? DateTime.SpecifyKind(v.Value, DateTimeKind.Utc) : v); - } - } - } -``` - - -To get our database schema updated: - -Once we have configured our models, and added the `FlightBookingContext`. -We also configure the `FlightBookingContext` in the `ConfigureService` method in our `Startup.cs` and initialize it. - -``` - string mysqlConnectionString = Configuration.GetConnectionString("FlightBookingServiceDb_Mysql")!; - var mySqlServerVersion = new MySqlServerVersion(new Version(8, 0, 36)); - services.AddDbContext<FlightBookingContext>(options => - { - options.UseMySql(mysqlConnectionString, mySqlServerVersion, opt => opt.EnableRetryOnFailure()) - .LogTo(Console.WriteLine, LogLevel.Warning) - .EnableSensitiveDataLogging() - .EnableDetailedErrors(); - }); - -``` -The code indicates that we get our connection string from appsettings.json -To get the version of MySql, run this on the MySql server: - -> SELECT VERSION(); - -Once we have our models, configuration and DbContext ready, we need to run Migrations. - -Migrations take a snapshot of our models and configuration and defines how they will be used to updated the database schema at that point in time. - -To run migrations, open the Package Manager Console and run: - -> Add-Migration InitialCreate - -This will create a migration named `InitialCreateMysql`. - -We then run - ->Update-Database - -This will configure the database with the code generated in `InitialCreateMysql`. - -Whenever we make a change to our models and configuration, we must create a new migration and update our database so that the database schema is kept updated - -If we wish to use a different database, we first configure the DbContext. E.g if using Sql Server: - -``` - services.AddDbContext<FlightBookingContext>(options => - { - options.UseSqlServer(Configuration.GetConnectionString("FlightBookingServiceDb")); - }); - -``` - -Then we must create a new migration. __It is important to note that Migrations are scoped to a database. So we need a new migration when we switched to a different database__ - -To run migrations, open the Package Manager Console and run: - -> Add-Migration InitialCreateMysql - -This will create a migration named `InitialCreateMysql`. - -We then run - ->Update-Database - - -The `ConfigSettings` contains a strongly type mapping of the content of appsettings.json and helps to avoid errors - -Our DTO folder contains DTOs for models. These are basically data structure we use to transfer data around - - -## Running this Project - -1. Open the solution in Visual Studio. Automatically, nugets are installed. - -2. Start your MySql Server. The project currently uses MySql version 8.0.36. If you wish to use a different version, update the version in ``Startup.cs` - -``` - var mySqlServerVersion = new MySqlServerVersion(new Version(8, 0, 36)); -``` - -3. Add your database connection string in appsettings.json and appsettings.Development.json - -3. In Visual Studio, go to Tools -> Nuget Package Manager -> Package Manager Console. Click the Package Manager Console and it open at the bottom - -4. In the Package Manager Console, Run - > Update-Database - - The Database and all tables will be created using the `InitialMysqlMigration` that is included in the Migrations folder. - -5. Press F5 or run the project by clicking the play button. - -6. Included in the project in the `Program.cs` is a method that seeds the database with some default data. The `DatabaseSeeding.cs` contains the code that adds `FlightInformation`, `FlightFares` and `ReservedSeats` - Once you run the project for the first time, if all goes well, the data is added to the database. - - - -Useful Links: -https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql -https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/providers?tabs=dotnet-core-cli -https://learn.microsoft.com/en-us/ef/core/modeling/relationships -https://learn.microsoft.com/en-us/ef/ef6/fundamentals/working-with-dbcontext - - - - diff --git a/Templates/login-register/login.html b/Templates/login-register/login.html deleted file mode 100644 index 092908c..0000000 --- a/Templates/login-register/login.html +++ /dev/null @@ -1,31 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>Airline Booking - Login</title> - <link rel="stylesheet" href="login_register_style.css"> - <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css"> - <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script> - <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script> -</head> -<body> - <div class="container"> - <form> - <div class="form-group"> - <label for="exampleInputEmail1">Email address</label> - <input type="email" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp" placeholder="Enter email"> - </div> - <div class="form-group"> - <label for="exampleInputPassword1">Password</label> - <input type="password" class="form-control" id="exampleInputPassword1" placeholder="Password"> - </div> - <div class="form-group form-check"> - <!-- <input type="checkbox" class="form-check-input" id="exampleCheck1"> --> - <!-- <label class="form-check-label" for="exampleCheck1">Check me out</label> --> - </div> - <button type="submit" class="btn btn-primary">Submit</button> - </form> - </div> -</body> -</html> \ No newline at end of file diff --git a/Templates/login-register/login_register_style.css b/Templates/login-register/login_register_style.css deleted file mode 100644 index 51fe1c5..0000000 --- a/Templates/login-register/login_register_style.css +++ /dev/null @@ -1,15 +0,0 @@ -form { - width: 30%; - border: 1px solid #ccc; /* Example border: 1px solid with color #ccc */ - padding: 20px; - border-radius: 5px; /* Optional: if you want rounded corners */ - background-color: #ffffff; - -} - -.container { - display: flex; - justify-content: center; - align-items: center; - height: 100vh; -} \ No newline at end of file diff --git a/Templates/login-register/register.html b/Templates/login-register/register.html deleted file mode 100644 index 8825269..0000000 --- a/Templates/login-register/register.html +++ /dev/null @@ -1,36 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>Airline Booking - Register</title> - <link rel="stylesheet" href="login_register_style.css"> - <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css"> - <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script> - <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script> - <script src="register.js"></script> -</head> -<body> - <div class="container"> - <form onsubmit="return checkPasswords()"> - <div class="form-group"> - <label for="inputFullName">Full name</label> - <input type="text" class="form-control" id="inputFullName" placeholder="Full name"> - </div> - <div class="form-group"> - <label for="inputEmail">Email address</label> - <input type="email" class="form-control" id="inputEmail" placeholder="Enter email"> - </div> - <div class="form-group"> - <label for="inputPassword">Password</label> - <input type="password" class="form-control" id="inputPassword" placeholder="Password"> - </div> - <div class="form-group"> - <label for="inputConfirmPassword">Confirm password</label> - <input type="password" class="form-control" id="inputConfirmPassword" placeholder="Confirm Password"> - </div> - <button type="submit" class="btn btn-primary">Submit</button> - </form> - </div> -</body> -</html> \ No newline at end of file diff --git a/Templates/login-register/register.js b/Templates/login-register/register.js deleted file mode 100644 index a4f443d..0000000 --- a/Templates/login-register/register.js +++ /dev/null @@ -1,15 +0,0 @@ -function checkPasswords() { - var password = document.getElementById('inputPassword').value; - var confirmPassword = document.getElementById('inputConfirmPassword').value; - - if (password === confirmPassword) { - // The passwords match - // You can add code here to handle the form submission - return true; // return true to submit the form - } else { - // The passwords do not match - // Alert the user - alert("The passwords do not match!"); - return false; // return false to prevent form submission - } -} \ No newline at end of file diff --git a/Templates/user_profile/avatar.jpg b/Templates/user_profile/avatar.jpg deleted file mode 100644 index 6cc1140f400c6e47c6ac1ddf985ee188f92c4ae2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6031 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 diff --git a/Templates/user_profile/profile.css b/Templates/user_profile/profile.css deleted file mode 100644 index 44709d2..0000000 --- a/Templates/user_profile/profile.css +++ /dev/null @@ -1,53 +0,0 @@ -body{ - margin-top:20px; - color: #1a202c; - text-align: left; - background-color: #e2e8f0; -} -.main-body { - padding: 15px; -} -.card { - box-shadow: 0 1px 3px 0 rgba(0,0,0,.1), 0 1px 2px 0 rgba(0,0,0,.06); -} - -.card { - position: relative; - display: flex; - flex-direction: column; - min-width: 0; - word-wrap: break-word; - background-color: #fff; - background-clip: border-box; - border: 1px solid #ddd; /* Light grey border */ - border-radius: 4px; -} - -.card-body { - flex: 1 1 auto; - min-height: 1px; - padding: 1rem; -} - -.gutters-sm { - margin-right: -8px; - margin-left: -8px; -} - -.gutters-sm>.col, .gutters-sm>[class*=col-] { - padding-right: 8px; - padding-left: 8px; -} -.mb-3, .my-3 { - margin-bottom: 1rem!important; -} - -.bg-gray-300 { - background-color: #e2e8f0; -} -.h-100 { - height: 100%!important; -} -.shadow-none { - box-shadow: none!important; -} \ No newline at end of file diff --git a/Templates/user_profile/profile.html b/Templates/user_profile/profile.html deleted file mode 100644 index 68302e7..0000000 --- a/Templates/user_profile/profile.html +++ /dev/null @@ -1,158 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>Airline Booking - Profile</title> - <link rel="stylesheet" href="profile.css"> - <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css"> - <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script> - <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script> - <script src="profile_script.js"></script> -</head> -<body> - <div class="container"> - <div class="main-body"> - - <div class="row gutters-sm"> - <div class="col-md-4 mb-3"> - <div class="card"> - <div class="card-body"> - <div class="d-flex flex-column align-items-center text-center"> - <img src="avatar.jpg" alt="Admin" class="rounded-circle" width="150"> - <div class="mt-3"> - <h4 id="profileName">Illya Globa</h4> - <p class="text-secondary mb-1">Loyal Customer</p> - <p class="text-muted font-size-sm">Guildford</p> - - </div> - </div> - </div> - </div> - </div> - <div class="col-md-8"> - <div class="card mb-3"> - <div class="card-body"> - <div class="row"> - <div class="col-sm-3"> - <h6 class="mb-0">Full Name</h6> - </div> - <div class="col-sm-9 text-secondary"> - <input type="text" id="fullName" class="form-control" value="Illya Globa" readonly> - </div> - </div> - <hr> - <div class="row"> - <div class="col-sm-3"> - <h6 class="mb-0">Email</h6> - </div> - <div class="col-sm-9 text-secondary"> - <input type="email" id="email" class="form-control" value="ig@surrey.ac.uk" readonly> - </div> - </div> - <hr> - <div class="row"> - <div class="col-sm-3"> - <h6 class="mb-0">Password</h6> - </div> - <div class="col-sm-9 text-secondary"> - <input type="password" id="password" class="form-control" value="password" readonly> - </div> - </div> - <hr> - <div class="row"> - <div class="col-sm-12"> - <button class="btn btn-primary" onclick="editProfile()">Edit</button> - </div> - </div> - </div> - </div> - </div> - - </div> - - </div> - <div class="row"> - <div class="col-md-12"> - <h3>Upcoming Flights</h3> - </div> - <div class="col-md-3"> - <div class="card" style="width: 25rem;"> - <div class="card-body"> - <h5 class="card-title">Flight number</h5> - <h6 class="card-subtitle mb-2 text-muted">LTN - MLG</h6> - <p class="card-text">London(LTN) - Spain(MLG)</p> - <a href="#" class="card-link">Card link</a> - <a href="#" class="card-link">Another link</a> - </div> - </div> - </div> - <div class="col-md-3"> - <div class="card" style="width: 25rem;"> - <div class="card-body"> - <h5 class="card-title">Flight number</h5> - <h6 class="card-subtitle mb-2 text-muted">LTN - MLG</h6> - <p class="card-text">London(LTN) - Spain(MLG)</p> - <a href="#" class="card-link">Card link</a> - <a href="#" class="card-link">Another link</a> - </div> - </div> - </div> - <div class="col-md-3"> - <div class="card" style="width: 25rem;"> - <div class="card-body"> - <h5 class="card-title">Flight number</h5> - <h6 class="card-subtitle mb-2 text-muted">LTN - MLG</h6> - <p class="card-text">London(LTN) - Spain(MLG)</p> - <a href="#" class="card-link">Card link</a> - <a href="#" class="card-link">Another link</a> - </div> - </div> - </div> - <!-- If you have another flight card add it here as another .col-md-6 --> - </div> - - <div class="row" style="margin-top: 20px;"> - <div class="col-md-12"> - <h3>Flights History</h3> - </div> - <div class="col-md-3"> - <div class="card" style="width: 25rem;"> - <div class="card-body"> - <h5 class="card-title">Flight number</h5> - <h6 class="card-subtitle mb-2 text-muted">LTN - MLG</h6> - <p class="card-text">London(LTN) - Spain(MLG)</p> - <a href="#" class="card-link">Card link</a> - <a href="#" class="card-link">Another link</a> - </div> - </div> - </div> - <div class="col-md-3"> - <div class="card" style="width: 25rem;"> - <div class="card-body"> - <h5 class="card-title">Flight number</h5> - <h6 class="card-subtitle mb-2 text-muted">LTN - MLG</h6> - <p class="card-text">London(LTN) - Spain(MLG)</p> - <a href="#" class="card-link">Card link</a> - <a href="#" class="card-link">Another link</a> - </div> - </div> - </div> - <div class="col-md-3"> - <div class="card" style="width: 25rem;"> - <div class="card-body"> - <h5 class="card-title">Flight number</h5> - <h6 class="card-subtitle mb-2 text-muted">LTN - MLG</h6> - <p class="card-text">London(LTN) - Spain(MLG)</p> - <a href="#" class="card-link">Card link</a> - <a href="#" class="card-link">Another link</a> - </div> - </div> - </div> - <!-- If you have another flight card add it here as another .col-md-6 --> - </div> - <button style="margin-top: 10px;" type="submit" class="btn btn-primary">View more</button> - </div> - </div> -</body> -</html> \ No newline at end of file diff --git a/Templates/user_profile/profile_script.js b/Templates/user_profile/profile_script.js deleted file mode 100644 index 0a70508..0000000 --- a/Templates/user_profile/profile_script.js +++ /dev/null @@ -1,19 +0,0 @@ -function editProfile() { - // Get the input fields - var fullNameField = document.getElementById('fullName'); - var emailField = document.getElementById('email'); - var passwordField = document.getElementById('password'); - - // Check if the input fields are readonly - if so, make them editable - if (fullNameField.readOnly === true) { - fullNameField.readOnly = false; - emailField.readOnly = false; - passwordField.readOnly = false; - fullNameField.focus(); // Set focus on the name field to start editing - } else { - fullNameField.readOnly = true; - emailField.readOnly = true; - passwordField.readOnly = true; - // Here you can also add an AJAX call to save the data if needed - } -} \ No newline at end of file diff --git a/Templates/user_profile/user.png b/Templates/user_profile/user.png deleted file mode 100644 index 063717636aec642b496d4c65e975311aa01fd429..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19382 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 LNkvXXu0mjfZPET- -- GitLab