diff options
| author | ivarlovlie <git@ivarlovlie.no> | 2022-11-14 05:25:12 +0100 |
|---|---|---|
| committer | ivarlovlie <git@ivarlovlie.no> | 2022-11-14 05:25:12 +0100 |
| commit | 99b0c09a6bb984d811b63788015cfad1855b5f3c (patch) | |
| tree | 8b6c3c9b70384bd3f00a46945e8bcb5bd474b3a1 | |
| parent | 798895a91f8533f22f94f4c4800dc9a2c9628ab6 (diff) | |
| download | greatoffice-99b0c09a6bb984d811b63788015cfad1855b5f3c.tar.xz greatoffice-99b0c09a6bb984d811b63788015cfad1855b5f3c.zip | |
refactor: Endpoints
- Model payloads in relevant route class
- Move RequestTimeZoneInfo out of EndpointBase
15 files changed, 91 insertions, 143 deletions
diff --git a/code/api/src/Data/Models/RequestTimeZoneInfo.cs b/code/api/src/Data/Models/RequestTimeZoneInfo.cs new file mode 100644 index 0000000..4c5aa13 --- /dev/null +++ b/code/api/src/Data/Models/RequestTimeZoneInfo.cs @@ -0,0 +1,8 @@ +namespace IOL.GreatOffice.Api.Data.Models; + +public class RequestTimeZoneInfo +{ + public TimeZoneInfo TimeZoneInfo { get; set; } + public int Offset { get; set; } + public DateTime LocalDateTime { get; set; } +}
\ No newline at end of file diff --git a/code/api/src/Endpoints/EndpointBase.cs b/code/api/src/Endpoints/EndpointBase.cs index a5b0931..6850755 100644 --- a/code/api/src/Endpoints/EndpointBase.cs +++ b/code/api/src/Endpoints/EndpointBase.cs @@ -24,6 +24,11 @@ public class EndpointBase : ControllerBase } [NonAction] + protected ActionResult KnownProblem(KnownProblemModel problem) { + return BadRequest(problem); + } + + [NonAction] protected RequestTimeZoneInfo GetRequestTimeZone(ILogger logger = default) { Request.Headers.TryGetValue(AppHeaders.BROWSER_TIME_ZONE, out var timeZoneHeader); var tz = TimeZoneInfo.FindSystemTimeZoneById(timeZoneHeader.ToString().HasValue() ? timeZoneHeader.ToString() : "UTC"); @@ -43,11 +48,4 @@ public class EndpointBase : ControllerBase LocalDateTime = TimeZoneInfo.ConvertTimeFromUtc(AppDateTime.UtcNow, tz) }; } - - public class RequestTimeZoneInfo - { - public TimeZoneInfo TimeZoneInfo { get; set; } - public int Offset { get; set; } - public DateTime LocalDateTime { get; set; } - } }
\ No newline at end of file diff --git a/code/api/src/Endpoints/Internal/Account/CreateAccountPayload.cs b/code/api/src/Endpoints/Internal/Account/CreateAccountPayload.cs deleted file mode 100644 index 1161af3..0000000 --- a/code/api/src/Endpoints/Internal/Account/CreateAccountPayload.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace IOL.GreatOffice.Api.Endpoints.Internal.Account; - -/// <summary> -/// Payload for creating new user accounts. -/// </summary> -public class CreateAccountPayload -{ - /// <summary> - /// Username for the new account. - /// </summary> - public string Username { get; set; } - - /// <summary> - /// Password for the new account. - /// </summary> - public string Password { get; set; } -}
\ No newline at end of file diff --git a/code/api/src/Endpoints/Internal/Account/CreateAccountRoute.cs b/code/api/src/Endpoints/Internal/Account/CreateAccountRoute.cs index f34056d..6b6e7bc 100644 --- a/code/api/src/Endpoints/Internal/Account/CreateAccountRoute.cs +++ b/code/api/src/Endpoints/Internal/Account/CreateAccountRoute.cs @@ -1,35 +1,44 @@ +using Microsoft.Extensions.Localization; + namespace IOL.GreatOffice.Api.Endpoints.Internal.Account; -public class CreateAccountRoute : RouteBaseAsync.WithRequest<CreateAccountPayload>.WithActionResult +public class CreateAccountRoute : RouteBaseAsync.WithRequest<CreateAccountRoute.Payload>.WithActionResult { private readonly MainAppDatabase _database; private readonly UserService _userService; + private readonly IStringLocalizer<SharedResources> _localizer; - public CreateAccountRoute(UserService userService, MainAppDatabase database) { + public CreateAccountRoute(UserService userService, MainAppDatabase database, IStringLocalizer<SharedResources> localizer) { _userService = userService; _database = database; + _localizer = localizer; + } + + public class Payload + { + public string Username { get; set; } + public string Password { get; set; } } - /// <summary> - /// Create a new user account. - /// </summary> - /// <param name="request"></param> - /// <param name="cancellationToken"></param> - /// <returns></returns> [AllowAnonymous] [HttpPost("~/_/account/create")] - public override async Task<ActionResult> HandleAsync(CreateAccountPayload request, CancellationToken cancellationToken = default) { + public override async Task<ActionResult> HandleAsync(Payload request, CancellationToken cancellationToken = default) { + var errors = new Dictionary<string, string>(); if (request.Username.IsValidEmailAddress() == false) { - return BadRequest(new KnownProblemModel("Invalid form", request.Username + " does not look like a valid email")); + errors.Add("username", _localizer["{0} does not look like a valid email", request.Username]); } if (request.Password.Length < 6) { - return BadRequest(new KnownProblemModel("Invalid form", "The password requires 6 or more characters.")); + errors.Add("password", _localizer["The password requires 6 or more characters."]); } var username = request.Username.Trim(); - if (_database.Users.Any(c => c.Username == username)) { - return BadRequest(new KnownProblemModel("Username is not available", "There is already a user registered with email: " + username)); + if (errors.All(p => p.Key != "username") && _database.Users.Any(c => c.Username == username)) { + errors.Add("username", _localizer["There is already a user registered with username: {0}", username]); + } + + if (errors.Any()) { + return KnownProblem(_localizer["Invalid form"], _localizer["You have invalid values"], errors); } var user = new User(username); diff --git a/code/api/src/Endpoints/Internal/Account/DeleteAccountRoute.cs b/code/api/src/Endpoints/Internal/Account/DeleteAccountRoute.cs index f0e8362..e9fe40d 100644 --- a/code/api/src/Endpoints/Internal/Account/DeleteAccountRoute.cs +++ b/code/api/src/Endpoints/Internal/Account/DeleteAccountRoute.cs @@ -10,11 +10,6 @@ public class DeleteAccountRoute : RouteBaseAsync.WithoutRequest.WithActionResult _userService = userService; } - /// <summary> - /// Delete the logged on user's account. - /// </summary> - /// <param name="cancellationToken"></param> - /// <returns></returns> [HttpDelete("~/_/account/delete")] public override async Task<ActionResult> HandleAsync(CancellationToken cancellationToken = default) { var user = _database.Users.SingleOrDefault(c => c.Id == LoggedInUser.Id); diff --git a/code/api/src/Endpoints/Internal/Account/GetRoute.cs b/code/api/src/Endpoints/Internal/Account/GetAccountRoute.cs index 8d6c50f..121b40f 100644 --- a/code/api/src/Endpoints/Internal/Account/GetRoute.cs +++ b/code/api/src/Endpoints/Internal/Account/GetAccountRoute.cs @@ -8,11 +8,6 @@ public class GetAccountRoute : RouteBaseAsync.WithoutRequest.WithActionResult<Lo _database = database; } - /// <summary> - /// Get the logged on user's session data. - /// </summary> - /// <param name="cancellationToken"></param> - /// <returns></returns> [HttpGet("~/_/account")] public override async Task<ActionResult<LoggedInUserModel>> HandleAsync(CancellationToken cancellationToken = default) { var user = _database.Users diff --git a/code/api/src/Endpoints/Internal/Account/LoginPayload.cs b/code/api/src/Endpoints/Internal/Account/LoginPayload.cs index 807662c..a5670c6 100644 --- a/code/api/src/Endpoints/Internal/Account/LoginPayload.cs +++ b/code/api/src/Endpoints/Internal/Account/LoginPayload.cs @@ -1,22 +1,6 @@ namespace IOL.GreatOffice.Api.Endpoints.Internal.Account; -/// <summary> -/// Payload for logging in a user. -/// </summary> public class LoginPayload { - /// <summary> - /// Username of the user's account. - /// </summary> - public string Username { get; set; } - /// <summary> - /// Password of the user's account. - /// </summary> - public string Password { get; set; } - - /// <summary> - /// Specify that the created session should be long lived and continually refreshed. - /// </summary> - public bool Persist { get; set; } -} +}
\ No newline at end of file diff --git a/code/api/src/Endpoints/Internal/Account/LoginRoute.cs b/code/api/src/Endpoints/Internal/Account/LoginRoute.cs index 696c3c2..eaebc2a 100644 --- a/code/api/src/Endpoints/Internal/Account/LoginRoute.cs +++ b/code/api/src/Endpoints/Internal/Account/LoginRoute.cs @@ -1,31 +1,32 @@ +using Microsoft.Extensions.Localization; + namespace IOL.GreatOffice.Api.Endpoints.Internal.Account; -public class LoginRoute : RouteBaseAsync.WithRequest<LoginPayload>.WithActionResult +public class LoginRoute : RouteBaseAsync.WithRequest<LoginRoute.Payload>.WithActionResult { private readonly MainAppDatabase _database; private readonly UserService _userService; + private readonly IStringLocalizer<SharedResources> _localizer; - public LoginRoute(MainAppDatabase database, UserService userService) { + public LoginRoute(MainAppDatabase database, UserService userService, IStringLocalizer<SharedResources> localizer) { _database = database; _userService = userService; + _localizer = localizer; + } + + public class Payload + { + public string Username { get; set; } + public string Password { get; set; } + public bool Persist { get; set; } } - /// <summary> - /// Login a user. - /// </summary> - /// <param name="request"></param> - /// <param name="cancellationToken"></param> - /// <returns></returns> [AllowAnonymous] [HttpPost("~/_/account/login")] - public override async Task<ActionResult> HandleAsync(LoginPayload request, CancellationToken cancellationToken = default) { - if (!ModelState.IsValid) { - return BadRequest(ModelState); - } - + public override async Task<ActionResult> HandleAsync(Payload request, CancellationToken cancellationToken = default) { var user = _database.Users.SingleOrDefault(u => u.Username == request.Username); if (user == default || !user.VerifyPassword(request.Password)) { - return BadRequest(new KnownProblemModel("Invalid username or password")); + return KnownProblem(_localizer["Invalid username or password"]); } await _userService.LogInUser(HttpContext, user, request.Persist); diff --git a/code/api/src/Endpoints/Internal/Account/UpdateAccountPayload.cs b/code/api/src/Endpoints/Internal/Account/UpdateAccountPayload.cs deleted file mode 100644 index 88a3237..0000000 --- a/code/api/src/Endpoints/Internal/Account/UpdateAccountPayload.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace IOL.GreatOffice.Api.Endpoints.Internal.Account; - -/// <summary> -/// Payload for updating an account. -/// </summary> -public class UpdatePayload -{ - /// <summary> - /// Username to set on the logged on user's account. - /// </summary> - public string Username { get; set; } - - /// <summary> - /// Password to set on the logged on user's account. - /// </summary> - public string Password { get; set; } -} diff --git a/code/api/src/Endpoints/Internal/Account/UpdateAccountRoute.cs b/code/api/src/Endpoints/Internal/Account/UpdateAccountRoute.cs index 02dc3f1..c8999e0 100644 --- a/code/api/src/Endpoints/Internal/Account/UpdateAccountRoute.cs +++ b/code/api/src/Endpoints/Internal/Account/UpdateAccountRoute.cs @@ -1,21 +1,26 @@ +using Microsoft.Extensions.Localization; + namespace IOL.GreatOffice.Api.Endpoints.Internal.Account; -public class UpdateAccountRoute : RouteBaseAsync.WithRequest<UpdatePayload>.WithActionResult +public class UpdateAccountRoute : RouteBaseAsync.WithRequest<UpdateAccountRoute.Payload>.WithActionResult { private readonly MainAppDatabase _database; + private readonly IStringLocalizer<SharedResources> _localizer; - public UpdateAccountRoute(MainAppDatabase database) { + public UpdateAccountRoute(MainAppDatabase database, IStringLocalizer<SharedResources> localizer) { _database = database; + _localizer = localizer; + } + + public class Payload + { + public string Username { get; set; } + + public string Password { get; set; } } - /// <summary> - /// Update the logged on user's data. - /// </summary> - /// <param name="request"></param> - /// <param name="cancellationToken"></param> - /// <returns></returns> [HttpPost("~/_/account/update")] - public override async Task<ActionResult> HandleAsync(UpdatePayload request, CancellationToken cancellationToken = default) { + public override async Task<ActionResult> HandleAsync(Payload request, CancellationToken cancellationToken = default) { var user = _database.Users.SingleOrDefault(c => c.Id == LoggedInUser.Id); if (user == default) { await HttpContext.SignOutAsync(); @@ -23,12 +28,13 @@ public class UpdateAccountRoute : RouteBaseAsync.WithRequest<UpdatePayload>.With } if (request.Password.IsNullOrWhiteSpace() && request.Username.IsNullOrWhiteSpace()) { - return BadRequest(new KnownProblemModel("Invalid request", "No data was submitted")); + return KnownProblem(_localizer["Invalid request"], _localizer["No data was submitted"]); } + var validationProblems = new Dictionary<string, string>(); + if (request.Password.HasValue() && request.Password.Length < 6) { - return BadRequest(new KnownProblemModel("Invalid request", - "The new password must contain at least 6 characters")); + validationProblems.Add("password", _localizer["The new password must contain at least 6 characters"]); } if (request.Password.HasValue()) { @@ -36,8 +42,11 @@ public class UpdateAccountRoute : RouteBaseAsync.WithRequest<UpdatePayload>.With } if (request.Username.HasValue() && !request.Username.IsValidEmailAddress()) { - return BadRequest(new KnownProblemModel("Invalid request", - "The new username does not look like a valid email address")); + validationProblems.Add("username", _localizer["The new username does not look like a valid email address"]); + } + + if (validationProblems.Any()) { + return KnownProblem(_localizer["Validation problems"], _localizer["Your form has invalid values"], validationProblems); } if (request.Username.HasValue()) { diff --git a/code/api/src/Endpoints/Internal/PasswordResetRequests/CreateResetRequestPayload.cs b/code/api/src/Endpoints/Internal/PasswordResetRequests/CreateResetRequestPayload.cs deleted file mode 100644 index 1adb344..0000000 --- a/code/api/src/Endpoints/Internal/PasswordResetRequests/CreateResetRequestPayload.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace IOL.GreatOffice.Api.Endpoints.Internal.PasswordResetRequests; - -public class CreateResetRequestPayload -{ - public string Username { get; set; } -}
\ No newline at end of file diff --git a/code/api/src/Endpoints/Internal/PasswordResetRequests/CreateResetRequestRoute.cs b/code/api/src/Endpoints/Internal/PasswordResetRequests/CreateResetRequestRoute.cs index bb72d38..49df35b 100644 --- a/code/api/src/Endpoints/Internal/PasswordResetRequests/CreateResetRequestRoute.cs +++ b/code/api/src/Endpoints/Internal/PasswordResetRequests/CreateResetRequestRoute.cs @@ -1,26 +1,25 @@ namespace IOL.GreatOffice.Api.Endpoints.Internal.PasswordResetRequests; -public class Route : RouteBaseAsync.WithRequest<CreateResetRequestPayload>.WithActionResult +public class CreateResetRequestRoute : RouteBaseAsync.WithRequest<CreateResetRequestRoute.Payload>.WithActionResult { - private readonly ILogger<Route> _logger; + private readonly ILogger<CreateResetRequestRoute> _logger; private readonly PasswordResetService _passwordResetService; private readonly MainAppDatabase _database; - public Route(ILogger<Route> logger, PasswordResetService passwordResetService, MainAppDatabase database) { + public CreateResetRequestRoute(ILogger<CreateResetRequestRoute> logger, PasswordResetService passwordResetService, MainAppDatabase database) { _logger = logger; _passwordResetService = passwordResetService; _database = database; } - /// <summary> - /// Create a new password reset request. - /// </summary> - /// <param name="request"></param> - /// <param name="cancellationToken"></param> - /// <returns></returns> + public class Payload + { + public string Username { get; set; } + } + [AllowAnonymous] [HttpPost("~/_/password-reset-request/create")] - public override async Task<ActionResult> HandleAsync(CreateResetRequestPayload request, CancellationToken cancellationToken = default) { + public override async Task<ActionResult> HandleAsync(Payload request, CancellationToken cancellationToken = default) { if (!request.Username.IsValidEmailAddress()) { _logger.LogInformation("Username is invalid, not doing request for password change"); return KnownProblem("Invalid email address", request.Username + " looks like an invalid email address"); diff --git a/code/api/src/Endpoints/Internal/PasswordResetRequests/FulfillResetRequestPayload.cs b/code/api/src/Endpoints/Internal/PasswordResetRequests/FulfillResetRequestPayload.cs deleted file mode 100644 index f0fb59f..0000000 --- a/code/api/src/Endpoints/Internal/PasswordResetRequests/FulfillResetRequestPayload.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace IOL.GreatOffice.Api.Endpoints.Internal.PasswordResetRequests; - -public class FulfillResetRequestPayload -{ - /// <summary> - /// Id of the password reset request to fulfill - /// </summary> - public Guid Id { get; set; } - - /// <summary> - /// New password to set on the relevant account - /// </summary> - public string NewPassword { get; set; } -} diff --git a/code/api/src/Endpoints/Internal/PasswordResetRequests/FulfillResetRequestRoute.cs b/code/api/src/Endpoints/Internal/PasswordResetRequests/FulfillResetRequestRoute.cs index 5749242..c831470 100644 --- a/code/api/src/Endpoints/Internal/PasswordResetRequests/FulfillResetRequestRoute.cs +++ b/code/api/src/Endpoints/Internal/PasswordResetRequests/FulfillResetRequestRoute.cs @@ -1,6 +1,6 @@ namespace IOL.GreatOffice.Api.Endpoints.Internal.PasswordResetRequests; -public class FulfillResetRequestRoute : RouteBaseAsync.WithRequest<FulfillResetRequestPayload>.WithActionResult +public class FulfillResetRequestRoute : RouteBaseAsync.WithRequest<FulfillResetRequestRoute.Payload>.WithActionResult { private readonly PasswordResetService _passwordResetService; @@ -8,9 +8,15 @@ public class FulfillResetRequestRoute : RouteBaseAsync.WithRequest<FulfillResetR _passwordResetService = passwordResetService; } + public class Payload + { + public Guid Id { get; set; } + public string NewPassword { get; set; } + } + [AllowAnonymous] [HttpPost("~/_/password-reset-request/fulfill")] - public override async Task<ActionResult> HandleAsync(FulfillResetRequestPayload request, CancellationToken cancellationToken = default) { + public override async Task<ActionResult> HandleAsync(Payload request, CancellationToken cancellationToken = default) { return await _passwordResetService.FulfillRequestAsync(request.Id, request.NewPassword, cancellationToken) switch { FulfillPasswordResetRequestResult.REQUEST_NOT_FOUND => NotFound(), FulfillPasswordResetRequestResult.USER_NOT_FOUND => NotFound(), diff --git a/code/api/src/Endpoints/V1/ApiTokens/CreateTokenRoute.cs b/code/api/src/Endpoints/V1/ApiTokens/CreateTokenRoute.cs index 6bc2fdc..c4f2ee5 100644 --- a/code/api/src/Endpoints/V1/ApiTokens/CreateTokenRoute.cs +++ b/code/api/src/Endpoints/V1/ApiTokens/CreateTokenRoute.cs @@ -21,8 +21,6 @@ public class CreateTokenRoute : RouteBaseSync.WithRequest<ApiAccessToken.ApiAcce /// <returns></returns> [ApiVersion(ApiSpecV1.VERSION_STRING)] [HttpPost("~/v{version:apiVersion}/api-tokens/create")] - [ProducesResponseType(200, Type = typeof(string))] - [ProducesResponseType(404, Type = typeof(KnownProblemModel))] public override ActionResult Handle(ApiAccessToken.ApiAccessTokenDto request) { var user = _database.Users.SingleOrDefault(c => c.Id == LoggedInUser.Id); if (user == default) { |
