summaryrefslogtreecommitdiffstats
path: root/apps/web-shared/src/lib/api
diff options
context:
space:
mode:
authorivarlovlie <git@ivarlovlie.no>2022-06-01 22:10:32 +0200
committerivarlovlie <git@ivarlovlie.no>2022-06-01 22:10:32 +0200
commita640703f2da8815dc26ad1600a6f206be1624379 (patch)
treedbda195fb5783d16487e557e06471cf848b75427 /apps/web-shared/src/lib/api
downloadgreatoffice-a640703f2da8815dc26ad1600a6f206be1624379.tar.xz
greatoffice-a640703f2da8815dc26ad1600a6f206be1624379.zip
feat: Initial after clean slate
Diffstat (limited to 'apps/web-shared/src/lib/api')
-rw-r--r--apps/web-shared/src/lib/api/internal-fetch.ts170
-rw-r--r--apps/web-shared/src/lib/api/root.ts6
-rw-r--r--apps/web-shared/src/lib/api/time-entry.ts86
-rw-r--r--apps/web-shared/src/lib/api/user.ts47
4 files changed, 309 insertions, 0 deletions
diff --git a/apps/web-shared/src/lib/api/internal-fetch.ts b/apps/web-shared/src/lib/api/internal-fetch.ts
new file mode 100644
index 0000000..8659ccb
--- /dev/null
+++ b/apps/web-shared/src/lib/api/internal-fetch.ts
@@ -0,0 +1,170 @@
+import {Temporal} from "@js-temporal/polyfill";
+import {clear_session_data} from "$shared/lib/session";
+import {resolve_references} from "$shared/lib/helpers";
+import {replace} from "svelte-spa-router";
+import type {IInternalFetchResponse} from "$shared/lib/models/IInternalFetchResponse";
+import type {IInternalFetchRequest} from "$shared/lib/models/IInternalFetchRequest";
+
+export async function http_post(url: string, body?: object|string, timeout = -1, skip_401_check = false, abort_signal: AbortSignal = undefined): Promise<IInternalFetchResponse> {
+ const init = {
+ method: "post",
+ } as RequestInit;
+
+ if (abort_signal) {
+ init.signal = abort_signal;
+ }
+
+ if (body) {
+ init.headers = {
+ "Content-Type": "application/json;charset=UTF-8",
+ };
+ init.body = JSON.stringify(body);
+ }
+
+ const response = await internal_fetch({url, init, timeout});
+ const result = {} as IInternalFetchResponse;
+
+ if (!skip_401_check && await is_401(response)) return result;
+
+ result.ok = response.ok;
+ result.status = response.status;
+ result.http_response = response;
+
+ if (response.status !== 204) {
+ try {
+ const ct = response.headers.get("Content-Type")?.toString() ?? "";
+ if (ct.startsWith("application/json")) {
+ const data = await response.json();
+ result.data = resolve_references(data);
+ } else if (ct.startsWith("text/plain")) {
+ const text = await response.text();
+ result.data = text as string;
+ }
+ } catch {
+ // Ignored
+ }
+ }
+
+ return result;
+}
+
+export async function http_get(url: string, timeout = -1, skip_401_check = false, abort_signal: AbortSignal = undefined): Promise<IInternalFetchResponse> {
+ const init = {
+ method: "get",
+ } as RequestInit;
+
+ if (abort_signal) {
+ init.signal = abort_signal;
+ }
+
+ const response = await internal_fetch({url, init, timeout});
+ const result = {} as IInternalFetchResponse;
+
+ if (!skip_401_check && await is_401(response)) return result;
+
+ result.ok = response.ok;
+ result.status = response.status;
+ result.http_response = response;
+
+ if (response.status !== 204) {
+ try {
+ const ct = response.headers.get("Content-Type")?.toString() ?? "";
+ if (ct.startsWith("application/json")) {
+ const data = await response.json();
+ result.data = resolve_references(data);
+ } else if (ct.startsWith("text/plain")) {
+ const text = await response.text();
+ result.data = text as string;
+ }
+ } catch {
+ // Ignored
+ }
+ }
+
+ return result;
+}
+
+export async function http_delete(url: string, body?: object|string, timeout = -1, skip_401_check = false, abort_signal: AbortSignal = undefined): Promise<IInternalFetchResponse> {
+ const init = {
+ method: "delete",
+ } as RequestInit;
+
+ if (abort_signal) {
+ init.signal = abort_signal;
+ }
+
+ if (body) {
+ init.headers = {
+ "Content-Type": "application/json;charset=UTF-8",
+ };
+ init.body = JSON.stringify(body);
+ }
+
+ const response = await internal_fetch({url, init, timeout});
+ const result = {} as IInternalFetchResponse;
+
+ if (!skip_401_check && await is_401(response)) return result;
+
+ result.ok = response.ok;
+ result.status = response.status;
+ result.http_response = response;
+
+ if (response.status !== 204) {
+ try {
+ const ct = response.headers.get("Content-Type")?.toString() ?? "";
+ if (ct.startsWith("application/json")) {
+ const data = await response.json();
+ result.data = resolve_references(data);
+ } else if (ct.startsWith("text/plain")) {
+ const text = await response.text();
+ result.data = text as string;
+ }
+ } catch (error) {
+ // ignored
+ }
+ }
+
+ return result;
+}
+
+async function internal_fetch(request: IInternalFetchRequest): Promise<Response> {
+ if (!request.init) request.init = {};
+ request.init.credentials = "include";
+ request.init.headers = {
+ "X-TimeZone": Temporal.Now.timeZone().id,
+ ...request.init.headers
+ };
+
+ const fetch_request = new Request(request.url, request.init);
+ let response: any;
+
+ try {
+ if (request.timeout > 500) {
+ response = await Promise.race([
+ fetch(fetch_request),
+ new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), request.timeout))
+ ]);
+ } else {
+ response = await fetch(fetch_request);
+ }
+ } catch (error) {
+ if (error.message === "Timeout") {
+ console.error("Request timed out");
+ } else if (error.message === "Network request failed") {
+ console.error("No internet connection");
+ } else {
+ throw error; // rethrow other unexpected errors
+ }
+ }
+
+ return response;
+}
+
+async function is_401(response: Response): Promise<boolean> {
+ if (response.status === 401) {
+ clear_session_data();
+ await replace("/login");
+ return true;
+ }
+ return false;
+}
diff --git a/apps/web-shared/src/lib/api/root.ts b/apps/web-shared/src/lib/api/root.ts
new file mode 100644
index 0000000..d65efc4
--- /dev/null
+++ b/apps/web-shared/src/lib/api/root.ts
@@ -0,0 +1,6 @@
+import {http_post} from "$shared/lib/api/internal-fetch";
+import {api_base} from "$shared/lib/configuration";
+
+export function server_log(message: string): void {
+ http_post(api_base("_/api/log"), message);
+}
diff --git a/apps/web-shared/src/lib/api/time-entry.ts b/apps/web-shared/src/lib/api/time-entry.ts
new file mode 100644
index 0000000..e81329d
--- /dev/null
+++ b/apps/web-shared/src/lib/api/time-entry.ts
@@ -0,0 +1,86 @@
+import {api_base} from "$shared/lib/configuration";
+import {is_guid} from "$shared/lib/helpers";
+import {http_delete, http_get, http_post} from "./internal-fetch";
+import type {TimeCategoryDto} from "$shared/lib/models/TimeCategoryDto";
+import type {TimeLabelDto} from "$shared/lib/models/TimeLabelDto";
+import type {TimeEntryDto} from "$shared/lib/models/TimeEntryDto";
+import type {TimeEntryQuery} from "$shared/lib/models/TimeEntryQuery";
+import type {IInternalFetchResponse} from "$shared/lib/models/IInternalFetchResponse";
+
+
+// ENTRIES
+
+export async function create_time_entry(payload: TimeEntryDto): Promise<IInternalFetchResponse> {
+ return http_post(api_base("v1/entries/create"), payload);
+}
+
+export async function get_time_entry(entryId: string): Promise<IInternalFetchResponse> {
+ if (is_guid(entryId)) {
+ return http_get(api_base("v1/entries/" + entryId));
+ }
+ throw new Error("entryId is not a valid guid.");
+}
+
+export async function get_time_entries(entryQuery: TimeEntryQuery): Promise<IInternalFetchResponse> {
+ return http_post(api_base("v1/entries/query"), entryQuery);
+}
+
+export async function delete_time_entry(id: string): Promise<IInternalFetchResponse> {
+ if (!is_guid(id)) throw new Error("id is not a valid guid");
+ return http_delete(api_base("v1/entries/" + id + "/delete"));
+}
+
+export async function update_time_entry(entryDto: TimeEntryDto): Promise<IInternalFetchResponse> {
+ if (!is_guid(entryDto.id ?? "")) throw new Error("id is not a valid guid");
+ if (!entryDto.category) throw new Error("category is empty");
+ if (!entryDto.stop) throw new Error("stop is empty");
+ if (!entryDto.start) throw new Error("start is empty");
+ return http_post(api_base("v1/entries/update"), entryDto);
+}
+
+// LABELS
+
+
+export async function create_time_label(labelDto: TimeLabelDto): Promise<IInternalFetchResponse> {
+ return http_post(api_base("v1/labels/create"), labelDto);
+}
+
+export async function get_time_labels(): Promise<IInternalFetchResponse> {
+ return http_get(api_base("v1/labels"));
+}
+
+export async function delete_time_label(id: string): Promise<IInternalFetchResponse> {
+ if (!is_guid(id)) throw new Error("id is not a valid guid");
+ return http_delete(api_base("v1/labels/" + id + "/delete"));
+}
+
+export async function update_time_label(labelDto: TimeLabelDto): Promise<IInternalFetchResponse> {
+ if (!is_guid(labelDto.id ?? "")) throw new Error("id is not a valid guid");
+ if (!labelDto.name) throw new Error("name is empty");
+ if (!labelDto.color) throw new Error("color is empty");
+ return http_post(api_base("v1/labels/update"), labelDto);
+}
+
+
+// CATEGORIES
+export async function create_time_category(category: TimeCategoryDto): Promise<IInternalFetchResponse> {
+ if (!category.name) throw new Error("name is empty");
+ if (!category.color) throw new Error("color is empty");
+ return http_post(api_base("v1/categories/create"), category);
+}
+
+export async function get_time_categories(): Promise<IInternalFetchResponse> {
+ return http_get(api_base("v1/categories"));
+}
+
+export async function delete_time_category(id: string): Promise<IInternalFetchResponse> {
+ if (!is_guid(id)) throw new Error("id is not a valid guid");
+ return http_delete(api_base("v1/categories/" + id + "/delete"));
+}
+
+export async function update_time_category(category: TimeCategoryDto): Promise<IInternalFetchResponse> {
+ if (!is_guid(category.id ?? "")) throw new Error("id is not a valid guid");
+ if (!category.name) throw new Error("name is empty");
+ if (!category.color) throw new Error("color is empty");
+ return http_post(api_base("v1/categories/update"), category);
+}
diff --git a/apps/web-shared/src/lib/api/user.ts b/apps/web-shared/src/lib/api/user.ts
new file mode 100644
index 0000000..a3a149e
--- /dev/null
+++ b/apps/web-shared/src/lib/api/user.ts
@@ -0,0 +1,47 @@
+import {api_base} from "$shared/lib/configuration";
+import {http_delete, http_get, http_post} from "./internal-fetch";
+import type {LoginPayload} from "$shared/lib/models/LoginPayload";
+import type {UpdateProfilePayload} from "$shared/lib/models/UpdateProfilePayload";
+import type {CreateAccountPayload} from "$shared/lib/models/CreateAccountPayload";
+import type {IInternalFetchResponse} from "$shared/lib/models/IInternalFetchResponse";
+
+export async function login(payload: LoginPayload): Promise<IInternalFetchResponse> {
+ return http_post(api_base("_/account/login"), payload);
+}
+
+export async function logout(): Promise<IInternalFetchResponse> {
+ return http_get(api_base("_/account/logout"));
+}
+
+export async function create_forgot_password_request(username: string): Promise<IInternalFetchResponse> {
+ if (!username) throw new Error("Username is empty");
+ return http_get(api_base("_/forgot-password-requests/create?username=" + username));
+}
+
+export async function check_forgot_password_request(public_id: string): Promise<IInternalFetchResponse> {
+ if (!public_id) throw new Error("Id is empty");
+ return http_get(api_base("_/forgot-password-requests/is-valid?id=" + public_id));
+}
+
+export async function fulfill_forgot_password_request(public_id: string, newPassword: string): Promise<IInternalFetchResponse> {
+ if (!public_id) throw new Error("Id is empty");
+ return http_post(api_base("_/forgot-password-requests/fulfill"), {id: public_id, newPassword});
+}
+
+export async function delete_account(): Promise<IInternalFetchResponse> {
+ return http_delete(api_base("_/account/delete"));
+}
+
+export async function update_profile(payload: UpdateProfilePayload): Promise<IInternalFetchResponse> {
+ if (!payload.password && !payload.username) throw new Error("Password and Username is empty");
+ return http_post(api_base("_/account/update"), payload);
+}
+
+export async function create_account(payload: CreateAccountPayload): Promise<IInternalFetchResponse> {
+ if (!payload.password && !payload.username) throw new Error("Password and Username is empty");
+ return http_post(api_base("_/account/create"), payload);
+}
+
+export async function get_profile_for_active_check(): Promise<IInternalFetchResponse> {
+ return http_get(api_base("_/account"), 0, true);
+}