aboutsummaryrefslogtreecommitdiffstats
path: root/code/app/src/services
diff options
context:
space:
mode:
authorivarlovlie <git@ivarlovlie.no>2023-02-25 13:15:44 +0100
committerivarlovlie <git@ivarlovlie.no>2023-02-25 13:15:44 +0100
commit900bb5e845c3ad44defbd427cae3d44a4a43321f (patch)
treedf3d96a93771884add571e82336c29fc3d9c7a1c /code/app/src/services
downloadgreatoffice-900bb5e845c3ad44defbd427cae3d44a4a43321f.tar.xz
greatoffice-900bb5e845c3ad44defbd427cae3d44a4a43321f.zip
feat: Initial commit
Diffstat (limited to 'code/app/src/services')
-rw-r--r--code/app/src/services/abstractions/IAccountService.ts54
-rw-r--r--code/app/src/services/abstractions/IApiTokenService.ts34
-rw-r--r--code/app/src/services/abstractions/IPasswordResetService.ts21
-rw-r--r--code/app/src/services/abstractions/ISettingsService.ts3
-rw-r--r--code/app/src/services/account-service.ts124
-rw-r--r--code/app/src/services/api-tokens-service.ts22
-rw-r--r--code/app/src/services/password-reset-service.ts48
-rw-r--r--code/app/src/services/settings-service.ts10
8 files changed, 316 insertions, 0 deletions
diff --git a/code/app/src/services/abstractions/IAccountService.ts b/code/app/src/services/abstractions/IAccountService.ts
new file mode 100644
index 0000000..d3d48b0
--- /dev/null
+++ b/code/app/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/app/src/services/abstractions/IApiTokenService.ts b/code/app/src/services/abstractions/IApiTokenService.ts
new file mode 100644
index 0000000..fdf82eb
--- /dev/null
+++ b/code/app/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/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<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/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<void>,
+} \ 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..b2bb375
--- /dev/null
+++ b/code/app/src/services/account-service.ts
@@ -0,0 +1,124 @@
+import { http_delete_async, http_get_async, http_post_async } from "$utilities/_fetch";
+import { browser } from "$app/environment";
+import { api_base, CookieNames, StorageKeys } from "$configuration";
+import { is_known_problem } from "$models/internal/KnownProblem";
+import { log_debug } from "$utilities/logger";
+import { StoreType, create_writable_persistent } from "$utilities/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) {
+ log_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/app/src/services/api-tokens-service.ts b/code/app/src/services/api-tokens-service.ts
new file mode 100644
index 0000000..fb8b126
--- /dev/null
+++ b/code/app/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 "$utilities/_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/app/src/services/password-reset-service.ts b/code/app/src/services/password-reset-service.ts
new file mode 100644
index 0000000..4a174fa
--- /dev/null
+++ b/code/app/src/services/password-reset-service.ts
@@ -0,0 +1,48 @@
+import { http_get_async, http_post_async } from "$utilities/_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/app/src/services/settings-service.ts b/code/app/src/services/settings-service.ts
new file mode 100644
index 0000000..a0a77d4
--- /dev/null
+++ b/code/app/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