From 2dc1ba6f043e55ef85985f2b55338402e77a3483 Mon Sep 17 00:00:00 2001 From: adisve Date: Wed, 4 Sep 2024 19:44:06 +0200 Subject: [PATCH 01/10] Create test user class --- TumbleBackend/Controllers/AdminController.cs | 7 ++---- TumbleBackend/StringConstants/EnvVar.cs | 4 +-- TumbleBackend/StringConstants/UserSecrets.cs | 4 +++ TumbleBackend/Utilities/TestUserUtil.cs | 26 ++++++++++++++++++++ TumbleBackend/Utilities/TranslatorUtil.cs | 7 +----- 5 files changed, 35 insertions(+), 13 deletions(-) create mode 100644 TumbleBackend/Utilities/TestUserUtil.cs diff --git a/TumbleBackend/Controllers/AdminController.cs b/TumbleBackend/Controllers/AdminController.cs index 0e162e9..76d324d 100644 --- a/TumbleBackend/Controllers/AdminController.cs +++ b/TumbleBackend/Controllers/AdminController.cs @@ -22,11 +22,8 @@ public class AdminController : Controller [HttpPut("notification")] public async Task SendMessage([FromServices] MobileMessagingClient messaging, [FromServices] IConfiguration configuration, [FromServices] IDbNewsService newsService, [FromHeader] string auth, [FromQuery] NotificationContent notificationContent) { - string? adminPassword = configuration[UserSecrets.AdminPass] ?? Environment.GetEnvironmentVariable(EnvVar.AdminPass); - - if (adminPassword == null) - throw new NullReferenceException("Ensure that AdminPass is defined in the environment."); - + string? adminPassword = (configuration[UserSecrets.AdminPass] ?? Environment.GetEnvironmentVariable(EnvVar.AdminPass)) ?? throw new NullReferenceException("Ensure that AdminPass is defined in the environment."); + if (auth != adminPassword) { return Unauthorized(); diff --git a/TumbleBackend/StringConstants/EnvVar.cs b/TumbleBackend/StringConstants/EnvVar.cs index fcc97d3..bbd1414 100644 --- a/TumbleBackend/StringConstants/EnvVar.cs +++ b/TumbleBackend/StringConstants/EnvVar.cs @@ -9,6 +9,6 @@ public static class EnvVar public static string AwsAccessKey => "AwsAccessKey"; public static string AwsSecretKey => "AwsSecretKey"; public static string AdminPass => "AdminPass"; - - public static string LokiUri => "LokiUri"; + public static string TestUserPass => "TestUserPass"; + public static string TestUserEmail => "TestUserEmail"; } diff --git a/TumbleBackend/StringConstants/UserSecrets.cs b/TumbleBackend/StringConstants/UserSecrets.cs index dd7a8ef..132aaae 100644 --- a/TumbleBackend/StringConstants/UserSecrets.cs +++ b/TumbleBackend/StringConstants/UserSecrets.cs @@ -9,4 +9,8 @@ public static class UserSecrets public static string AwsAccessKey => "Aws:AccessKey"; public static string AwsSecretKey => "Aws:SecretKey"; public static string AdminPass => "Admin:Pass"; + + public static string TestUserPass => "TestUser:Pass"; + + public static string TestUserEmail => "TestUser:Email"; } diff --git a/TumbleBackend/Utilities/TestUserUtil.cs b/TumbleBackend/Utilities/TestUserUtil.cs new file mode 100644 index 0000000..4046497 --- /dev/null +++ b/TumbleBackend/Utilities/TestUserUtil.cs @@ -0,0 +1,26 @@ +using TumbleBackend.StringConstants; +using TumbleBackend.Utilities; + +namespace TumbleBackend.Utilities; + +public class TestUserUtil { + private readonly IConfiguration _configuration; + private readonly string _testUserPass; + private readonly string _testUserEmail; + + public TestUserUtil(IConfiguration configuration) { + _configuration = configuration; + _testUserPass = _configuration[UserSecrets.TestUserPass] ?? Environment.GetEnvironmentVariable(EnvVar.TestUserPass) ?? throw new NullReferenceException("Ensure that TestUserPass is defined in the environment."); + } + + public bool IsTestUser(string username, string password) { + string? testUserPass = _configuration[UserSecrets.TestUserPass] ?? Environment.GetEnvironmentVariable(EnvVar.TestUserPass); + string? testUserEmail = _configuration[UserSecrets.TestUserEmail] ?? Environment.GetEnvironmentVariable(EnvVar.TestUserEmail); + + if (testUserPass == null || testUserEmail == null) + throw new NullReferenceException("It should not be possible for testUserPass OR testUserEmail to be null at this point."); + + return username == testUserEmail && password == testUserPass; + } + +} \ No newline at end of file diff --git a/TumbleBackend/Utilities/TranslatorUtil.cs b/TumbleBackend/Utilities/TranslatorUtil.cs index 9a092c2..3fa245c 100644 --- a/TumbleBackend/Utilities/TranslatorUtil.cs +++ b/TumbleBackend/Utilities/TranslatorUtil.cs @@ -1,9 +1,4 @@ -using System; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; -using WebAPIModels; -using TumbleBackend.Exceptions; +using System.Text; using Newtonsoft.Json.Linq; using Newtonsoft.Json; From 8bf3e04f6c06952331962cdd5114f755fe5cb072 Mon Sep 17 00:00:00 2001 From: adisve Date: Wed, 4 Sep 2024 20:38:17 +0200 Subject: [PATCH 02/10] Edit UserController to conform to test user. Clean up UserEventController --- TumbleBackend/Controllers/UserController.cs | 55 +++++- .../Controllers/UserEventController.cs | 174 +++++++----------- TumbleBackend/Program.cs | 5 +- TumbleBackend/StringConstants/EnvVar.cs | 1 + TumbleBackend/StringConstants/UserSecrets.cs | 3 +- TumbleBackend/Utilities/JwtUtil.cs | 154 +++++++++------- TumbleBackend/Utilities/TestUserUtil.cs | 19 +- 7 files changed, 217 insertions(+), 194 deletions(-) diff --git a/TumbleBackend/Controllers/UserController.cs b/TumbleBackend/Controllers/UserController.cs index 3f1b0de..e7c01be 100644 --- a/TumbleBackend/Controllers/UserController.cs +++ b/TumbleBackend/Controllers/UserController.cs @@ -3,11 +3,9 @@ using TumbleBackend.Extensions; using WebAPIModels.ResponseModels; using WebAPIModels.RequestModels; -using KronoxAPI.Model.Users; using KronoxAPI.Exceptions; using TumbleBackend.Utilities; using WebAPIModels.Extensions; -using TumbleBackend.InternalModels; using TumbleBackend.StringConstants; using TumbleBackend.ActionFilters; using Microsoft.AspNetCore.Cors; @@ -29,17 +27,36 @@ public UserController(ILogger logger) _logger = logger; } + /// + /// Retrieves a KronoxUser object for the user with the provided refreshToken. If the user is a test user, a test user object is returned with mock data upon further requests. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// [HttpGet] - public async Task GetKronoxUserAsync([FromServices] JwtUtil jwtUtil, [FromQuery] SchoolEnum schoolId, [FromHeader(Name = "X-auth-token")] string refreshToken) + public async Task GetKronoxUserAsync([FromServices] JwtUtil jwtUtil, [FromServices] TestUserUtil testUserUtil, [FromQuery] SchoolEnum schoolId, [FromHeader(Name = "X-auth-token")] string refreshToken) { var kronoxReqClient = (IKronoxRequestClient)HttpContext.Items[KronoxReqClientKeys.SingleClient]!; - var school = schoolId.GetSchool()!; - var creds = jwtUtil.ValidateAndReadRefreshToken(refreshToken); if (creds == null) return Unauthorized(new Error("Couldn't login user from refreshToken, please log out and back in manually.")); - + + // If the user is a test user, return the test user object. + // This is useful for testing the frontend without having to log in, as we will send mock data. + if (testUserUtil.IsTestUser(creds.Username, creds.Password)) + return Ok(testUserUtil.GetTestUser().ToWebModel(refreshToken, "", new SessionDetails("", ""))); + + // If the user is not a test user, attempt to auto-login the user and return the KronoxUser object. try { var kronoxUser = await School.LoginAsync(kronoxReqClient, creds.Username, creds.Password); @@ -63,11 +80,31 @@ public async Task GetKronoxUserAsync([FromServices] JwtUtil jwtUt } } + /// + /// Logs in a user with the provided username and password. If the user is a test user, a test user object is returned with mock data upon further requests. + /// + /// + /// + /// + /// + /// [HttpPost("login")] - public async Task LoginKronoxUserAsync([FromServices] JwtUtil jwtUtil, [FromQuery] SchoolEnum schoolId, [FromBody] LoginRequest body) + public async Task LoginKronoxUserAsync([FromServices] JwtUtil jwtUtil, [FromServices] TestUserUtil testUserUtil, [FromQuery] SchoolEnum schoolId, [FromBody] LoginRequest body) { var kronoxReqClient = (IKronoxRequestClient)HttpContext.Items[KronoxReqClientKeys.SingleClient]!; - var school = schoolId.GetSchool()!; + + // Determine if the user is a test user and return the test user object if they are. + if (testUserUtil.IsTestUser(body.Username, body.Password)) + { + var testUser = testUserUtil.GetTestUser(); + var newRefreshToken = testUserUtil.GetTestUserSessionToken(); + SessionDetails sessionDetails = new("", ""); + + Response.Headers.Add("X-auth-token", newRefreshToken); + Response.Headers.Add("X-session-token", sessionDetails.ToJson()); + + return Ok(testUser.ToWebModel(newRefreshToken, "", sessionDetails)); + } try { @@ -77,11 +114,11 @@ public async Task LoginKronoxUserAsync([FromServices] JwtUtil jwt return StatusCode(StatusCodes.Status500InternalServerError, new Error("There was an unknown error while fetching user data from Kronox.")); var newRefreshToken = jwtUtil.GenerateRefreshToken(body.Username, body.Password); - SessionDetails sessionDetails = new(kronoxUser.SessionToken, kronoxReqClient.BaseUrl!.ToString()); Response.Headers.Add("X-auth-token", newRefreshToken); Response.Headers.Add("X-session-token", sessionDetails.ToJson()); + return Ok(kronoxUser.ToWebModel(newRefreshToken, "", sessionDetails)); } catch (LoginException e) diff --git a/TumbleBackend/Controllers/UserEventController.cs b/TumbleBackend/Controllers/UserEventController.cs index ae9e5fd..bb678b8 100644 --- a/TumbleBackend/Controllers/UserEventController.cs +++ b/TumbleBackend/Controllers/UserEventController.cs @@ -26,49 +26,49 @@ public UserEventController(ILogger logger) _logger = logger; } - [HttpGet] - public async Task GetAllUserEvents([FromQuery] SchoolEnum schoolId) + private IKronoxRequestClient GetAuthenticatedClient() { - IKronoxRequestClient kronoxReqClient = (IKronoxRequestClient)HttpContext.Items[KronoxReqClientKeys.SingleClient]!; - School school = schoolId.GetSchool()!; + if (HttpContext.Items[KronoxReqClientKeys.SingleClient] is not IKronoxRequestClient client || !client.IsAuthenticated) + { + throw new UnauthorizedAccessException("Requires provided auth token"); + } + return client; + } - if (!kronoxReqClient.IsAuthenticated) - return BadRequest(new Error("Requires provided auth token")); + private IActionResult HandleError(Exception ex, string message, int statusCode = StatusCodes.Status500InternalServerError) + { + _logger.LogError(ex.ToString()); + return StatusCode(statusCode, new Error(message)); + } + [HttpGet] + public async Task GetAllUserEvents([FromQuery] SchoolEnum schoolId) + { try { - Dictionary>? userEvents = await School.GetUserEventsAsync(kronoxReqClient); - UserEventCollection? webSafeUserEvents = userEvents?.ToWebModel(); + var kronoxReqClient = GetAuthenticatedClient(); + var userEvents = await School.GetUserEventsAsync(kronoxReqClient); + var webSafeUserEvents = userEvents?.ToWebModel(); + if (webSafeUserEvents == null) return StatusCode(StatusCodes.Status500InternalServerError, new Error("We're having trouble getting your data from Kronox, please try again later.")); return Ok(webSafeUserEvents); } - catch (ParseException e) - { - _logger.LogError(e.ToString()); - return StatusCode(StatusCodes.Status500InternalServerError, new Error("We're having trouble getting your data from Kronox, please try again later.")); - } - catch (LoginException e) + catch (Exception ex) when (ex is ParseException or LoginException or HttpRequestException) { - _logger.LogError(e.ToString()); - return Unauthorized(new Error("There was an issue with your login information, please log back in or try again later.")); + return HandleError(ex, "We're having trouble getting your data from Kronox, please try again later."); } } [HttpPut("register/all")] public async Task RegisterAllAvailableResults([FromQuery] SchoolEnum schoolId) { - IKronoxRequestClient kronoxReqClient = (IKronoxRequestClient)HttpContext.Items[KronoxReqClientKeys.SingleClient]!; - School school = schoolId.GetSchool()!; - - if (!kronoxReqClient.IsAuthenticated) - return BadRequest(new Error("Requires provided auth token")); - try { - Dictionary>? userEvents = await School.GetUserEventsAsync(kronoxReqClient); - UserEventCollection? webSafeUserEvents = userEvents?.ToWebModel(); + var kronoxReqClient = GetAuthenticatedClient(); + var userEvents = await School.GetUserEventsAsync(kronoxReqClient); + var webSafeUserEvents = userEvents?.ToWebModel(); if (webSafeUserEvents == null) return StatusCode(StatusCodes.Status500InternalServerError, new Error("We're having trouble getting your data from Kronox, please try again later.")); @@ -76,125 +76,85 @@ public async Task RegisterAllAvailableResults([FromQuery] SchoolE if (!webSafeUserEvents.UnregisteredEvents.Any()) return NotFound(new Error("No unregistered events.")); - List failedRegistrations = new(); - List successfulRegistrations = new(); - foreach (AvailableUserEvent availableUserEvent in webSafeUserEvents.UnregisteredEvents) - { - if (!await availableUserEvent.Register(kronoxReqClient)) - failedRegistrations.Add(availableUserEvent); - else - successfulRegistrations.Add(availableUserEvent); - } + var (successfulRegistrations, failedRegistrations) = await RegisterEventsAsync(kronoxReqClient, webSafeUserEvents.UnregisteredEvents); return Ok(new MultiRegistrationResult(successfulRegistrations, failedRegistrations)); } - catch (ParseException e) + catch (Exception ex) when (ex is ParseException or LoginException or HttpRequestException) { - _logger.LogError(e.ToString()); - return StatusCode(StatusCodes.Status500InternalServerError, new Error("We're having trouble getting your data from Kronox, please try again later.")); - } - catch (LoginException e) - { - _logger.LogError(e.ToString()); - return Unauthorized(new Error("There was an issue with your login information, please log back in or try again later.")); - } - catch (HttpRequestException e) - { - _logger.LogError(e.ToString()); - return StatusCode(StatusCodes.Status500InternalServerError, new Error("We're having trouble getting your data from Kronox, please try again later.")); + return HandleError(ex, "We're having trouble getting your data from Kronox, please try again later."); } } [HttpPut("register/{eventId}")] public async Task RegisterUserEvent([FromRoute] string eventId, [FromQuery] SchoolEnum schoolId) { - IKronoxRequestClient kronoxReqClient = (IKronoxRequestClient)HttpContext.Items[KronoxReqClientKeys.SingleClient]!; - School school = schoolId.GetSchool()!; - - if (!kronoxReqClient.IsAuthenticated) - return BadRequest(new Error("Requires provided auth token")); - try { - Dictionary>? userEvents = await School.GetUserEventsAsync(kronoxReqClient); - UserEventCollection? webSafeUserEvents = userEvents?.ToWebModel(); + var kronoxReqClient = GetAuthenticatedClient(); + var userEvents = await School.GetUserEventsAsync(kronoxReqClient); + var webSafeUserEvents = userEvents?.ToWebModel(); + if (webSafeUserEvents == null) return StatusCode(StatusCodes.Status500InternalServerError, new Error("We're having trouble getting your data from Kronox, please try again later.")); - foreach (AvailableUserEvent userEvent in webSafeUserEvents.UnregisteredEvents) - { - if (userEvent.Id == eventId) - { - if (await userEvent.Register(kronoxReqClient)) - return Ok(); + var eventToRegister = webSafeUserEvents.UnregisteredEvents.FirstOrDefault(e => e.Id == eventId); - return StatusCode(StatusCodes.Status500InternalServerError, new Error("There was an error signing up to the event, please try again later.")); - } - } + if (eventToRegister == null) + return NotFound(new Error("The event specified couldn't be found in Kronox's system, please log out and back in or try again later.")); - return NotFound(new Error("The event specified couldn't be found in Kronox's system, please log out and back in or try again later.")); - } - catch (ParseException e) - { - _logger.LogError(e.ToString()); - return StatusCode(StatusCodes.Status500InternalServerError, new Error("We're having trouble getting your data from Kronox, please try again later.")); - } - catch (LoginException e) - { - _logger.LogError(e.ToString()); - return Unauthorized(new Error("There was an issue with your login information, please log back in or try again later.")); + if (await eventToRegister.Register(kronoxReqClient)) + return Ok(); + + return StatusCode(StatusCodes.Status500InternalServerError, new Error("There was an error signing up to the event, please try again later.")); } - catch (HttpRequestException e) + catch (Exception ex) when (ex is ParseException or LoginException or HttpRequestException) { - _logger.LogError(e.ToString()); - return StatusCode(StatusCodes.Status500InternalServerError, new Error("We're having trouble getting your data from Kronox, please try again later.")); + return HandleError(ex, "We're having trouble getting your data from Kronox, please try again later."); } } [HttpPut("unregister/{eventId}")] public async Task UnregisterUserEvent([FromRoute] string eventId, [FromQuery] SchoolEnum schoolId) { - IKronoxRequestClient kronoxReqClient = (IKronoxRequestClient)HttpContext.Items[KronoxReqClientKeys.SingleClient]!; - School school = schoolId.GetSchool()!; - - if (!kronoxReqClient.IsAuthenticated) - return BadRequest(new Error("Requires provided auth token")); - try { - Dictionary>? userEvents = await School.GetUserEventsAsync(kronoxReqClient); - UserEventCollection? webSafeUserEvents = userEvents?.ToWebModel(); + var kronoxReqClient = GetAuthenticatedClient(); + var userEvents = await School.GetUserEventsAsync(kronoxReqClient); + var webSafeUserEvents = userEvents?.ToWebModel(); + if (webSafeUserEvents == null) return StatusCode(StatusCodes.Status500InternalServerError, new Error("We're having trouble getting your data from Kronox, please try again later.")); - foreach (AvailableUserEvent userEvent in webSafeUserEvents.RegisteredEvents) - { - if (userEvent.Id == eventId) - { - if (await userEvent.Unregister(kronoxReqClient)) - return Ok(); + var eventToUnregister = webSafeUserEvents.RegisteredEvents.FirstOrDefault(e => e.Id == eventId); - return StatusCode(StatusCodes.Status500InternalServerError, new Error("There was an error signing up to the event, please try again later.")); - } - } + if (eventToUnregister == null) + return NotFound(new Error("The event specified couldn't be found in Kronox's system, please log out and back in or try again later.")); - return NotFound(new Error("The event specified couldn't be found in Kronox's system, please log out and back in or try again later.")); - } - catch (ParseException e) - { - _logger.LogError(e.ToString()); - return StatusCode(StatusCodes.Status500InternalServerError, new Error("We're having trouble getting your data from Kronox, please try again later.")); + if (await eventToUnregister.Unregister(kronoxReqClient)) + return Ok(); + + return StatusCode(StatusCodes.Status500InternalServerError, new Error("There was an error signing up to the event, please try again later.")); } - catch (LoginException e) + catch (Exception ex) when (ex is ParseException or LoginException or HttpRequestException) { - _logger.LogError(e.ToString()); - return Unauthorized(new Error("There was an issue with your login information, please log back in or try again later.")); + return HandleError(ex, "We're having trouble getting your data from Kronox, please try again later."); } - catch (HttpRequestException e) + } + + private async Task<(List successful, List failed)> RegisterEventsAsync(IKronoxRequestClient client, IEnumerable events) + { + var successful = new List(); + var failed = new List(); + + foreach (var userEvent in events) { - _logger.LogError(e.ToString()); - return StatusCode(StatusCodes.Status500InternalServerError, new Error("We're having trouble getting your data from Kronox, please try again later.")); + if (await userEvent.Register(client)) + successful.Add(userEvent); + else + failed.Add(userEvent); } - } + return (successful, failed); + } } diff --git a/TumbleBackend/Program.cs b/TumbleBackend/Program.cs index b65cc61..cea7bab 100644 --- a/TumbleBackend/Program.cs +++ b/TumbleBackend/Program.cs @@ -109,6 +109,7 @@ void RegisterServices(IServiceCollection services, IConfiguration configuration, services.AddSingleton(sp => new MongoKronoxCacheService(sp.GetService()!)); services.AddSingleton(); services.AddTransient(); + services.AddTransient(); services.AddScoped(); services.AddScoped(); @@ -121,10 +122,6 @@ void RegisterServices(IServiceCollection services, IConfiguration configuration, config.OperationFilter(); }); - services.AddHttpClient("KronoxClient", client => - { - client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"); - }); services.AddCors(options => { diff --git a/TumbleBackend/StringConstants/EnvVar.cs b/TumbleBackend/StringConstants/EnvVar.cs index bbd1414..f007885 100644 --- a/TumbleBackend/StringConstants/EnvVar.cs +++ b/TumbleBackend/StringConstants/EnvVar.cs @@ -11,4 +11,5 @@ public static class EnvVar public static string AdminPass => "AdminPass"; public static string TestUserPass => "TestUserPass"; public static string TestUserEmail => "TestUserEmail"; + public static string TestUserSessionToken => "TestUserSessionToken"; } diff --git a/TumbleBackend/StringConstants/UserSecrets.cs b/TumbleBackend/StringConstants/UserSecrets.cs index 132aaae..e9ec33f 100644 --- a/TumbleBackend/StringConstants/UserSecrets.cs +++ b/TumbleBackend/StringConstants/UserSecrets.cs @@ -9,8 +9,7 @@ public static class UserSecrets public static string AwsAccessKey => "Aws:AccessKey"; public static string AwsSecretKey => "Aws:SecretKey"; public static string AdminPass => "Admin:Pass"; - public static string TestUserPass => "TestUser:Pass"; - public static string TestUserEmail => "TestUser:Email"; + public static string TestUserSessionToken => "TestUser:SessionToken"; } diff --git a/TumbleBackend/Utilities/JwtUtil.cs b/TumbleBackend/Utilities/JwtUtil.cs index 3363aa0..f4672e5 100644 --- a/TumbleBackend/Utilities/JwtUtil.cs +++ b/TumbleBackend/Utilities/JwtUtil.cs @@ -6,89 +6,105 @@ using Microsoft.AspNetCore.Mvc; using TumbleBackend.StringConstants; -namespace TumbleBackend.Utilities; - -public class JwtUtil +namespace TumbleBackend.Utilities { - readonly string jwtEncryptionKey; - readonly string jwtSignatureKey; - readonly int refreshTokenExpireTime; - - public JwtUtil([FromServices] IConfiguration configuration) + public class JwtUtil { - string? jwtEncKey = configuration[UserSecrets.JwtEncryptionKey] ?? Environment.GetEnvironmentVariable(EnvVar.JwtEncryptionKey); - string? jwtSigKey = configuration[UserSecrets.JwtSignatureKey] ?? Environment.GetEnvironmentVariable(EnvVar.JwtSignatureKey); - string? refreshTokenExpiration = configuration[UserSecrets.JwtRefreshTokenExpiration] ?? Environment.GetEnvironmentVariable(EnvVar.JwtRefreshTokenExpiration); - if (jwtEncKey == null || refreshTokenExpiration == null || jwtSigKey == null) - throw new NullReferenceException("It should not be possible for jwtEncKey OR refreshTokenExpirationTime OR jwtSigKey to be null at this point."); + readonly string _jwtEncryptionKey; + readonly string _jwtSignatureKey; + readonly int _refreshTokenExpireTime; + readonly string _testToken; + readonly string _testPassword; + readonly string _testUsername; - jwtEncryptionKey = jwtEncKey; - jwtSignatureKey = jwtSigKey; - refreshTokenExpireTime = int.Parse(refreshTokenExpiration); - } + public JwtUtil([FromServices] IConfiguration configuration) + { + string? jwtEncKey = configuration[UserSecrets.JwtEncryptionKey] ?? Environment.GetEnvironmentVariable(EnvVar.JwtEncryptionKey); + string? jwtSigKey = configuration[UserSecrets.JwtSignatureKey] ?? Environment.GetEnvironmentVariable(EnvVar.JwtSignatureKey); + string? refreshTokenExpiration = configuration[UserSecrets.JwtRefreshTokenExpiration] ?? Environment.GetEnvironmentVariable(EnvVar.JwtRefreshTokenExpiration); + string? configuredTestToken = configuration[UserSecrets.TestUserSessionToken] ?? Environment.GetEnvironmentVariable(EnvVar.TestUserSessionToken); + string? testUserPass = configuration[UserSecrets.TestUserPass] ?? Environment.GetEnvironmentVariable(EnvVar.TestUserPass); + string? testUserEmail = configuration[UserSecrets.TestUserEmail] ?? Environment.GetEnvironmentVariable(EnvVar.TestUserEmail); - public string GenerateRefreshToken(string username, string password) - { - JwtSecurityTokenHandler jwtHandler = new(); - byte[] ecKeyTemp = Encoding.UTF8.GetBytes(jwtEncryptionKey); - byte[] scKey = Encoding.UTF8.GetBytes(jwtSignatureKey); + if (jwtEncKey == null || refreshTokenExpiration == null || jwtSigKey == null) + throw new NullReferenceException("It should not be possible for jwtEncKey OR refreshTokenExpirationTime OR jwtSigKey to be null at this point."); - byte[] ecKey = new byte[256 / 8]; - Array.Copy(ecKeyTemp, ecKey, 256 / 8); + _jwtEncryptionKey = jwtEncKey; + _jwtSignatureKey = jwtSigKey; + _refreshTokenExpireTime = int.Parse(refreshTokenExpiration); + _testToken = configuredTestToken ?? throw new NullReferenceException("Test token not configured"); + _testPassword = testUserPass ?? throw new NullReferenceException("Test user password not configured"); + _testUsername = testUserEmail ?? throw new NullReferenceException("Test user email not configured"); + } - SecurityTokenDescriptor tokenDescriptor = new SecurityTokenDescriptor + public string GenerateRefreshToken(string username, string password) { - Subject = new ClaimsIdentity(new[] { new Claim("username", username), new Claim("password", password) }), - Expires = DateTime.UtcNow + TimeSpan.FromSeconds(refreshTokenExpireTime), - SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(scKey), SecurityAlgorithms.HmacSha256Signature), - EncryptingCredentials = new EncryptingCredentials(new SymmetricSecurityKey(ecKey), SecurityAlgorithms.Aes256KW, - SecurityAlgorithms.Aes256CbcHmacSha512), - Issuer = "TumbleBackend", - Audience = "TumbleApp", - }; - - return jwtHandler.CreateEncodedJwt(tokenDescriptor); - } + JwtSecurityTokenHandler jwtHandler = new(); + byte[] ecKeyTemp = Encoding.UTF8.GetBytes(_jwtEncryptionKey); + byte[] scKey = Encoding.UTF8.GetBytes(_jwtSignatureKey); - public RefreshTokenResponseModel? ValidateAndReadRefreshToken(string token) - { - if (token == null) - return null; + byte[] ecKey = new byte[256 / 8]; + Array.Copy(ecKeyTemp, ecKey, 256 / 8); - JwtSecurityTokenHandler tokenHandler = new(); - byte[] ecKeyTemp = Encoding.UTF8.GetBytes(jwtEncryptionKey); - byte[] scKey = Encoding.UTF8.GetBytes(jwtSignatureKey); + SecurityTokenDescriptor tokenDescriptor = new() + { + Subject = new ClaimsIdentity(new[] { new Claim("username", username), new Claim("password", password) }), + Expires = DateTime.UtcNow + TimeSpan.FromSeconds(_refreshTokenExpireTime), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(scKey), SecurityAlgorithms.HmacSha256Signature), + EncryptingCredentials = new EncryptingCredentials(new SymmetricSecurityKey(ecKey), SecurityAlgorithms.Aes256KW, + SecurityAlgorithms.Aes256CbcHmacSha512), + Issuer = "TumbleBackend", + Audience = "TumbleApp", + }; - byte[] ecKey = new byte[256 / 8]; - Array.Copy(ecKeyTemp, ecKey, 256 / 8); + return jwtHandler.CreateEncodedJwt(tokenDescriptor); + } - try + public RefreshTokenResponseModel? ValidateAndReadRefreshToken(string token) { - tokenHandler.ValidateToken( - token, - new TokenValidationParameters - { - ValidateLifetime = true, - RequireSignedTokens = true, - ValidateIssuer = true, - ValidateAudience = true, - ValidAudience = "TumbleApp", - ValidIssuer = "TumbleBackend", - IssuerSigningKey = new SymmetricSecurityKey(scKey), - TokenDecryptionKey = new SymmetricSecurityKey(ecKey), - }, - out SecurityToken validatedToken - ); + if (token == null) + return null; - JwtSecurityToken jwtToken = (JwtSecurityToken)validatedToken; - string username = jwtToken.Claims.First(x => x.Type == "username").Value; - string password = jwtToken.Claims.First(x => x.Type == "password").Value; + // Check if the token is a test token (allows reuse and bypasses expiration/signature checks) + if (token == _testToken) + return new RefreshTokenResponseModel(_testUsername, _testPassword); - return new(username, password); - } - catch - { - return null; + JwtSecurityTokenHandler tokenHandler = new(); + byte[] ecKeyTemp = Encoding.UTF8.GetBytes(_jwtEncryptionKey); + byte[] scKey = Encoding.UTF8.GetBytes(_jwtSignatureKey); + + byte[] ecKey = new byte[256 / 8]; + Array.Copy(ecKeyTemp, ecKey, 256 / 8); + + try + { + // Normal token validation + tokenHandler.ValidateToken( + token, + new TokenValidationParameters + { + ValidateLifetime = true, + RequireSignedTokens = true, + ValidateIssuer = true, + ValidateAudience = true, + ValidAudience = "TumbleApp", + ValidIssuer = "TumbleBackend", + IssuerSigningKey = new SymmetricSecurityKey(scKey), + TokenDecryptionKey = new SymmetricSecurityKey(ecKey), + }, + out SecurityToken validatedToken + ); + + JwtSecurityToken jwtToken = (JwtSecurityToken)validatedToken; + string username = jwtToken.Claims.First(x => x.Type == "username").Value; + string password = jwtToken.Claims.First(x => x.Type == "password").Value; + + return new RefreshTokenResponseModel(username, password); + } + catch + { + return null; + } } } } diff --git a/TumbleBackend/Utilities/TestUserUtil.cs b/TumbleBackend/Utilities/TestUserUtil.cs index 4046497..bfd07f6 100644 --- a/TumbleBackend/Utilities/TestUserUtil.cs +++ b/TumbleBackend/Utilities/TestUserUtil.cs @@ -1,3 +1,4 @@ +using KronoxAPI.Model.Users; using TumbleBackend.StringConstants; using TumbleBackend.Utilities; @@ -5,17 +6,15 @@ namespace TumbleBackend.Utilities; public class TestUserUtil { private readonly IConfiguration _configuration; - private readonly string _testUserPass; - private readonly string _testUserEmail; public TestUserUtil(IConfiguration configuration) { _configuration = configuration; - _testUserPass = _configuration[UserSecrets.TestUserPass] ?? Environment.GetEnvironmentVariable(EnvVar.TestUserPass) ?? throw new NullReferenceException("Ensure that TestUserPass is defined in the environment."); } public bool IsTestUser(string username, string password) { string? testUserPass = _configuration[UserSecrets.TestUserPass] ?? Environment.GetEnvironmentVariable(EnvVar.TestUserPass); string? testUserEmail = _configuration[UserSecrets.TestUserEmail] ?? Environment.GetEnvironmentVariable(EnvVar.TestUserEmail); + string? testUserSessionToken = _configuration[UserSecrets.TestUserSessionToken] ?? Environment.GetEnvironmentVariable(EnvVar.TestUserSessionToken); if (testUserPass == null || testUserEmail == null) throw new NullReferenceException("It should not be possible for testUserPass OR testUserEmail to be null at this point."); @@ -23,4 +22,18 @@ public bool IsTestUser(string username, string password) { return username == testUserEmail && password == testUserPass; } + public User GetTestUser() { + string? testUserEmail = _configuration[UserSecrets.TestUserEmail] ?? Environment.GetEnvironmentVariable(EnvVar.TestUserEmail); + string? testUserPass = _configuration[UserSecrets.TestUserPass] ?? Environment.GetEnvironmentVariable(EnvVar.TestUserPass); + + if (testUserEmail == null || testUserPass == null) + throw new NullReferenceException("Ensure that TestUserEmail and TestUserPass are defined in the environment."); + + return new User("Test User", testUserEmail, "testSessionToken"); + } + + public string GetTestUserSessionToken() { + string? testUserSessionToken = (_configuration[UserSecrets.TestUserSessionToken] ?? Environment.GetEnvironmentVariable(EnvVar.TestUserSessionToken)) ?? throw new NullReferenceException("Ensure that TestUserSessionToken is defined in the environment."); + return testUserSessionToken; + } } \ No newline at end of file From a8fae131863a1ae806473cf8b4f0763458163d66 Mon Sep 17 00:00:00 2001 From: adisve Date: Wed, 4 Sep 2024 21:07:18 +0200 Subject: [PATCH 03/10] Restore some files, make test user middleware instead --- .../Controllers/BookingController.cs | 2 - .../Controllers/UserEventController.cs | 2 +- .../Middleware/TestUserMiddleware.cs | 167 ++++++++++++++++++ .../MockData/mockBookingConfirmation.json | 0 TumbleBackend/MockData/mockBookingResult.json | 0 .../MockData/mockRegistrationResult.json | 0 TumbleBackend/MockData/mockResources.json | 0 .../mockResourcesWithAvailabilities.json | 0 .../MockData/mockUnbookingResult.json | 0 TumbleBackend/MockData/mockUserBookings.json | 0 TumbleBackend/MockData/mockUserEvents.json | 0 TumbleBackend/Program.cs | 18 +- TumbleBackend/StringConstants/EnvVar.cs | 1 - TumbleBackend/StringConstants/UserSecrets.cs | 1 - TumbleBackend/Utilities/JwtUtil.cs | 156 ++++++++-------- TumbleBackend/Utilities/TestUserUtil.cs | 5 - 16 files changed, 241 insertions(+), 111 deletions(-) create mode 100644 TumbleBackend/Middleware/TestUserMiddleware.cs create mode 100644 TumbleBackend/MockData/mockBookingConfirmation.json create mode 100644 TumbleBackend/MockData/mockBookingResult.json create mode 100644 TumbleBackend/MockData/mockRegistrationResult.json create mode 100644 TumbleBackend/MockData/mockResources.json create mode 100644 TumbleBackend/MockData/mockResourcesWithAvailabilities.json create mode 100644 TumbleBackend/MockData/mockUnbookingResult.json create mode 100644 TumbleBackend/MockData/mockUserBookings.json create mode 100644 TumbleBackend/MockData/mockUserEvents.json diff --git a/TumbleBackend/Controllers/BookingController.cs b/TumbleBackend/Controllers/BookingController.cs index ccb7322..dd1a794 100644 --- a/TumbleBackend/Controllers/BookingController.cs +++ b/TumbleBackend/Controllers/BookingController.cs @@ -1,8 +1,6 @@ using KronoxAPI.Exceptions; using KronoxAPI.Model.Booking; using KronoxAPI.Model.Schools; -using Microsoft.AspNetCore.Cors; -using Microsoft.AspNetCore.Mvc; using TumbleBackend.ActionFilters; using TumbleBackend.Extensions; using TumbleBackend.StringConstants; diff --git a/TumbleBackend/Controllers/UserEventController.cs b/TumbleBackend/Controllers/UserEventController.cs index bb678b8..660c221 100644 --- a/TumbleBackend/Controllers/UserEventController.cs +++ b/TumbleBackend/Controllers/UserEventController.cs @@ -26,7 +26,7 @@ public UserEventController(ILogger logger) _logger = logger; } - private IKronoxRequestClient GetAuthenticatedClient() + private static IKronoxRequestClient GetAuthenticatedClient() { if (HttpContext.Items[KronoxReqClientKeys.SingleClient] is not IKronoxRequestClient client || !client.IsAuthenticated) { diff --git a/TumbleBackend/Middleware/TestUserMiddleware.cs b/TumbleBackend/Middleware/TestUserMiddleware.cs new file mode 100644 index 0000000..a9753c8 --- /dev/null +++ b/TumbleBackend/Middleware/TestUserMiddleware.cs @@ -0,0 +1,167 @@ +using TumbleBackend.Utilities; +using WebAPIModels.MiscModels; + +namespace TumbleBackend.Middleware; + +public class TestUserMiddleware +{ + private readonly RequestDelegate _next; + private readonly string _mockDataPath; + + public TestUserMiddleware(RequestDelegate next, IWebHostEnvironment env) + { + _next = next; + _mockDataPath = Path.Combine(env.ContentRootPath, "MockData"); + } + + public async Task InvokeAsync(HttpContext context, JwtUtil jwtUtil, TestUserUtil testUserUtil) + { + var refreshToken = context.Request.Headers["X-auth-token"].FirstOrDefault(); + var path = context.Request.Path; + + if (IsLoginRequest(context) && TryGetCredentials(context, out var username, out var password) && testUserUtil.IsTestUser(username, password)) + { + await HandleTestUserLoginResponse(context, jwtUtil, testUserUtil, username, password); + return; + } + else if (refreshToken != null) + { + var creds = jwtUtil.ValidateAndReadRefreshToken(refreshToken); + if (creds != null && testUserUtil.IsTestUser(creds.Username, creds.Password)) + { + if (path.StartsWithSegments("/api/resources")) + { + await HandleTestUserBookingRoutes(context, path); + return; + } + + await HandleTestUserTokenResponse(context, jwtUtil, testUserUtil, creds.Username, creds.Password, refreshToken); + return; + } + } + + await _next(context); + } + + private static bool IsLoginRequest(HttpContext context) + { + return context.Request.Method == HttpMethods.Post && context.Request.Path == "/api/users/login"; + } + + private static bool TryGetCredentials(HttpContext context, out string username, out string password) + { + username = context.Request.Form["username"].FirstOrDefault(); + password = context.Request.Form["password"].FirstOrDefault(); + return username != null && password != null; + } + + private static async Task HandleTestUserLoginResponse(HttpContext context, JwtUtil jwtUtil, TestUserUtil testUserUtil, string username, string password) + { + var testUser = testUserUtil.GetTestUser(); + var newRefreshToken = jwtUtil.GenerateRefreshToken(username, password); + var sessionDetails = new SessionDetails("", ""); + + context.Response.Headers.Add("X-auth-token", newRefreshToken); + context.Response.Headers.Add("X-session-token", sessionDetails.ToJson()); + + var testUserModel = testUser.ToWebModel(newRefreshToken, "", sessionDetails); + await context.Response.WriteAsJsonAsync(testUserModel); + } + + private static async Task HandleTestUserTokenResponse(HttpContext context, JwtUtil jwtUtil, TestUserUtil testUserUtil, string username, string password, string refreshToken) + { + var testUser = testUserUtil.GetTestUser(); + var sessionDetails = new SessionDetails("", ""); + + context.Response.Headers.Add("X-auth-token", refreshToken); + context.Response.Headers.Add("X-session-token", sessionDetails.ToJson()); + + var testUserModel = testUser.ToWebModel(refreshToken, "", sessionDetails); + await context.Response.WriteAsJsonAsync(testUserModel); + } + + private async Task HandleTestUserBookingRoutes(HttpContext context, PathString path) + { + if (context.Request.Method == HttpMethods.Get && path == "/api/resources") + { + await ReturnMockResources(context); + } + else if (context.Request.Method == HttpMethods.Get && path == "/api/resources/all") + { + await ReturnMockResourcesWithAvailabilities(context); + } + else if (context.Request.Method == HttpMethods.Get && path == "/api/resources/userbookings") + { + await ReturnMockUserBookings(context); + } + else if (context.Request.Method == HttpMethods.Put && path.StartsWithSegments("/api/resources/book")) + { + await ReturnMockBookingResult(context); + } + else if (context.Request.Method == HttpMethods.Put && path.StartsWithSegments("/api/resources/unbook")) + { + await ReturnMockUnbookingResult(context); + } + else if (context.Request.Method == HttpMethods.Put && path.StartsWithSegments("/api/resources/confirm")) + { + await ReturnMockBookingConfirmation(context); + } + } + + private async Task ReturnMockResources(HttpContext context) + { + var mockFilePath = Path.Combine(_mockDataPath, "mockResources.json"); + var mockResources = await File.ReadAllTextAsync(mockFilePath); + var resources = JsonSerializer.Deserialize>(mockResources); + + context.Response.StatusCode = StatusCodes.Status200OK; + await context.Response.WriteAsJsonAsync(resources); + } + + private async Task ReturnMockResourcesWithAvailabilities(HttpContext context) + { + var mockFilePath = Path.Combine(_mockDataPath, "mockResourcesWithAvailabilities.json"); + var mockResources = await File.ReadAllTextAsync(mockFilePath); + var resources = JsonSerializer.Deserialize>(mockResources); + + context.Response.StatusCode = StatusCodes.Status200OK; + await context.Response.WriteAsJsonAsync(resources); + } + + private async Task ReturnMockUserBookings(HttpContext context) + { + var mockFilePath = Path.Combine(_mockDataPath, "mockUserBookings.json"); + var mockBookings = await File.ReadAllTextAsync(mockFilePath); + var bookings = JsonSerializer.Deserialize>(mockBookings); + + context.Response.StatusCode = StatusCodes.Status200OK; + await context.Response.WriteAsJsonAsync(bookings); + } + + private async Task ReturnMockBookingResult(HttpContext context) + { + var mockFilePath = Path.Combine(_mockDataPath, "mockBookingResult.json"); + var mockResult = await File.ReadAllTextAsync(mockFilePath); + + context.Response.StatusCode = StatusCodes.Status200OK; + await context.Response.WriteAsJsonAsync(JsonSerializer.Deserialize(mockResult)); + } + + private async Task ReturnMockUnbookingResult(HttpContext context) + { + var mockFilePath = Path.Combine(_mockDataPath, "mockUnbookingResult.json"); + var mockResult = await File.ReadAllTextAsync(mockFilePath); + + context.Response.StatusCode = StatusCodes.Status200OK; + await context.Response.WriteAsJsonAsync(JsonSerializer.Deserialize(mockResult)); + } + + private async Task ReturnMockBookingConfirmation(HttpContext context) + { + var mockFilePath = Path.Combine(_mockDataPath, "mockBookingConfirmation.json"); + var mockResult = await File.ReadAllTextAsync(mockFilePath); + + context.Response.StatusCode = StatusCodes.Status200OK; + await context.Response.WriteAsJsonAsync(JsonSerializer.Deserialize(mockResult)); + } +} diff --git a/TumbleBackend/MockData/mockBookingConfirmation.json b/TumbleBackend/MockData/mockBookingConfirmation.json new file mode 100644 index 0000000..e69de29 diff --git a/TumbleBackend/MockData/mockBookingResult.json b/TumbleBackend/MockData/mockBookingResult.json new file mode 100644 index 0000000..e69de29 diff --git a/TumbleBackend/MockData/mockRegistrationResult.json b/TumbleBackend/MockData/mockRegistrationResult.json new file mode 100644 index 0000000..e69de29 diff --git a/TumbleBackend/MockData/mockResources.json b/TumbleBackend/MockData/mockResources.json new file mode 100644 index 0000000..e69de29 diff --git a/TumbleBackend/MockData/mockResourcesWithAvailabilities.json b/TumbleBackend/MockData/mockResourcesWithAvailabilities.json new file mode 100644 index 0000000..e69de29 diff --git a/TumbleBackend/MockData/mockUnbookingResult.json b/TumbleBackend/MockData/mockUnbookingResult.json new file mode 100644 index 0000000..e69de29 diff --git a/TumbleBackend/MockData/mockUserBookings.json b/TumbleBackend/MockData/mockUserBookings.json new file mode 100644 index 0000000..e69de29 diff --git a/TumbleBackend/MockData/mockUserEvents.json b/TumbleBackend/MockData/mockUserEvents.json new file mode 100644 index 0000000..e69de29 diff --git a/TumbleBackend/Program.cs b/TumbleBackend/Program.cs index cea7bab..147786a 100644 --- a/TumbleBackend/Program.cs +++ b/TumbleBackend/Program.cs @@ -1,17 +1,8 @@ using DatabaseAPI; using DatabaseAPI.Interfaces; -using Grafana.OpenTelemetry; -using Microsoft.AspNetCore.RateLimiting; -using MongoDB.Bson.Serialization; -using MongoDB.Bson.Serialization.Conventions; -using OpenTelemetry.Logs; -using OpenTelemetry.Metrics; -using OpenTelemetry.Trace; -using Prometheus; -using System.Diagnostics; -using System.Threading.RateLimiting; using TumbleBackend.ActionFilters; using TumbleBackend.ExceptionMiddleware; +using TumbleBackend.Middleware; using TumbleBackend.Library; using TumbleBackend.OperationFilters; using TumbleBackend.StringConstants; @@ -21,20 +12,16 @@ var builder = WebApplication.CreateBuilder(args); -// Configuration ConfigureConfiguration(builder); ConfigureTracing(builder); ConfigureRateLimiting(builder); ConfigureMongoDb(); -// Service registration RegisterServices(builder.Services, builder.Configuration, builder.Environment); -// Build and configure middleware var app = builder.Build(); ConfigureMiddleware(app); -// Initialize utilities EmailUtil.Init(GetAwsAccessKey(builder.Environment, builder.Configuration), GetAwsSecretKey(builder.Environment, builder.Configuration)); app.Run(); @@ -53,7 +40,7 @@ void ConfigureTracing(WebApplicationBuilder builder) tracerProviderBuilder .AddAspNetCoreInstrumentation() .AddHttpClientInstrumentation() - .UseGrafana() // Sets up Grafana's OpenTelemetry distribution + .UseGrafana() .AddConsoleExporter(); }); @@ -152,6 +139,7 @@ void ConfigureMiddleware(WebApplication app) app.UseMiddleware(); app.UseMiddleware(); + app.UseMiddleware(); app.UseMetricServer("/metrics"); app.UseHttpMetrics(); diff --git a/TumbleBackend/StringConstants/EnvVar.cs b/TumbleBackend/StringConstants/EnvVar.cs index f007885..bbd1414 100644 --- a/TumbleBackend/StringConstants/EnvVar.cs +++ b/TumbleBackend/StringConstants/EnvVar.cs @@ -11,5 +11,4 @@ public static class EnvVar public static string AdminPass => "AdminPass"; public static string TestUserPass => "TestUserPass"; public static string TestUserEmail => "TestUserEmail"; - public static string TestUserSessionToken => "TestUserSessionToken"; } diff --git a/TumbleBackend/StringConstants/UserSecrets.cs b/TumbleBackend/StringConstants/UserSecrets.cs index e9ec33f..1b2cbc8 100644 --- a/TumbleBackend/StringConstants/UserSecrets.cs +++ b/TumbleBackend/StringConstants/UserSecrets.cs @@ -11,5 +11,4 @@ public static class UserSecrets public static string AdminPass => "Admin:Pass"; public static string TestUserPass => "TestUser:Pass"; public static string TestUserEmail => "TestUser:Email"; - public static string TestUserSessionToken => "TestUser:SessionToken"; } diff --git a/TumbleBackend/Utilities/JwtUtil.cs b/TumbleBackend/Utilities/JwtUtil.cs index f4672e5..beeb3a2 100644 --- a/TumbleBackend/Utilities/JwtUtil.cs +++ b/TumbleBackend/Utilities/JwtUtil.cs @@ -6,105 +6,89 @@ using Microsoft.AspNetCore.Mvc; using TumbleBackend.StringConstants; -namespace TumbleBackend.Utilities +namespace TumbleBackend.Utilities; + +public class JwtUtil { - public class JwtUtil + readonly string jwtEncryptionKey; + readonly string jwtSignatureKey; + readonly int refreshTokenExpireTime; + + public JwtUtil([FromServices] IConfiguration configuration) { - readonly string _jwtEncryptionKey; - readonly string _jwtSignatureKey; - readonly int _refreshTokenExpireTime; - readonly string _testToken; - readonly string _testPassword; - readonly string _testUsername; + string? jwtEncKey = configuration[UserSecrets.JwtEncryptionKey] ?? Environment.GetEnvironmentVariable(EnvVar.JwtEncryptionKey); + string? jwtSigKey = configuration[UserSecrets.JwtSignatureKey] ?? Environment.GetEnvironmentVariable(EnvVar.JwtSignatureKey); + string? refreshTokenExpiration = configuration[UserSecrets.JwtRefreshTokenExpiration] ?? Environment.GetEnvironmentVariable(EnvVar.JwtRefreshTokenExpiration); + if (jwtEncKey == null || refreshTokenExpiration == null || jwtSigKey == null) + throw new NullReferenceException("It should not be possible for jwtEncKey OR refreshTokenExpirationTime OR jwtSigKey to be null at this point."); - public JwtUtil([FromServices] IConfiguration configuration) - { - string? jwtEncKey = configuration[UserSecrets.JwtEncryptionKey] ?? Environment.GetEnvironmentVariable(EnvVar.JwtEncryptionKey); - string? jwtSigKey = configuration[UserSecrets.JwtSignatureKey] ?? Environment.GetEnvironmentVariable(EnvVar.JwtSignatureKey); - string? refreshTokenExpiration = configuration[UserSecrets.JwtRefreshTokenExpiration] ?? Environment.GetEnvironmentVariable(EnvVar.JwtRefreshTokenExpiration); - string? configuredTestToken = configuration[UserSecrets.TestUserSessionToken] ?? Environment.GetEnvironmentVariable(EnvVar.TestUserSessionToken); - string? testUserPass = configuration[UserSecrets.TestUserPass] ?? Environment.GetEnvironmentVariable(EnvVar.TestUserPass); - string? testUserEmail = configuration[UserSecrets.TestUserEmail] ?? Environment.GetEnvironmentVariable(EnvVar.TestUserEmail); + jwtEncryptionKey = jwtEncKey; + jwtSignatureKey = jwtSigKey; + refreshTokenExpireTime = int.Parse(refreshTokenExpiration); + } - if (jwtEncKey == null || refreshTokenExpiration == null || jwtSigKey == null) - throw new NullReferenceException("It should not be possible for jwtEncKey OR refreshTokenExpirationTime OR jwtSigKey to be null at this point."); + public string GenerateRefreshToken(string username, string password) + { + JwtSecurityTokenHandler jwtHandler = new(); + byte[] ecKeyTemp = Encoding.UTF8.GetBytes(jwtEncryptionKey); + byte[] scKey = Encoding.UTF8.GetBytes(jwtSignatureKey); - _jwtEncryptionKey = jwtEncKey; - _jwtSignatureKey = jwtSigKey; - _refreshTokenExpireTime = int.Parse(refreshTokenExpiration); - _testToken = configuredTestToken ?? throw new NullReferenceException("Test token not configured"); - _testPassword = testUserPass ?? throw new NullReferenceException("Test user password not configured"); - _testUsername = testUserEmail ?? throw new NullReferenceException("Test user email not configured"); - } + byte[] ecKey = new byte[256 / 8]; + Array.Copy(ecKeyTemp, ecKey, 256 / 8); - public string GenerateRefreshToken(string username, string password) + SecurityTokenDescriptor tokenDescriptor = new SecurityTokenDescriptor { - JwtSecurityTokenHandler jwtHandler = new(); - byte[] ecKeyTemp = Encoding.UTF8.GetBytes(_jwtEncryptionKey); - byte[] scKey = Encoding.UTF8.GetBytes(_jwtSignatureKey); - - byte[] ecKey = new byte[256 / 8]; - Array.Copy(ecKeyTemp, ecKey, 256 / 8); - - SecurityTokenDescriptor tokenDescriptor = new() - { - Subject = new ClaimsIdentity(new[] { new Claim("username", username), new Claim("password", password) }), - Expires = DateTime.UtcNow + TimeSpan.FromSeconds(_refreshTokenExpireTime), - SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(scKey), SecurityAlgorithms.HmacSha256Signature), - EncryptingCredentials = new EncryptingCredentials(new SymmetricSecurityKey(ecKey), SecurityAlgorithms.Aes256KW, - SecurityAlgorithms.Aes256CbcHmacSha512), - Issuer = "TumbleBackend", - Audience = "TumbleApp", - }; + Subject = new ClaimsIdentity(new[] { new Claim("username", username), new Claim("password", password) }), + Expires = DateTime.UtcNow + TimeSpan.FromSeconds(refreshTokenExpireTime), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(scKey), SecurityAlgorithms.HmacSha256Signature), + EncryptingCredentials = new EncryptingCredentials(new SymmetricSecurityKey(ecKey), SecurityAlgorithms.Aes256KW, + SecurityAlgorithms.Aes256CbcHmacSha512), + Issuer = "TumbleBackend", + Audience = "TumbleApp", + }; - return jwtHandler.CreateEncodedJwt(tokenDescriptor); - } - - public RefreshTokenResponseModel? ValidateAndReadRefreshToken(string token) - { - if (token == null) - return null; + return jwtHandler.CreateEncodedJwt(tokenDescriptor); + } - // Check if the token is a test token (allows reuse and bypasses expiration/signature checks) - if (token == _testToken) - return new RefreshTokenResponseModel(_testUsername, _testPassword); + public RefreshTokenResponseModel? ValidateAndReadRefreshToken(string token) + { + if (token == null) + return null; - JwtSecurityTokenHandler tokenHandler = new(); - byte[] ecKeyTemp = Encoding.UTF8.GetBytes(_jwtEncryptionKey); - byte[] scKey = Encoding.UTF8.GetBytes(_jwtSignatureKey); + JwtSecurityTokenHandler tokenHandler = new(); + byte[] ecKeyTemp = Encoding.UTF8.GetBytes(jwtEncryptionKey); + byte[] scKey = Encoding.UTF8.GetBytes(jwtSignatureKey); - byte[] ecKey = new byte[256 / 8]; - Array.Copy(ecKeyTemp, ecKey, 256 / 8); + byte[] ecKey = new byte[256 / 8]; + Array.Copy(ecKeyTemp, ecKey, 256 / 8); - try - { - // Normal token validation - tokenHandler.ValidateToken( - token, - new TokenValidationParameters - { - ValidateLifetime = true, - RequireSignedTokens = true, - ValidateIssuer = true, - ValidateAudience = true, - ValidAudience = "TumbleApp", - ValidIssuer = "TumbleBackend", - IssuerSigningKey = new SymmetricSecurityKey(scKey), - TokenDecryptionKey = new SymmetricSecurityKey(ecKey), - }, - out SecurityToken validatedToken - ); + try + { + tokenHandler.ValidateToken( + token, + new TokenValidationParameters + { + ValidateLifetime = true, + RequireSignedTokens = true, + ValidateIssuer = true, + ValidateAudience = true, + ValidAudience = "TumbleApp", + ValidIssuer = "TumbleBackend", + IssuerSigningKey = new SymmetricSecurityKey(scKey), + TokenDecryptionKey = new SymmetricSecurityKey(ecKey), + }, + out SecurityToken validatedToken + ); - JwtSecurityToken jwtToken = (JwtSecurityToken)validatedToken; - string username = jwtToken.Claims.First(x => x.Type == "username").Value; - string password = jwtToken.Claims.First(x => x.Type == "password").Value; + JwtSecurityToken jwtToken = (JwtSecurityToken)validatedToken; + string username = jwtToken.Claims.First(x => x.Type == "username").Value; + string password = jwtToken.Claims.First(x => x.Type == "password").Value; - return new RefreshTokenResponseModel(username, password); - } - catch - { - return null; - } + return new(username, password); + } + catch + { + return null; } } -} +} \ No newline at end of file diff --git a/TumbleBackend/Utilities/TestUserUtil.cs b/TumbleBackend/Utilities/TestUserUtil.cs index bfd07f6..d46874c 100644 --- a/TumbleBackend/Utilities/TestUserUtil.cs +++ b/TumbleBackend/Utilities/TestUserUtil.cs @@ -31,9 +31,4 @@ public User GetTestUser() { return new User("Test User", testUserEmail, "testSessionToken"); } - - public string GetTestUserSessionToken() { - string? testUserSessionToken = (_configuration[UserSecrets.TestUserSessionToken] ?? Environment.GetEnvironmentVariable(EnvVar.TestUserSessionToken)) ?? throw new NullReferenceException("Ensure that TestUserSessionToken is defined in the environment."); - return testUserSessionToken; - } } \ No newline at end of file From 99f857c808c1adaf2e4e8c1b0779c65f5c60baf3 Mon Sep 17 00:00:00 2001 From: adisve Date: Thu, 5 Sep 2024 18:46:43 +0200 Subject: [PATCH 04/10] Remove old references to testuser, only middleware --- TumbleBackend/Controllers/UserController.cs | 22 ++----------------- .../Controllers/UserEventController.cs | 3 ++- .../Middleware/TestUserMiddleware.cs | 10 +++++++-- TumbleBackend/Program.cs | 13 +++++++++-- TumbleBackend/Utilities/TestUserUtil.cs | 18 ++++++++++----- 5 files changed, 35 insertions(+), 31 deletions(-) diff --git a/TumbleBackend/Controllers/UserController.cs b/TumbleBackend/Controllers/UserController.cs index e7c01be..bc72fcb 100644 --- a/TumbleBackend/Controllers/UserController.cs +++ b/TumbleBackend/Controllers/UserController.cs @@ -43,7 +43,7 @@ public UserController(ILogger logger) /// /// [HttpGet] - public async Task GetKronoxUserAsync([FromServices] JwtUtil jwtUtil, [FromServices] TestUserUtil testUserUtil, [FromQuery] SchoolEnum schoolId, [FromHeader(Name = "X-auth-token")] string refreshToken) + public async Task GetKronoxUserAsync([FromServices] JwtUtil jwtUtil, [FromQuery] SchoolEnum schoolId, [FromHeader(Name = "X-auth-token")] string refreshToken) { var kronoxReqClient = (IKronoxRequestClient)HttpContext.Items[KronoxReqClientKeys.SingleClient]!; var creds = jwtUtil.ValidateAndReadRefreshToken(refreshToken); @@ -51,11 +51,6 @@ public async Task GetKronoxUserAsync([FromServices] JwtUtil jwtUt if (creds == null) return Unauthorized(new Error("Couldn't login user from refreshToken, please log out and back in manually.")); - // If the user is a test user, return the test user object. - // This is useful for testing the frontend without having to log in, as we will send mock data. - if (testUserUtil.IsTestUser(creds.Username, creds.Password)) - return Ok(testUserUtil.GetTestUser().ToWebModel(refreshToken, "", new SessionDetails("", ""))); - // If the user is not a test user, attempt to auto-login the user and return the KronoxUser object. try { @@ -89,23 +84,10 @@ public async Task GetKronoxUserAsync([FromServices] JwtUtil jwtUt /// /// [HttpPost("login")] - public async Task LoginKronoxUserAsync([FromServices] JwtUtil jwtUtil, [FromServices] TestUserUtil testUserUtil, [FromQuery] SchoolEnum schoolId, [FromBody] LoginRequest body) + public async Task LoginKronoxUserAsync([FromServices] JwtUtil jwtUtil, [FromQuery] SchoolEnum schoolId, [FromBody] LoginRequest body) { var kronoxReqClient = (IKronoxRequestClient)HttpContext.Items[KronoxReqClientKeys.SingleClient]!; - // Determine if the user is a test user and return the test user object if they are. - if (testUserUtil.IsTestUser(body.Username, body.Password)) - { - var testUser = testUserUtil.GetTestUser(); - var newRefreshToken = testUserUtil.GetTestUserSessionToken(); - SessionDetails sessionDetails = new("", ""); - - Response.Headers.Add("X-auth-token", newRefreshToken); - Response.Headers.Add("X-session-token", sessionDetails.ToJson()); - - return Ok(testUser.ToWebModel(newRefreshToken, "", sessionDetails)); - } - try { var kronoxUser = await School.LoginAsync(kronoxReqClient, body.Username, body.Password); diff --git a/TumbleBackend/Controllers/UserEventController.cs b/TumbleBackend/Controllers/UserEventController.cs index 660c221..c1903d3 100644 --- a/TumbleBackend/Controllers/UserEventController.cs +++ b/TumbleBackend/Controllers/UserEventController.cs @@ -26,7 +26,7 @@ public UserEventController(ILogger logger) _logger = logger; } - private static IKronoxRequestClient GetAuthenticatedClient() + private IKronoxRequestClient GetAuthenticatedClient() { if (HttpContext.Items[KronoxReqClientKeys.SingleClient] is not IKronoxRequestClient client || !client.IsAuthenticated) { @@ -35,6 +35,7 @@ private static IKronoxRequestClient GetAuthenticatedClient() return client; } + private IActionResult HandleError(Exception ex, string message, int statusCode = StatusCodes.Status500InternalServerError) { _logger.LogError(ex.ToString()); diff --git a/TumbleBackend/Middleware/TestUserMiddleware.cs b/TumbleBackend/Middleware/TestUserMiddleware.cs index a9753c8..dc07a3b 100644 --- a/TumbleBackend/Middleware/TestUserMiddleware.cs +++ b/TumbleBackend/Middleware/TestUserMiddleware.cs @@ -1,4 +1,7 @@ +using System.Text.Json; +using KronoxAPI.Model.Booking; using TumbleBackend.Utilities; +using WebAPIModels.Extensions; using WebAPIModels.MiscModels; namespace TumbleBackend.Middleware; @@ -48,15 +51,18 @@ private static bool IsLoginRequest(HttpContext context) return context.Request.Method == HttpMethods.Post && context.Request.Path == "/api/users/login"; } - private static bool TryGetCredentials(HttpContext context, out string username, out string password) + private static bool TryGetCredentials(HttpContext context, out string? username, out string? password) { username = context.Request.Form["username"].FirstOrDefault(); password = context.Request.Form["password"].FirstOrDefault(); return username != null && password != null; } - private static async Task HandleTestUserLoginResponse(HttpContext context, JwtUtil jwtUtil, TestUserUtil testUserUtil, string username, string password) + private static async Task HandleTestUserLoginResponse(HttpContext context, JwtUtil jwtUtil, TestUserUtil testUserUtil, string? username, string? password) { + if (username == null || password == null) + throw new ArgumentNullException("Username and password must be provided."); + var testUser = testUserUtil.GetTestUser(); var newRefreshToken = jwtUtil.GenerateRefreshToken(username, password); var sessionDetails = new SessionDetails("", ""); diff --git a/TumbleBackend/Program.cs b/TumbleBackend/Program.cs index 147786a..9abded3 100644 --- a/TumbleBackend/Program.cs +++ b/TumbleBackend/Program.cs @@ -9,6 +9,15 @@ using TumbleBackend.Utilities; using TumbleHttpClient; using WebAPIModels.ResponseModels; +using OpenTelemetry.Trace; +using Grafana.OpenTelemetry; +using OpenTelemetry.Logs; +using Microsoft.AspNetCore.RateLimiting; +using System.Threading.RateLimiting; +using MongoDB.Bson.Serialization; +using MongoDB.Bson.Serialization.Conventions; +using System.Diagnostics; +using Prometheus; var builder = WebApplication.CreateBuilder(args); @@ -85,7 +94,7 @@ void RegisterServices(IServiceCollection services, IConfiguration configuration, { string? dbConnectionString = GetDbConnectionString(environment, configuration); string? dbName = GetDbName(environment, configuration); - MongoDBSettings dbSettings = new(dbConnectionString!, dbName); + MongoDBSettings dbSettings = new(dbConnectionString!, dbName!); services.AddSingleton(configuration); services.AddSingleton(dbSettings); @@ -141,7 +150,7 @@ void ConfigureMiddleware(WebApplication app) app.UseMiddleware(); app.UseMiddleware(); - app.UseMetricServer("/metrics"); + app.UseMetricServer(); app.UseHttpMetrics(); app.UseEndpoints(endpoints => diff --git a/TumbleBackend/Utilities/TestUserUtil.cs b/TumbleBackend/Utilities/TestUserUtil.cs index d46874c..fa3732e 100644 --- a/TumbleBackend/Utilities/TestUserUtil.cs +++ b/TumbleBackend/Utilities/TestUserUtil.cs @@ -1,23 +1,29 @@ using KronoxAPI.Model.Users; using TumbleBackend.StringConstants; -using TumbleBackend.Utilities; namespace TumbleBackend.Utilities; public class TestUserUtil { private readonly IConfiguration _configuration; + private readonly ILogger _logger; - public TestUserUtil(IConfiguration configuration) { + public TestUserUtil(IConfiguration configuration, ILogger logger) { + _logger = logger; _configuration = configuration; } - public bool IsTestUser(string username, string password) { + public bool IsTestUser(string? username, string? password) { + if (username == null || password == null) + { + _logger.LogError("Username or password was null."); + return false; + } + string? testUserPass = _configuration[UserSecrets.TestUserPass] ?? Environment.GetEnvironmentVariable(EnvVar.TestUserPass); string? testUserEmail = _configuration[UserSecrets.TestUserEmail] ?? Environment.GetEnvironmentVariable(EnvVar.TestUserEmail); - string? testUserSessionToken = _configuration[UserSecrets.TestUserSessionToken] ?? Environment.GetEnvironmentVariable(EnvVar.TestUserSessionToken); if (testUserPass == null || testUserEmail == null) - throw new NullReferenceException("It should not be possible for testUserPass OR testUserEmail to be null at this point."); + throw new NullReferenceException("Test user credentials are null. Ensure that TestUserEmail and TestUserPass are defined"); return username == testUserEmail && password == testUserPass; } @@ -27,7 +33,7 @@ public User GetTestUser() { string? testUserPass = _configuration[UserSecrets.TestUserPass] ?? Environment.GetEnvironmentVariable(EnvVar.TestUserPass); if (testUserEmail == null || testUserPass == null) - throw new NullReferenceException("Ensure that TestUserEmail and TestUserPass are defined in the environment."); + throw new NullReferenceException("Test user credentials are null. Ensure that TestUserEmail and TestUserPass are defined"); return new User("Test User", testUserEmail, "testSessionToken"); } From 1b2509e1dac43f6e610ffd7e69719aa4dcc7b524 Mon Sep 17 00:00:00 2001 From: adisve Date: Thu, 5 Sep 2024 19:37:41 +0200 Subject: [PATCH 05/10] Fix bookingcontroller --- TumbleBackend/Controllers/BookingController.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TumbleBackend/Controllers/BookingController.cs b/TumbleBackend/Controllers/BookingController.cs index dd1a794..ccb7322 100644 --- a/TumbleBackend/Controllers/BookingController.cs +++ b/TumbleBackend/Controllers/BookingController.cs @@ -1,6 +1,8 @@ using KronoxAPI.Exceptions; using KronoxAPI.Model.Booking; using KronoxAPI.Model.Schools; +using Microsoft.AspNetCore.Cors; +using Microsoft.AspNetCore.Mvc; using TumbleBackend.ActionFilters; using TumbleBackend.Extensions; using TumbleBackend.StringConstants; From ed2275b862c154d8f09eb7406d90a41c3d86b5e7 Mon Sep 17 00:00:00 2001 From: adisve Date: Thu, 5 Sep 2024 19:40:30 +0200 Subject: [PATCH 06/10] Migrate to .net8 --- DatabaseAPI/DatabaseAPI.csproj | 2 +- HttpClient/TumbleHttpClient.csproj | 2 +- KronoxAPI/KronoxAPI - Backup.csproj | 2 +- KronoxAPI/KronoxAPI.csproj | 2 +- Models/WebAPIModels.csproj | 2 +- TumbleBackend/TumbleBackend.csproj | 2 +- Utilities/Utilities.csproj | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/DatabaseAPI/DatabaseAPI.csproj b/DatabaseAPI/DatabaseAPI.csproj index 86e338b..b5cdc7e 100644 --- a/DatabaseAPI/DatabaseAPI.csproj +++ b/DatabaseAPI/DatabaseAPI.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable enable diff --git a/HttpClient/TumbleHttpClient.csproj b/HttpClient/TumbleHttpClient.csproj index cfadb03..30402ac 100644 --- a/HttpClient/TumbleHttpClient.csproj +++ b/HttpClient/TumbleHttpClient.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable enable diff --git a/KronoxAPI/KronoxAPI - Backup.csproj b/KronoxAPI/KronoxAPI - Backup.csproj index 6cd03a4..5f26e47 100644 --- a/KronoxAPI/KronoxAPI - Backup.csproj +++ b/KronoxAPI/KronoxAPI - Backup.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable enable diff --git a/KronoxAPI/KronoxAPI.csproj b/KronoxAPI/KronoxAPI.csproj index 674de88..44a4bc2 100644 --- a/KronoxAPI/KronoxAPI.csproj +++ b/KronoxAPI/KronoxAPI.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable enable diff --git a/Models/WebAPIModels.csproj b/Models/WebAPIModels.csproj index 0c37b0f..0e1fe4f 100644 --- a/Models/WebAPIModels.csproj +++ b/Models/WebAPIModels.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable enable diff --git a/TumbleBackend/TumbleBackend.csproj b/TumbleBackend/TumbleBackend.csproj index c5c68bd..3057e22 100644 --- a/TumbleBackend/TumbleBackend.csproj +++ b/TumbleBackend/TumbleBackend.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable enable diff --git a/Utilities/Utilities.csproj b/Utilities/Utilities.csproj index 3fef810..586dd55 100644 --- a/Utilities/Utilities.csproj +++ b/Utilities/Utilities.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable enable From 2658790fe5fa7506dfdeff054bf4b16304fcd852 Mon Sep 17 00:00:00 2001 From: adisve Date: Sat, 7 Sep 2024 18:54:24 +0200 Subject: [PATCH 07/10] Add working stuff for test user --- DatabaseAPI/DatabaseAPI.csproj | 2 +- Dockerfile | 4 - HttpClient/TumbleHttpClient.csproj | 2 +- KronoxAPI/KronoxAPI - Backup.csproj | 2 +- KronoxAPI/KronoxAPI.csproj | 2 +- KronoxAPI/Utilities/MultiRequest.cs | 8 +- Models/WebAPIModels.csproj | 2 +- TumbleBackend/Controllers/UserController.cs | 7 +- .../Middleware/TestUserMiddleware.cs | 65 ++- TumbleBackend/MockData/mockResources.json | 94 ++++ .../mockResourcesWithAvailabilities.json | 489 ++++++++++++++++++ TumbleBackend/Program.cs | 6 +- TumbleBackend/TumbleBackend.csproj | 2 +- Utilities/Utilities.csproj | 2 +- 14 files changed, 657 insertions(+), 30 deletions(-) diff --git a/DatabaseAPI/DatabaseAPI.csproj b/DatabaseAPI/DatabaseAPI.csproj index b5cdc7e..86e338b 100644 --- a/DatabaseAPI/DatabaseAPI.csproj +++ b/DatabaseAPI/DatabaseAPI.csproj @@ -1,7 +1,7 @@ - net8.0 + net7.0 enable enable diff --git a/Dockerfile b/Dockerfile index e1d9249..a5d2dbd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,6 @@ FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:7.0 AS build WORKDIR /build -ENV DOTNET_NUGET_SIGNATURE_VERIFICATION=false # Copy csproj and restore as distinct layers COPY ["TumbleBackend/*.csproj", "TumbleBackend/"] RUN dotnet restore "TumbleBackend/TumbleBackend.csproj" @@ -17,13 +16,10 @@ RUN dotnet publish "TumbleBackend.csproj" -c Release -o /app/publish # Stage 2: Prepare the runtime image FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS final -ENV DOTNET_NUGET_SIGNATURE_VERIFICATION=false WORKDIR /app COPY --from=build /app/publish . -# Open port 443 for SSL/TLS EXPOSE 80 -EXPOSE 7036 ENTRYPOINT ["dotnet", "TumbleBackend.dll"] diff --git a/HttpClient/TumbleHttpClient.csproj b/HttpClient/TumbleHttpClient.csproj index 30402ac..cfadb03 100644 --- a/HttpClient/TumbleHttpClient.csproj +++ b/HttpClient/TumbleHttpClient.csproj @@ -1,7 +1,7 @@ - net8.0 + net7.0 enable enable diff --git a/KronoxAPI/KronoxAPI - Backup.csproj b/KronoxAPI/KronoxAPI - Backup.csproj index 5f26e47..6cd03a4 100644 --- a/KronoxAPI/KronoxAPI - Backup.csproj +++ b/KronoxAPI/KronoxAPI - Backup.csproj @@ -1,7 +1,7 @@ - net8.0 + net7.0 enable enable diff --git a/KronoxAPI/KronoxAPI.csproj b/KronoxAPI/KronoxAPI.csproj index 44a4bc2..674de88 100644 --- a/KronoxAPI/KronoxAPI.csproj +++ b/KronoxAPI/KronoxAPI.csproj @@ -1,7 +1,7 @@ - net8.0 + net7.0 enable enable diff --git a/KronoxAPI/Utilities/MultiRequest.cs b/KronoxAPI/Utilities/MultiRequest.cs index e313ee8..3647369 100644 --- a/KronoxAPI/Utilities/MultiRequest.cs +++ b/KronoxAPI/Utilities/MultiRequest.cs @@ -19,9 +19,11 @@ public class MultiRequest public MultiRequest(int timeout = 5) { var httpClientHandler = new HttpClientHandler(); - client = new HttpClient(httpClientHandler, false); - client.Timeout = TimeSpan.FromSeconds(timeout); - client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"); + client = new HttpClient(httpClientHandler, false) + { + Timeout = TimeSpan.FromSeconds(timeout) + }; + //client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"); } /// diff --git a/Models/WebAPIModels.csproj b/Models/WebAPIModels.csproj index 0e1fe4f..0c37b0f 100644 --- a/Models/WebAPIModels.csproj +++ b/Models/WebAPIModels.csproj @@ -1,7 +1,7 @@ - net8.0 + net7.0 enable enable diff --git a/TumbleBackend/Controllers/UserController.cs b/TumbleBackend/Controllers/UserController.cs index bc72fcb..218b8b0 100644 --- a/TumbleBackend/Controllers/UserController.cs +++ b/TumbleBackend/Controllers/UserController.cs @@ -31,14 +31,12 @@ public UserController(ILogger logger) /// Retrieves a KronoxUser object for the user with the provided refreshToken. If the user is a test user, a test user object is returned with mock data upon further requests. /// /// - /// /// /// /// /// /// /// - /// /// /// /// @@ -46,12 +44,13 @@ public UserController(ILogger logger) public async Task GetKronoxUserAsync([FromServices] JwtUtil jwtUtil, [FromQuery] SchoolEnum schoolId, [FromHeader(Name = "X-auth-token")] string refreshToken) { var kronoxReqClient = (IKronoxRequestClient)HttpContext.Items[KronoxReqClientKeys.SingleClient]!; + var school = schoolId.GetSchool()!; + var creds = jwtUtil.ValidateAndReadRefreshToken(refreshToken); if (creds == null) return Unauthorized(new Error("Couldn't login user from refreshToken, please log out and back in manually.")); - - // If the user is not a test user, attempt to auto-login the user and return the KronoxUser object. + try { var kronoxUser = await School.LoginAsync(kronoxReqClient, creds.Username, creds.Password); diff --git a/TumbleBackend/Middleware/TestUserMiddleware.cs b/TumbleBackend/Middleware/TestUserMiddleware.cs index dc07a3b..b7aec67 100644 --- a/TumbleBackend/Middleware/TestUserMiddleware.cs +++ b/TumbleBackend/Middleware/TestUserMiddleware.cs @@ -10,11 +10,13 @@ public class TestUserMiddleware { private readonly RequestDelegate _next; private readonly string _mockDataPath; + private readonly ILogger _logger; - public TestUserMiddleware(RequestDelegate next, IWebHostEnvironment env) + public TestUserMiddleware(RequestDelegate next, IWebHostEnvironment env, ILogger logger) { _next = next; _mockDataPath = Path.Combine(env.ContentRootPath, "MockData"); + _logger = logger; } public async Task InvokeAsync(HttpContext context, JwtUtil jwtUtil, TestUserUtil testUserUtil) @@ -22,14 +24,22 @@ public async Task InvokeAsync(HttpContext context, JwtUtil jwtUtil, TestUserUtil var refreshToken = context.Request.Headers["X-auth-token"].FirstOrDefault(); var path = context.Request.Path; - if (IsLoginRequest(context) && TryGetCredentials(context, out var username, out var password) && testUserUtil.IsTestUser(username, password)) + context.Request.EnableBuffering(); + var body = await new StreamReader(context.Request.Body).ReadToEndAsync(); + context.Request.Body.Position = 0; + + if (IsLoginRequest(context) && TryGetCredentials(body, out var username, out var password)) { - await HandleTestUserLoginResponse(context, jwtUtil, testUserUtil, username, password); - return; + if (testUserUtil.IsTestUser(username, password)) + { + await HandleTestUserLoginResponse(context, jwtUtil, testUserUtil, username, password); + return; + } } else if (refreshToken != null) { var creds = jwtUtil.ValidateAndReadRefreshToken(refreshToken); + if (creds != null && testUserUtil.IsTestUser(creds.Username, creds.Password)) { if (path.StartsWithSegments("/api/resources")) @@ -38,31 +48,68 @@ public async Task InvokeAsync(HttpContext context, JwtUtil jwtUtil, TestUserUtil return; } + if (path.StartsWithSegments("/api/users")) + { + _logger.LogInformation("Returning test user information."); + await HandleTestUserInfoResponse(context, jwtUtil, testUserUtil, creds.Username, creds.Password); + return; + } + await HandleTestUserTokenResponse(context, jwtUtil, testUserUtil, creds.Username, creds.Password, refreshToken); return; } } + _logger.LogInformation("TestUserMiddleware: No test user found. Proceeding to next middleware."); await _next(context); } + + private static async Task HandleTestUserInfoResponse(HttpContext context, JwtUtil jwtUtil, TestUserUtil testUserUtil, string username, string password) + { + var testUser = testUserUtil.GetTestUser(); + var newRefreshToken = jwtUtil.GenerateRefreshToken(username, password); + var sessionDetails = new SessionDetails("", ""); + + context.Response.Headers.Add("X-auth-token", newRefreshToken); + context.Response.Headers.Add("X-session-token", sessionDetails.ToJson()); + + var testUserModel = testUser.ToWebModel(newRefreshToken, "", sessionDetails); + await context.Response.WriteAsJsonAsync(testUserModel); + } + + private static bool IsLoginRequest(HttpContext context) { return context.Request.Method == HttpMethods.Post && context.Request.Path == "/api/users/login"; } - private static bool TryGetCredentials(HttpContext context, out string? username, out string? password) + private static bool IsGetUserRequest(HttpContext context) { - username = context.Request.Form["username"].FirstOrDefault(); - password = context.Request.Form["password"].FirstOrDefault(); - return username != null && password != null; + return context.Request.Method == HttpMethods.Get && context.Request.Path == "/api/users"; + } + + private static bool TryGetCredentials(string body, out string? username, out string? password) + { + username = null; + password = null; + + var json = JsonSerializer.Deserialize>(body); + if (json != null && json.ContainsKey("username") && json.ContainsKey("password")) + { + username = json["username"]; + password = json["password"]; + return true; + } + + return false; } private static async Task HandleTestUserLoginResponse(HttpContext context, JwtUtil jwtUtil, TestUserUtil testUserUtil, string? username, string? password) { if (username == null || password == null) throw new ArgumentNullException("Username and password must be provided."); - + var testUser = testUserUtil.GetTestUser(); var newRefreshToken = jwtUtil.GenerateRefreshToken(username, password); var sessionDetails = new SessionDetails("", ""); diff --git a/TumbleBackend/MockData/mockResources.json b/TumbleBackend/MockData/mockResources.json index e69de29..4d2bb52 100644 --- a/TumbleBackend/MockData/mockResources.json +++ b/TumbleBackend/MockData/mockResources.json @@ -0,0 +1,94 @@ +[ + { + "id": "string", + "name": "string", + "timeSlots": [ + { + "id": 0, + "from": "2024-09-07T15:34:24.615Z", + "to": "2024-09-07T15:34:24.615Z", + "duration": { + "ticks": 0, + "days": 0, + "hours": 0, + "milliseconds": 0, + "microseconds": 0, + "nanoseconds": 0, + "minutes": 0, + "seconds": 0, + "totalDays": 0, + "totalHours": 0, + "totalMilliseconds": 0, + "totalMicroseconds": 0, + "totalNanoseconds": 0, + "totalMinutes": 0, + "totalSeconds": 0 + } + } + ], + "date": "2024-09-07T15:34:24.615Z", + "locationIds": ["string"], + "availabilities": { + "additionalProp1": { + "additionalProp1": { + "availability": "UNAVAILABLE", + "locationId": "string", + "resourceType": "string", + "timeSlotId": "string" + }, + "additionalProp2": { + "availability": "UNAVAILABLE", + "locationId": "string", + "resourceType": "string", + "timeSlotId": "string" + }, + "additionalProp3": { + "availability": "UNAVAILABLE", + "locationId": "string", + "resourceType": "string", + "timeSlotId": "string" + } + }, + "additionalProp2": { + "additionalProp1": { + "availability": "UNAVAILABLE", + "locationId": "string", + "resourceType": "string", + "timeSlotId": "string" + }, + "additionalProp2": { + "availability": "UNAVAILABLE", + "locationId": "string", + "resourceType": "string", + "timeSlotId": "string" + }, + "additionalProp3": { + "availability": "UNAVAILABLE", + "locationId": "string", + "resourceType": "string", + "timeSlotId": "string" + } + }, + "additionalProp3": { + "additionalProp1": { + "availability": "UNAVAILABLE", + "locationId": "string", + "resourceType": "string", + "timeSlotId": "string" + }, + "additionalProp2": { + "availability": "UNAVAILABLE", + "locationId": "string", + "resourceType": "string", + "timeSlotId": "string" + }, + "additionalProp3": { + "availability": "UNAVAILABLE", + "locationId": "string", + "resourceType": "string", + "timeSlotId": "string" + } + } + } + } +] diff --git a/TumbleBackend/MockData/mockResourcesWithAvailabilities.json b/TumbleBackend/MockData/mockResourcesWithAvailabilities.json index e69de29..0207dca 100644 --- a/TumbleBackend/MockData/mockResourcesWithAvailabilities.json +++ b/TumbleBackend/MockData/mockResourcesWithAvailabilities.json @@ -0,0 +1,489 @@ +[ + { + "id": "FLIK_0005", + "name": "Grouproom building15", + "timeSlots": [ + { + "id": 0, + "from": "0001-01-01T08:00:00", + "to": "0001-01-01T09:00:00", + "duration": "01:00:00" + }, + { + "id": 1, + "from": "0001-01-01T09:00:00", + "to": "0001-01-01T12:00:00", + "duration": "03:00:00" + }, + { + "id": 2, + "from": "0001-01-01T12:00:00", + "to": "0001-01-01T15:00:00", + "duration": "03:00:00" + }, + { + "id": 3, + "from": "0001-01-01T15:00:00", + "to": "0001-01-01T18:00:00", + "duration": "03:00:00" + } + ], + "date": "0001-01-01T00:00:00", + "locationIds": ["15-416", "15-417", "15-430", "15-431", "15-432"], + "availabilities": { + "15-416": { + "0": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "1": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "2": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "3": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + } + }, + "15-417": { + "0": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "1": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "2": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "3": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + } + }, + "15-430": { + "0": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "1": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "2": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "3": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + } + }, + "15-431": { + "0": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "1": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "2": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "3": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + } + }, + "15-432": { + "0": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "1": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "2": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "3": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + } + } + } + }, + { + "id": "FLIK_0010", + "name": "Grouproom in the library.", + "timeSlots": [ + { + "id": 0, + "from": "0001-01-01T08:00:00", + "to": "0001-01-01T09:00:00", + "duration": "01:00:00" + }, + { + "id": 1, + "from": "0001-01-01T09:00:00", + "to": "0001-01-01T12:00:00", + "duration": "03:00:00" + }, + { + "id": 2, + "from": "0001-01-01T12:00:00", + "to": "0001-01-01T15:00:00", + "duration": "03:00:00" + } + ], + "date": "0001-01-01T00:00:00", + "locationIds": [ + "07-320A", + "07-320B", + "07-320C", + "07-320D", + "07-320E", + "07-320F", + "07-320G", + "07-320H", + "07-320J", + "07-320K", + "07-320L", + "07-320M", + "07-320N", + "07-320O" + ], + "availabilities": { + "07-320A": { + "0": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "1": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "2": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + } + }, + "07-320B": { + "0": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "1": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "2": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + } + }, + "07-320C": { + "0": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "1": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "2": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + } + }, + "07-320D": { + "0": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "1": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "2": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + } + }, + "07-320E": { + "0": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "1": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "2": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + } + }, + "07-320F": { + "0": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "1": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "2": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + } + }, + "07-320G": { + "0": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "1": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "2": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + } + }, + "07-320H": { + "0": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "1": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "2": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + } + }, + "07-320J": { + "0": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "1": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "2": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + } + }, + "07-320K": { + "0": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "1": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "2": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + } + }, + "07-320L": { + "0": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "1": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "2": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + } + }, + "07-320M": { + "0": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "1": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "2": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + } + }, + "07-320N": { + "0": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "1": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "2": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + } + }, + "07-320O": { + "0": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "1": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + }, + "2": { + "availability": "UNAVAILABLE", + "locationId": null, + "resourceType": null, + "timeSlotId": null + } + } + } + } +] diff --git a/TumbleBackend/Program.cs b/TumbleBackend/Program.cs index 9abded3..d2a26e2 100644 --- a/TumbleBackend/Program.cs +++ b/TumbleBackend/Program.cs @@ -21,8 +21,8 @@ var builder = WebApplication.CreateBuilder(args); -ConfigureConfiguration(builder); -ConfigureTracing(builder); +ConfigureEnvironmentAndSecrets(builder); +//ConfigureTracing(builder); ConfigureRateLimiting(builder); ConfigureMongoDb(); @@ -35,7 +35,7 @@ app.Run(); -void ConfigureConfiguration(WebApplicationBuilder builder) +void ConfigureEnvironmentAndSecrets(WebApplicationBuilder builder) { builder.Configuration.AddJsonFile("secrets/secrets.json", optional: true); builder.Configuration.AddEnvironmentVariables(); diff --git a/TumbleBackend/TumbleBackend.csproj b/TumbleBackend/TumbleBackend.csproj index 3057e22..c5c68bd 100644 --- a/TumbleBackend/TumbleBackend.csproj +++ b/TumbleBackend/TumbleBackend.csproj @@ -1,7 +1,7 @@ - net8.0 + net7.0 enable enable diff --git a/Utilities/Utilities.csproj b/Utilities/Utilities.csproj index 586dd55..3fef810 100644 --- a/Utilities/Utilities.csproj +++ b/Utilities/Utilities.csproj @@ -1,7 +1,7 @@ - net8.0 + net7.0 enable enable From ff9f5745871d7d0d3d9ef71c2bf7d71741354e40 Mon Sep 17 00:00:00 2001 From: Lasse Poulsen Date: Sat, 7 Sep 2024 22:21:48 +0200 Subject: [PATCH 08/10] Add mock data to json files --- TumbleBackend/MockData/mockBookingResult.json | 15 + TumbleBackend/MockData/mockResources.json | 106 +---- .../mockResourcesWithAvailabilities.json | 382 +++++++++--------- TumbleBackend/MockData/mockUserBookings.json | 17 + TumbleBackend/MockData/mockUserEvents.json | 30 ++ docs/.obsidian/workspace.json | 4 +- push.sh | 34 ++ run.sh | 1 + 8 files changed, 309 insertions(+), 280 deletions(-) create mode 100644 push.sh create mode 100644 run.sh diff --git a/TumbleBackend/MockData/mockBookingResult.json b/TumbleBackend/MockData/mockBookingResult.json index e69de29..188eb50 100644 --- a/TumbleBackend/MockData/mockBookingResult.json +++ b/TumbleBackend/MockData/mockBookingResult.json @@ -0,0 +1,15 @@ +{ + "id": "BokningsId_20240907_000000015", + "resourceId": "FLIK_0005", + "timeSlot": { + "id": null, + "from": "2024-09-09T08:00:00", + "to": "2024-09-09T09:00:00", + "duration": "01:00:00" + }, + "locationId": "15-416", + "showConfirmButton": false, + "showUnbookButton": true, + "confirmationOpen": "2024-09-09T07:30:00", + "confirmationClosed": "2024-09-09T08:15:00" +} diff --git a/TumbleBackend/MockData/mockResources.json b/TumbleBackend/MockData/mockResources.json index 4d2bb52..d436bf8 100644 --- a/TumbleBackend/MockData/mockResources.json +++ b/TumbleBackend/MockData/mockResources.json @@ -1,94 +1,18 @@ [ - { - "id": "string", - "name": "string", - "timeSlots": [ - { - "id": 0, - "from": "2024-09-07T15:34:24.615Z", - "to": "2024-09-07T15:34:24.615Z", - "duration": { - "ticks": 0, - "days": 0, - "hours": 0, - "milliseconds": 0, - "microseconds": 0, - "nanoseconds": 0, - "minutes": 0, - "seconds": 0, - "totalDays": 0, - "totalHours": 0, - "totalMilliseconds": 0, - "totalMicroseconds": 0, - "totalNanoseconds": 0, - "totalMinutes": 0, - "totalSeconds": 0 - } - } - ], - "date": "2024-09-07T15:34:24.615Z", - "locationIds": ["string"], - "availabilities": { - "additionalProp1": { - "additionalProp1": { - "availability": "UNAVAILABLE", - "locationId": "string", - "resourceType": "string", - "timeSlotId": "string" - }, - "additionalProp2": { - "availability": "UNAVAILABLE", - "locationId": "string", - "resourceType": "string", - "timeSlotId": "string" - }, - "additionalProp3": { - "availability": "UNAVAILABLE", - "locationId": "string", - "resourceType": "string", - "timeSlotId": "string" - } - }, - "additionalProp2": { - "additionalProp1": { - "availability": "UNAVAILABLE", - "locationId": "string", - "resourceType": "string", - "timeSlotId": "string" - }, - "additionalProp2": { - "availability": "UNAVAILABLE", - "locationId": "string", - "resourceType": "string", - "timeSlotId": "string" - }, - "additionalProp3": { - "availability": "UNAVAILABLE", - "locationId": "string", - "resourceType": "string", - "timeSlotId": "string" - } - }, - "additionalProp3": { - "additionalProp1": { - "availability": "UNAVAILABLE", - "locationId": "string", - "resourceType": "string", - "timeSlotId": "string" - }, - "additionalProp2": { - "availability": "UNAVAILABLE", - "locationId": "string", - "resourceType": "string", - "timeSlotId": "string" - }, - "additionalProp3": { - "availability": "UNAVAILABLE", - "locationId": "string", - "resourceType": "string", - "timeSlotId": "string" - } - } + { + "id": "FLIK_0005", + "name": "Grouproom building15", + "timeSlots": null, + "date": null, + "locationIds": null, + "availabilities": null + }, + { + "id": "FLIK_0010", + "name": "Grouproom in the library.", + "timeSlots": null, + "date": null, + "locationIds": null, + "availabilities": null } - } ] diff --git a/TumbleBackend/MockData/mockResourcesWithAvailabilities.json b/TumbleBackend/MockData/mockResourcesWithAvailabilities.json index 0207dca..fa7a202 100644 --- a/TumbleBackend/MockData/mockResourcesWithAvailabilities.json +++ b/TumbleBackend/MockData/mockResourcesWithAvailabilities.json @@ -5,105 +5,111 @@ "timeSlots": [ { "id": 0, - "from": "0001-01-01T08:00:00", - "to": "0001-01-01T09:00:00", + "from": "2024-09-09T08:00:00", + "to": "2024-09-09T09:00:00", "duration": "01:00:00" }, { "id": 1, - "from": "0001-01-01T09:00:00", - "to": "0001-01-01T12:00:00", + "from": "2024-09-09T09:00:00", + "to": "2024-09-09T12:00:00", "duration": "03:00:00" }, { "id": 2, - "from": "0001-01-01T12:00:00", - "to": "0001-01-01T15:00:00", + "from": "2024-09-09T12:00:00", + "to": "2024-09-09T15:00:00", "duration": "03:00:00" }, { "id": 3, - "from": "0001-01-01T15:00:00", - "to": "0001-01-01T18:00:00", + "from": "2024-09-09T15:00:00", + "to": "2024-09-09T18:00:00", "duration": "03:00:00" } ], - "date": "0001-01-01T00:00:00", - "locationIds": ["15-416", "15-417", "15-430", "15-431", "15-432"], + "date": "2024-09-09T00:00:00", + "locationIds": [ + "15-416", + "15-417", + "15-430", + "15-431", + "15-432" + ], "availabilities": { "15-416": { "0": { - "availability": "UNAVAILABLE", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "availability": "AVAILABLE", + "locationId": "15-416", + "resourceType": "RESURSER_LOKALER", + "timeSlotId": "0" }, "1": { - "availability": "UNAVAILABLE", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "availability": "AVAILABLE", + "locationId": "15-416", + "resourceType": "RESURSER_LOKALER", + "timeSlotId": "1" }, "2": { - "availability": "UNAVAILABLE", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "availability": "AVAILABLE", + "locationId": "15-416", + "resourceType": "RESURSER_LOKALER", + "timeSlotId": "2" }, "3": { - "availability": "UNAVAILABLE", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "availability": "AVAILABLE", + "locationId": "15-416", + "resourceType": "RESURSER_LOKALER", + "timeSlotId": "3" } }, "15-417": { "0": { - "availability": "UNAVAILABLE", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "availability": "AVAILABLE", + "locationId": "15-417", + "resourceType": "RESURSER_LOKALER", + "timeSlotId": "0" }, "1": { - "availability": "UNAVAILABLE", + "availability": "BOOKED", "locationId": null, "resourceType": null, "timeSlotId": null }, "2": { - "availability": "UNAVAILABLE", + "availability": "BOOKED", "locationId": null, "resourceType": null, "timeSlotId": null }, "3": { - "availability": "UNAVAILABLE", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "availability": "AVAILABLE", + "locationId": "15-417", + "resourceType": "RESURSER_LOKALER", + "timeSlotId": "3" } }, "15-430": { "0": { - "availability": "UNAVAILABLE", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "availability": "AVAILABLE", + "locationId": "15-430", + "resourceType": "RESURSER_LOKALER", + "timeSlotId": "0" }, "1": { - "availability": "UNAVAILABLE", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "availability": "AVAILABLE", + "locationId": "15-430", + "resourceType": "RESURSER_LOKALER", + "timeSlotId": "1" }, "2": { - "availability": "UNAVAILABLE", + "availability": "BOOKED", "locationId": null, "resourceType": null, "timeSlotId": null }, "3": { - "availability": "UNAVAILABLE", + "availability": "BOOKED", "locationId": null, "resourceType": null, "timeSlotId": null @@ -111,54 +117,54 @@ }, "15-431": { "0": { - "availability": "UNAVAILABLE", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "availability": "AVAILABLE", + "locationId": "15-431", + "resourceType": "RESURSER_LOKALER", + "timeSlotId": "0" }, "1": { - "availability": "UNAVAILABLE", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "availability": "AVAILABLE", + "locationId": "15-431", + "resourceType": "RESURSER_LOKALER", + "timeSlotId": "1" }, "2": { - "availability": "UNAVAILABLE", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "availability": "AVAILABLE", + "locationId": "15-431", + "resourceType": "RESURSER_LOKALER", + "timeSlotId": "2" }, "3": { - "availability": "UNAVAILABLE", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "availability": "AVAILABLE", + "locationId": "15-431", + "resourceType": "RESURSER_LOKALER", + "timeSlotId": "3" } }, "15-432": { "0": { - "availability": "UNAVAILABLE", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "availability": "AVAILABLE", + "locationId": "15-432", + "resourceType": "RESURSER_LOKALER", + "timeSlotId": "0" }, "1": { - "availability": "UNAVAILABLE", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "availability": "AVAILABLE", + "locationId": "15-432", + "resourceType": "RESURSER_LOKALER", + "timeSlotId": "1" }, "2": { - "availability": "UNAVAILABLE", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "availability": "AVAILABLE", + "locationId": "15-432", + "resourceType": "RESURSER_LOKALER", + "timeSlotId": "2" }, "3": { - "availability": "UNAVAILABLE", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "availability": "AVAILABLE", + "locationId": "15-432", + "resourceType": "RESURSER_LOKALER", + "timeSlotId": "3" } } } @@ -169,24 +175,24 @@ "timeSlots": [ { "id": 0, - "from": "0001-01-01T08:00:00", - "to": "0001-01-01T09:00:00", + "from": "2024-09-09T08:00:00", + "to": "2024-09-09T09:00:00", "duration": "01:00:00" }, { "id": 1, - "from": "0001-01-01T09:00:00", - "to": "0001-01-01T12:00:00", + "from": "2024-09-09T09:00:00", + "to": "2024-09-09T12:00:00", "duration": "03:00:00" }, { "id": 2, - "from": "0001-01-01T12:00:00", - "to": "0001-01-01T15:00:00", + "from": "2024-09-09T12:00:00", + "to": "2024-09-09T15:00:00", "duration": "03:00:00" } ], - "date": "0001-01-01T00:00:00", + "date": "2024-09-09T00:00:00", "locationIds": [ "07-320A", "07-320B", @@ -206,19 +212,19 @@ "availabilities": { "07-320A": { "0": { - "availability": "UNAVAILABLE", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "availability": "AVAILABLE", + "locationId": "07-320A", + "resourceType": "RESURSER_LOKALER", + "timeSlotId": "0" }, "1": { - "availability": "UNAVAILABLE", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "availability": "AVAILABLE", + "locationId": "07-320A", + "resourceType": "RESURSER_LOKALER", + "timeSlotId": "1" }, "2": { - "availability": "UNAVAILABLE", + "availability": "BOOKED", "locationId": null, "resourceType": null, "timeSlotId": null @@ -226,19 +232,19 @@ }, "07-320B": { "0": { - "availability": "UNAVAILABLE", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "availability": "AVAILABLE", + "locationId": "07-320B", + "resourceType": "RESURSER_LOKALER", + "timeSlotId": "0" }, "1": { - "availability": "UNAVAILABLE", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "availability": "AVAILABLE", + "locationId": "07-320B", + "resourceType": "RESURSER_LOKALER", + "timeSlotId": "1" }, "2": { - "availability": "UNAVAILABLE", + "availability": "BOOKED", "locationId": null, "resourceType": null, "timeSlotId": null @@ -246,19 +252,19 @@ }, "07-320C": { "0": { - "availability": "UNAVAILABLE", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "availability": "AVAILABLE", + "locationId": "07-320C", + "resourceType": "RESURSER_LOKALER", + "timeSlotId": "0" }, "1": { - "availability": "UNAVAILABLE", + "availability": "BOOKED", "locationId": null, "resourceType": null, "timeSlotId": null }, "2": { - "availability": "UNAVAILABLE", + "availability": "BOOKED", "locationId": null, "resourceType": null, "timeSlotId": null @@ -266,19 +272,19 @@ }, "07-320D": { "0": { - "availability": "UNAVAILABLE", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "availability": "AVAILABLE", + "locationId": "07-320D", + "resourceType": "RESURSER_LOKALER", + "timeSlotId": "0" }, "1": { - "availability": "UNAVAILABLE", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "availability": "AVAILABLE", + "locationId": "07-320D", + "resourceType": "RESURSER_LOKALER", + "timeSlotId": "1" }, "2": { - "availability": "UNAVAILABLE", + "availability": "BOOKED", "locationId": null, "resourceType": null, "timeSlotId": null @@ -286,39 +292,39 @@ }, "07-320E": { "0": { - "availability": "UNAVAILABLE", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "availability": "AVAILABLE", + "locationId": "07-320E", + "resourceType": "RESURSER_LOKALER", + "timeSlotId": "0" }, "1": { - "availability": "UNAVAILABLE", + "availability": "BOOKED", "locationId": null, "resourceType": null, "timeSlotId": null }, "2": { - "availability": "UNAVAILABLE", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "availability": "AVAILABLE", + "locationId": "07-320E", + "resourceType": "RESURSER_LOKALER", + "timeSlotId": "2" } }, "07-320F": { "0": { - "availability": "UNAVAILABLE", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "availability": "AVAILABLE", + "locationId": "07-320F", + "resourceType": "RESURSER_LOKALER", + "timeSlotId": "0" }, "1": { - "availability": "UNAVAILABLE", + "availability": "BOOKED", "locationId": null, "resourceType": null, "timeSlotId": null }, "2": { - "availability": "UNAVAILABLE", + "availability": "BOOKED", "locationId": null, "resourceType": null, "timeSlotId": null @@ -326,19 +332,19 @@ }, "07-320G": { "0": { - "availability": "UNAVAILABLE", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "availability": "AVAILABLE", + "locationId": "07-320G", + "resourceType": "RESURSER_LOKALER", + "timeSlotId": "0" }, "1": { - "availability": "UNAVAILABLE", + "availability": "BOOKED", "locationId": null, "resourceType": null, "timeSlotId": null }, "2": { - "availability": "UNAVAILABLE", + "availability": "BOOKED", "locationId": null, "resourceType": null, "timeSlotId": null @@ -346,19 +352,19 @@ }, "07-320H": { "0": { - "availability": "UNAVAILABLE", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "availability": "AVAILABLE", + "locationId": "07-320H", + "resourceType": "RESURSER_LOKALER", + "timeSlotId": "0" }, "1": { - "availability": "UNAVAILABLE", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "availability": "AVAILABLE", + "locationId": "07-320H", + "resourceType": "RESURSER_LOKALER", + "timeSlotId": "1" }, "2": { - "availability": "UNAVAILABLE", + "availability": "BOOKED", "locationId": null, "resourceType": null, "timeSlotId": null @@ -366,19 +372,19 @@ }, "07-320J": { "0": { - "availability": "UNAVAILABLE", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "availability": "AVAILABLE", + "locationId": "07-320J", + "resourceType": "RESURSER_LOKALER", + "timeSlotId": "0" }, "1": { - "availability": "UNAVAILABLE", + "availability": "BOOKED", "locationId": null, "resourceType": null, "timeSlotId": null }, "2": { - "availability": "UNAVAILABLE", + "availability": "BOOKED", "locationId": null, "resourceType": null, "timeSlotId": null @@ -386,19 +392,19 @@ }, "07-320K": { "0": { - "availability": "UNAVAILABLE", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "availability": "AVAILABLE", + "locationId": "07-320K", + "resourceType": "RESURSER_LOKALER", + "timeSlotId": "0" }, "1": { - "availability": "UNAVAILABLE", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "availability": "AVAILABLE", + "locationId": "07-320K", + "resourceType": "RESURSER_LOKALER", + "timeSlotId": "1" }, "2": { - "availability": "UNAVAILABLE", + "availability": "BOOKED", "locationId": null, "resourceType": null, "timeSlotId": null @@ -406,19 +412,19 @@ }, "07-320L": { "0": { - "availability": "UNAVAILABLE", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "availability": "AVAILABLE", + "locationId": "07-320L", + "resourceType": "RESURSER_LOKALER", + "timeSlotId": "0" }, "1": { - "availability": "UNAVAILABLE", + "availability": "BOOKED", "locationId": null, "resourceType": null, "timeSlotId": null }, "2": { - "availability": "UNAVAILABLE", + "availability": "BOOKED", "locationId": null, "resourceType": null, "timeSlotId": null @@ -426,19 +432,19 @@ }, "07-320M": { "0": { - "availability": "UNAVAILABLE", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "availability": "AVAILABLE", + "locationId": "07-320M", + "resourceType": "RESURSER_LOKALER", + "timeSlotId": "0" }, "1": { - "availability": "UNAVAILABLE", + "availability": "BOOKED", "locationId": null, "resourceType": null, "timeSlotId": null }, "2": { - "availability": "UNAVAILABLE", + "availability": "BOOKED", "locationId": null, "resourceType": null, "timeSlotId": null @@ -446,19 +452,19 @@ }, "07-320N": { "0": { - "availability": "UNAVAILABLE", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "availability": "AVAILABLE", + "locationId": "07-320N", + "resourceType": "RESURSER_LOKALER", + "timeSlotId": "0" }, "1": { - "availability": "UNAVAILABLE", + "availability": "BOOKED", "locationId": null, "resourceType": null, "timeSlotId": null }, "2": { - "availability": "UNAVAILABLE", + "availability": "BOOKED", "locationId": null, "resourceType": null, "timeSlotId": null @@ -466,19 +472,19 @@ }, "07-320O": { "0": { - "availability": "UNAVAILABLE", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "availability": "AVAILABLE", + "locationId": "07-320O", + "resourceType": "RESURSER_LOKALER", + "timeSlotId": "0" }, "1": { - "availability": "UNAVAILABLE", + "availability": "BOOKED", "locationId": null, "resourceType": null, "timeSlotId": null }, "2": { - "availability": "UNAVAILABLE", + "availability": "BOOKED", "locationId": null, "resourceType": null, "timeSlotId": null @@ -486,4 +492,4 @@ } } } -] +] \ No newline at end of file diff --git a/TumbleBackend/MockData/mockUserBookings.json b/TumbleBackend/MockData/mockUserBookings.json index e69de29..5280b2a 100644 --- a/TumbleBackend/MockData/mockUserBookings.json +++ b/TumbleBackend/MockData/mockUserBookings.json @@ -0,0 +1,17 @@ +[ + { + "id": "BokningsId_20240907_000000015", + "resourceId": "FLIK_0005", + "timeSlot": { + "id": null, + "from": "2024-09-09T08:00:00", + "to": "2024-09-09T09:00:00", + "duration": "01:00:00" + }, + "locationId": "15-416", + "showConfirmButton": false, + "showUnbookButton": true, + "confirmationOpen": "2024-09-09T07:30:00", + "confirmationClosed": "2024-09-09T08:15:00" + } +] diff --git a/TumbleBackend/MockData/mockUserEvents.json b/TumbleBackend/MockData/mockUserEvents.json index e69de29..b33aceb 100644 --- a/TumbleBackend/MockData/mockUserEvents.json +++ b/TumbleBackend/MockData/mockUserEvents.json @@ -0,0 +1,30 @@ +{ + "upcomingEvents": [ + { + "title": "English Exam", + "type": "Written exam", + "eventStart": "2024-12-01T09:00:00", + "eventEnd": "2024-12-01T12:00:00", + "location": "Room 101", + "firstSignupDate": "2024-11-01T00:00:00" + } + ], + "registeredEvents": [], + "unregisteredEvents": [ + { + "id": "1", + "participatorId": "1", + "supportId": null, + "anonymousCode": "", + "isRegistered": false, + "supportAvailable": false, + "requiresChoosingLocation": false, + "title": "Math Exam", + "type": "Written exam", + "eventStart": "2024-12-02T10:00:00", + "eventEnd": "2024-12-02T14:00:00", + "location": "Room 131", + "lastSignupDate": "2024-11-29T00:00:00" + } + ] +} diff --git a/docs/.obsidian/workspace.json b/docs/.obsidian/workspace.json index 30d4480..d8fec3a 100644 --- a/docs/.obsidian/workspace.json +++ b/docs/.obsidian/workspace.json @@ -136,7 +136,9 @@ "state": { "type": "all-properties", "state": { - "sortOrder": "frequency" + "sortOrder": "frequency", + "showSearch": false, + "searchQuery": "" } } } diff --git a/push.sh b/push.sh new file mode 100644 index 0000000..37c5af9 --- /dev/null +++ b/push.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# Ensure one argument is provided +if [ "$#" -ne 1 ]; then + echo "Usage: $0 " + exit 1 +fi + +# Assign argument to variable +DOTNET_VERSION=$1 + +# Determine the architecture of the current machine +ARCH=$(uname -m) + +# Translate the architecture to the desired format +case "$ARCH" in + x86_64) + LOCAL_PC_ARCHITECTURE="amd64" + ;; + aarch64) + LOCAL_PC_ARCHITECTURE="arm64" + ;; + *) + echo "Unsupported architecture: $ARCH" + exit 1 + ;; +esac + +# Construct the Docker buildx build command +DOCKER_COMMAND="docker buildx build -t ghcr.io/tumble-for-kronox/tumble-backend-dotnet-${DOTNET_VERSION}-${LOCAL_PC_ARCHITECTURE}_notrace:1.0.0 . --push" + +# Run the Docker buildx build command +echo "Running: $DOCKER_COMMAND" +$DOCKER_COMMAND \ No newline at end of file diff --git a/run.sh b/run.sh new file mode 100644 index 0000000..940206a --- /dev/null +++ b/run.sh @@ -0,0 +1 @@ +kubectl port-forward svc/tumble-backend -n development 7036:80 \ No newline at end of file From 73874707d74a619c23f25b3f13eaa29519b05859 Mon Sep 17 00:00:00 2001 From: adisve Date: Sun, 8 Sep 2024 13:56:24 +0200 Subject: [PATCH 09/10] Finish test-user implementation --- Dockerfile | 6 + Models/ResponseModels/UserEventCollection.cs | 37 +- .../Middleware/TestUserMiddleware.cs | 59 +- TumbleBackend/MockData/mockBookingResult.json | 26 +- TumbleBackend/MockData/mockResources.json | 32 +- .../mockResourcesWithAvailabilities.json | 584 +++++++++--------- TumbleBackend/MockData/mockUserBookings.json | 30 +- TumbleBackend/MockData/mockUserEvents.json | 56 +- TumbleBackend/Program.cs | 2 +- 9 files changed, 437 insertions(+), 395 deletions(-) diff --git a/Dockerfile b/Dockerfile index a5d2dbd..cacd257 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,12 @@ FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:7.0 AS build WORKDIR /build +RUN apt-get update && \ + apt-get install -y \ + curl \ + vim \ + nano + # Copy csproj and restore as distinct layers COPY ["TumbleBackend/*.csproj", "TumbleBackend/"] RUN dotnet restore "TumbleBackend/TumbleBackend.csproj" diff --git a/Models/ResponseModels/UserEventCollection.cs b/Models/ResponseModels/UserEventCollection.cs index 22b91d6..0658fd4 100644 --- a/Models/ResponseModels/UserEventCollection.cs +++ b/Models/ResponseModels/UserEventCollection.cs @@ -1,31 +1,30 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Text.Json; -using System.Threading.Tasks; +using System.Text.Json.Serialization; using KronoxAPI.Model.Users; -namespace WebAPIModels.ResponseModels; - -public class UserEventCollection +namespace WebAPIModels.ResponseModels { - private readonly List _upcomingEvents; - private readonly List _registeredEvents; - private readonly List _unregisteredEvents; + public class UserEventCollection + { + // Use public properties for JSON deserialization + public List UpcomingEvents { get; set; } - public List UpcomingEvents => _upcomingEvents; + public List RegisteredEvents { get; set; } - public List RegisteredEvents => _registeredEvents; + public List UnregisteredEvents { get; set; } - public List UnregisteredEvents => _unregisteredEvents; + // Parameterless constructor is required for deserialization + public UserEventCollection() { } - public UserEventCollection(List upcomingUserEvents, List registeredUserEvents, List unregisteredUserEvents) - { - _upcomingEvents = upcomingUserEvents; - _registeredEvents = registeredUserEvents; - _unregisteredEvents = unregisteredUserEvents; - } + public UserEventCollection(List upcomingUserEvents, List registeredUserEvents, List unregisteredUserEvents) + { + UpcomingEvents = upcomingUserEvents; + RegisteredEvents = registeredUserEvents; + UnregisteredEvents = unregisteredUserEvents; + } - public string ToJson() => JsonSerializer.Serialize(this, new JsonSerializerOptions() { WriteIndented = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); + public string ToJson() => JsonSerializer.Serialize(this, new JsonSerializerOptions() { WriteIndented = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); + } } diff --git a/TumbleBackend/Middleware/TestUserMiddleware.cs b/TumbleBackend/Middleware/TestUserMiddleware.cs index b7aec67..57c839a 100644 --- a/TumbleBackend/Middleware/TestUserMiddleware.cs +++ b/TumbleBackend/Middleware/TestUserMiddleware.cs @@ -3,6 +3,7 @@ using TumbleBackend.Utilities; using WebAPIModels.Extensions; using WebAPIModels.MiscModels; +using WebAPIModels.ResponseModels; namespace TumbleBackend.Middleware; @@ -27,11 +28,14 @@ public async Task InvokeAsync(HttpContext context, JwtUtil jwtUtil, TestUserUtil context.Request.EnableBuffering(); var body = await new StreamReader(context.Request.Body).ReadToEndAsync(); context.Request.Body.Position = 0; + + _logger.LogInformation("HTTP path: " + path); if (IsLoginRequest(context) && TryGetCredentials(body, out var username, out var password)) { if (testUserUtil.IsTestUser(username, password)) { + _logger.LogInformation("Handling test user login response."); await HandleTestUserLoginResponse(context, jwtUtil, testUserUtil, username, password); return; } @@ -44,14 +48,14 @@ public async Task InvokeAsync(HttpContext context, JwtUtil jwtUtil, TestUserUtil { if (path.StartsWithSegments("/api/resources")) { + _logger.LogInformation("Handling test user booking routes."); await HandleTestUserBookingRoutes(context, path); return; } - if (path.StartsWithSegments("/api/users")) - { - _logger.LogInformation("Returning test user information."); - await HandleTestUserInfoResponse(context, jwtUtil, testUserUtil, creds.Username, creds.Password); + if (path.StartsWithSegments("/api/users")) { + _logger.LogInformation("Handling test user routes."); + await HandleTestUserRoutes(context, jwtUtil, testUserUtil, path, creds.Username, creds.Password); return; } @@ -78,16 +82,26 @@ private static async Task HandleTestUserInfoResponse(HttpContext context, JwtUti await context.Response.WriteAsJsonAsync(testUserModel); } + private async Task HandleTestUserRoutes(HttpContext context, JwtUtil jwtUtil, TestUserUtil testUserUtil, PathString path, string username, string password) + { + if (context.Request.Method == HttpMethods.Get && path == "/api/users/events/") { + _logger.LogInformation("Returning mock user events."); + await ReturnMockUserEvents(context); + } + else if (context.Request.Method == HttpMethods.Get && path == "/api/users") + { + _logger.LogInformation("Returning test user information."); + await HandleTestUserInfoResponse(context, jwtUtil, testUserUtil, username, password); + return; + } + } + private static bool IsLoginRequest(HttpContext context) { return context.Request.Method == HttpMethods.Post && context.Request.Path == "/api/users/login"; } - private static bool IsGetUserRequest(HttpContext context) - { - return context.Request.Method == HttpMethods.Get && context.Request.Path == "/api/users"; - } private static bool TryGetCredentials(string body, out string? username, out string? password) { @@ -135,35 +149,58 @@ private static async Task HandleTestUserTokenResponse(HttpContext context, JwtUt private async Task HandleTestUserBookingRoutes(HttpContext context, PathString path) { + _logger.LogInformation("Mock data path is: " + _mockDataPath); if (context.Request.Method == HttpMethods.Get && path == "/api/resources") { + _logger.LogInformation("Returning mock resources."); await ReturnMockResources(context); } else if (context.Request.Method == HttpMethods.Get && path == "/api/resources/all") { + _logger.LogInformation("Returning mock resources with availabilities."); await ReturnMockResourcesWithAvailabilities(context); } + else if (context.Request.Method == HttpMethods.Get && path == "/api/users/events") { + _logger.LogInformation("Returning mock user events."); + await ReturnMockUserEvents(context); + } else if (context.Request.Method == HttpMethods.Get && path == "/api/resources/userbookings") { + _logger.LogInformation("Returning mock user bookings."); await ReturnMockUserBookings(context); } else if (context.Request.Method == HttpMethods.Put && path.StartsWithSegments("/api/resources/book")) { + _logger.LogInformation("Returning mock booking result."); await ReturnMockBookingResult(context); } else if (context.Request.Method == HttpMethods.Put && path.StartsWithSegments("/api/resources/unbook")) { + _logger.LogInformation("Returning mock unbooking result."); await ReturnMockUnbookingResult(context); } else if (context.Request.Method == HttpMethods.Put && path.StartsWithSegments("/api/resources/confirm")) { + _logger.LogInformation("Returning mock booking confirmation."); await ReturnMockBookingConfirmation(context); } } + private async Task ReturnMockUserEvents(HttpContext context) + { + var mockFilePath = Path.Combine(_mockDataPath, "mockUserEvents.json"); + _logger.LogInformation("Retrieving mock data from path: " + mockFilePath); + var mockEvents = await File.ReadAllTextAsync(mockFilePath); + var events = JsonSerializer.Deserialize(mockEvents); + + context.Response.StatusCode = StatusCodes.Status200OK; + await context.Response.WriteAsJsonAsync(events); + } + private async Task ReturnMockResources(HttpContext context) { var mockFilePath = Path.Combine(_mockDataPath, "mockResources.json"); + _logger.LogInformation("Retrieving mock data from path: " + mockFilePath); var mockResources = await File.ReadAllTextAsync(mockFilePath); var resources = JsonSerializer.Deserialize>(mockResources); @@ -171,9 +208,11 @@ private async Task ReturnMockResources(HttpContext context) await context.Response.WriteAsJsonAsync(resources); } + private async Task ReturnMockResourcesWithAvailabilities(HttpContext context) { var mockFilePath = Path.Combine(_mockDataPath, "mockResourcesWithAvailabilities.json"); + _logger.LogInformation("Retrieving mock data from path: " + mockFilePath); var mockResources = await File.ReadAllTextAsync(mockFilePath); var resources = JsonSerializer.Deserialize>(mockResources); @@ -184,6 +223,7 @@ private async Task ReturnMockResourcesWithAvailabilities(HttpContext context) private async Task ReturnMockUserBookings(HttpContext context) { var mockFilePath = Path.Combine(_mockDataPath, "mockUserBookings.json"); + _logger.LogInformation("Retrieving mock data from path: " + mockFilePath); var mockBookings = await File.ReadAllTextAsync(mockFilePath); var bookings = JsonSerializer.Deserialize>(mockBookings); @@ -194,6 +234,7 @@ private async Task ReturnMockUserBookings(HttpContext context) private async Task ReturnMockBookingResult(HttpContext context) { var mockFilePath = Path.Combine(_mockDataPath, "mockBookingResult.json"); + _logger.LogInformation("Retrieving mock data from path: " + mockFilePath); var mockResult = await File.ReadAllTextAsync(mockFilePath); context.Response.StatusCode = StatusCodes.Status200OK; @@ -203,6 +244,7 @@ private async Task ReturnMockBookingResult(HttpContext context) private async Task ReturnMockUnbookingResult(HttpContext context) { var mockFilePath = Path.Combine(_mockDataPath, "mockUnbookingResult.json"); + _logger.LogInformation("Retrieving mock data from path: " + mockFilePath); var mockResult = await File.ReadAllTextAsync(mockFilePath); context.Response.StatusCode = StatusCodes.Status200OK; @@ -212,6 +254,7 @@ private async Task ReturnMockUnbookingResult(HttpContext context) private async Task ReturnMockBookingConfirmation(HttpContext context) { var mockFilePath = Path.Combine(_mockDataPath, "mockBookingConfirmation.json"); + _logger.LogInformation("Retrieving mock data from path: " + mockFilePath); var mockResult = await File.ReadAllTextAsync(mockFilePath); context.Response.StatusCode = StatusCodes.Status200OK; diff --git a/TumbleBackend/MockData/mockBookingResult.json b/TumbleBackend/MockData/mockBookingResult.json index 188eb50..3814818 100644 --- a/TumbleBackend/MockData/mockBookingResult.json +++ b/TumbleBackend/MockData/mockBookingResult.json @@ -1,15 +1,15 @@ { - "id": "BokningsId_20240907_000000015", - "resourceId": "FLIK_0005", - "timeSlot": { - "id": null, - "from": "2024-09-09T08:00:00", - "to": "2024-09-09T09:00:00", - "duration": "01:00:00" - }, - "locationId": "15-416", - "showConfirmButton": false, - "showUnbookButton": true, - "confirmationOpen": "2024-09-09T07:30:00", - "confirmationClosed": "2024-09-09T08:15:00" + "Id": "BokningsId_20240907_000000015", + "ResourceId": "FLIK_0005", + "TimeSlot": { + "Id": null, + "From": "2024-09-09T08:00:00", + "To": "2024-09-09T09:00:00", + "Duration": "01:00:00" + }, + "LocationId": "15-416", + "ShowConfirmButton": false, + "ShowUnbookButton": true, + "ConfirmationOpen": "2024-09-09T07:30:00", + "ConfirmationClosed": "2024-09-09T08:15:00" } diff --git a/TumbleBackend/MockData/mockResources.json b/TumbleBackend/MockData/mockResources.json index d436bf8..24b13d7 100644 --- a/TumbleBackend/MockData/mockResources.json +++ b/TumbleBackend/MockData/mockResources.json @@ -1,18 +1,18 @@ [ - { - "id": "FLIK_0005", - "name": "Grouproom building15", - "timeSlots": null, - "date": null, - "locationIds": null, - "availabilities": null - }, - { - "id": "FLIK_0010", - "name": "Grouproom in the library.", - "timeSlots": null, - "date": null, - "locationIds": null, - "availabilities": null - } + { + "Id": "FLIK_0005", + "Name": "Grouproom building15", + "TimeSlots": null, + "Date": null, + "LocationIds": null, + "Availabilities": null + }, + { + "Id": "FLIK_0010", + "Name": "Grouproom in the library.", + "TimeSlots": null, + "Date": null, + "LocationIds": null, + "Availabilities": null + } ] diff --git a/TumbleBackend/MockData/mockResourcesWithAvailabilities.json b/TumbleBackend/MockData/mockResourcesWithAvailabilities.json index fa7a202..1773f07 100644 --- a/TumbleBackend/MockData/mockResourcesWithAvailabilities.json +++ b/TumbleBackend/MockData/mockResourcesWithAvailabilities.json @@ -1,199 +1,193 @@ [ { - "id": "FLIK_0005", - "name": "Grouproom building15", - "timeSlots": [ + "Id": "FLIK_0005", + "Name": "Grouproom building15", + "TimeSlots": [ { - "id": 0, - "from": "2024-09-09T08:00:00", - "to": "2024-09-09T09:00:00", - "duration": "01:00:00" + "Id": 0, + "From": "2024-09-09T08:00:00", + "To": "2024-09-09T09:00:00", + "Duration": "01:00:00" }, { - "id": 1, - "from": "2024-09-09T09:00:00", - "to": "2024-09-09T12:00:00", - "duration": "03:00:00" + "Id": 1, + "From": "2024-09-09T09:00:00", + "To": "2024-09-09T12:00:00", + "Duration": "03:00:00" }, { - "id": 2, - "from": "2024-09-09T12:00:00", - "to": "2024-09-09T15:00:00", - "duration": "03:00:00" + "Id": 2, + "From": "2024-09-09T12:00:00", + "To": "2024-09-09T15:00:00", + "Duration": "03:00:00" }, { - "id": 3, - "from": "2024-09-09T15:00:00", - "to": "2024-09-09T18:00:00", - "duration": "03:00:00" + "Id": 3, + "From": "2024-09-09T15:00:00", + "To": "2024-09-09T18:00:00", + "Duration": "03:00:00" } ], - "date": "2024-09-09T00:00:00", - "locationIds": [ - "15-416", - "15-417", - "15-430", - "15-431", - "15-432" - ], - "availabilities": { + "Date": "2024-09-09T00:00:00", + "LocationIds": ["15-416", "15-417", "15-430", "15-431", "15-432"], + "Availabilities": { "15-416": { "0": { - "availability": "AVAILABLE", - "locationId": "15-416", - "resourceType": "RESURSER_LOKALER", - "timeSlotId": "0" + "Availability": "AVAILABLE", + "LocationId": "15-416", + "ResourceType": "RESURSER_LOKALER", + "TimeSlotId": "0" }, "1": { - "availability": "AVAILABLE", - "locationId": "15-416", - "resourceType": "RESURSER_LOKALER", - "timeSlotId": "1" + "Availability": "AVAILABLE", + "LocationId": "15-416", + "ResourceType": "RESURSER_LOKALER", + "TimeSlotId": "1" }, "2": { - "availability": "AVAILABLE", - "locationId": "15-416", - "resourceType": "RESURSER_LOKALER", - "timeSlotId": "2" + "Availability": "AVAILABLE", + "LocationId": "15-416", + "ResourceType": "RESURSER_LOKALER", + "TimeSlotId": "2" }, "3": { - "availability": "AVAILABLE", - "locationId": "15-416", - "resourceType": "RESURSER_LOKALER", - "timeSlotId": "3" + "Availability": "AVAILABLE", + "LocationId": "15-416", + "ResourceType": "RESURSER_LOKALER", + "TimeSlotId": "3" } }, "15-417": { "0": { - "availability": "AVAILABLE", - "locationId": "15-417", - "resourceType": "RESURSER_LOKALER", - "timeSlotId": "0" + "Availability": "AVAILABLE", + "LocationId": "15-417", + "ResourceType": "RESURSER_LOKALER", + "TimeSlotId": "0" }, "1": { - "availability": "BOOKED", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "Availability": "BOOKED", + "LocationId": null, + "ResourceType": null, + "TimeSlotId": null }, "2": { - "availability": "BOOKED", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "Availability": "BOOKED", + "LocationId": null, + "ResourceType": null, + "TimeSlotId": null }, "3": { - "availability": "AVAILABLE", - "locationId": "15-417", - "resourceType": "RESURSER_LOKALER", - "timeSlotId": "3" + "Availability": "AVAILABLE", + "LocationId": "15-417", + "ResourceType": "RESURSER_LOKALER", + "TimeSlotId": "3" } }, "15-430": { "0": { - "availability": "AVAILABLE", - "locationId": "15-430", - "resourceType": "RESURSER_LOKALER", - "timeSlotId": "0" + "Availability": "AVAILABLE", + "LocationId": "15-430", + "ResourceType": "RESURSER_LOKALER", + "TimeSlotId": "0" }, "1": { - "availability": "AVAILABLE", - "locationId": "15-430", - "resourceType": "RESURSER_LOKALER", - "timeSlotId": "1" + "Availability": "AVAILABLE", + "LocationId": "15-430", + "ResourceType": "RESURSER_LOKALER", + "TimeSlotId": "1" }, "2": { - "availability": "BOOKED", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "Availability": "BOOKED", + "LocationId": null, + "ResourceType": null, + "TimeSlotId": null }, "3": { - "availability": "BOOKED", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "Availability": "BOOKED", + "LocationId": null, + "ResourceType": null, + "TimeSlotId": null } }, "15-431": { "0": { - "availability": "AVAILABLE", - "locationId": "15-431", - "resourceType": "RESURSER_LOKALER", - "timeSlotId": "0" + "Availability": "AVAILABLE", + "LocationId": "15-431", + "ResourceType": "RESURSER_LOKALER", + "TimeSlotId": "0" }, "1": { - "availability": "AVAILABLE", - "locationId": "15-431", - "resourceType": "RESURSER_LOKALER", - "timeSlotId": "1" + "Availability": "AVAILABLE", + "LocationId": "15-431", + "ResourceType": "RESURSER_LOKALER", + "TimeSlotId": "1" }, "2": { - "availability": "AVAILABLE", - "locationId": "15-431", - "resourceType": "RESURSER_LOKALER", - "timeSlotId": "2" + "Availability": "AVAILABLE", + "LocationId": "15-431", + "ResourceType": "RESURSER_LOKALER", + "TimeSlotId": "2" }, "3": { - "availability": "AVAILABLE", - "locationId": "15-431", - "resourceType": "RESURSER_LOKALER", - "timeSlotId": "3" + "Availability": "AVAILABLE", + "LocationId": "15-431", + "ResourceType": "RESURSER_LOKALER", + "TimeSlotId": "3" } }, "15-432": { "0": { - "availability": "AVAILABLE", - "locationId": "15-432", - "resourceType": "RESURSER_LOKALER", - "timeSlotId": "0" + "Availability": "AVAILABLE", + "LocationId": "15-432", + "ResourceType": "RESURSER_LOKALER", + "TimeSlotId": "0" }, "1": { - "availability": "AVAILABLE", - "locationId": "15-432", - "resourceType": "RESURSER_LOKALER", - "timeSlotId": "1" + "Availability": "AVAILABLE", + "LocationId": "15-432", + "ResourceType": "RESURSER_LOKALER", + "TimeSlotId": "1" }, "2": { - "availability": "AVAILABLE", - "locationId": "15-432", - "resourceType": "RESURSER_LOKALER", - "timeSlotId": "2" + "Availability": "AVAILABLE", + "LocationId": "15-432", + "ResourceType": "RESURSER_LOKALER", + "TimeSlotId": "2" }, "3": { - "availability": "AVAILABLE", - "locationId": "15-432", - "resourceType": "RESURSER_LOKALER", - "timeSlotId": "3" + "Availability": "AVAILABLE", + "LocationId": "15-432", + "ResourceType": "RESURSER_LOKALER", + "TimeSlotId": "3" } } } }, { - "id": "FLIK_0010", - "name": "Grouproom in the library.", - "timeSlots": [ + "Id": "FLIK_0010", + "Name": "Grouproom in the library.", + "TimeSlots": [ { - "id": 0, - "from": "2024-09-09T08:00:00", - "to": "2024-09-09T09:00:00", - "duration": "01:00:00" + "Id": 0, + "From": "2024-09-09T08:00:00", + "To": "2024-09-09T09:00:00", + "Duration": "01:00:00" }, { - "id": 1, - "from": "2024-09-09T09:00:00", - "to": "2024-09-09T12:00:00", - "duration": "03:00:00" + "Id": 1, + "From": "2024-09-09T09:00:00", + "To": "2024-09-09T12:00:00", + "Duration": "03:00:00" }, { - "id": 2, - "from": "2024-09-09T12:00:00", - "to": "2024-09-09T15:00:00", - "duration": "03:00:00" + "Id": 2, + "From": "2024-09-09T12:00:00", + "To": "2024-09-09T15:00:00", + "Duration": "03:00:00" } ], - "date": "2024-09-09T00:00:00", - "locationIds": [ + "Date": "2024-09-09T00:00:00", + "LocationIds": [ "07-320A", "07-320B", "07-320C", @@ -209,287 +203,287 @@ "07-320N", "07-320O" ], - "availabilities": { + "Availabilities": { "07-320A": { "0": { - "availability": "AVAILABLE", - "locationId": "07-320A", - "resourceType": "RESURSER_LOKALER", - "timeSlotId": "0" + "Availability": "AVAILABLE", + "LocationId": "07-320A", + "ResourceType": "RESURSER_LOKALER", + "TimeSlotId": "0" }, "1": { - "availability": "AVAILABLE", - "locationId": "07-320A", - "resourceType": "RESURSER_LOKALER", - "timeSlotId": "1" + "Availability": "AVAILABLE", + "LocationId": "07-320A", + "ResourceType": "RESURSER_LOKALER", + "TimeSlotId": "1" }, "2": { - "availability": "BOOKED", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "Availability": "BOOKED", + "LocationId": null, + "ResourceType": null, + "TimeSlotId": null } }, "07-320B": { "0": { - "availability": "AVAILABLE", - "locationId": "07-320B", - "resourceType": "RESURSER_LOKALER", - "timeSlotId": "0" + "Availability": "AVAILABLE", + "LocationId": "07-320B", + "ResourceType": "RESURSER_LOKALER", + "TimeSlotId": "0" }, "1": { - "availability": "AVAILABLE", - "locationId": "07-320B", - "resourceType": "RESURSER_LOKALER", - "timeSlotId": "1" + "Availability": "AVAILABLE", + "LocationId": "07-320B", + "ResourceType": "RESURSER_LOKALER", + "TimeSlotId": "1" }, "2": { - "availability": "BOOKED", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "Availability": "BOOKED", + "LocationId": null, + "ResourceType": null, + "TimeSlotId": null } }, "07-320C": { "0": { - "availability": "AVAILABLE", - "locationId": "07-320C", - "resourceType": "RESURSER_LOKALER", - "timeSlotId": "0" + "Availability": "AVAILABLE", + "LocationId": "07-320C", + "ResourceType": "RESURSER_LOKALER", + "TimeSlotId": "0" }, "1": { - "availability": "BOOKED", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "Availability": "BOOKED", + "LocationId": null, + "ResourceType": null, + "TimeSlotId": null }, "2": { - "availability": "BOOKED", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "Availability": "BOOKED", + "LocationId": null, + "ResourceType": null, + "TimeSlotId": null } }, "07-320D": { "0": { - "availability": "AVAILABLE", - "locationId": "07-320D", - "resourceType": "RESURSER_LOKALER", - "timeSlotId": "0" + "Availability": "AVAILABLE", + "LocationId": "07-320D", + "ResourceType": "RESURSER_LOKALER", + "TimeSlotId": "0" }, "1": { - "availability": "AVAILABLE", - "locationId": "07-320D", - "resourceType": "RESURSER_LOKALER", - "timeSlotId": "1" + "Availability": "AVAILABLE", + "LocationId": "07-320D", + "ResourceType": "RESURSER_LOKALER", + "TimeSlotId": "1" }, "2": { - "availability": "BOOKED", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "Availability": "BOOKED", + "LocationId": null, + "ResourceType": null, + "TimeSlotId": null } }, "07-320E": { "0": { - "availability": "AVAILABLE", - "locationId": "07-320E", - "resourceType": "RESURSER_LOKALER", - "timeSlotId": "0" + "Availability": "AVAILABLE", + "LocationId": "07-320E", + "ResourceType": "RESURSER_LOKALER", + "TimeSlotId": "0" }, "1": { - "availability": "BOOKED", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "Availability": "BOOKED", + "LocationId": null, + "ResourceType": null, + "TimeSlotId": null }, "2": { - "availability": "AVAILABLE", - "locationId": "07-320E", - "resourceType": "RESURSER_LOKALER", - "timeSlotId": "2" + "Availability": "AVAILABLE", + "LocationId": "07-320E", + "ResourceType": "RESURSER_LOKALER", + "TimeSlotId": "2" } }, "07-320F": { "0": { - "availability": "AVAILABLE", - "locationId": "07-320F", - "resourceType": "RESURSER_LOKALER", - "timeSlotId": "0" + "Availability": "AVAILABLE", + "LocationId": "07-320F", + "ResourceType": "RESURSER_LOKALER", + "TimeSlotId": "0" }, "1": { - "availability": "BOOKED", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "Availability": "BOOKED", + "LocationId": null, + "ResourceType": null, + "TimeSlotId": null }, "2": { - "availability": "BOOKED", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "Availability": "BOOKED", + "LocationId": null, + "ResourceType": null, + "TimeSlotId": null } }, "07-320G": { "0": { - "availability": "AVAILABLE", - "locationId": "07-320G", - "resourceType": "RESURSER_LOKALER", - "timeSlotId": "0" + "Availability": "AVAILABLE", + "LocationId": "07-320G", + "ResourceType": "RESURSER_LOKALER", + "TimeSlotId": "0" }, "1": { - "availability": "BOOKED", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "Availability": "BOOKED", + "LocationId": null, + "ResourceType": null, + "TimeSlotId": null }, "2": { - "availability": "BOOKED", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "Availability": "BOOKED", + "LocationId": null, + "ResourceType": null, + "TimeSlotId": null } }, "07-320H": { "0": { - "availability": "AVAILABLE", - "locationId": "07-320H", - "resourceType": "RESURSER_LOKALER", - "timeSlotId": "0" + "Availability": "AVAILABLE", + "LocationId": "07-320H", + "ResourceType": "RESURSER_LOKALER", + "TimeSlotId": "0" }, "1": { - "availability": "AVAILABLE", - "locationId": "07-320H", - "resourceType": "RESURSER_LOKALER", - "timeSlotId": "1" + "Availability": "AVAILABLE", + "LocationId": "07-320H", + "ResourceType": "RESURSER_LOKALER", + "TimeSlotId": "1" }, "2": { - "availability": "BOOKED", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "Availability": "BOOKED", + "LocationId": null, + "ResourceType": null, + "TimeSlotId": null } }, "07-320J": { "0": { - "availability": "AVAILABLE", - "locationId": "07-320J", - "resourceType": "RESURSER_LOKALER", - "timeSlotId": "0" + "Availability": "AVAILABLE", + "LocationId": "07-320J", + "ResourceType": "RESURSER_LOKALER", + "TimeSlotId": "0" }, "1": { - "availability": "BOOKED", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "Availability": "BOOKED", + "LocationId": null, + "ResourceType": null, + "TimeSlotId": null }, "2": { - "availability": "BOOKED", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "Availability": "BOOKED", + "LocationId": null, + "ResourceType": null, + "TimeSlotId": null } }, "07-320K": { "0": { - "availability": "AVAILABLE", - "locationId": "07-320K", - "resourceType": "RESURSER_LOKALER", - "timeSlotId": "0" + "Availability": "AVAILABLE", + "LocationId": "07-320K", + "ResourceType": "RESURSER_LOKALER", + "TimeSlotId": "0" }, "1": { - "availability": "AVAILABLE", - "locationId": "07-320K", - "resourceType": "RESURSER_LOKALER", - "timeSlotId": "1" + "Availability": "AVAILABLE", + "LocationId": "07-320K", + "ResourceType": "RESURSER_LOKALER", + "TimeSlotId": "1" }, "2": { - "availability": "BOOKED", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "Availability": "BOOKED", + "LocationId": null, + "ResourceType": null, + "TimeSlotId": null } }, "07-320L": { "0": { - "availability": "AVAILABLE", - "locationId": "07-320L", - "resourceType": "RESURSER_LOKALER", - "timeSlotId": "0" + "Availability": "AVAILABLE", + "LocationId": "07-320L", + "ResourceType": "RESURSER_LOKALER", + "TimeSlotId": "0" }, "1": { - "availability": "BOOKED", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "Availability": "BOOKED", + "LocationId": null, + "ResourceType": null, + "TimeSlotId": null }, "2": { - "availability": "BOOKED", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "Availability": "BOOKED", + "LocationId": null, + "ResourceType": null, + "TimeSlotId": null } }, "07-320M": { "0": { - "availability": "AVAILABLE", - "locationId": "07-320M", - "resourceType": "RESURSER_LOKALER", - "timeSlotId": "0" + "Availability": "AVAILABLE", + "LocationId": "07-320M", + "ResourceType": "RESURSER_LOKALER", + "TimeSlotId": "0" }, "1": { - "availability": "BOOKED", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "Availability": "BOOKED", + "LocationId": null, + "ResourceType": null, + "TimeSlotId": null }, "2": { - "availability": "BOOKED", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "Availability": "BOOKED", + "LocationId": null, + "ResourceType": null, + "TimeSlotId": null } }, "07-320N": { "0": { - "availability": "AVAILABLE", - "locationId": "07-320N", - "resourceType": "RESURSER_LOKALER", - "timeSlotId": "0" + "Availability": "AVAILABLE", + "LocationId": "07-320N", + "ResourceType": "RESURSER_LOKALER", + "TimeSlotId": "0" }, "1": { - "availability": "BOOKED", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "Availability": "BOOKED", + "LocationId": null, + "ResourceType": null, + "TimeSlotId": null }, "2": { - "availability": "BOOKED", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "Availability": "BOOKED", + "LocationId": null, + "ResourceType": null, + "TimeSlotId": null } }, "07-320O": { "0": { - "availability": "AVAILABLE", - "locationId": "07-320O", - "resourceType": "RESURSER_LOKALER", - "timeSlotId": "0" + "Availability": "AVAILABLE", + "LocationId": "07-320O", + "ResourceType": "RESURSER_LOKALER", + "TimeSlotId": "0" }, "1": { - "availability": "BOOKED", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "Availability": "BOOKED", + "LocationId": null, + "ResourceType": null, + "TimeSlotId": null }, "2": { - "availability": "BOOKED", - "locationId": null, - "resourceType": null, - "timeSlotId": null + "Availability": "BOOKED", + "LocationId": null, + "ResourceType": null, + "TimeSlotId": null } } } } -] \ No newline at end of file +] diff --git a/TumbleBackend/MockData/mockUserBookings.json b/TumbleBackend/MockData/mockUserBookings.json index 5280b2a..b97af97 100644 --- a/TumbleBackend/MockData/mockUserBookings.json +++ b/TumbleBackend/MockData/mockUserBookings.json @@ -1,17 +1,17 @@ [ - { - "id": "BokningsId_20240907_000000015", - "resourceId": "FLIK_0005", - "timeSlot": { - "id": null, - "from": "2024-09-09T08:00:00", - "to": "2024-09-09T09:00:00", - "duration": "01:00:00" - }, - "locationId": "15-416", - "showConfirmButton": false, - "showUnbookButton": true, - "confirmationOpen": "2024-09-09T07:30:00", - "confirmationClosed": "2024-09-09T08:15:00" - } + { + "Id": "BokningsId_20240907_000000015", + "ResourceId": "FLIK_0005", + "TimeSlot": { + "Id": null, + "From": "2024-09-09T08:00:00", + "To": "2024-09-09T09:00:00", + "Duration": "01:00:00" + }, + "LocationId": "15-416", + "ShowConfirmButton": false, + "ShowUnbookButton": true, + "ConfirmationOpen": "2024-09-09T07:30:00", + "ConfirmationClosed": "2024-09-09T08:15:00" + } ] diff --git a/TumbleBackend/MockData/mockUserEvents.json b/TumbleBackend/MockData/mockUserEvents.json index b33aceb..7b03482 100644 --- a/TumbleBackend/MockData/mockUserEvents.json +++ b/TumbleBackend/MockData/mockUserEvents.json @@ -1,30 +1,30 @@ { - "upcomingEvents": [ - { - "title": "English Exam", - "type": "Written exam", - "eventStart": "2024-12-01T09:00:00", - "eventEnd": "2024-12-01T12:00:00", - "location": "Room 101", - "firstSignupDate": "2024-11-01T00:00:00" - } - ], - "registeredEvents": [], - "unregisteredEvents": [ - { - "id": "1", - "participatorId": "1", - "supportId": null, - "anonymousCode": "", - "isRegistered": false, - "supportAvailable": false, - "requiresChoosingLocation": false, - "title": "Math Exam", - "type": "Written exam", - "eventStart": "2024-12-02T10:00:00", - "eventEnd": "2024-12-02T14:00:00", - "location": "Room 131", - "lastSignupDate": "2024-11-29T00:00:00" - } - ] + "UpcomingEvents": [ + { + "Title": "English Exam", + "Type": "Written exam", + "EventStart": "2024-12-01T09:00:00", + "EventEnd": "2024-12-01T12:00:00", + "Location": "Room 101", + "FirstSignupDate": "2024-11-01T00:00:00" + } + ], + "RegisteredEvents": [], + "UnregisteredEvents": [ + { + "Id": "1", + "ParticipatorId": "1", + "SupportId": null, + "AnonymousCode": "", + "IsRegistered": false, + "ShowConfirmButton": false, + "RequiresChoosingLocation": false, + "Title": "Math Exam", + "Type": "Written exam", + "EventStart": "2024-12-02T10:00:00", + "EventEnd": "2024-12-02T14:00:00", + "Location": "Room 131", + "LastSignupDate": "2024-11-29T00:00:00" + } + ] } diff --git a/TumbleBackend/Program.cs b/TumbleBackend/Program.cs index d2a26e2..be621c8 100644 --- a/TumbleBackend/Program.cs +++ b/TumbleBackend/Program.cs @@ -22,7 +22,7 @@ var builder = WebApplication.CreateBuilder(args); ConfigureEnvironmentAndSecrets(builder); -//ConfigureTracing(builder); +ConfigureTracing(builder); ConfigureRateLimiting(builder); ConfigureMongoDb(); From 9a168607f6d4818f85d695336dafe56535d5850e Mon Sep 17 00:00:00 2001 From: adisve Date: Fri, 13 Sep 2024 23:01:34 +0200 Subject: [PATCH 10/10] comment out tracing shit in program.cs. Clean up testuser middleware --- KronoxAPI/Utilities/MultiRequest.cs | 2 +- .../Middleware/TestUserMiddleware.cs | 100 +++--------------- TumbleBackend/Program.cs | 19 ---- push.sh | 77 +++++++++----- 4 files changed, 66 insertions(+), 132 deletions(-) mode change 100644 => 100755 push.sh diff --git a/KronoxAPI/Utilities/MultiRequest.cs b/KronoxAPI/Utilities/MultiRequest.cs index 3647369..b0307d1 100644 --- a/KronoxAPI/Utilities/MultiRequest.cs +++ b/KronoxAPI/Utilities/MultiRequest.cs @@ -23,7 +23,7 @@ public MultiRequest(int timeout = 5) { Timeout = TimeSpan.FromSeconds(timeout) }; - //client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"); + client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"); } /// diff --git a/TumbleBackend/Middleware/TestUserMiddleware.cs b/TumbleBackend/Middleware/TestUserMiddleware.cs index 57c839a..5bffc1c 100644 --- a/TumbleBackend/Middleware/TestUserMiddleware.cs +++ b/TumbleBackend/Middleware/TestUserMiddleware.cs @@ -59,7 +59,7 @@ public async Task InvokeAsync(HttpContext context, JwtUtil jwtUtil, TestUserUtil return; } - await HandleTestUserTokenResponse(context, jwtUtil, testUserUtil, creds.Username, creds.Password, refreshToken); + await HandleTestUserTokenResponse(context, testUserUtil, refreshToken); return; } } @@ -68,7 +68,6 @@ public async Task InvokeAsync(HttpContext context, JwtUtil jwtUtil, TestUserUtil await _next(context); } - private static async Task HandleTestUserInfoResponse(HttpContext context, JwtUtil jwtUtil, TestUserUtil testUserUtil, string username, string password) { var testUser = testUserUtil.GetTestUser(); @@ -86,23 +85,20 @@ private async Task HandleTestUserRoutes(HttpContext context, JwtUtil jwtUtil, Te { if (context.Request.Method == HttpMethods.Get && path == "/api/users/events/") { _logger.LogInformation("Returning mock user events."); - await ReturnMockUserEvents(context); + await ReturnMockData(context, "mockUserEvents.json"); } else if (context.Request.Method == HttpMethods.Get && path == "/api/users") { _logger.LogInformation("Returning test user information."); await HandleTestUserInfoResponse(context, jwtUtil, testUserUtil, username, password); - return; } } - private static bool IsLoginRequest(HttpContext context) { return context.Request.Method == HttpMethods.Post && context.Request.Path == "/api/users/login"; } - private static bool TryGetCredentials(string body, out string? username, out string? password) { username = null; @@ -135,7 +131,7 @@ private static async Task HandleTestUserLoginResponse(HttpContext context, JwtUt await context.Response.WriteAsJsonAsync(testUserModel); } - private static async Task HandleTestUserTokenResponse(HttpContext context, JwtUtil jwtUtil, TestUserUtil testUserUtil, string username, string password, string refreshToken) + private static async Task HandleTestUserTokenResponse(HttpContext context, TestUserUtil testUserUtil, string refreshToken) { var testUser = testUserUtil.GetTestUser(); var sessionDetails = new SessionDetails("", ""); @@ -153,111 +149,47 @@ private async Task HandleTestUserBookingRoutes(HttpContext context, PathString p if (context.Request.Method == HttpMethods.Get && path == "/api/resources") { _logger.LogInformation("Returning mock resources."); - await ReturnMockResources(context); + await ReturnMockData>(context, "mockResources.json"); } else if (context.Request.Method == HttpMethods.Get && path == "/api/resources/all") { _logger.LogInformation("Returning mock resources with availabilities."); - await ReturnMockResourcesWithAvailabilities(context); + await ReturnMockData>(context, "mockResourcesWithAvailabilities.json"); } else if (context.Request.Method == HttpMethods.Get && path == "/api/users/events") { _logger.LogInformation("Returning mock user events."); - await ReturnMockUserEvents(context); + await ReturnMockData(context, "mockUserEvents.json"); } else if (context.Request.Method == HttpMethods.Get && path == "/api/resources/userbookings") { _logger.LogInformation("Returning mock user bookings."); - await ReturnMockUserBookings(context); + await ReturnMockData>(context, "mockUserBookings.json"); } else if (context.Request.Method == HttpMethods.Put && path.StartsWithSegments("/api/resources/book")) { _logger.LogInformation("Returning mock booking result."); - await ReturnMockBookingResult(context); + await ReturnMockData(context, "mockBookingResult.json"); } else if (context.Request.Method == HttpMethods.Put && path.StartsWithSegments("/api/resources/unbook")) { _logger.LogInformation("Returning mock unbooking result."); - await ReturnMockUnbookingResult(context); + await ReturnMockData(context, "mockUnbookingResult.json"); } else if (context.Request.Method == HttpMethods.Put && path.StartsWithSegments("/api/resources/confirm")) { _logger.LogInformation("Returning mock booking confirmation."); - await ReturnMockBookingConfirmation(context); + await ReturnMockData(context, "mockBookingConfirmation.json"); } } - private async Task ReturnMockUserEvents(HttpContext context) - { - var mockFilePath = Path.Combine(_mockDataPath, "mockUserEvents.json"); - _logger.LogInformation("Retrieving mock data from path: " + mockFilePath); - var mockEvents = await File.ReadAllTextAsync(mockFilePath); - var events = JsonSerializer.Deserialize(mockEvents); - - context.Response.StatusCode = StatusCodes.Status200OK; - await context.Response.WriteAsJsonAsync(events); - } - - private async Task ReturnMockResources(HttpContext context) - { - var mockFilePath = Path.Combine(_mockDataPath, "mockResources.json"); - _logger.LogInformation("Retrieving mock data from path: " + mockFilePath); - var mockResources = await File.ReadAllTextAsync(mockFilePath); - var resources = JsonSerializer.Deserialize>(mockResources); - - context.Response.StatusCode = StatusCodes.Status200OK; - await context.Response.WriteAsJsonAsync(resources); - } - - - private async Task ReturnMockResourcesWithAvailabilities(HttpContext context) - { - var mockFilePath = Path.Combine(_mockDataPath, "mockResourcesWithAvailabilities.json"); - _logger.LogInformation("Retrieving mock data from path: " + mockFilePath); - var mockResources = await File.ReadAllTextAsync(mockFilePath); - var resources = JsonSerializer.Deserialize>(mockResources); - - context.Response.StatusCode = StatusCodes.Status200OK; - await context.Response.WriteAsJsonAsync(resources); - } - - private async Task ReturnMockUserBookings(HttpContext context) - { - var mockFilePath = Path.Combine(_mockDataPath, "mockUserBookings.json"); - _logger.LogInformation("Retrieving mock data from path: " + mockFilePath); - var mockBookings = await File.ReadAllTextAsync(mockFilePath); - var bookings = JsonSerializer.Deserialize>(mockBookings); - - context.Response.StatusCode = StatusCodes.Status200OK; - await context.Response.WriteAsJsonAsync(bookings); - } - - private async Task ReturnMockBookingResult(HttpContext context) - { - var mockFilePath = Path.Combine(_mockDataPath, "mockBookingResult.json"); - _logger.LogInformation("Retrieving mock data from path: " + mockFilePath); - var mockResult = await File.ReadAllTextAsync(mockFilePath); - - context.Response.StatusCode = StatusCodes.Status200OK; - await context.Response.WriteAsJsonAsync(JsonSerializer.Deserialize(mockResult)); - } - - private async Task ReturnMockUnbookingResult(HttpContext context) - { - var mockFilePath = Path.Combine(_mockDataPath, "mockUnbookingResult.json"); - _logger.LogInformation("Retrieving mock data from path: " + mockFilePath); - var mockResult = await File.ReadAllTextAsync(mockFilePath); - - context.Response.StatusCode = StatusCodes.Status200OK; - await context.Response.WriteAsJsonAsync(JsonSerializer.Deserialize(mockResult)); - } - - private async Task ReturnMockBookingConfirmation(HttpContext context) + private async Task ReturnMockData(HttpContext context, string fileName) { - var mockFilePath = Path.Combine(_mockDataPath, "mockBookingConfirmation.json"); + var mockFilePath = Path.Combine(_mockDataPath, fileName); _logger.LogInformation("Retrieving mock data from path: " + mockFilePath); - var mockResult = await File.ReadAllTextAsync(mockFilePath); + var mockData = await File.ReadAllTextAsync(mockFilePath); + var data = JsonSerializer.Deserialize(mockData); context.Response.StatusCode = StatusCodes.Status200OK; - await context.Response.WriteAsJsonAsync(JsonSerializer.Deserialize(mockResult)); + await context.Response.WriteAsJsonAsync(data); } -} +} \ No newline at end of file diff --git a/TumbleBackend/Program.cs b/TumbleBackend/Program.cs index be621c8..8477bd1 100644 --- a/TumbleBackend/Program.cs +++ b/TumbleBackend/Program.cs @@ -22,7 +22,6 @@ var builder = WebApplication.CreateBuilder(args); ConfigureEnvironmentAndSecrets(builder); -ConfigureTracing(builder); ConfigureRateLimiting(builder); ConfigureMongoDb(); @@ -41,24 +40,6 @@ void ConfigureEnvironmentAndSecrets(WebApplicationBuilder builder) builder.Configuration.AddEnvironmentVariables(); } -void ConfigureTracing(WebApplicationBuilder builder) -{ - builder.Services.AddOpenTelemetry() - .WithTracing(tracerProviderBuilder => - { - tracerProviderBuilder - .AddAspNetCoreInstrumentation() - .AddHttpClientInstrumentation() - .UseGrafana() - .AddConsoleExporter(); - }); - - builder.Logging.AddOpenTelemetry(options => - { - options.UseGrafana().AddConsoleExporter(); - }); -} - void ConfigureRateLimiting(WebApplicationBuilder builder) { builder.Services.AddRateLimiter(_ => _ diff --git a/push.sh b/push.sh old mode 100644 new mode 100755 index 37c5af9..35f8a50 --- a/push.sh +++ b/push.sh @@ -1,34 +1,55 @@ -#!/bin/bash +#!/usr/bin/env bash -# Ensure one argument is provided -if [ "$#" -ne 1 ]; then +set -euo pipefail + +usage() { echo "Usage: $0 " + echo "Example: $0 6.0" exit 1 -fi - -# Assign argument to variable -DOTNET_VERSION=$1 - -# Determine the architecture of the current machine -ARCH=$(uname -m) - -# Translate the architecture to the desired format -case "$ARCH" in - x86_64) - LOCAL_PC_ARCHITECTURE="amd64" - ;; - aarch64) - LOCAL_PC_ARCHITECTURE="arm64" - ;; - *) - echo "Unsupported architecture: $ARCH" +} + +get_arch() { + local arch + arch=$(uname -m) + case "$arch" in + x86_64) echo "amd64" ;; + aarch64) echo "arm64" ;; + arm64) echo "arm64" ;; + *) echo "unsupported" ;; + esac +} + +build_docker_image() { + local dotnet_version=$1 + local architecture=$2 + local image_name="ghcr.io/tumble-for-kronox/tumble-backend-dotnet-${dotnet_version}-${architecture}_notrace:1.0.0" + + echo "Building Docker image: $image_name" + if docker buildx build -t "$image_name" . --push; then + echo "Successfully built and pushed: $image_name" + else + echo "Failed to build or push: $image_name" >&2 + exit 1 + fi +} + +main() { + [[ $# -eq 1 ]] || usage + + [[ $1 =~ ^[0-9]+(\.[0-9]+)*$ ]] || { + echo "Invalid dotnet version format. Example: 6.0" >&2 + exit 1 + } + + DOTNET_VERSION=$1 + ARCH=$(get_arch) + + if [[ $ARCH == "unsupported" ]]; then + echo "Unsupported architecture: $(uname -m)" >&2 exit 1 - ;; -esac + fi -# Construct the Docker buildx build command -DOCKER_COMMAND="docker buildx build -t ghcr.io/tumble-for-kronox/tumble-backend-dotnet-${DOTNET_VERSION}-${LOCAL_PC_ARCHITECTURE}_notrace:1.0.0 . --push" + build_docker_image "$DOTNET_VERSION" "$ARCH" +} -# Run the Docker buildx build command -echo "Running: $DOCKER_COMMAND" -$DOCKER_COMMAND \ No newline at end of file +main "$@"