diff options
Diffstat (limited to 'code/frontend/src/services')
8 files changed, 315 insertions, 0 deletions
diff --git a/code/frontend/src/services/abstractions/IAccountService.ts b/code/frontend/src/services/abstractions/IAccountService.ts new file mode 100644 index 0000000..d3d48b0 --- /dev/null +++ b/code/frontend/src/services/abstractions/IAccountService.ts @@ -0,0 +1,54 @@ +import type { KnownProblem } from "$models/internal/KnownProblem"; +import type { Writable } from "svelte/store"; + +export interface IAccountService { + session: Writable<Session>, + login_async(payload: LoginPayload): Promise<LoginResponse>, + logout_async(): Promise<void>, + end_session_async(callback?: Function): Promise<void>, + create_account_async(payload: CreateAccountPayload): Promise<CreateAccountResponse>, + delete_current_async(): Promise<DeleteAccountResponse>, + update_current_async(payload: UpdateAccountPayload): Promise<UpdateAccountResponse>, +} + +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/frontend/src/services/abstractions/IApiTokenService.ts b/code/frontend/src/services/abstractions/IApiTokenService.ts new file mode 100644 index 0000000..fdf82eb --- /dev/null +++ b/code/frontend/src/services/abstractions/IApiTokenService.ts @@ -0,0 +1,34 @@ +import type { Temporal } from "temporal-polyfill" + +export interface IApiTokenService { + create_token_async(payload: CreateTokenPayload): Promise<CreateTokenResponse>, + delete_token_async(payload: DeleteTokenPayload): Promise<DeleteTokenResponse>, + get_tokens_async(query: TokenQuery): Promise<GetTokensResponse> +} +export type GetTokensResponse = { + results: Array<GetTokensTokenModel> +}; +export type GetTokensTokenModel = { + id: string, + name: string, + permissions: string[] +} +export type TokenQuery = { + includeStale: boolean +}; +export type DeleteTokenResponse = { + isDeleted: boolean +}; +export type DeleteTokenPayload = { + id: string +}; +export type CreateTokenResponse = { + isCreated: boolean +}; +export type CreateTokenPayload = { + expiryDate: Temporal.PlainDateTime, + allowRead: boolean, + allowCreate: boolean, + allowUpdate: boolean, + allowDelete: boolean +};
\ No newline at end of file diff --git a/code/frontend/src/services/abstractions/IPasswordResetService.ts b/code/frontend/src/services/abstractions/IPasswordResetService.ts new file mode 100644 index 0000000..59d2bc6 --- /dev/null +++ b/code/frontend/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<CreateRequestResponse>, + fulfill_request_async(id: string, newPassword: string): Promise<FulfillRequestResponse>, + request_is_valid_async(id: string): Promise<RequestIsValidResponse> +} + +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/frontend/src/services/abstractions/ISettingsService.ts b/code/frontend/src/services/abstractions/ISettingsService.ts new file mode 100644 index 0000000..366e337 --- /dev/null +++ b/code/frontend/src/services/abstractions/ISettingsService.ts @@ -0,0 +1,3 @@ +export interface ISettingsService { + get_user_settings(): Promise<void>, +}
\ No newline at end of file diff --git a/code/frontend/src/services/account-service.ts b/code/frontend/src/services/account-service.ts new file mode 100644 index 0000000..96e75f5 --- /dev/null +++ b/code/frontend/src/services/account-service.ts @@ -0,0 +1,123 @@ +import {http_delete_async, http_get_async, http_post_async} from "$utils/_fetch"; +import {browser} from "$app/environment"; +import {api_base, CookieNames, StorageKeys} from "$configuration"; +import {is_known_problem} from "$models/internal/KnownProblem"; +import {StoreType, create_writable_persistent} from "$utils/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<Session> | undefined; + private sessionCooldown = 3600; + + constructor() { + if (browser) { + this.session = create_writable_persistent({ + name: StorageKeys.session, + initialState: {} as Session, + options: { + store: StoreType.LOCAL, + }, + }); + this.refresh_session(); + } else { + this.session = undefined; + } + } + + static resolve(): IAccountService { + return new AccountService(); + } + + async refresh_session(forceRefresh: boolean = false): Promise<void> { + if (!this.session) return; + const currentValue = get(this.session); + const currentEpoch = Temporal.Now.instant().epochSeconds; + if (!forceRefresh && ((currentValue?._lastUpdated ?? 0) + this.sessionCooldown) > currentEpoch) { + console.debug("Session is not stale yet", { + currentEpoch, + staleEpoch: currentValue?._lastUpdated + this.sessionCooldown, + }); + return; + } + const sessionResponse = await http_get_async(api_base("_/session-data")); + if (sessionResponse.ok) { + this.session.set(await sessionResponse.json()); + } else { + this.session.set(null); + } + } + + async end_session_async(callback: Function = undefined): Promise<void> { + if (!this.session) return; + await this.logout_async(); + this.session.set(null); + if (callback && typeof callback === "function") callback(); + } + + async login_async(payload: LoginPayload): Promise<LoginResponse> { + 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<void> { + 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<CreateAccountResponse> { + 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<DeleteAccountResponse> { + const response = await http_delete_async(api_base("_/account/delete")); + return { + isDeleted: response.ok, + }; + } + + async update_current_async(payload: UpdateAccountPayload): Promise<UpdateAccountResponse> { + 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/frontend/src/services/api-tokens-service.ts b/code/frontend/src/services/api-tokens-service.ts new file mode 100644 index 0000000..e0f2c2a --- /dev/null +++ b/code/frontend/src/services/api-tokens-service.ts @@ -0,0 +1,22 @@ +import { api_base } from "$configuration"; +import { http_delete_async, http_get_async, http_post_async } from "$utils/_fetch"; +import type { CreateTokenPayload, CreateTokenResponse, DeleteTokenPayload, DeleteTokenResponse, GetTokensResponse, IApiTokenService, TokenQuery } from "./abstractions/IApiTokenService"; + +export class ApiTokenService implements IApiTokenService { + constructor() { } + static resolve() { + return new ApiTokenService(); + } + async create_token_async(payload: CreateTokenPayload): Promise<CreateTokenResponse> { + const response = await http_post_async(api_base("v1/api-tokens/create"), payload); + return; + }; + async delete_token_async(payload: DeleteTokenPayload): Promise<DeleteTokenResponse> { + const response = await http_delete_async(api_base("v1/api-tokens/delete"), payload); + return; + }; + async get_tokens_async(query: TokenQuery): Promise<GetTokensResponse> { + const response = await http_get_async(api_base("v1/api-tokens")); + return; + }; +}
\ No newline at end of file diff --git a/code/frontend/src/services/password-reset-service.ts b/code/frontend/src/services/password-reset-service.ts new file mode 100644 index 0000000..edecee2 --- /dev/null +++ b/code/frontend/src/services/password-reset-service.ts @@ -0,0 +1,48 @@ +import { http_get_async, http_post_async } from "$utils/_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 { + static resolve(): IPasswordResetService { + return new PasswordResetService(); + } + async create_request_async(email: string): Promise<CreateRequestResponse> { + 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<FulfillRequestResponse> { + 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<RequestIsValidResponse> { + 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/frontend/src/services/settings-service.ts b/code/frontend/src/services/settings-service.ts new file mode 100644 index 0000000..a0a77d4 --- /dev/null +++ b/code/frontend/src/services/settings-service.ts @@ -0,0 +1,10 @@ +import type { ISettingsService } from "./abstractions/ISettingsService"; + +export class SettingService implements ISettingsService { + static resolve(): ISettingsService { + return new SettingService(); + } + get_user_settings(): Promise<void> { + throw new Error("Method not implemented."); + } +}
\ No newline at end of file |
