From c7dbb436f5c725e9af55775489b18af1f9abb80b Mon Sep 17 00:00:00 2001 From: Mouaz Abdelsamad <ma03081@surrey.ac.uk> Date: Tue, 2 Apr 2024 00:56:50 +0100 Subject: [PATCH] Dockerize Gateway and improvments --- .env | 15 ++++--- Database/InitTables.sql | 34 ++++++++++++++ .../Clients/UserService/IUserServiceClient.cs | 1 + .../Clients/UserService/UserServiceClient.cs | 5 +++ GatewayAPI/Controllers/UserController.cs | 8 ++++ GatewayAPI/Models/UserUpdateInfo.cs | 9 ++++ GatewayAPI/Program.cs | 2 +- GatewayAPI/ServiceCollectionExtensions.cs | 8 ++-- GatewayAPI/appsettings.json | 4 +- .../Controllers/UserController.cs | 44 ++++++++++++++----- UserMicroservice/Models/UpdateUserModel.cs | 9 ++++ UserMicroservice/Services/IUserService.cs | 2 +- UserMicroservice/Services/UserService.cs | 27 +++++++++--- docker-compose.yml | 41 ++++++++++++----- 14 files changed, 169 insertions(+), 40 deletions(-) create mode 100644 Database/InitTables.sql create mode 100644 GatewayAPI/Models/UserUpdateInfo.cs create mode 100644 UserMicroservice/Models/UpdateUserModel.cs diff --git a/.env b/.env index 09923e3..673e590 100644 --- a/.env +++ b/.env @@ -1,12 +1,17 @@ # Image settings IMAGE_TAG=0.0.1 +MYSQL_IMAGE_TAG=8.0 + # Database configuration -DB_PORT=3308 -DB_NAME=AspNetCoreDb -DB_USER=root -DB_PASSWORD= +DB_PORT=3307 +DB_NAME=myDB +DB_USER=user +DB_PASSWORD=user DB_CHARSET=utf8mb4 # Service ports -USER_MICROSERVICE_PORT=5089 # This port will be used as the JWT issuer +USER_MICROSERVICE_PORT=5089 +FLIGHT_MICROSERVICE_PORT=5175 +GATEWAY_API_PORT=5267 +CLIENT_PORT=4200 \ No newline at end of file diff --git a/Database/InitTables.sql b/Database/InitTables.sql new file mode 100644 index 0000000..6805d23 --- /dev/null +++ b/Database/InitTables.sql @@ -0,0 +1,34 @@ +CREATE TABLE Users ( + Id INT AUTO_INCREMENT PRIMARY KEY, + Username LONGTEXT NOT NULL, + Email LONGTEXT NOT NULL, + PasswordHash LONGTEXT NOT NULL, + Type INT NOT NULL +); + +CREATE TABLE RefreshTokens ( + Id INT AUTO_INCREMENT PRIMARY KEY, + UserId INT NOT NULL, + Token LONGTEXT NOT NULL, + ExpirationDate DATETIME(6) NOT NULL +); + +CREATE TABLE Flights ( + Id INT AUTO_INCREMENT PRIMARY KEY, + Origin LONGTEXT NOT NULL, + Destination LONGTEXT NOT NULL, + DepartureTime DATETIME(6) NOT NULL, + ArrivalTime DATETIME(6) NOT NULL, + EconomyCapacity INT NOT NULL, + BusinessCapacity INT NOT NULL, + EconomyPrice DECIMAL(65, 30) NOT NULL, + BusinessPrice DECIMAL(65, 30) NOT NULL +); + +CREATE TABLE Seats ( + Id INT AUTO_INCREMENT PRIMARY KEY, + FlightId INT NOT NULL, + SeatNumber LONGTEXT NOT NULL, + ClassType INT NOT NULL, + IsAvailable TINYINT(1) NOT NULL +); diff --git a/GatewayAPI/Clients/UserService/IUserServiceClient.cs b/GatewayAPI/Clients/UserService/IUserServiceClient.cs index b37273f..dfcaeb7 100644 --- a/GatewayAPI/Clients/UserService/IUserServiceClient.cs +++ b/GatewayAPI/Clients/UserService/IUserServiceClient.cs @@ -10,5 +10,6 @@ namespace GatewayAPI.Clients.UserService Task<HttpResponseMessage> LoginUserAsync(UserLogin user); Task<HttpResponseMessage> AuthorizeUserAsync(); Task<HttpResponseMessage> LogoutUserAsync(); + Task<HttpResponseMessage> UpdateUserAsync(int id, UserUpdateInfo updateInfo); } } diff --git a/GatewayAPI/Clients/UserService/UserServiceClient.cs b/GatewayAPI/Clients/UserService/UserServiceClient.cs index e6533bd..46752a4 100644 --- a/GatewayAPI/Clients/UserService/UserServiceClient.cs +++ b/GatewayAPI/Clients/UserService/UserServiceClient.cs @@ -45,5 +45,10 @@ namespace GatewayAPI.Clients.UserService return await httpClient.PostAsync($"{API_PATH}/logout", null); } + public async Task<HttpResponseMessage> UpdateUserAsync(int id, UserUpdateInfo updateInfo) + { + return await httpClient.PatchAsJsonAsync($"{API_PATH}/{id}", updateInfo); + } + } } diff --git a/GatewayAPI/Controllers/UserController.cs b/GatewayAPI/Controllers/UserController.cs index 2971951..2061f69 100644 --- a/GatewayAPI/Controllers/UserController.cs +++ b/GatewayAPI/Controllers/UserController.cs @@ -56,5 +56,13 @@ namespace GatewayAPI.Controllers HttpResponseMessage response = await userServiceClient.GetUserAsync(id); return new HttpResponseMessageResult(response); } + + [HttpPatch("{id}")] + public async Task<IActionResult> UpdateUser(int id, [FromBody] UserUpdateInfo updateInfo) + { + HttpResponseMessage response = await userServiceClient.UpdateUserAsync(id, updateInfo); + return new HttpResponseMessageResult(response); + } } + } diff --git a/GatewayAPI/Models/UserUpdateInfo.cs b/GatewayAPI/Models/UserUpdateInfo.cs new file mode 100644 index 0000000..bf4c9c3 --- /dev/null +++ b/GatewayAPI/Models/UserUpdateInfo.cs @@ -0,0 +1,9 @@ +namespace GatewayAPI.Models +{ + public class UserUpdateInfo + { + public string? Username { get; set; } + public string? Email { get; set; } + public string? Password { get; set; } + } +} diff --git a/GatewayAPI/Program.cs b/GatewayAPI/Program.cs index 6f900f8..8f7722b 100644 --- a/GatewayAPI/Program.cs +++ b/GatewayAPI/Program.cs @@ -21,7 +21,7 @@ if (app.Environment.IsDevelopment()) app.UseSwaggerUI(); } -app.UseHttpsRedirection(); +//app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run(); diff --git a/GatewayAPI/ServiceCollectionExtensions.cs b/GatewayAPI/ServiceCollectionExtensions.cs index b928371..7235c81 100644 --- a/GatewayAPI/ServiceCollectionExtensions.cs +++ b/GatewayAPI/ServiceCollectionExtensions.cs @@ -10,14 +10,14 @@ namespace GatewayAPI { services.AddHttpClient<IUserServiceClient, UserServiceClient>(client => { - string? baseUrl = configurationManager["UserMicroservice:BaseUrl"]; - client.BaseAddress = new Uri(baseUrl ?? throw new InvalidOperationException("UserMicroservice BaseUrl is not configured.")); + string baseUrl = configurationManager["UserMicroservice:BaseUrl"] ?? throw new InvalidOperationException("UserMicroservice BaseUrl is not configured."); + client.BaseAddress = new Uri(baseUrl.EndsWith("/") ? baseUrl : baseUrl + "/"); }).AddHttpMessageHandler<RequestCookieHandler>(); services.AddHttpClient<IFlightServiceClient, FlightServiceClient>(client => { - string? baseUrl = configurationManager["FlightMicroservice:BaseUrl"]; - client.BaseAddress = new Uri(baseUrl ?? throw new InvalidOperationException("FlightMicroservice BaseUrl is not configured.")); + string baseUrl = configurationManager["FlightMicroservice:BaseUrl"] ?? throw new InvalidOperationException("FlightMicroservice 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 0345e2e..f0c8836 100644 --- a/GatewayAPI/appsettings.json +++ b/GatewayAPI/appsettings.json @@ -7,9 +7,9 @@ }, "AllowedHosts": "*", "UserMicroservice": { - "BaseUrl": "http://localhost:5089/" + "BaseUrl": "http://localhost:5089" }, "FlightMicroservice": { - "BaseUrl": "http://localhost:5175/" + "BaseUrl": "http://localhost:5175" } } diff --git a/UserMicroservice/Controllers/UserController.cs b/UserMicroservice/Controllers/UserController.cs index ea86b63..b14e108 100644 --- a/UserMicroservice/Controllers/UserController.cs +++ b/UserMicroservice/Controllers/UserController.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; using System.Security.Claims; using UserMicroservice.Models; using UserMicroservice.Services; @@ -31,9 +32,10 @@ namespace UserMicroservice.Controllers if (user == null) return BadRequest(); - return authenticateUser(user.Id); - - } catch (InvalidOperationException ex) + setAuthCookies(user.Id); + return Ok(new { user.Id, user.Username, user.Email, user.Type }); + } + catch (InvalidOperationException ex) { return BadRequest(ex.Message); } @@ -57,7 +59,8 @@ namespace UserMicroservice.Controllers if (!int.TryParse(userIdString, out int userId)) return BadRequest("User ID is invalid."); - return authenticateUser(userId); + setAuthCookies(userId); + return Ok(); } // POST: api/User/login @@ -68,14 +71,15 @@ namespace UserMicroservice.Controllers if(user == null) return Unauthorized(); - return authenticateUser(user.Id); + setAuthCookies(user.Id); + return Ok(new { user.Id, user.Username, user.Email, user.Type }); } - private IActionResult authenticateUser(int userId) + private void setAuthCookies(int userId) { AuthTokenPair authToken = _authService.AuthenticateUser(userId); if (authToken == null) - return BadRequest(); + throw new ArgumentNullException(nameof(authToken)); // Set the access token as an HttpOnly cookie Response.Cookies.Append("AccessToken", authToken.AccessToken, new CookieOptions @@ -94,8 +98,6 @@ namespace UserMicroservice.Controllers SameSite = SameSiteMode.Strict, Expires = DateTimeOffset.UtcNow.AddDays(2) }); - - return Ok(); } // POST: api/User/logout @@ -153,8 +155,28 @@ namespace UserMicroservice.Controllers User? user = _userService.GetUser(id); if(user == null) return NotFound($"User with {id} doesnt exist"); - - return Ok(user); + + return Ok(new{ user.Id, user.Username, user.Email, user.Type }); + } + + // PUT: api/User/{id} + [Authorize] + [HttpPatch("{id}")] + public IActionResult UpdateUser(int id, [FromBody] UpdateUserModel model) + { + try + { + _userService.UpdateUser(id, model.Username, model.Email, model.Password); + return Ok(); + } + catch (KeyNotFoundException exception) + { + return NotFound(exception.Message); + } + catch (DbUpdateException exception) + { + return BadRequest(exception.Message); + } } } diff --git a/UserMicroservice/Models/UpdateUserModel.cs b/UserMicroservice/Models/UpdateUserModel.cs new file mode 100644 index 0000000..bda556a --- /dev/null +++ b/UserMicroservice/Models/UpdateUserModel.cs @@ -0,0 +1,9 @@ +namespace UserMicroservice.Models +{ + public class UpdateUserModel + { + public string? Username { get; set; } + public string? Email { get; set; } + public string? Password { get; set; } + } +} diff --git a/UserMicroservice/Services/IUserService.cs b/UserMicroservice/Services/IUserService.cs index 2a4e75b..0e6deff 100644 --- a/UserMicroservice/Services/IUserService.cs +++ b/UserMicroservice/Services/IUserService.cs @@ -10,7 +10,7 @@ namespace UserMicroservice.Services User? GetUser(string username, string password); List<User> GetUsers(); User CreateUser(string email, string userName, string password, UserType UserType); - User UpdateUser(User updatedUser); + void UpdateUser(int id, string? username, string? email, string? password); bool DeleteUser(string username); User? GetUserByEmail(string email); } diff --git a/UserMicroservice/Services/UserService.cs b/UserMicroservice/Services/UserService.cs index 9288ae8..59c0f37 100644 --- a/UserMicroservice/Services/UserService.cs +++ b/UserMicroservice/Services/UserService.cs @@ -80,17 +80,32 @@ namespace UserMicroservice.Services return user; } - public User UpdateUser(User updatedUser) + public void UpdateUser(int id, string? username, string? email, string? password) { - _context.Users - .Where(user => user.Id == updatedUser.Id) + User? user = GetUser(id); + if(user == null) + throw new KeyNotFoundException($"A User with the provided Id {id} doesnt exist"); + + string updatedEmail = string.IsNullOrEmpty(email) ? user.Email : email; + string updatedUserName = string.IsNullOrEmpty(username) ? user.Username : username; + string updatedPassword = user.PasswordHash; + + if (!string.IsNullOrEmpty(password)) { + user.SetPasswordHash(_passwordHasher, password); + updatedPassword = user.PasswordHash; + } + + int affectedRows = _context.Users + .Where(user => user.Id == id) .ExecuteUpdate(setters => setters - .SetProperty(user => user.Username, updatedUser.Username) - .SetProperty(user => user.PasswordHash, updatedUser.PasswordHash)); + .SetProperty(user => user.Username, updatedUserName) + .SetProperty(user => user.Email, updatedEmail) + .SetProperty(user => user.PasswordHash, updatedPassword)); _context.SaveChanges(); - return updatedUser; + if (affectedRows == 0) + throw new DbUpdateException("Operation was not able to update the Database"); } } } diff --git a/docker-compose.yml b/docker-compose.yml index 83dca10..109ae71 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,4 @@ version: '3.8' - services: usermicroservice: build: @@ -12,30 +11,52 @@ services: - 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://localhost:${USER_MICROSERVICE_PORT} - - Jwt__Audience=http://localhost:${USER_MICROSERVICE_PORT} + - Jwt__Issuer=http://usermicroservice:8080 + - Jwt__Audience=http://usermicroservice:8080 flightmicroservice: build: context: ./FlightMicroservice image: flightmicroservice:${IMAGE_TAG} ports: - - "5175:8080" + - "${FLIGHT_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://localhost:${USER_MICROSERVICE_PORT} - - Jwt__Audience=http://localhost:${USER_MICROSERVICE_PORT} + - Jwt__Issuer=http://usermicroservice:8080 + - Jwt__Audience=http://usermicroservice:8080 + + gatewayapi: + build: + context: ./GatewayAPI + image: gatewayapi:${IMAGE_TAG} + ports: + - "${GATEWAY_API_PORT}:8080" + environment: + - ASPNETCORE_ENVIRONMENT=Production + - DOTNET_RUNNING_IN_CONTAINER=true + - UserMicroservice__BaseUrl=http://usermicroservice:8080 + - FlightMicroservice__BaseUrl=http://flightmicroservice:8080 client: build: context: ./client image: client:${IMAGE_TAG} ports: - - "4200:4200" - - + - "${CLIENT_PORT}:4200" -# ... other services + 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