diff --git a/.env b/.env index 673e590786e96e46a0ea58e0f4a06002a86fd218..28e4f237c3b58cb9062b03189e4c31dbb7d58033 100644 --- a/.env +++ b/.env @@ -10,8 +10,16 @@ DB_USER=user DB_PASSWORD=user DB_CHARSET=utf8mb4 +# Keeping these for dev purposes, will remove at the end +#DB_PORT=3308 +#DB_NAME=AspNetCoreDb +#DB_USER=root +#DB_PASSWORD= +#DB_CHARSET=utf8mb4 + # Service ports USER_MICROSERVICE_PORT=5089 FLIGHT_MICROSERVICE_PORT=5175 GATEWAY_API_PORT=5267 -CLIENT_PORT=4200 \ No newline at end of file +BOOKING_MICROSERVICE_PORT=5207 +CLIENT_PORT=4200 diff --git a/BookingMicroservice/.dockerignore b/BookingMicroservice/.dockerignore new file mode 100644 index 0000000000000000000000000000000000000000..fe1152bdb8442f4d14f9b9533e63fe0c2680bcee --- /dev/null +++ b/BookingMicroservice/.dockerignore @@ -0,0 +1,30 @@ +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md +!**/.gitignore +!.git/HEAD +!.git/config +!.git/packed-refs +!.git/refs/heads/** \ No newline at end of file diff --git a/BookingMicroservice/BookingMicroservice.csproj b/BookingMicroservice/BookingMicroservice.csproj new file mode 100644 index 0000000000000000000000000000000000000000..aac30ec61683d673dfb036a93dd6bf5fc43013a9 --- /dev/null +++ b/BookingMicroservice/BookingMicroservice.csproj @@ -0,0 +1,24 @@ +<Project Sdk="Microsoft.NET.Sdk.Web"> + + <PropertyGroup> + <TargetFramework>net8.0</TargetFramework> + <Nullable>enable</Nullable> + <ImplicitUsings>enable</ImplicitUsings> + <UserSecretsId>1b0c76fc-55fe-4ae7-9921-1d071955c125</UserSecretsId> + <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS> + <DockerfileContext>.</DockerfileContext> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.3" /> + <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.3" /> + <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.3"> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + <PrivateAssets>all</PrivateAssets> + </PackageReference> + <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.6" /> + <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" /> + <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" /> + </ItemGroup> + +</Project> diff --git a/BookingMicroservice/BookingMicroservice.csproj.user b/BookingMicroservice/BookingMicroservice.csproj.user new file mode 100644 index 0000000000000000000000000000000000000000..983ecfc07a3622c3f9e0e73b96ae3e61aada8cb9 --- /dev/null +++ b/BookingMicroservice/BookingMicroservice.csproj.user @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <ActiveDebugProfile>http</ActiveDebugProfile> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> + <DebuggerFlavor>ProjectDebugger</DebuggerFlavor> + </PropertyGroup> +</Project> \ No newline at end of file diff --git a/BookingMicroservice/BookingMicroservice.http b/BookingMicroservice/BookingMicroservice.http new file mode 100644 index 0000000000000000000000000000000000000000..f8e15261dedeacfb4f47ecbce18e4ebc2454441d --- /dev/null +++ b/BookingMicroservice/BookingMicroservice.http @@ -0,0 +1,6 @@ +@BookingMicroservice_HostAddress = http://localhost:5207 + +GET {{BookingMicroservice_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/BookingMicroservice/BookingMicroservice.sln b/BookingMicroservice/BookingMicroservice.sln new file mode 100644 index 0000000000000000000000000000000000000000..db249cd6e108f883cba15fbe43561f0f1094e215 --- /dev/null +++ b/BookingMicroservice/BookingMicroservice.sln @@ -0,0 +1,25 @@ + +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}") = "BookingMicroservice", "BookingMicroservice.csproj", "{FF90ADFD-EC0A-4189-B734-59CD531D1FD6}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FF90ADFD-EC0A-4189-B734-59CD531D1FD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FF90ADFD-EC0A-4189-B734-59CD531D1FD6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FF90ADFD-EC0A-4189-B734-59CD531D1FD6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FF90ADFD-EC0A-4189-B734-59CD531D1FD6}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {1F564B53-A9C7-46CC-8ADD-A2CB5381ADB7} + EndGlobalSection +EndGlobal diff --git a/BookingMicroservice/Clients/FlightServiceClient.cs b/BookingMicroservice/Clients/FlightServiceClient.cs new file mode 100644 index 0000000000000000000000000000000000000000..4d54fd5bd9ce1231b7bafdecc5f2dab1d49c7771 --- /dev/null +++ b/BookingMicroservice/Clients/FlightServiceClient.cs @@ -0,0 +1,31 @@ +namespace BookingMicroservice.Clients +{ + public class FlightServiceClient : IFlightServiceClient + { + private readonly HttpClient httpClient; + private const string FLIGHT_API_PATH = "api/Flight"; + private const string SEAT_API_PATH = "api/Seat"; + + public FlightServiceClient(HttpClient httpClient) + { + this.httpClient = httpClient; + } + + + public async Task<HttpResponseMessage> GetFlightCapacityAsync(int flightId, int classType) + { + return await httpClient.GetAsync($"{FLIGHT_API_PATH}/{flightId}/capacity?classType={classType}"); + } + + public async Task<HttpResponseMessage> IsSeatAvailableAsync(int seatId) + { + return await httpClient.GetAsync($"{SEAT_API_PATH}/{seatId}/isAvailable"); + } + + public async Task<HttpResponseMessage> BookSeatAsync(int seatId) + { + return await httpClient.PutAsync($"{SEAT_API_PATH}/{seatId}", null); + } + + } +} diff --git a/BookingMicroservice/Clients/IFlightServiceClient.cs b/BookingMicroservice/Clients/IFlightServiceClient.cs new file mode 100644 index 0000000000000000000000000000000000000000..c3e9dcb075454b975abd7927b37979c33056ba96 --- /dev/null +++ b/BookingMicroservice/Clients/IFlightServiceClient.cs @@ -0,0 +1,9 @@ +namespace BookingMicroservice.Clients +{ + public interface IFlightServiceClient + { + Task<HttpResponseMessage> GetFlightCapacityAsync(int flightId, int classType); + Task<HttpResponseMessage> IsSeatAvailableAsync(int seatId); + Task<HttpResponseMessage> BookSeatAsync(int seatId); + } +} diff --git a/BookingMicroservice/Controllers/BookingController.cs b/BookingMicroservice/Controllers/BookingController.cs new file mode 100644 index 0000000000000000000000000000000000000000..de800b69a4e0fbdae5b598ed496a012cddd9d6ed --- /dev/null +++ b/BookingMicroservice/Controllers/BookingController.cs @@ -0,0 +1,91 @@ +using BookingMicroservice.Exceptions; +using BookingMicroservice.Models; +using BookingMicroservice.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System.Security.Claims; + +namespace BookingMicroservice.Controllers +{ + [ApiController] + [Route("api/[Controller]")] + public class BookingController : ControllerBase + { + private readonly IReservationComplianceService reservationComplianceService; + private readonly IBookingService bookingService; + + public BookingController(IReservationComplianceService reservationComplianceService, IBookingService bookingService) + { + this.reservationComplianceService = reservationComplianceService; + this.bookingService = bookingService; + } + + [Authorize] + [HttpGet()] + public IActionResult GetBookings(int? flightId = null, int? userId = null, BookingClass? bookingClass = null) + { + List<Booking> bookings = bookingService.GetBookings(flightId, userId, bookingClass); + if (bookings == null) + return BadRequest("Unable to get bookings"); + + return Ok(bookings); + } + + [Authorize] + [HttpGet("{id}")] + public IActionResult GetBooking([FromRoute] int id) + { + Booking? booking = bookingService.GetBooking(id); + if(booking == null) + return NotFound($"Could not find booking with id: {id}"); + + return Ok(booking); + } + + [Authorize] + [HttpPost()] + public async Task<IActionResult> MakeBooking([FromBody] BookingCreation bookingCreationModel) + { + // TODO: add stripe handling + string? userIdValue = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + if (!int.TryParse(userIdValue, out int userId)) + return BadRequest("Unable to get User Id from Token"); + + try + { + Booking? booking = await reservationComplianceService.TryCreateBookingAsync(bookingCreationModel.FlightId, userId, bookingCreationModel.BookingClass, bookingCreationModel.SeatId); + if (booking == null) + return BadRequest("Error in creating booking"); + + return Ok(booking); + } + catch (InvalidOperationException exception) + { + return BadRequest(exception.Message); + } + catch (BookingException exception) + { + return BadRequest(exception.Message); + } + } + + [Authorize] + [HttpPut("{bookingId}")] + public async Task<IActionResult> UpdatedBookingSeat([FromRoute] int bookingId, [FromBody] BookingUpdate bookingUpdateModel) + { + try + { + await reservationComplianceService.TryBookSeatAsync(bookingId, bookingUpdateModel.SeatId); + return Ok(new { BookedSeat = true, Message = "Seat booked successfully." }); + } + catch (InvalidOperationException exception) + { + return BadRequest(exception.Message); + } + catch (BookingException exception) + { + return Ok(new { BookedSeat = false, Message = exception.Message }); + } + } + } +} diff --git a/BookingMicroservice/Dockerfile b/BookingMicroservice/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..7bb0d3f09bf36acdb9ad1517fa3f501d47d008c5 --- /dev/null +++ b/BookingMicroservice/Dockerfile @@ -0,0 +1,23 @@ +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +USER app +WORKDIR /app +EXPOSE 8080 +EXPOSE 8081 + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["BookingMicroservice.csproj", "."] +RUN dotnet restore "./BookingMicroservice.csproj" +COPY . . +WORKDIR "/src/." +RUN dotnet build "./BookingMicroservice.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./BookingMicroservice.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "BookingMicroservice.dll"] \ No newline at end of file diff --git a/BookingMicroservice/Exceptions/BookingException.cs b/BookingMicroservice/Exceptions/BookingException.cs new file mode 100644 index 0000000000000000000000000000000000000000..deea4bfd9945a4ad7b62c3f5c3a6a4a2a55e7bee --- /dev/null +++ b/BookingMicroservice/Exceptions/BookingException.cs @@ -0,0 +1,7 @@ +namespace BookingMicroservice.Exceptions +{ + public class BookingException : Exception + { + public BookingException(string message) : base(message) { } + } +} diff --git a/BookingMicroservice/Handlers/ExceptionHandler.cs b/BookingMicroservice/Handlers/ExceptionHandler.cs new file mode 100644 index 0000000000000000000000000000000000000000..b0c6a8d371af49be2e08a67ca5082d18a37e8019 --- /dev/null +++ b/BookingMicroservice/Handlers/ExceptionHandler.cs @@ -0,0 +1,41 @@ +using System.Net; +using System.Text.Json; + +namespace BookingMicroservice.Handlers +{ + public class ExceptionHandler + { + private readonly RequestDelegate next; + + public ExceptionHandler(RequestDelegate next) + { + this.next = next; + } + + public async Task Invoke(HttpContext context) + { + try + { + await next(context); + } + catch (Exception ex) + { + if (!context.Response.HasStarted) + { + var response = context.Response; + response.ContentType = "application/json"; + response.StatusCode = (int)HttpStatusCode.InternalServerError; + + var result = JsonSerializer.Serialize(new + { + response.StatusCode, + ex.Message + }); + + await response.WriteAsync(result); + } + } + } + + } +} diff --git a/BookingMicroservice/Handlers/RequestCookieHandler.cs b/BookingMicroservice/Handlers/RequestCookieHandler.cs new file mode 100644 index 0000000000000000000000000000000000000000..7ac6698ceac2ff4e74e8cd2bcbe787be93f67289 --- /dev/null +++ b/BookingMicroservice/Handlers/RequestCookieHandler.cs @@ -0,0 +1,37 @@ +using System.Net; + +namespace BookingMicroservice.Handlers +{ + public class RequestCookieHandler : DelegatingHandler + { + private readonly IHttpContextAccessor httpContextAccessor; + + public RequestCookieHandler(IHttpContextAccessor httpContextAccessor) + { + this.httpContextAccessor = httpContextAccessor; + } + + protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + HttpContext? context = httpContextAccessor.HttpContext; + + if (context?.Request.Cookies != null && request.RequestUri != null) + { + CookieContainer cookieContainer = new CookieContainer(); + foreach (KeyValuePair<string, string> cookie in context.Request.Cookies) + { + if (!string.IsNullOrEmpty(cookie.Value) && (cookie.Key == "AccessToken" || cookie.Key == "RefreshToken")) + cookieContainer.Add(request.RequestUri, new Cookie(cookie.Key, cookie.Value)); + } + + var cookieHeader = cookieContainer.GetCookieHeader(request.RequestUri); + if (!string.IsNullOrEmpty(cookieHeader)) + request.Headers.Add("Cookie", cookieHeader); + } + + return await base.SendAsync(request, cancellationToken); + } + + + } +} diff --git a/BookingMicroservice/Models/ApplicationDbContext.cs b/BookingMicroservice/Models/ApplicationDbContext.cs new file mode 100644 index 0000000000000000000000000000000000000000..0ae7907d01070fddfd9930ca6bf65b674ae7fe53 --- /dev/null +++ b/BookingMicroservice/Models/ApplicationDbContext.cs @@ -0,0 +1,20 @@ +using Microsoft.EntityFrameworkCore; + +namespace BookingMicroservice.Models +{ + public class ApplicationDbContext : DbContext + { + public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) + : base(options) + { + } + + public DbSet<Booking> Bookings { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + } + + } +} diff --git a/BookingMicroservice/Models/Booking.cs b/BookingMicroservice/Models/Booking.cs new file mode 100644 index 0000000000000000000000000000000000000000..61eaa3e0fb7e3a44e44ef47ab18df4ea39ca8b0e --- /dev/null +++ b/BookingMicroservice/Models/Booking.cs @@ -0,0 +1,32 @@ +namespace BookingMicroservice.Models +{ + public class Booking + { + public int Id { get; private set; } + public int FlightId { get; private set; } + public int UserId { get; private set; } + public BookingClass BookingClass { get; private set; } + public int? SeatId { get; private set; } + + public Booking(int flightId, int userId, BookingClass bookingClass, int? seatId) + { + FlightId = flightId; + UserId = userId; + BookingClass = bookingClass; + SeatId = seatId; + } + + public Booking() { } + + public void SetSeatNumber(int seatId) + { + SeatId = seatId; + } + } + + public enum BookingClass + { + BUSINESS = 0, + ECONOMY = 1 + } +} diff --git a/BookingMicroservice/Models/BookingCreation.cs b/BookingMicroservice/Models/BookingCreation.cs new file mode 100644 index 0000000000000000000000000000000000000000..0e398b9d3e6c6e9eff874335902c683ccaf1f42c --- /dev/null +++ b/BookingMicroservice/Models/BookingCreation.cs @@ -0,0 +1,9 @@ +namespace BookingMicroservice.Models +{ + public class BookingCreation + { + public required int FlightId { get; set; } + public required BookingClass BookingClass { get; set; } + public int? SeatId { get; set; } + } +} diff --git a/BookingMicroservice/Models/BookingUpdate.cs b/BookingMicroservice/Models/BookingUpdate.cs new file mode 100644 index 0000000000000000000000000000000000000000..101d91806036a4fe39903b2460315aa9c649cb4c --- /dev/null +++ b/BookingMicroservice/Models/BookingUpdate.cs @@ -0,0 +1,7 @@ +namespace BookingMicroservice.Models +{ + public class BookingUpdate + { + public required int SeatId { get; set; } + } +} diff --git a/BookingMicroservice/Program.cs b/BookingMicroservice/Program.cs new file mode 100644 index 0000000000000000000000000000000000000000..74cabbd729d2e59148649da179c50c918bf9dc0d --- /dev/null +++ b/BookingMicroservice/Program.cs @@ -0,0 +1,90 @@ +using BookingMicroservice.Clients; +using BookingMicroservice.Handlers; +using BookingMicroservice.Models; +using BookingMicroservice.Services; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.EntityFrameworkCore; +using Microsoft.IdentityModel.Tokens; +using System.Text; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddHttpContextAccessor(); +builder.Services.AddTransient<RequestCookieHandler>(); +builder.Services.AddControllers(); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +builder.Services.AddDbContext<ApplicationDbContext>(options => + options.UseMySql(builder.Configuration.GetConnectionString("DefaultConnection"), + new MariaDbServerVersion(new Version(10, 4, 20)))); + +builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => + { + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"] ?? throw new InvalidOperationException("JWT Key is not configured."))), + ValidateIssuer = true, + ValidateAudience = true, + ValidIssuer = builder.Configuration["Jwt:Issuer"] ?? throw new InvalidOperationException("JWT Issuer is not configured."), + ValidAudience = builder.Configuration["Jwt:Audience"] ?? throw new InvalidOperationException("JWT Audience is not configured.") + }; + + options.Events = new JwtBearerEvents + { + OnAuthenticationFailed = context => + { + Console.WriteLine("Authentication failed: " + context.Exception.Message); + return Task.CompletedTask; + }, + OnTokenValidated = context => + { + Console.WriteLine("Token validated"); + return Task.CompletedTask; + } + }; + }); + +builder.Services.AddHttpClient<IFlightServiceClient, FlightServiceClient>(client => +{ + string? baseURL = builder.Configuration["FlightMicroservice:BaseUrl"]; + if (string.IsNullOrEmpty(baseURL)) + throw new InvalidOperationException("FlightMicroservice BaseUrl is not configured."); + + baseURL = baseURL.EndsWith("/") ? baseURL : baseURL + "/"; + client.BaseAddress = new Uri(baseURL); +}).AddHttpMessageHandler<RequestCookieHandler>(); ; + +builder.Services.AddScoped<IBookingService, BookingService>(); +builder.Services.AddScoped<IReservationComplianceService, ReservationComplianceService>(); + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseMiddleware<ExceptionHandler>(); +app.UseHttpsRedirection(); + +// Middleware to check for the access token in cookies +app.Use(async (context, next) => +{ + var accessToken = context.Request.Cookies["AccessToken"]; + if (!string.IsNullOrEmpty(accessToken)) + { + context.Request.Headers.Append("Authorization", "Bearer " + accessToken); + } + + await next(); +}); + +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapControllers(); +app.Run(); diff --git a/BookingService/FlightBooking.Service/Properties/launchSettings.json b/BookingMicroservice/Properties/launchSettings.json similarity index 61% rename from BookingService/FlightBooking.Service/Properties/launchSettings.json rename to BookingMicroservice/Properties/launchSettings.json index 434256cccf1cee91e2a84219b2a8a1e813d4c7cd..41814ad30f411b69f7b23d7e016b6ad79bfdebb5 100644 --- a/BookingService/FlightBooking.Service/Properties/launchSettings.json +++ b/BookingMicroservice/Properties/launchSettings.json @@ -1,33 +1,24 @@ -{ - "$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" - } + }, + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:5207" }, "https": { "commandName": "Project", - "dotnetRunMessages": true, "launchBrowser": true, "launchUrl": "swagger", - "applicationUrl": "https://localhost:7287;http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" - } + }, + "dotnetRunMessages": true, + "applicationUrl": "https://localhost:7116;http://localhost:5207" }, "IIS Express": { "commandName": "IISExpress", @@ -36,6 +27,26 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } + }, + "Container (Dockerfile)": { + "commandName": "Docker", + "launchBrowser": true, + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger", + "environmentVariables": { + "ASPNETCORE_HTTPS_PORTS": "8081", + "ASPNETCORE_HTTP_PORTS": "8080" + }, + "publishAllPorts": true, + "useSSL": true + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:4639", + "sslPort": 44398 } } } \ No newline at end of file diff --git a/BookingMicroservice/Services/BookingService.cs b/BookingMicroservice/Services/BookingService.cs new file mode 100644 index 0000000000000000000000000000000000000000..4c7a4fd983614f290d28e1ad3ee52bfaaf63d842 --- /dev/null +++ b/BookingMicroservice/Services/BookingService.cs @@ -0,0 +1,80 @@ +using BookingMicroservice.Models; +using Microsoft.EntityFrameworkCore; +using System.Diagnostics.CodeAnalysis; + +namespace BookingMicroservice.Services +{ + public class BookingService : IBookingService + { + private readonly ApplicationDbContext dbContext; + + public BookingService(ApplicationDbContext dbContext) + { + this.dbContext = dbContext; + } + + public List<Booking> GetBookings(int? flightId = null, int? userId = null, BookingClass? bookingClass = null) + { + IQueryable<Booking> query = dbContext.Bookings.AsQueryable(); + + if (flightId.HasValue) + query = query.Where(booking => booking.FlightId == flightId.Value); + + if (userId.HasValue) + query = query.Where(booking => booking.UserId == userId.Value); + + if (bookingClass.HasValue) + query = query.Where(booking => booking.BookingClass == bookingClass.Value); + + List<Booking> bookings = query.ToList(); + return bookings; + } + + public Booking CreateBooking(int flightId, int userId, BookingClass bookingClass) + { + Booking booking = new Booking(flightId, userId, bookingClass, null); + + dbContext.Bookings.Add(booking); + dbContext.SaveChanges(); + + return booking; + } + + public Booking? GetBooking(int bookingId) + { + Booking? booking = dbContext.Bookings.SingleOrDefault(booking => booking.Id == bookingId); + return booking; + } + + public void DeleteBooking(int bookingId) + { + Booking? booking = GetBooking(bookingId); + if (booking == null) + throw new KeyNotFoundException($"A Booking with the provided Id: {bookingId} doesnt exist"); + + dbContext.Bookings.Remove(booking); + dbContext.SaveChanges(); + } + + public Booking UpdateBooking(int bookingId, int seatId) + { + Booking? booking = GetBooking(bookingId); + if (booking == null) + throw new KeyNotFoundException($"A Booking with the provided Id: {bookingId} doesnt exist"); + + booking.SetSeatNumber(seatId); + + int affectedRows = dbContext.Bookings + .Where(booking => booking.Id == bookingId) + .ExecuteUpdate(setters => setters + .SetProperty(booking => booking.SeatId, seatId)); + + dbContext.SaveChanges(); + + if (affectedRows == 0) + throw new DbUpdateException("Operation was not able to update the Database"); + + return booking; + } + } +} diff --git a/BookingMicroservice/Services/IBookingService.cs b/BookingMicroservice/Services/IBookingService.cs new file mode 100644 index 0000000000000000000000000000000000000000..b5bd32727c612296a115e6323e204eac9741dd4c --- /dev/null +++ b/BookingMicroservice/Services/IBookingService.cs @@ -0,0 +1,13 @@ +using BookingMicroservice.Models; + +namespace BookingMicroservice.Services +{ + public interface IBookingService + { + Booking CreateBooking(int flightId, int userId, BookingClass bookingClass); + List<Booking> GetBookings(int? flightId = null, int? userId = null, BookingClass? bookingClass = null); + Booking? GetBooking(int bookingId); + void DeleteBooking(int bookingId); + Booking UpdateBooking(int bookingId, int seatId); + } +} diff --git a/BookingMicroservice/Services/IReservationComplianceService.cs b/BookingMicroservice/Services/IReservationComplianceService.cs new file mode 100644 index 0000000000000000000000000000000000000000..7f82720510a1f07e8016add0940502dd22bf822c --- /dev/null +++ b/BookingMicroservice/Services/IReservationComplianceService.cs @@ -0,0 +1,11 @@ +using BookingMicroservice.Models; + +namespace BookingMicroservice.Services +{ + public interface IReservationComplianceService + { + Task<Booking?> TryCreateBookingAsync(int flightId, int userId, BookingClass bookingClass, int? seatId); + Task TryBookSeatAsync(int bookingId, int seatId); + + } +} diff --git a/BookingMicroservice/Services/ReservationComplianceService.cs b/BookingMicroservice/Services/ReservationComplianceService.cs new file mode 100644 index 0000000000000000000000000000000000000000..df3f6a6effa87f3aa15568b52b339ec3456a81fb --- /dev/null +++ b/BookingMicroservice/Services/ReservationComplianceService.cs @@ -0,0 +1,80 @@ +using BookingMicroservice.Clients; +using BookingMicroservice.Exceptions; +using BookingMicroservice.Models; + +namespace BookingMicroservice.Services +{ + public class ReservationComplianceService : IReservationComplianceService + { + private readonly IBookingService bookingService; + private readonly IFlightServiceClient flightServiceClient; + + private const string NO_AVAILABLE_SEAT_MSG = "Unfortunately, there are no {0} seats available for this flight. Please try selecting a different class, flight, or date."; + private const string BOOKED_SEAT_MSG = "The selected seat is already booked. Please choose a different seat."; + + public ReservationComplianceService(IBookingService bookingService, IFlightServiceClient flightServiceClient) + { + this.bookingService = bookingService; + this.flightServiceClient = flightServiceClient; + } + + public async Task<Booking?> TryCreateBookingAsync(int flightId, int userId, BookingClass bookingClass, int? seatId) + { + HttpResponseMessage capacityResponse = await flightServiceClient.GetFlightCapacityAsync(flightId, (int)bookingClass); + if (!capacityResponse.IsSuccessStatusCode) + throw new InvalidOperationException("Could not retrieve flight capacity."); + + string capacityString = await capacityResponse.Content.ReadAsStringAsync(); + if (!int.TryParse(capacityString, out int capacity)) + throw new InvalidOperationException("Invalid capacity value received from flight service."); + + // Get current bookings for the flight and class + int currentBookings = bookingService.GetBookings(flightId: flightId, bookingClass: bookingClass).Count(); + + if (currentBookings >= capacity) + throw new BookingException(string.Format(NO_AVAILABLE_SEAT_MSG, bookingClass.ToString().ToLower())); + + Booking booking = bookingService.CreateBooking(flightId, userId, bookingClass); + if(seatId.HasValue && booking?.Id != null) + { + try + { + await TryBookSeatAsync(booking.Id, seatId.Value); + booking.SetSeatNumber(seatId.Value); + } + catch (Exception exception) + { + // delete booking as there has been a failure in booking the seat + bookingService.DeleteBooking(booking.Id); + throw new InvalidOperationException($@"Booking has been cancelled as there has been an issue with selecting the seat.{Environment.NewLine}{exception.Message}{Environment.NewLine}Please try again."); + } + } + + return booking; + } + + public async Task TryBookSeatAsync(int bookingId, int seatId) + { + HttpResponseMessage seatAvailabilityResponse = await flightServiceClient.IsSeatAvailableAsync(seatId); + if (!seatAvailabilityResponse.IsSuccessStatusCode) + throw new InvalidOperationException("Could not retrieve seat availability."); + + string seatAvailabilityString = await seatAvailabilityResponse.Content.ReadAsStringAsync(); + if (!bool.TryParse(seatAvailabilityString, out bool isSeatAvailable)) + throw new InvalidOperationException("Invalid seat availability value received from flight service."); + + if(!isSeatAvailable) + throw new BookingException(BOOKED_SEAT_MSG); + + HttpResponseMessage seatBookingRespone = await flightServiceClient.BookSeatAsync(seatId); + + if (!seatBookingRespone.IsSuccessStatusCode) { + string? failureMessage = seatBookingRespone.ReasonPhrase; + throw new InvalidOperationException($"Error booking seat; {failureMessage}"); + } + + bookingService.UpdateBooking(bookingId, seatId); + } + + } +} diff --git a/BookingMicroservice/appsettings.Development.json b/BookingMicroservice/appsettings.Development.json new file mode 100644 index 0000000000000000000000000000000000000000..0c208ae9181e5e5717e47ec1bd59368aebc6066e --- /dev/null +++ b/BookingMicroservice/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/BookingMicroservice/appsettings.json b/BookingMicroservice/appsettings.json new file mode 100644 index 0000000000000000000000000000000000000000..7ce4758e8bacea93b6bfa282c518089174e34fb3 --- /dev/null +++ b/BookingMicroservice/appsettings.json @@ -0,0 +1,20 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "Jwt": { + "Key": "0QTrd3jToEYj205k01A2R87Hc5YpqDNeywg7JzQpczs=", + "Issuer": "http://localhost:5089", + "Audience": "http://localhost:5089" + }, + "ConnectionStrings": { + "DefaultConnection": "server=localhost;port=3308;database=AspNetCoreDb;user=root;password=;CharSet=utf8mb4;" + }, + "FlightMicroservice": { + "BaseUrl": "http://localhost:5175" + } +} diff --git a/BookingService/.gitignore b/BookingService/.gitignore deleted file mode 100644 index 1242aad84b43939c4860165458c6b3d8ab4716d9..0000000000000000000000000000000000000000 --- a/BookingService/.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/FlightBooking.Service.Tests/FlightBooking.Service.Tests.csproj b/BookingService/FlightBooking.Service.Tests/FlightBooking.Service.Tests.csproj deleted file mode 100644 index 78b328aefd3a040945634a37f6d84b87e561ed89..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service.Tests/UnitTest1.cs b/BookingService/FlightBooking.Service.Tests/UnitTest1.cs deleted file mode 100644 index 127caef0f8fb524181ec652ba30c1324b6b76b60..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service.sln b/BookingService/FlightBooking.Service.sln deleted file mode 100644 index 2a079f38ba238be39114cdc4fd397aa41de7549b..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Controllers/BookingsController.cs b/BookingService/FlightBooking.Service/Controllers/BookingsController.cs deleted file mode 100644 index d9d2de405db28a1495766a7dc5b70ede07c602d6..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Controllers/SeatsController.cs b/BookingService/FlightBooking.Service/Controllers/SeatsController.cs deleted file mode 100644 index a6ca8bb54ae688f0060fd3551b898dd99b61a8f7..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Controllers/StripeController.cs b/BookingService/FlightBooking.Service/Controllers/StripeController.cs deleted file mode 100644 index 0f9bf55d73401f9e463b38e26b917a523de71957..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Data/Configs/ConfigSettings.cs b/BookingService/FlightBooking.Service/Data/Configs/ConfigSettings.cs deleted file mode 100644 index 72ff348b15776926609d4182391c61d8a14306de..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Data/Configs/ConfigSettingsModule.cs b/BookingService/FlightBooking.Service/Data/Configs/ConfigSettingsModule.cs deleted file mode 100644 index 21c0d09183ecca2df9ec48a1bbcb4543c094c946..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Data/Constants.cs b/BookingService/FlightBooking.Service/Data/Constants.cs deleted file mode 100644 index ff8ff81ab5323e78ee37f76423f5dae22528158c..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Data/DTO/BookingDTO.cs b/BookingService/FlightBooking.Service/Data/DTO/BookingDTO.cs deleted file mode 100644 index 8a933331d7639d615f0ef640bd9b94834d64f31f..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Data/DTO/BookingOrderDTO.cs b/BookingService/FlightBooking.Service/Data/DTO/BookingOrderDTO.cs deleted file mode 100644 index 80a6ee1a7ff81a645f723738e626853c0671e564..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Data/DTO/FlightFareDTO.cs b/BookingService/FlightBooking.Service/Data/DTO/FlightFareDTO.cs deleted file mode 100644 index ae7db016b2ccd6973066e39811147794062ad37c..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Data/DTO/FlightInformationDTO.cs b/BookingService/FlightBooking.Service/Data/DTO/FlightInformationDTO.cs deleted file mode 100644 index eb8acdadf51ec0dd635768c710605e9f16f8ee0b..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Data/DTO/ReservedSeatDTO.cs b/BookingService/FlightBooking.Service/Data/DTO/ReservedSeatDTO.cs deleted file mode 100644 index 862b363a860ae893d8d404ac56c5705937bbf60c..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Data/DTO/StripeDataDTO.cs b/BookingService/FlightBooking.Service/Data/DTO/StripeDataDTO.cs deleted file mode 100644 index b6ca8686745e2745b7d8032fbc36c021da592515..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Data/DatabaseSeeding.cs b/BookingService/FlightBooking.Service/Data/DatabaseSeeding.cs deleted file mode 100644 index bd275966c7d42fd736f41371dbca64fb9141c7e1..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Data/Enums.cs b/BookingService/FlightBooking.Service/Data/Enums.cs deleted file mode 100644 index 15a8a628fd9ad1691dbfcb8458227115ad77c3bb..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Data/FlightBookingContext.cs b/BookingService/FlightBooking.Service/Data/FlightBookingContext.cs deleted file mode 100644 index ecdf63e93b35d7da98e11fa731bd2c0f5dd3b30e..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Data/ModelConfigurations/BookingConfiguration.cs b/BookingService/FlightBooking.Service/Data/ModelConfigurations/BookingConfiguration.cs deleted file mode 100644 index a07fc63af5f3f747088ab8dcab01455d6882bd23..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Data/ModelConfigurations/BookingOrderConfiguration.cs b/BookingService/FlightBooking.Service/Data/ModelConfigurations/BookingOrderConfiguration.cs deleted file mode 100644 index 7ab617095a05ea7f4491ad106f8a7f7c0c6a7561..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Data/ModelConfigurations/FlightFareConfiguration.cs b/BookingService/FlightBooking.Service/Data/ModelConfigurations/FlightFareConfiguration.cs deleted file mode 100644 index 298a02f84eab1e9ee44e9e5d2e268bc1a6937cad..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Data/ModelConfigurations/FlightInformationConfiguration.cs b/BookingService/FlightBooking.Service/Data/ModelConfigurations/FlightInformationConfiguration.cs deleted file mode 100644 index 2af41e5de7a079bb7886b7420c5da5ab10cf2191..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Data/ModelConfigurations/PaymentConfiguration.cs b/BookingService/FlightBooking.Service/Data/ModelConfigurations/PaymentConfiguration.cs deleted file mode 100644 index 5b132341148c5b31b01a7af5c3de867190d9cf1d..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Data/ModelConfigurations/ReservedSeatConfiguration.cs b/BookingService/FlightBooking.Service/Data/ModelConfigurations/ReservedSeatConfiguration.cs deleted file mode 100644 index 9ca62f100a2cda976b82863a2f25f9613decb55b..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Data/Models/Booking.cs b/BookingService/FlightBooking.Service/Data/Models/Booking.cs deleted file mode 100644 index 315ea18be1d8aa0cb1aaef39de96b04e88f00bad..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Data/Models/BookingOrder.cs b/BookingService/FlightBooking.Service/Data/Models/BookingOrder.cs deleted file mode 100644 index 5dce348a6e7708e951022eef22cb60aa4271252f..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Data/Models/FlightFare.cs b/BookingService/FlightBooking.Service/Data/Models/FlightFare.cs deleted file mode 100644 index 9dfca35a6849135bcd65a5bbf3de96113dafabdd..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Data/Models/FlightInformation.cs b/BookingService/FlightBooking.Service/Data/Models/FlightInformation.cs deleted file mode 100644 index ed01d2e26d2731ca4fdc3a13d133e367ca8b6195..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Data/Models/PassengerInformation.cs b/BookingService/FlightBooking.Service/Data/Models/PassengerInformation.cs deleted file mode 100644 index 1bcb85141c988c21a69145b38e6d33ad832dd484..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Data/Models/Payment.cs b/BookingService/FlightBooking.Service/Data/Models/Payment.cs deleted file mode 100644 index 4e9e2cb9ed35370da2d3e7683b309bdd244ef1a1..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Data/Models/ReservedSeat.cs b/BookingService/FlightBooking.Service/Data/Models/ReservedSeat.cs deleted file mode 100644 index 6f6fb691507908b491412220cbe86c2cac5aef29..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Data/Repository/GenericRepository.cs b/BookingService/FlightBooking.Service/Data/Repository/GenericRepository.cs deleted file mode 100644 index 584354a1e8df7d1628f6876bddb32e78b5a232cc..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Data/Repository/IGenericRepository.cs b/BookingService/FlightBooking.Service/Data/Repository/IGenericRepository.cs deleted file mode 100644 index 74e90c859cd23a16a7eac64dbe93a2ab7f8a32a5..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Data/Repository/RepositoryModule.cs b/BookingService/FlightBooking.Service/Data/Repository/RepositoryModule.cs deleted file mode 100644 index 90b6c0ecd46573181b22b6c0812453513fd89104..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/FlightBooking.Service.csproj b/BookingService/FlightBooking.Service/FlightBooking.Service.csproj deleted file mode 100644 index 217d79c9aa3c8e30a20af8912fb29b1e0b9be963..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/FlightBooking.Service.http b/BookingService/FlightBooking.Service/FlightBooking.Service.http deleted file mode 100644 index 045f70b4b3efb3716860cf654699c759ef728cdf..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Middleware/ErrorHandlingMiddleware.cs b/BookingService/FlightBooking.Service/Middleware/ErrorHandlingMiddleware.cs deleted file mode 100644 index 1510d26e45fde58df949f75a8f5965a902d00227..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Middleware/MiddlewareExtensions.cs b/BookingService/FlightBooking.Service/Middleware/MiddlewareExtensions.cs deleted file mode 100644 index 6b50b9388e8b0af3068a7c4f71b69ee86a4d7476..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Program.cs b/BookingService/FlightBooking.Service/Program.cs deleted file mode 100644 index 041f1b1795fe3de8b53c6693047c19e475694640..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Services/BookingOrderService.cs b/BookingService/FlightBooking.Service/Services/BookingOrderService.cs deleted file mode 100644 index f1d70bd5a6829dd9eceaaa29886018ca598128ca..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Services/BookingService.cs b/BookingService/FlightBooking.Service/Services/BookingService.cs deleted file mode 100644 index 9dd41fe760428835b979c9dcc30f7f288a760e8a..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Services/FlightBookingProfile.cs b/BookingService/FlightBooking.Service/Services/FlightBookingProfile.cs deleted file mode 100644 index 4a7d890344bb2640f474347debefa2304fb92099..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Services/FlightFareService.cs b/BookingService/FlightBooking.Service/Services/FlightFareService.cs deleted file mode 100644 index 33bb18c338e64cdc5330a09fe245cec6b5fc789e..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Services/FlightService.cs b/BookingService/FlightBooking.Service/Services/FlightService.cs deleted file mode 100644 index f7f4e13c2326cbb16622424f4357ca299b38ad96..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Services/Interfaces/IBookingOrderService.cs b/BookingService/FlightBooking.Service/Services/Interfaces/IBookingOrderService.cs deleted file mode 100644 index 7e1b124edb5df21db71d9ce06cd4ced52f9a8536..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Services/Interfaces/IBookingService.cs b/BookingService/FlightBooking.Service/Services/Interfaces/IBookingService.cs deleted file mode 100644 index 47c5aae0247c4c4bbe0d37646e67e4257fe5195d..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Services/Interfaces/IFlightFareService.cs b/BookingService/FlightBooking.Service/Services/Interfaces/IFlightFareService.cs deleted file mode 100644 index 34e4545563f4920e336608961cb63ba9372bc998..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Services/Interfaces/IFlightService.cs b/BookingService/FlightBooking.Service/Services/Interfaces/IFlightService.cs deleted file mode 100644 index 290f5d247d3c85854228536bddd12be50fbcfeb7..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Services/Interfaces/IReservedSeatService.cs b/BookingService/FlightBooking.Service/Services/Interfaces/IReservedSeatService.cs deleted file mode 100644 index c0f70557c739824a7fc7f872dd1a170cfe8cb509..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Services/Interfaces/IStripeService.cs b/BookingService/FlightBooking.Service/Services/Interfaces/IStripeService.cs deleted file mode 100644 index abfb0f0fb9864a3dbda4f38ba2bd2f07db97fb4e..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Services/PaymentService.cs b/BookingService/FlightBooking.Service/Services/PaymentService.cs deleted file mode 100644 index b66d125e813677ea35afef3ec8d9c1b7c154210b..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Services/ReservedSeatService.cs b/BookingService/FlightBooking.Service/Services/ReservedSeatService.cs deleted file mode 100644 index 16126d17e4e94c58796ae0d3e1af122ac508b47f..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Services/ResponseFormatter.cs b/BookingService/FlightBooking.Service/Services/ResponseFormatter.cs deleted file mode 100644 index 504c41820df3c87e6e6b7026f8181382cab22ed9..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Services/ServicesModule.cs b/BookingService/FlightBooking.Service/Services/ServicesModule.cs deleted file mode 100644 index 30710dcf1366aea965037c89bdd0e022901cab64..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Services/StripeService.cs b/BookingService/FlightBooking.Service/Services/StripeService.cs deleted file mode 100644 index ab7557149e2e9fd3765d8123d0e5a2ecd83b7ed8..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/Startup.cs b/BookingService/FlightBooking.Service/Startup.cs deleted file mode 100644 index 540cfff397cf962eba9d196c05dbf08806ecc1a6..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/appsettings.Development.json b/BookingService/FlightBooking.Service/appsettings.Development.json deleted file mode 100644 index 14d2ada275401963af066006c415ad4c6f6f5632..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/appsettings.json b/BookingService/FlightBooking.Service/appsettings.json deleted file mode 100644 index 31a3e8d4be37041572b3f732452c32cece264feb..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/nlog.config b/BookingService/FlightBooking.Service/nlog.config deleted file mode 100644 index 1967321ff68e857eb426ef25973180f849964756..0000000000000000000000000000000000000000 --- a/BookingService/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/FlightBooking.Service/readme.md b/BookingService/FlightBooking.Service/readme.md deleted file mode 100644 index 77617ad64dd70c1dcf44caaa9bf2264cabcb785f..0000000000000000000000000000000000000000 --- a/BookingService/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/Database/InitTables.sql b/Database/InitTables.sql index dd06e9e1cf83cfba34888ade63dc7cf2cc0d8399..7d7bcf9bd1eecd2feed4485e30421662de6a622d 100644 --- a/Database/InitTables.sql +++ b/Database/InitTables.sql @@ -33,3 +33,13 @@ CREATE TABLE Seats ( ClassType INT NOT NULL, IsAvailable TINYINT(1) NOT NULL ); + +CREATE TABLE Bookings ( + Id INT(11) NOT NULL AUTO_INCREMENT, + FlightId INT(11) NOT NULL, + UserId INT(11) NOT NULL, + BookingClass INT(11) NOT NULL, + SeatId INT(11) DEFAULT NULL, + PRIMARY KEY (Id) +); + diff --git a/FlightMicroservice/Controllers/FlightController.cs b/FlightMicroservice/Controllers/FlightController.cs index ea3411bda1e1510ea3269969c853d6844a58e385..649e10418beb7922f494983520f430ab359ab7b4 100644 --- a/FlightMicroservice/Controllers/FlightController.cs +++ b/FlightMicroservice/Controllers/FlightController.cs @@ -64,7 +64,7 @@ namespace FlightMicroservice.Controllers int seatCapacity = classType == ClassType.BUSINESS ? flight.BusinessCapacity : flight.EconomyCapacity; - return Ok(new { ClassType = classType.ToString(), Capacity = seatCapacity }); + return Ok(seatCapacity); } diff --git a/FlightMicroservice/Controllers/SeatController.cs b/FlightMicroservice/Controllers/SeatController.cs index 47491f7bad0747f8eee64445dea864beb54a24b3..758bffff97652ceae598f74790737a3432c1868a 100644 --- a/FlightMicroservice/Controllers/SeatController.cs +++ b/FlightMicroservice/Controllers/SeatController.cs @@ -58,7 +58,7 @@ namespace FlightMicroservice.Controllers try { bool isAvailable = seatService.IsAvailable(id); - return Ok(new { IsAvailable = isAvailable }); + return Ok(isAvailable); } catch (KeyNotFoundException ex) { diff --git a/GatewayAPI/Clients/BookingService/BookingServiceClient.cs b/GatewayAPI/Clients/BookingService/BookingServiceClient.cs new file mode 100644 index 0000000000000000000000000000000000000000..dab63de5d0387d0608ea5710ff98cd180b622299 --- /dev/null +++ b/GatewayAPI/Clients/BookingService/BookingServiceClient.cs @@ -0,0 +1,51 @@ +using GatewayAPI.Models; +using Microsoft.AspNetCore.WebUtilities; + +namespace GatewayAPI.Clients.BookingService +{ + public class BookingServiceClient : IBookingServiceClient + { + private readonly HttpClient httpClient; + private const string API_PATH = "api/Booking"; + + public BookingServiceClient(HttpClient httpClient) + { + this.httpClient = httpClient; + } + + public Task<HttpResponseMessage> GetBookingAsync(int id) + { + return httpClient.GetAsync($"{API_PATH}/{id}"); + } + + public Task<HttpResponseMessage> GetBookingsAsync(int? flightId = null, int? userId = null, int? bookingClass = null) + { + Dictionary<string, string?> queryParams = new Dictionary<string, string?>(); + + if (flightId.HasValue) + queryParams.Add("flightId", flightId.Value.ToString()); + + if (userId.HasValue) + queryParams.Add("userId", userId.Value.ToString()); + + if (bookingClass.HasValue) + queryParams.Add("bookingClass", bookingClass.Value.ToString()); + + string url = QueryHelpers.AddQueryString(API_PATH, queryParams); + + return httpClient.GetAsync(url); + } + + public Task<HttpResponseMessage> MakeBookingAsync(BookingCreation bookingModel) + { + return httpClient.PostAsJsonAsync($"{API_PATH}", bookingModel); + } + + public Task<HttpResponseMessage> UpdateBookingAsync(int bookindId, BookingUpdate bookingModel) + { + return httpClient.PutAsJsonAsync($"{API_PATH}/{bookindId}", bookingModel); + } + + + } +} diff --git a/GatewayAPI/Clients/BookingService/IBookingServiceClient.cs b/GatewayAPI/Clients/BookingService/IBookingServiceClient.cs new file mode 100644 index 0000000000000000000000000000000000000000..9b4d6b244c135dc5e08033893f571618d105ae0f --- /dev/null +++ b/GatewayAPI/Clients/BookingService/IBookingServiceClient.cs @@ -0,0 +1,11 @@ +using GatewayAPI.Models; + +namespace GatewayAPI.Clients.BookingService +{ + public interface IBookingServiceClient + { + Task<HttpResponseMessage> GetBookingAsync(int id); + Task<HttpResponseMessage> GetBookingsAsync(int? flightId = null, int? userId = null, int? bookingClass = null); + Task<HttpResponseMessage> MakeBookingAsync(BookingCreation bookingModel); + Task<HttpResponseMessage> UpdateBookingAsync(int bookindId, BookingUpdate bookingModel); } +} diff --git a/GatewayAPI/Controllers/BookingController.cs b/GatewayAPI/Controllers/BookingController.cs new file mode 100644 index 0000000000000000000000000000000000000000..3828149606d8c81453d89723f06c10c1992bd4e4 --- /dev/null +++ b/GatewayAPI/Controllers/BookingController.cs @@ -0,0 +1,47 @@ +using GatewayAPI.Clients.BookingService; +using GatewayAPI.Models; +using Microsoft.AspNetCore.Mvc; + +namespace GatewayAPI.Controllers +{ + + [ApiController] + [Route("api/[Controller]")] + public class BookingController : ControllerBase + { + private readonly IBookingServiceClient bookingServiceClient; + + public BookingController(IBookingServiceClient bookingServiceClient) + { + this.bookingServiceClient = bookingServiceClient; + } + + [HttpGet("{id}")] + public async Task<IActionResult> GetBooking(int id) + { + HttpResponseMessage response = await bookingServiceClient.GetBookingAsync(id); + return new HttpResponseMessageResult(response); + } + + [HttpGet()] + public async Task<IActionResult> GetBookings(int? flightId = null, int? userId = null, int? bookingClass = null) + { + HttpResponseMessage response = await bookingServiceClient.GetBookingsAsync(flightId, userId, bookingClass); + return new HttpResponseMessageResult(response); + } + + [HttpPost()] + public async Task<IActionResult> MakeBooking([FromBody] BookingCreation bookingCreationModel) + { + HttpResponseMessage response = await bookingServiceClient.MakeBookingAsync(bookingCreationModel); + return new HttpResponseMessageResult(response); + } + + [HttpPut("{id}")] + public async Task<IActionResult> UpdateBooking([FromRoute] int id, [FromBody] BookingUpdate bookingUpdateModel) + { + HttpResponseMessage response = await bookingServiceClient.UpdateBookingAsync(id, bookingUpdateModel); + return new HttpResponseMessageResult(response); + } + } +} diff --git a/GatewayAPI/Models/BookingCreation.cs b/GatewayAPI/Models/BookingCreation.cs new file mode 100644 index 0000000000000000000000000000000000000000..c3134926859c658ace68b0dc5806c7c398270dc8 --- /dev/null +++ b/GatewayAPI/Models/BookingCreation.cs @@ -0,0 +1,10 @@ +namespace GatewayAPI.Models +{ + public class BookingCreation + { + public required int FlightId { get; set; } + public required int BookingClass { get; set; } + public int? SeatId { get; set; } + + } +} diff --git a/GatewayAPI/Models/BookingUpdate.cs b/GatewayAPI/Models/BookingUpdate.cs new file mode 100644 index 0000000000000000000000000000000000000000..e723879bb78c69547b21bb9412ca444603f632b6 --- /dev/null +++ b/GatewayAPI/Models/BookingUpdate.cs @@ -0,0 +1,7 @@ +namespace GatewayAPI.Models +{ + public class BookingUpdate + { + public required int SeatId { get; set; } + } +} diff --git a/GatewayAPI/ServiceCollectionExtensions.cs b/GatewayAPI/ServiceCollectionExtensions.cs index 7235c816dcbe5767cfb5c3ef2ccdce5d583e42c4..25d509a6376142baed07f611fd0fa05134283fee 100644 --- a/GatewayAPI/ServiceCollectionExtensions.cs +++ b/GatewayAPI/ServiceCollectionExtensions.cs @@ -1,4 +1,5 @@ -using GatewayAPI.Clients.FlightService; +using GatewayAPI.Clients.BookingService; +using GatewayAPI.Clients.FlightService; using GatewayAPI.Clients.UserService; using System.Runtime.CompilerServices; @@ -20,6 +21,12 @@ namespace GatewayAPI client.BaseAddress = new Uri(baseUrl.EndsWith("/") ? baseUrl : baseUrl + "/"); }).AddHttpMessageHandler<RequestCookieHandler>(); + services.AddHttpClient<IBookingServiceClient, BookingServiceClient>(client => + { + string baseUrl = configurationManager["BookingMicroservice:BaseUrl"] ?? throw new InvalidOperationException("BookingMicroservice BaseUrl is not configured."); + client.BaseAddress = new Uri(baseUrl.EndsWith("/") ? baseUrl : baseUrl + "/"); + }).AddHttpMessageHandler<RequestCookieHandler>(); + return services; } } diff --git a/GatewayAPI/appsettings.json b/GatewayAPI/appsettings.json index f0c8836097302b9aad3a4302088f49f88bc4e46a..a5a7960c6015131a93db0f6f52bdbb4192f56f45 100644 --- a/GatewayAPI/appsettings.json +++ b/GatewayAPI/appsettings.json @@ -11,5 +11,8 @@ }, "FlightMicroservice": { "BaseUrl": "http://localhost:5175" + }, + "BookingMicroservice": { + "BaseUrl": "http://localhost:5207" } } diff --git a/Templates/login-register/login.html b/Templates/login-register/login.html deleted file mode 100644 index 092908ccc5f9ce4e7c31f84b25ec6bd3f07dd3a2..0000000000000000000000000000000000000000 --- 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 51fe1c5f4cf17c3b25ed2b1397b08be2a1046dad..0000000000000000000000000000000000000000 --- 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 88252690fc4f234f0faa3399d12a9a320effc4e0..0000000000000000000000000000000000000000 --- 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 a4f443ddbcb5f90f6b395c532028511aa0b5ea74..0000000000000000000000000000000000000000 --- 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 Binary files a/Templates/user_profile/avatar.jpg and /dev/null differ diff --git a/Templates/user_profile/profile.css b/Templates/user_profile/profile.css deleted file mode 100644 index 44709d293b4e4371a128b360ee799ddb76f9813c..0000000000000000000000000000000000000000 --- 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 68302e72d1107e459360f835062c811d8b6eab52..0000000000000000000000000000000000000000 --- 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 0a70508a83911d2a9ea0943f159c7e5d093a0580..0000000000000000000000000000000000000000 --- 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 Binary files a/Templates/user_profile/user.png and /dev/null differ diff --git a/docker-compose.yml b/docker-compose.yml index 109ae714e84c633d4d1f750a619d017a61d67d84..1826cc11fca902bbd9de45380c217e0efca0c9be 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,6 +28,21 @@ services: - Jwt__Issuer=http://usermicroservice:8080 - Jwt__Audience=http://usermicroservice:8080 + bookingmicroservice: + build: + context: ./BookingMicroservice + image: bookingmicroservice:${IMAGE_TAG} + ports: + - "${BOOKING_MICROSERVICE_PORT}:8080" + environment: + - ASPNETCORE_ENVIRONMENT=Production + - DOTNET_RUNNING_IN_CONTAINER=true + - ConnectionStrings__DefaultConnection=Server=host.docker.internal;Port=${DB_PORT};Database=${DB_NAME};User=${DB_USER};Password=${DB_PASSWORD};CharSet=${DB_CHARSET} + - Jwt__Key=0QTrd3jToEYj205k01A2R87Hc5YpqDNeywg7JzQpczs= + - Jwt__Issuer=http://usermicroservice:8080 + - Jwt__Audience=http://usermicroservice:8080 + - FlightMicroservice__BaseUrl=http://flightmicroservice:8080 + gatewayapi: build: context: ./GatewayAPI @@ -39,6 +54,7 @@ services: - DOTNET_RUNNING_IN_CONTAINER=true - UserMicroservice__BaseUrl=http://usermicroservice:8080 - FlightMicroservice__BaseUrl=http://flightmicroservice:8080 + - BookingMicroservice__BaseUrl=http://bookingmicroservice:8080 client: build: