From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server-vie001.gnuweeb.org X-Spam-Level: X-Spam-Status: No, score=-1.2 required=5.0 tests=ALL_TRUSTED,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,URIBL_ZEN_BLOCKED_OPENDNS autolearn=ham autolearn_force=no version=3.4.6 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=gnuweeb.org; s=default; t=1737247204; bh=18HWKbUEWrx4o+gu2Iifo/tUHY1MLRvLJ0pXNsEeZV0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Transfer-Encoding:Message-ID:Date:From: Reply-To:Subject:To:Cc:In-Reply-To:References:Resent-Date: Resent-From:Resent-To:Resent-Cc:User-Agent:Content-Type: Content-Transfer-Encoding; b=qdu4yICKv3Gjh+3Ftcbq9Jc+3Qaedn+pAzOVeI6cIL35/cqP4eZCZjIJ8WLakrZSI inGO+2+3xcJo3s28AeeSBvDIJEvvqLVe4NqkW0u+VwSEnH830NVpiAyZgfz1uNmeDB ngU+hgqcSzBshz5tTwkfhLXzK91ydcYdjHZzIpSiVUW3qRWj7dF0r/wArIgoDJzAWh NQgBfa236lwlPe/Ew9+3ubSMFAOnlmeQndtwKii4ucZm3JDcMayrug833RgHpyarFU 50zbS3uTfF2ofXdu9a9u1b+A7b4TeoXZOFayBA2Z9zcU/Rdie0nHvr94sAUF1Go8QN lQLjR3UCNi8/w== Received: from localhost.localdomain (unknown [101.128.125.239]) by server-vie001.gnuweeb.org (Postfix) with ESMTPSA id 9AD642061C33; Sun, 19 Jan 2025 00:40:03 +0000 (UTC) From: Muhammad Rizki To: Ammar Faizi Cc: Kiizuha Kanazawa , Alviro Iskandar Setiawan , Dwiky Rizky Ananditya , GNU/Weeb Mailing List Subject: [PATCH v1 09/16] feat: initial hooks, schemas, typings for the login functions Date: Sun, 19 Jan 2025 07:39:02 +0700 Message-ID: <20250119003911.1183-10-kiizuha@gnuweeb.org> X-Mailer: git-send-email 2.45.2.windows.1 In-Reply-To: <20250119003911.1183-1-kiizuha@gnuweeb.org> References: <20250119003911.1183-1-kiizuha@gnuweeb.org> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit List-Id: From: Kiizuha Kanazawa added some lib for the login functionality: - hooks/auth: this hooks include base logic for the auth. - hooks/http: this hooks used for http request. - schemas/login: this schema is to create a schema for the login form. - typings/credential: this types stores user-related type - typings/http: this types stores http-related type Signed-off-by: Kiizuha Kanazawa --- src/lib/hooks/auth.svelte.ts | 64 ++++++++++++++++ src/lib/hooks/http.svelte.ts | 129 ++++++++++++++++++++++++++++++++ src/lib/schemas/login.ts | 6 ++ src/lib/typings/credential.d.ts | 7 ++ src/lib/typings/http.d.ts | 27 +++++++ src/lib/typings/index.ts | 12 ++- 6 files changed, 244 insertions(+), 1 deletion(-) create mode 100644 src/lib/hooks/auth.svelte.ts create mode 100644 src/lib/hooks/http.svelte.ts create mode 100644 src/lib/schemas/login.ts create mode 100644 src/lib/typings/credential.d.ts create mode 100644 src/lib/typings/http.d.ts diff --git a/src/lib/hooks/auth.svelte.ts b/src/lib/hooks/auth.svelte.ts new file mode 100644 index 0000000..ba357f7 --- /dev/null +++ b/src/lib/hooks/auth.svelte.ts @@ -0,0 +1,64 @@ +import type { LoginResponse, User } from "$typings"; + +let data = $state({ + token: "", + token_exp_at: 0 +}); + +export function useAuth() { + return { + get token() { + return data.token; + }, + + get exp() { + return data.token_exp_at; + }, + + get user() { + return data.user_info; + }, + + set token(newValue: string) { + data.token = newValue; + }, + + refresh() { + const user = localStorage.getItem("gwm_uinfo"); + data.user_info = JSON.parse(user!) as User; + }, + + save(newData: LoginResponse) { + data = newData; + localStorage.setItem("gwm_token", newData.token); + localStorage.setItem("gwm_token_exp_at", newData.token_exp_at.toString()); + localStorage.setItem("gwm_uinfo", JSON.stringify(newData.user_info)); + }, + + clear() { + localStorage.removeItem("gwm_token"); + localStorage.removeItem("gwm_token_exp_at"); + localStorage.removeItem("gwm_uinfo"); + }, + + isValid() { + const token = localStorage.getItem("gwm_token"); + const user = localStorage.getItem("gwm_uinfo"); + + if (!token || !user) { + this.clear(); + return false; + } + + const exp = Number(localStorage.getItem("gwm_token_exp_at")); + const unix = Math.round(new Date().getTime() / 1000); + + if (unix >= exp) { + this.clear(); + return false; + } + + return true; + } + }; +} diff --git a/src/lib/hooks/http.svelte.ts b/src/lib/hooks/http.svelte.ts new file mode 100644 index 0000000..fd4114d --- /dev/null +++ b/src/lib/hooks/http.svelte.ts @@ -0,0 +1,129 @@ +import axios, { AxiosError } from "axios"; +import { useAuth } from "./auth.svelte"; +import type { RecordString, UseHttpProps } from "$typings"; +import type { ResponseAPI } from "$typings/http"; + +const transformToFormData = (initialData: RecordString, requestData: RecordString | FormData) => { + const formData = new FormData(); + + let data = requestData; + + Object.keys(initialData).forEach((key) => { + const value = initialData[key]; + + if (Array.isArray(value)) { + for (const val of value) { + formData.append( + key, + typeof val === "object" && !(val instanceof File) ? JSON.stringify(val) : val + ); + } + } else { + formData.append(key, value); + } + }); + + data = formData; + return data; +}; + +const GWM_API_URL = "https://mail.gnuweeb.org/api.php?action="; + +export const useHttp = ({ + action, + payload, + params = {}, + method = "GET", + timeout, + beforeExecute = () => true, + onComplete = () => true, + formatter = () => true, + onError = () => false, + onFinally = () => false, + isFormData = false, + responseType +}: UseHttpProps) => { + const url = GWM_API_URL + action; + let isLoading = $state(true); + let data = $state(null); + + formatter(data, null); + + let errors = $state(null); + let errorMessage = $state(null); + const auth = useAuth(); + + const execute = async (replace?: { + payload?: RecordString | null; + params?: RecordString | null; + }) => { + if (!beforeExecute()) { + onFinally(); + return; + } + + isLoading = true; + + if (replace?.payload?.defaultPrevented != null) { + replace!.payload = null; + } + + const initialData: RecordString = replace?.payload ?? payload ?? {}; + let requestData: RecordString | FormData = initialData; + + if (isFormData) { + requestData = transformToFormData(initialData, requestData); + } + + return await axios({ + url: url, + method, + data: requestData, + headers: { + Authorization: `Bearer ${auth.token}`, + "Content-Type": "application/json" + }, + params: replace?.params ?? params, + responseType: responseType, + timeout: timeout ?? 60000 + }) + .then((res) => { + isLoading = false; + data = res?.data; + + onComplete(res); + onFinally(); + formatter(data, res); + + errors = null; + errorMessage = ""; + + return res; + }) + + .catch((res: AxiosError>) => { + isLoading = false; + errors = res.stack!; + + const response = res.response?.data; + + errorMessage = typeof response?.res === "string" ? response.res : "Something went wrong!"; + + onError(res.response!.data, errorMessage); + onFinally(); + + return res; + }); + }; + + const refetch = execute; + + return { + data, + isLoading, + refetch, + execute, + errorMessage, + errors + }; +}; diff --git a/src/lib/schemas/login.ts b/src/lib/schemas/login.ts new file mode 100644 index 0000000..5cd5ef2 --- /dev/null +++ b/src/lib/schemas/login.ts @@ -0,0 +1,6 @@ +import { z } from "zod"; + +export const loginSchema = z.object({ + username_or_email: z.string(), + password: z.string() +}); diff --git a/src/lib/typings/credential.d.ts b/src/lib/typings/credential.d.ts new file mode 100644 index 0000000..df02c86 --- /dev/null +++ b/src/lib/typings/credential.d.ts @@ -0,0 +1,7 @@ +export interface User { + user_id: number; + full_name: string; + gender: string; + username: string; + role: string; +} diff --git a/src/lib/typings/http.d.ts b/src/lib/typings/http.d.ts new file mode 100644 index 0000000..decf147 --- /dev/null +++ b/src/lib/typings/http.d.ts @@ -0,0 +1,27 @@ +import type { AxiosRequestConfig, AxiosResponse } from "axios"; +import type { RecordString } from "./common"; +import type { User } from "./credential"; + +interface ResponseInterface { + code: number; + res?: Data; +} + +type ResponseAPI = ResponseInterface; + +export type UseHttpProps = AxiosRequestConfig & { + action: string; + payload?: RecordString; + isFormData?: boolean; + beforeExecute?: () => boolean; + onComplete?: (resp: AxiosResponse>) => void; + formatter?: (data: Formatter | null, resp: AxiosResponse> | null) => void; + onError?: (data: ResponseAPI, msg?: string | null) => void; + onFinally?: () => void; +}; + +export interface LoginResponse { + token: string; + token_exp_at: number; + user_info?: User | null; +} diff --git a/src/lib/typings/index.ts b/src/lib/typings/index.ts index 26ded78..6ae23a1 100644 --- a/src/lib/typings/index.ts +++ b/src/lib/typings/index.ts @@ -1,3 +1,13 @@ import type { RecordString, Navigations, LabelAndValue, MailConfig } from "./common"; +import type { UseHttpProps, LoginResponse } from "./http"; +import type { User } from "./credential"; -export type { RecordString, Navigations, LabelAndValue, MailConfig }; +export type { + RecordString, + Navigations, + LabelAndValue, + MailConfig, + UseHttpProps, + LoginResponse, + User +}; -- Muhammad Rizki