From dfb49f2b514584d09a2fbfe689b93956fc961342 Mon Sep 17 00:00:00 2001
From: Mouaz Abdelsamad <ma03081@surrey.ac.uk>
Date: Tue, 9 Apr 2024 03:35:51 +0100
Subject: [PATCH] improvements

---
 .env                                          | 14 +++++--
 .../Controllers/BookingController.cs          | 28 +++++++++++---
 .../Handlers/RequestCookieHandler.cs          | 37 +++++++++++++++++++
 BookingMicroservice/Program.cs                | 26 +++++++++++--
 .../Services/BookingService.cs                |  4 +-
 .../Services/IBookingService.cs               |  2 +-
 .../Services/IReservationComplianceService.cs |  2 +-
 .../Services/ReservationComplianceService.cs  | 30 +++++++++++----
 .../Controllers/FlightController.cs           |  2 +-
 .../Controllers/SeatController.cs             |  2 +-
 docker-compose.yml                            | 26 ++++++-------
 11 files changed, 134 insertions(+), 39 deletions(-)
 create mode 100644 BookingMicroservice/Handlers/RequestCookieHandler.cs

diff --git a/.env b/.env
index 673e590..c15e04b 100644
--- a/.env
+++ b/.env
@@ -4,10 +4,16 @@ MYSQL_IMAGE_TAG=8.0
 
 
 # Database configuration
-DB_PORT=3307
-DB_NAME=myDB
-DB_USER=user
-DB_PASSWORD=user
+#DB_PORT=3307
+#DB_NAME=myDB
+#DB_USER=user
+#DB_PASSWORD=user
+#DB_CHARSET=utf8mb4
+
+DB_PORT=3308
+DB_NAME=AspNetCoreDb
+DB_USER=root
+DB_PASSWORD=
 DB_CHARSET=utf8mb4
 
 # Service ports
diff --git a/BookingMicroservice/Controllers/BookingController.cs b/BookingMicroservice/Controllers/BookingController.cs
index 2f43c11..de800b6 100644
--- a/BookingMicroservice/Controllers/BookingController.cs
+++ b/BookingMicroservice/Controllers/BookingController.cs
@@ -1,6 +1,7 @@
 using BookingMicroservice.Exceptions;
 using BookingMicroservice.Models;
 using BookingMicroservice.Services;
+using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Mvc;
 using System.Security.Claims;
 
@@ -19,6 +20,7 @@ namespace BookingMicroservice.Controllers
             this.bookingService = bookingService;
         }
 
+        [Authorize]
         [HttpGet()]
         public IActionResult GetBookings(int? flightId = null, int? userId = null, BookingClass? bookingClass = null) 
         {
@@ -29,6 +31,7 @@ namespace BookingMicroservice.Controllers
             return Ok(bookings);
         }
 
+        [Authorize]
         [HttpGet("{id}")]
         public IActionResult GetBooking([FromRoute] int id) 
         {
@@ -39,6 +42,7 @@ namespace BookingMicroservice.Controllers
             return Ok(booking);
         }
 
+        [Authorize]
         [HttpPost()]
         public async Task<IActionResult> MakeBooking([FromBody] BookingCreation bookingCreationModel)
         {
@@ -47,20 +51,32 @@ namespace BookingMicroservice.Controllers
             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");
+            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);
+                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();
+                return Ok(new { BookedSeat = true, Message = "Seat booked successfully." });
             } 
             catch (InvalidOperationException exception)
             {
@@ -68,7 +84,7 @@ namespace BookingMicroservice.Controllers
             } 
             catch (BookingException exception)
             {
-                return BadRequest(exception.Message);
+                return Ok(new { BookedSeat = false, Message = exception.Message });
             }
         }
     }
diff --git a/BookingMicroservice/Handlers/RequestCookieHandler.cs b/BookingMicroservice/Handlers/RequestCookieHandler.cs
new file mode 100644
index 0000000..7ac6698
--- /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/Program.cs b/BookingMicroservice/Program.cs
index c10b8dc..74cabbd 100644
--- a/BookingMicroservice/Program.cs
+++ b/BookingMicroservice/Program.cs
@@ -9,6 +9,8 @@ using System.Text;
 
 var builder = WebApplication.CreateBuilder(args);
 
+builder.Services.AddHttpContextAccessor();
+builder.Services.AddTransient<RequestCookieHandler>();
 builder.Services.AddControllers();
 builder.Services.AddEndpointsApiExplorer();
 builder.Services.AddSwaggerGen();
@@ -29,14 +31,31 @@ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
             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"] ?? throw new InvalidOperationException("FlightMicroservice BaseUrl is not configured.");
+    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>();
@@ -64,7 +83,8 @@ app.Use(async (context, next) =>
     await next();
 });
 
-
+app.UseAuthentication();
 app.UseAuthorization();
+
 app.MapControllers();
 app.Run();
diff --git a/BookingMicroservice/Services/BookingService.cs b/BookingMicroservice/Services/BookingService.cs
index d5efd29..4c7a4fd 100644
--- a/BookingMicroservice/Services/BookingService.cs
+++ b/BookingMicroservice/Services/BookingService.cs
@@ -30,9 +30,9 @@ namespace BookingMicroservice.Services
             return bookings;
         }
 
-        public Booking CreateBooking(int flightId, int userId, BookingClass bookingClass, int? seatId)
+        public Booking CreateBooking(int flightId, int userId, BookingClass bookingClass)
         {
-            Booking booking = new Booking(flightId, userId, bookingClass, seatId);
+            Booking booking = new Booking(flightId, userId, bookingClass, null);
 
             dbContext.Bookings.Add(booking);
             dbContext.SaveChanges();
diff --git a/BookingMicroservice/Services/IBookingService.cs b/BookingMicroservice/Services/IBookingService.cs
index d679c2e..b5bd327 100644
--- a/BookingMicroservice/Services/IBookingService.cs
+++ b/BookingMicroservice/Services/IBookingService.cs
@@ -4,7 +4,7 @@ namespace BookingMicroservice.Services
 {
     public interface IBookingService
     {
-        Booking CreateBooking(int flightId, int userId, BookingClass bookingClass, int? seatId);
+        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);
diff --git a/BookingMicroservice/Services/IReservationComplianceService.cs b/BookingMicroservice/Services/IReservationComplianceService.cs
index 98364a1..7f82720 100644
--- a/BookingMicroservice/Services/IReservationComplianceService.cs
+++ b/BookingMicroservice/Services/IReservationComplianceService.cs
@@ -4,7 +4,7 @@ namespace BookingMicroservice.Services
 {
     public interface IReservationComplianceService
     {
-        Task<Booking> TryCreateBookingAsync(int flightId, int userId, BookingClass bookingClass, int? seatId);
+        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
index 6aa0296..df3f6a6 100644
--- a/BookingMicroservice/Services/ReservationComplianceService.cs
+++ b/BookingMicroservice/Services/ReservationComplianceService.cs
@@ -10,7 +10,7 @@ namespace BookingMicroservice.Services
         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";
+        private const string BOOKED_SEAT_MSG = "The selected seat is already booked. Please choose a different seat.";
 
         public ReservationComplianceService(IBookingService bookingService, IFlightServiceClient flightServiceClient)
         {
@@ -18,7 +18,7 @@ namespace BookingMicroservice.Services
             this.flightServiceClient = flightServiceClient;
         }
 
-        public async Task<Booking> TryCreateBookingAsync(int flightId, int userId, BookingClass bookingClass, int? seatId)
+        public async Task<Booking?> TryCreateBookingAsync(int flightId, int userId, BookingClass bookingClass, int? seatId)
         {
             HttpResponseMessage capacityResponse = await flightServiceClient.GetFlightCapacityAsync(flightId, (int)bookingClass);
             if (!capacityResponse.IsSuccessStatusCode)
@@ -29,13 +29,28 @@ namespace BookingMicroservice.Services
                 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);
+            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)
@@ -52,10 +67,11 @@ namespace BookingMicroservice.Services
                 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.");
+            if (!seatBookingRespone.IsSuccessStatusCode) {
+                string? failureMessage = seatBookingRespone.ReasonPhrase;
+                throw new InvalidOperationException($"Error booking seat; {failureMessage}");
+            }
 
             bookingService.UpdateBooking(bookingId, seatId);
         }
diff --git a/FlightMicroservice/Controllers/FlightController.cs b/FlightMicroservice/Controllers/FlightController.cs
index ea3411b..649e104 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 47491f7..758bfff 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/docker-compose.yml b/docker-compose.yml
index 109ae71..ac1f7f9 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -47,16 +47,16 @@ services:
     ports:
       - "${CLIENT_PORT}:4200"
 
-  db:
-    image: mysql:${MYSQL_IMAGE_TAG}
-    command: --default-authentication-plugin=mysql_native_password
-    restart: always
-    environment:
-      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
-      MYSQL_DATABASE: ${DB_NAME}
-      MYSQL_USER: ${DB_USER}
-      MYSQL_PASSWORD: ${DB_PASSWORD}
-    volumes:
-      - ./Database:/docker-entrypoint-initdb.d
-    ports:
-      - "${DB_PORT}:3306"
+  # db:
+  #   image: mysql:${MYSQL_IMAGE_TAG}
+  #   command: --default-authentication-plugin=mysql_native_password
+  #   restart: always
+  #   environment:
+  #     MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
+  #     MYSQL_DATABASE: ${DB_NAME}
+  #     MYSQL_USER: ${DB_USER}
+  #     MYSQL_PASSWORD: ${DB_PASSWORD}
+  #   volumes:
+  #     - ./Database:/docker-entrypoint-initdb.d
+  #   ports:
+  #     - "${DB_PORT}:3306"
-- 
GitLab