From 4dbef3fcd7a14437d55c555cf10d50de8e50d7d1 Mon Sep 17 00:00:00 2001 From: ivarlovlie Date: Fri, 9 Dec 2022 11:57:12 +0900 Subject: feat: Move everything out of $lib --- .../src/services/abstractions/IAccountService.ts | 58 +++++++++++ .../services/abstractions/IPasswordResetService.ts | 21 ++++ .../src/services/abstractions/ISettingsService.ts | 3 + code/app/src/services/account-service.ts | 116 +++++++++++++++++++++ code/app/src/services/password-reset-service.ts | 45 ++++++++ code/app/src/services/settings-service.ts | 7 ++ 6 files changed, 250 insertions(+) create mode 100644 code/app/src/services/abstractions/IAccountService.ts create mode 100644 code/app/src/services/abstractions/IPasswordResetService.ts create mode 100644 code/app/src/services/abstractions/ISettingsService.ts create mode 100644 code/app/src/services/account-service.ts create mode 100644 code/app/src/services/password-reset-service.ts create mode 100644 code/app/src/services/settings-service.ts (limited to 'code/app/src/services') diff --git a/code/app/src/services/abstractions/IAccountService.ts b/code/app/src/services/abstractions/IAccountService.ts new file mode 100644 index 0000000..0645eb6 --- /dev/null +++ b/code/app/src/services/abstractions/IAccountService.ts @@ -0,0 +1,58 @@ +import type {KnownProblem} from "$models/internal/KnownProblem"; +import type {Writable} from "svelte/store"; + +export interface IAccountService { + session: Writable, + + login_async(payload: LoginPayload): Promise, + + logout_async(): Promise, + + create_account_async(payload: CreateAccountPayload): Promise, + + delete_current_async(): Promise, + + update_current_async(payload: UpdateAccountPayload): Promise, +} + +export type Session = { + username: string, + displayName: string, + id: string, + _lastUpdated: number +} + +export type LoginPayload = { + username: string, + password: string, + persist: boolean +} + +export type LoginResponse = { + isLoggedIn: boolean, + knownProblem?: KnownProblem +} + +export type CreateAccountPayload = { + username: string, + password: string, +} + +export type CreateAccountResponse = { + isCreated: boolean, + knownProblem?: KnownProblem +} + +export type DeleteAccountResponse = { + isDeleted: boolean +} + +export type UpdateAccountPayload = { + username: string, + password: string +} + +export type UpdateAccountResponse = { + isUpdated: boolean, + knownProblem?: KnownProblem +} \ No newline at end of file diff --git a/code/app/src/services/abstractions/IPasswordResetService.ts b/code/app/src/services/abstractions/IPasswordResetService.ts new file mode 100644 index 0000000..59d2bc6 --- /dev/null +++ b/code/app/src/services/abstractions/IPasswordResetService.ts @@ -0,0 +1,21 @@ +import type { KnownProblem } from "$models/internal/KnownProblem" + +export interface IPasswordResetService { + create_request_async(email: string): Promise, + fulfill_request_async(id: string, newPassword: string): Promise, + request_is_valid_async(id: string): Promise +} + +export type RequestIsValidResponse = { + isValid: boolean +} + +export type FulfillRequestResponse = { + isFulfilled: boolean, + knownProblem?: KnownProblem +} + +export type CreateRequestResponse = { + isCreated: boolean, + knownProblem?: KnownProblem +} \ No newline at end of file diff --git a/code/app/src/services/abstractions/ISettingsService.ts b/code/app/src/services/abstractions/ISettingsService.ts new file mode 100644 index 0000000..366e337 --- /dev/null +++ b/code/app/src/services/abstractions/ISettingsService.ts @@ -0,0 +1,3 @@ +export interface ISettingsService { + get_user_settings(): Promise, +} \ No newline at end of file diff --git a/code/app/src/services/account-service.ts b/code/app/src/services/account-service.ts new file mode 100644 index 0000000..9d45950 --- /dev/null +++ b/code/app/src/services/account-service.ts @@ -0,0 +1,116 @@ +import {http_delete_async, http_get_async, http_post_async} from "$api/_fetch"; +import {api_base, CookieNames, StorageKeys} from "$configuration"; +import {is_known_problem} from "$models/internal/KnownProblem"; +import {log_debug} from "$help/logger"; +import {StoreType, writable_persistent} from "$help/persistent-store"; +import {get} from "svelte/store"; +import type {Writable} from "svelte/store"; +import {Temporal} from "temporal-polyfill"; +import type { + CreateAccountPayload, + CreateAccountResponse, + DeleteAccountResponse, + IAccountService, + LoginPayload, + LoginResponse, + Session, + UpdateAccountPayload, + UpdateAccountResponse, +} from "./abstractions/IAccountService"; + +export class AccountService implements IAccountService { + session: Writable; + private sessionCooldown = 3600; + + constructor() { + this.session = writable_persistent({ + name: StorageKeys.session, + initialState: {} as Session, + options: { + store: StoreType.LOCAL, + }, + }); + this.refresh_session(); + } + + async refresh_session(forceRefresh: boolean = false): Promise { + const currentValue = get(this.session); + const currentEpoch = Temporal.Now.instant().epochSeconds; + if (currentValue?._lastUpdated + this.sessionCooldown < currentEpoch) { + log_debug("Session is not stale yet", { + currentEpoch, + staleEpoch: currentValue?._lastUpdated + this.sessionCooldown, + }); + return; + } + const sessionResponse = await http_get_async(api_base("_/account/session")); + if (sessionResponse.ok) { + + } + } + + async end_session(callback: Function): Promise { + await this.logout_async(); + this.session.set(null); + if (typeof callback === "function") callback(); + return; + } + + async login_async(payload: LoginPayload): Promise { + const response = await http_post_async(api_base("_/account/login"), payload); + if (response.ok) return {isLoggedIn: true}; + if (is_known_problem(response)) return { + isLoggedIn: false, + knownProblem: await response.json(), + }; + return { + isLoggedIn: false, + }; + } + + async logout_async(): Promise { + const response = await http_get_async(api_base("_/account/logout")); + + if (!response.ok) { + const deleteCookieResponse = await fetch("/delete-cookie?key=" + CookieNames.session); + if (!deleteCookieResponse.ok) { + throw new Error("Could neither logout nor delete session cookie."); + } + } + + return; + } + + async create_account_async(payload: CreateAccountPayload): Promise { + const response = await http_post_async(api_base("_/account/create"), payload); + if (response.ok) return {isCreated: true}; + if (is_known_problem(response)) return { + isCreated: false, + knownProblem: await response.json(), + }; + + return { + isCreated: false, + }; + } + + async delete_current_async(): Promise { + const response = await http_delete_async(api_base("_/account/delete")); + return { + isDeleted: response.ok, + }; + } + + async update_current_async(payload: UpdateAccountPayload): Promise { + const response = await http_post_async(api_base("_/account/update"), payload); + if (response.ok) return {isUpdated: true}; + if (is_known_problem(response)) return { + isUpdated: false, + knownProblem: await response.json(), + }; + + return { + isUpdated: false, + }; + } +} \ No newline at end of file diff --git a/code/app/src/services/password-reset-service.ts b/code/app/src/services/password-reset-service.ts new file mode 100644 index 0000000..ab3a953 --- /dev/null +++ b/code/app/src/services/password-reset-service.ts @@ -0,0 +1,45 @@ +import {http_get_async, http_post_async} from "$api/_fetch"; +import {api_base} from "$configuration"; +import {is_known_problem} from "$models/internal/KnownProblem"; +import type { + CreateRequestResponse, + FulfillRequestResponse, + IPasswordResetService, + RequestIsValidResponse, +} from "./abstractions/IPasswordResetService"; + +export class PasswordResetService implements IPasswordResetService { + async create_request_async(email: string): Promise { + const response = await http_post_async(api_base("_/password-reset-request/create"), {email}); + if (response.ok) return {isCreated: true}; + if (is_known_problem(response)) return { + isCreated: false, + knownProblem: await response.json(), + }; + + return { + isCreated: false, + }; + } + + async fulfill_request_async(id: string, newPassword: string): Promise { + const response = await http_post_async(api_base("_/password-reset-request/fulfill"), {id: id, newPassword}); + if (response.ok) return {isFulfilled: true}; + if (is_known_problem(response)) return { + isFulfilled: false, + knownProblem: await response.json(), + }; + + return { + isFulfilled: false, + }; + } + + async request_is_valid_async(id: string): Promise { + const response = await http_get_async(api_base("_/password-reset-request/is-valid?id=" + id)); + const responseBody = await response.json() as { isValid: boolean }; + return { + isValid: responseBody.isValid, + }; + } +} \ No newline at end of file diff --git a/code/app/src/services/settings-service.ts b/code/app/src/services/settings-service.ts new file mode 100644 index 0000000..20053a9 --- /dev/null +++ b/code/app/src/services/settings-service.ts @@ -0,0 +1,7 @@ +import type {ISettingsService} from "./abstractions/ISettingsService"; + +export class SettingsService implements ISettingsService { + get_user_settings(): Promise { + throw new Error("Method not implemented."); + } +} \ No newline at end of file -- cgit v1.3