From: Muhammad Rizki <[email protected]>
To: Ammar Faizi <[email protected]>
Cc: Kiizuha Kanazawa <[email protected]>,
Alviro Iskandar Setiawan <[email protected]>,
Dwiky Rizky Ananditya <[email protected]>,
GNU/Weeb Mailing List <[email protected]>
Subject: [PATCH v1 09/16] feat: initial hooks, schemas, typings for the login functions
Date: Sun, 19 Jan 2025 07:39:02 +0700 [thread overview]
Message-ID: <[email protected]> (raw)
In-Reply-To: <[email protected]>
From: Kiizuha Kanazawa <[email protected]>
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 <[email protected]>
---
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<LoginResponse>({
+ 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 = <Response = unknown, Formatter = unknown>({
+ action,
+ payload,
+ params = {},
+ method = "GET",
+ timeout,
+ beforeExecute = () => true,
+ onComplete = () => true,
+ formatter = () => true,
+ onError = () => false,
+ onFinally = () => false,
+ isFormData = false,
+ responseType
+}: UseHttpProps<Response, Formatter>) => {
+ const url = GWM_API_URL + action;
+ let isLoading = $state<boolean>(true);
+ let data = $state<Formatter | null>(null);
+
+ formatter(data, null);
+
+ let errors = $state<string | null>(null);
+ let errorMessage = $state<string | null>(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<ResponseAPI<Response>>) => {
+ 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<Data> {
+ code: number;
+ res?: Data;
+}
+
+type ResponseAPI<ArrayType = null> = ResponseInterface<ArrayType>;
+
+export type UseHttpProps<Response, Formatter> = AxiosRequestConfig & {
+ action: string;
+ payload?: RecordString;
+ isFormData?: boolean;
+ beforeExecute?: () => boolean;
+ onComplete?: (resp: AxiosResponse<ResponseAPI<Response>>) => void;
+ formatter?: (data: Formatter | null, resp: AxiosResponse<ResponseAPI<Response>> | null) => void;
+ onError?: (data: ResponseAPI<Response>, 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
next prev parent reply other threads:[~2025-01-19 0:40 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-01-19 0:38 [PATCH v1 00/16] Refactor GNU/Weeb Mail Site Muhammad Rizki
2025-01-19 0:38 ` [PATCH v1 01/16] feat(routes): configure page layout options Muhammad Rizki
2025-01-19 0:38 ` [PATCH v1 02/16] feat(fonts): add Google fonts Muhammad Rizki
2025-01-19 0:38 ` [PATCH v1 03/16] feat(components): add shadcn-svelte components Muhammad Rizki
2025-01-19 0:38 ` [PATCH v1 04/16] feat(deps): add `axios` as HTTP client Muhammad Rizki
2025-01-19 0:38 ` [PATCH v1 05/16] feat(deps): add `svelte-copy` dependency Muhammad Rizki
2025-01-19 0:38 ` [PATCH v1 06/16] feat(constants): add navigations and mail-config constant Muhammad Rizki
2025-01-19 0:39 ` [PATCH v1 07/16] feat(components): add loading spinner component Muhammad Rizki
2025-01-19 0:39 ` [PATCH v1 08/16] feat(components): add header component Muhammad Rizki
2025-01-19 0:39 ` Muhammad Rizki [this message]
2025-01-19 0:39 ` [PATCH v1 10/16] feat(components): add app-sidebar component Muhammad Rizki
2025-01-19 0:39 ` [PATCH v1 11/16] feat(routes): create initial settings page Muhammad Rizki
2025-01-19 0:39 ` [PATCH v1 12/16] feat(routes): add home page Muhammad Rizki
2025-01-19 0:39 ` [PATCH v1 13/16] feat(routes): add main layout for the protected routes Muhammad Rizki
2025-01-19 0:39 ` [PATCH v1 14/16] feat(routes): add login page Muhammad Rizki
2025-01-19 0:39 ` [PATCH v1 15/16] chore(.gitignore): add patch files ignore Muhammad Rizki
2025-01-19 0:39 ` [PATCH v1 16/16] chore: update README.md Muhammad Rizki
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
[email protected] \
[email protected] \
[email protected] \
[email protected] \
[email protected] \
[email protected] \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox