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..2f43c11993c21fa2f169c74583a39e2e95509f44 --- /dev/null +++ b/BookingMicroservice/Controllers/BookingController.cs @@ -0,0 +1,75 @@ +using BookingMicroservice.Exceptions; +using BookingMicroservice.Models; +using BookingMicroservice.Services; +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; + } + + [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); + } + + [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); + } + + [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"); + + Booking booking = await reservationComplianceService.TryCreateBookingAsync(bookingCreationModel.FlightId, userId, bookingCreationModel.BookingClass, bookingCreationModel.SeatId); + if (booking == null) + return BadRequest("Error in creating booking"); + + return Ok(booking); + } + + [HttpPut("{bookingId}")] + public async Task<IActionResult> UpdatedBookingSeat([FromRoute] int bookingId, [FromBody] BookingUpdate bookingUpdateModel) + { + try + { + await reservationComplianceService.TryBookSeatAsync(bookingId, bookingUpdateModel.SeatId); + return Ok(); + } + catch (InvalidOperationException exception) + { + return BadRequest(exception.Message); + } + catch (BookingException exception) + { + return BadRequest(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/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..c10b8dcfa3d98b6df96eedefff2344c3092c88a4 --- /dev/null +++ b/BookingMicroservice/Program.cs @@ -0,0 +1,70 @@ +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.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.") + }; + }); + +builder.Services.AddHttpClient<IFlightServiceClient, FlightServiceClient>(client => +{ + string baseURL = builder.Configuration["FlightMicroservice:BaseUrl"] ?? throw new InvalidOperationException("FlightMicroservice BaseUrl is not configured."); + baseURL = baseURL.EndsWith("/") ? baseURL : baseURL + "/"; + client.BaseAddress = new Uri(baseURL); +}); + +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.UseAuthorization(); +app.MapControllers(); +app.Run(); diff --git a/BookingMicroservice/Properties/launchSettings.json b/BookingMicroservice/Properties/launchSettings.json new file mode 100644 index 0000000000000000000000000000000000000000..41814ad30f411b69f7b23d7e016b6ad79bfdebb5 --- /dev/null +++ b/BookingMicroservice/Properties/launchSettings.json @@ -0,0 +1,52 @@ +{ + "profiles": { + "http": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:5207" + }, + "https": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "https://localhost:7116;http://localhost:5207" + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "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..d5efd2941e115d3ff46b3abe4c2a617ae3c9a8db --- /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, int? seatId) + { + Booking booking = new Booking(flightId, userId, bookingClass, seatId); + + 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..d679c2eff5ed9fe8cc1d85c282800b013a0e9d8d --- /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, int? seatId); + 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..98364a189804ab74a6b2acdd01f23307fae690e8 --- /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..6aa02963716fa70d867b486c07d80b3f4b82e408 --- /dev/null +++ b/BookingMicroservice/Services/ReservationComplianceService.cs @@ -0,0 +1,64 @@ +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.GetBookingForFlight(flightId).Count(booking => booking.BookingClass == bookingClass); + int currentBookings = bookingService.GetBookings(flightId: flightId, bookingClass: bookingClass).Count(); + + if (currentBookings >= capacity) + throw new BookingException(string.Format(NO_AVAILABLE_SEAT_MSG, bookingClass.ToString().ToLower())); + + return bookingService.CreateBooking(flightId, userId, bookingClass, seatId); + } + + 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); + string capacityString = await seatBookingRespone.Content.ReadAsStringAsync(); // remove this later + + if (!seatAvailabilityResponse.IsSuccessStatusCode) + throw new InvalidOperationException("Error booking seat."); + + 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/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) +); +