diff options
| author | ivarlovlie <git@ivarlovlie.no> | 2022-11-14 07:56:56 +0100 |
|---|---|---|
| committer | ivarlovlie <git@ivarlovlie.no> | 2022-11-14 08:05:36 +0100 |
| commit | 4b5597b3fe6e02f1655e6a731e83bdcdf1017d63 (patch) | |
| tree | 818580507641787380b58bdcfa7d0ed7026f4e82 /code | |
| parent | 99b0c09a6bb984d811b63788015cfad1855b5f3c (diff) | |
| download | greatoffice-4b5597b3fe6e02f1655e6a731e83bdcdf1017d63.tar.xz greatoffice-4b5597b3fe6e02f1655e6a731e83bdcdf1017d63.zip | |
refactor: Api files always returns Response
Diffstat (limited to 'code')
8 files changed, 105 insertions, 162 deletions
diff --git a/code/api/src/Data/Enums/PasswordResetRequestStatus.cs b/code/api/src/Data/Enums/PasswordResetRequestStatus.cs new file mode 100644 index 0000000..5629e6f --- /dev/null +++ b/code/api/src/Data/Enums/PasswordResetRequestStatus.cs @@ -0,0 +1,6 @@ +namespace IOL.GreatOffice.Api.Data.Enums; + +public enum PasswordResetRequestStatus +{ + +}
\ 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 49df35b..edf825e 100644 --- a/code/api/src/Endpoints/Internal/PasswordResetRequests/CreateResetRequestRoute.cs +++ b/code/api/src/Endpoints/Internal/PasswordResetRequests/CreateResetRequestRoute.cs @@ -1,6 +1,6 @@ namespace IOL.GreatOffice.Api.Endpoints.Internal.PasswordResetRequests; -public class CreateResetRequestRoute : RouteBaseAsync.WithRequest<CreateResetRequestRoute.Payload>.WithActionResult +public class CreateResetRequestRoute : RouteBaseAsync.WithRequest<string>.WithActionResult { private readonly ILogger<CreateResetRequestRoute> _logger; private readonly PasswordResetService _passwordResetService; @@ -12,34 +12,18 @@ public class CreateResetRequestRoute : RouteBaseAsync.WithRequest<CreateResetReq _database = database; } - public class Payload - { - public string Username { get; set; } - } - [AllowAnonymous] [HttpPost("~/_/password-reset-request/create")] - 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"); - } - + public override async Task<ActionResult> HandleAsync([FromQuery(Name = "for_user")] string username, CancellationToken cancellationToken = default) { var tz = GetRequestTimeZone(_logger); _logger.LogInformation("Creating forgot password request with local date time: " + tz.LocalDateTime.ToString("u")); - try { - var user = _database.Users.SingleOrDefault(c => c.Username.Equals(request.Username)); - if (user != default) { - await _passwordResetService.AddRequestAsync(user, tz.TimeZoneInfo, cancellationToken); - return Ok(); - } + var user = _database.Users.FirstOrDefault(c => c.Username.Equals(username)); + // Don't inform the caller that the user does not exist. + if (user == default) return Ok(); + + await _passwordResetService.AddRequestAsync(user, tz.TimeZoneInfo, cancellationToken); - _logger.LogInformation("User was not found, not doing request for password change"); - return Ok(); - } catch (Exception e) { - _logger.LogError(e, "_/password-reset-request/create threw an exception"); - return Ok(); - } + return Ok(); } }
\ No newline at end of file diff --git a/code/api/src/Endpoints/Internal/PasswordResetRequests/IsResetRequestValidRoute.cs b/code/api/src/Endpoints/Internal/PasswordResetRequests/IsResetRequestValidRoute.cs index 687cef6..1ad0f47 100644 --- a/code/api/src/Endpoints/Internal/PasswordResetRequests/IsResetRequestValidRoute.cs +++ b/code/api/src/Endpoints/Internal/PasswordResetRequests/IsResetRequestValidRoute.cs @@ -8,12 +8,6 @@ public class IsResetRequestValidRoute : RouteBaseAsync.WithRequest<Guid>.WithAct _passwordResetService = passwordResetService; } - /// <summary> - /// Check if a given password reset request is still valid. - /// </summary> - /// <param name="id"></param> - /// <param name="cancellationToken"></param> - /// <returns></returns> [AllowAnonymous] [HttpGet("~/_/password-reset-request/is-valid")] public override async Task<ActionResult> HandleAsync(Guid id, CancellationToken cancellationToken = default) { diff --git a/code/api/src/Utilities/SwaggerGenOptionsExtensions.cs b/code/api/src/Utilities/SwaggerGenOptionsExtensions.cs index 9b70194..a3d9036 100644 --- a/code/api/src/Utilities/SwaggerGenOptionsExtensions.cs +++ b/code/api/src/Utilities/SwaggerGenOptionsExtensions.cs @@ -8,36 +8,36 @@ namespace IOL.GreatOffice.Api.Utilities; public static class SwaggerGenOptionsExtensions { - /// <summary> - /// Updates Swagger document to support ApiEndpoints.<br/><br/> - /// For controllers inherited from <see cref="EndpointBase"/>:<br/> - /// - Replaces action Tag with <c>[namespace]</c><br/> - /// </summary> - public static void UseApiEndpoints(this SwaggerGenOptions options) { - options.TagActionsBy(EndpointNamespaceOrDefault); - } + /// <summary> + /// Updates Swagger document to support ApiEndpoints.<br/><br/> + /// For controllers inherited from <see cref="EndpointBase"/>:<br/> + /// - Replaces action Tag with <c>[namespace]</c><br/> + /// </summary> + public static void UseApiEndpoints(this SwaggerGenOptions options) { + options.TagActionsBy(EndpointNamespaceOrDefault); + } - private static IList<string?> EndpointNamespaceOrDefault(ApiDescription api) { - if (api.ActionDescriptor is not ControllerActionDescriptor actionDescriptor) { - throw new InvalidOperationException($"Unable to determine tag for endpoint: {api.ActionDescriptor.DisplayName}"); - } + private static IList<string?> EndpointNamespaceOrDefault(ApiDescription api) { + if (api.ActionDescriptor is not ControllerActionDescriptor actionDescriptor) { + throw new InvalidOperationException($"Unable to determine tag for endpoint: {api.ActionDescriptor.DisplayName}"); + } - if (actionDescriptor.ControllerTypeInfo.GetBaseTypesAndThis().Any(t => t == typeof(EndpointBase))) { - return new[] { - actionDescriptor.ControllerTypeInfo.Namespace?.Split('.').Last() - }; - } + if (actionDescriptor.ControllerTypeInfo.GetBaseTypesAndThis().Any(t => t == typeof(EndpointBase))) { + return new[] { + actionDescriptor.ControllerTypeInfo.Namespace?.Split('.').Last() + }; + } - return new[] { - actionDescriptor.ControllerName - }; - } + return new[] { + actionDescriptor.ControllerName + }; + } - public static IEnumerable<Type> GetBaseTypesAndThis(this Type type) { - var current = type; - while (current != null) { - yield return current; - current = current.BaseType; - } - } -} + private static IEnumerable<Type> GetBaseTypesAndThis(this Type type) { + var current = type; + while (current != null) { + yield return current; + current = current.BaseType; + } + } +}
\ No newline at end of file diff --git a/code/app/src/lib/api/_fetch.ts b/code/app/src/lib/api/_fetch.ts index b28e398..62cdf84 100644 --- a/code/app/src/lib/api/_fetch.ts +++ b/code/app/src/lib/api/_fetch.ts @@ -1,32 +1,30 @@ -import { Temporal } from "temporal-polyfill"; -import { clear_session_data } from "$lib/session"; -import type { Result } from "rustic"; -import { Err, Ok } from "rustic"; -import { redirect } from "@sveltejs/kit"; -import { browser } from "$app/environment"; -import { goto } from "$app/navigation"; -import { SignInPageMessage, signInPageMessageQueryKey } from "$routes/(main)/(public)/sign-in"; -import { log_error } from "$lib/logger"; +import {Temporal} from "temporal-polyfill"; +import {clear_session_data} from "$lib/session"; +import {redirect} from "@sveltejs/kit"; +import {browser} from "$app/environment"; +import {goto} from "$app/navigation"; +import {SignInPageMessage, signInPageMessageQueryKey} from "$routes/(main)/(public)/sign-in"; +import {log_error} from "$lib/logger"; -export async function http_post_async<T>(url: string, body?: object | string, timeout = -1, skip_401_check = false, abort_signal?: AbortSignal): Promise<InternalFetchResponse<T>> { +export async function http_post_async(url: string, body?: object | string, timeout = -1, skip_401_check = false, abort_signal?: AbortSignal): Promise<Response> { const init = make_request_init("post", body, abort_signal); - const response = await internal_fetch_async({ url, init, timeout }); - if (!skip_401_check && await redirect_if_401_async(response)) return Err("Server returned 401"); - return make_response_async(response); + const response = await internal_fetch_async({url, init, timeout}); + if (!skip_401_check && await redirect_if_401_async(response)) throw new Error("Server returned 401"); + return response; } -export async function http_get_async<T>(url: string, timeout = -1, skip_401_check = false, abort_signal?: AbortSignal): Promise<Result<InternalFetchResponse<T>, string>> { +export async function http_get_async(url: string, timeout = -1, skip_401_check = false, abort_signal?: AbortSignal): Promise<Response> { const init = make_request_init("get", undefined, abort_signal); - const response = await internal_fetch_async({ url, init, timeout }); - if (!skip_401_check && await redirect_if_401_async(response)) return Err("Server returned 401"); - return make_response_async(response); + const response = await internal_fetch_async({url, init, timeout}); + if (!skip_401_check && await redirect_if_401_async(response)) throw new Error("Server returned 401"); + return response; } -export async function http_delete_async<T>(url: string, body?: object | string, timeout = -1, skip_401_check = false, abort_signal?: AbortSignal): Promise<Result<InternalFetchResponse<T>, string>> { +export async function http_delete_async(url: string, body?: object | string, timeout = -1, skip_401_check = false, abort_signal?: AbortSignal): Promise<Response> { const init = make_request_init("delete", body, abort_signal); - const response = await internal_fetch_async({ url, init, timeout }); - if (!skip_401_check && await redirect_if_401_async(response)) return Err("Server returned 401"); - return make_response_async(response); + const response = await internal_fetch_async({url, init, timeout}); + if (!skip_401_check && await redirect_if_401_async(response)) throw new Error("Server returned 401"); + return response; } async function internal_fetch_async(request: InternalFetchRequest): Promise<Response> { @@ -38,7 +36,7 @@ async function internal_fetch_async(request: InternalFetchRequest): Promise<Resp if (request.timeout && request.timeout > 500) { response = await Promise.race([ fetch(fetch_request), - new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), request.timeout)) + new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), request.timeout)), ]); } else { response = await fetch(fetch_request); @@ -62,7 +60,7 @@ async function redirect_if_401_async(response: Response): Promise<boolean> { const redirectUrl = `/sign-in?${signInPageMessageQueryKey}=${SignInPageMessage.LOGGED_OUT}`; clear_session_data(); if (browser) { - await goto(redirectUrl) + await goto(redirectUrl); } else { throw redirect(307, redirectUrl); } @@ -70,36 +68,18 @@ async function redirect_if_401_async(response: Response): Promise<boolean> { return false; } -async function make_response_async<T>(response: Response): Promise<Result<InternalFetchResponse<T>, string>> { - const result = { - ok: response.ok, - status: response.status, - http_response: response, - } as InternalFetchResponse<T>; - - if (response.status !== 204) { - try { - result.data = await response.json() as T; - } catch (error) { - log_error("", { error, result }) - return Err("Deserialisation threw"); - } - } - return Ok(result); -} - function make_request_init(method: string, body?: any, signal?: AbortSignal): RequestInit { const init = { method, signal, headers: { "X-TimeZone": Temporal.Now.timeZone().id, - } + }, } as RequestInit; if (body) { init.body = JSON.stringify(body); - init.headers["Content-Type"] = "application/json;charset=UTF-8" + init.headers["Content-Type"] = "application/json;charset=UTF-8"; } return init; @@ -111,11 +91,4 @@ export type InternalFetchRequest = { init: RequestInit, timeout?: number retry_count?: number, -} - -export type InternalFetchResponse<T> = { - ok: boolean, - status: number, - data: T | undefined, - http_response: Response }
\ No newline at end of file diff --git a/code/app/src/lib/api/account/index.ts b/code/app/src/lib/api/account/index.ts index 305bd9f..cfd627b 100644 --- a/code/app/src/lib/api/account/index.ts +++ b/code/app/src/lib/api/account/index.ts @@ -1,39 +1,40 @@ -import { api_base } from "$lib/configuration"; -import { http_delete_async, http_get_async, http_post_async, type InternalFetchResponse } from "../_fetch"; -import type { LoginPayload } from "$lib/api/account/models/LoginPayload"; -import type { Result } from "rustic"; -import { isOk, Ok, Err } from "rustic"; -import type { SessionData } from "$lib/models/base/SessionData"; -import type { CreateAccountPayload } from "./models/CreateAccountPayload"; -import type { UpdateProfilePayload } from "./models/UpdateProfilePayload"; -import type { ErrorResult } from "$lib/models/internal/ErrorResult"; +import {api_base} from "$lib/configuration"; +import type {SessionData} from "src/lib/models/base/SessionData"; +import {http_delete_async, http_get_async, http_post_async} from "../_fetch"; export const http_account = { - async login_async(payload: LoginPayload): Promise<InternalFetchResponse<Result<void, ErrorResult>>> { - const response = await http_post_async<Result<void, ErrorResult>>(api_base("_/account/login"), payload); - if (isOk(response)) { - return Ok(); - } - return Err(response.data); + login_async(payload: LoginPayload): Promise<Response> { + return http_post_async(api_base("_/account/login"), payload); }, - logout_async(): Promise<InternalFetchResponse<void>> { - return http_get_async<void>(api_base("_/account/logout")); + logout_async(): Promise<Response> { + return http_get_async(api_base("_/account/logout")); }, - delete_account_async(): Promise<InternalFetchResponse> { + delete_account_async(): Promise<Response> { return http_delete_async(api_base("_/account/delete")); }, - update_profile_async(payload: UpdateProfilePayload): Promise<InternalFetchResponse> { - if (!payload.password && !payload.username) throw new Error("Password and Username is empty"); + update_profile_async(payload: UpdateProfilePayload): Promise<Response> { return http_post_async(api_base("_/account/update"), payload); }, - create_account_async(payload: CreateAccountPayload): Promise<InternalFetchResponse> { - if (!payload.password && !payload.username) throw new Error("Password and Username is empty"); + create_account_async(payload: CreateAccountPayload): Promise<Response> { return http_post_async(api_base("_/account/create"), payload); }, - async get_profile_async(suppress_401: boolean): Promise<Result<SessionData, string>> { - const response = await http_get_async<SessionData>(api_base("_/account"), 0, true); - if (isOk(response)) { - return Ok(response.data.data); - } - } -}
\ No newline at end of file + get_profile_async(suppress_401: boolean): Promise<Response> { + return http_get_async(api_base("_/account"), 0, suppress_401); + }, +}; + +export interface CreateAccountPayload { + username: string, + password: string +} + +export interface LoginPayload { + username: string, + password: string, + persist: boolean +} + +export interface UpdateProfilePayload { + username?: string, + password?: string, +} diff --git a/code/app/src/lib/api/password-reset-request/index.ts b/code/app/src/lib/api/password-reset-request/index.ts index 9d6f0dc..53cb1d2 100644 --- a/code/app/src/lib/api/password-reset-request/index.ts +++ b/code/app/src/lib/api/password-reset-request/index.ts @@ -1,17 +1,14 @@ -import { api_base } from "$lib/configuration"; -import { http_get_async, http_post_async, type InternalFetchResponse } from "../_fetch"; +import {api_base} from "$lib/configuration"; +import {http_get_async, http_post_async} from "../_fetch"; export const http_password_reset_request = { - create_forgot_password_request(username: string): Promise<InternalFetchResponse> { - if (!username) throw new Error("Username is empty"); - return http_get_async(api_base("_/forgot-password-requests/create?username=" + username)); + create_request_async(username: string): Promise<Response> { + return http_get_async(api_base("_/password-reset-request/create?for_user=" + username)); }, - check_forgot_password_request(public_id: string): Promise<InternalFetchResponse> { - if (!public_id) throw new Error("Id is empty"); - return http_get_async(api_base("_/forgot-password-requests/is-valid?id=" + public_id)); + check_request_async(publicId: string): Promise<Response> { + return http_get_async(api_base("_/password-reset-request/is-valid?id=" + publicId)); }, - fulfill_forgot_password_request(public_id: string, newPassword: string): Promise<InternalFetchResponse> { - if (!public_id) throw new Error("Id is empty"); - return http_post_async(api_base("_/forgot-password-requests/fulfill"), { id: public_id, newPassword }); + fulfill_request_async(publicId: string, newPassword: string): Promise<Response> { + return http_post_async(api_base("_/password-reset-request/fulfill"), {id: publicId, newPassword}); }, -}
\ No newline at end of file +};
\ No newline at end of file diff --git a/code/app/src/lib/api/root.ts b/code/app/src/lib/api/root.ts deleted file mode 100644 index 661f24b..0000000 --- a/code/app/src/lib/api/root.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { http_get_async, http_post_async } from "$lib/api/_fetch"; -import { api_base } from "$lib/configuration"; -import type { IInternalFetchResponse } from "$lib/models/internal/IInternalFetchResponse"; -import type { Result } from "rustic"; - -export function server_log(message: string): void { - http_post_async(api_base("_/api/log"), message); -} - -export function server_version(): Promise<Result<IInternalFetchResponse<string>, string>> { - return http_get_async(api_base("/version.txt")); -}
\ No newline at end of file |
