diff --git a/ServicesWeb/customer/src/config/config.ts b/ServicesWeb/customer/src/config/config.ts new file mode 100644 index 0000000..168e7a0 --- /dev/null +++ b/ServicesWeb/customer/src/config/config.ts @@ -0,0 +1,5 @@ +const BASE_URL = "http://localhost:3000"; +const API_URL = "http://localhost:3000/api"; +export const WEB_BASE_URL = process.env.WEB_BASE_URL || BASE_URL; +export const API_BASE_URL = process.env.API_BASE_URL || API_URL; +0 \ No newline at end of file diff --git a/ServicesWeb/customer/src/fetchers/base.ts b/ServicesWeb/customer/src/fetchers/base.ts index 6a24785..f32899a 100644 --- a/ServicesWeb/customer/src/fetchers/base.ts +++ b/ServicesWeb/customer/src/fetchers/base.ts @@ -3,8 +3,8 @@ import NextCrypto from "next-crypto"; const tokenSecretEnv = process.env.TOKENSECRET_90; const tokenSecret = tokenSecretEnv || "e781d1b0-9418-40b3-9940-385abf81a0b7"; +const REDIS_TIMEOUT = 5000; // Redis operation timeout (5 seconds) const nextCrypto = new NextCrypto(tokenSecret); - const cookieObject: CookieObject = { httpOnly: true, path: "/", @@ -13,7 +13,6 @@ const cookieObject: CookieObject = { maxAge: 3600, priority: "high", }; - const DEFAULT_TIMEOUT: number = 10000; // 10 seconds const defaultHeaders: Record = { accept: "application/json", @@ -31,6 +30,7 @@ const DEFAULT_RESPONSE: ApiResponse = { export { DEFAULT_TIMEOUT, DEFAULT_RESPONSE, + REDIS_TIMEOUT, defaultHeaders, tokenSecret, cookieObject, diff --git a/ServicesWeb/customer/src/fetchers/custom/context/complete/fetch.tsx b/ServicesWeb/customer/src/fetchers/custom/context/complete/fetch.tsx new file mode 100644 index 0000000..d522dff --- /dev/null +++ b/ServicesWeb/customer/src/fetchers/custom/context/complete/fetch.tsx @@ -0,0 +1,69 @@ +"use server"; +import { redis } from "@/lib/redis"; +import { functionRetrieveUserSelection } from "@/fetchers/fecther"; +import { ClientRedisToken, defaultClientRedisToken } from "@/fetchers/types/context"; +import { REDIS_TIMEOUT } from "@/fetchers/base"; + +/** + * Gets the complete data from Redis with improved error handling and timeouts + * @returns The complete Redis data or default values if there's an error + */ +const getCompleteFromRedis = async (): Promise => { + try { + let decrpytUserSelection; + try { decrpytUserSelection = await functionRetrieveUserSelection() } catch (error) { return defaultClientRedisToken } + const redisKey = decrpytUserSelection?.redisKey; + if (!redisKey) { return defaultClientRedisToken } + if (redisKey === "default") { return defaultClientRedisToken } + + try { + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => reject(new Error('Redis operation timed out')), REDIS_TIMEOUT); + }); + const result = await Promise.race([redis.get(`${redisKey}`), timeoutPromise]); + if (!result) { return defaultClientRedisToken } + try { const parsedResult = JSON.parse(result); return parsedResult } catch (parseError) { return defaultClientRedisToken } + } catch (redisError) { return defaultClientRedisToken } + } catch (error) { return defaultClientRedisToken } +} + +/** + * Sets the complete data in Redis with improved error handling and timeouts + * @param completeObject The complete data to set in Redis + * @returns True if successful, false otherwise + */ +const setCompleteToRedis = async (completeObject: ClientRedisToken): Promise => { + try { + if (!completeObject) { return false } + let decrpytUserSelection; + try { decrpytUserSelection = await functionRetrieveUserSelection() } catch (error) { return false } + if (!decrpytUserSelection) { return false } + const redisKey = decrpytUserSelection?.redisKey; + if (!redisKey) { return false } + try { + const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error('Redis operation timed out')), REDIS_TIMEOUT) }); + await Promise.race([redis.set(redisKey, JSON.stringify(completeObject)), timeoutPromise]); + return true; + } catch (redisError) { return false } + } catch (error) { return false } +} + +/** + * Sets new complete data in Redis with a specific key with improved error handling and timeouts + * @param completeObject The complete data to set in Redis + * @param redisKey The specific Redis key to use + * @returns True if successful, false otherwise + */ +const setNewCompleteToRedis = async (completeObject: ClientRedisToken, redisKey: string): Promise => { + try { + if (!redisKey) { return false } + if (!completeObject) { return false } + try { + const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error('Redis operation timed out')), REDIS_TIMEOUT) }); + await Promise.race([redis.set(redisKey, JSON.stringify(completeObject)), timeoutPromise]) + return true; + } catch (redisError) { return false } + } catch (error) { return false } +} + +export { getCompleteFromRedis, setCompleteToRedis, setNewCompleteToRedis }; diff --git a/ServicesWeb/customer/src/fetchers/custom/context/dash/selection/fetch.tsx b/ServicesWeb/customer/src/fetchers/custom/context/dash/selection/fetch.tsx new file mode 100644 index 0000000..558473e --- /dev/null +++ b/ServicesWeb/customer/src/fetchers/custom/context/dash/selection/fetch.tsx @@ -0,0 +1,45 @@ +"use server"; +import { redis } from "@/lib/redis"; +import { functionRetrieveUserSelection } from "@/fetchers/fecther"; +import { ClientSelection, AuthError } from "@/fetchers/types/context"; +import { getCompleteFromRedis, setCompleteToRedis } from "@/fetchers/custom/context/complete/fetch"; + +const getSelectionFromRedis = async (): Promise => { + try { + const decrpytUserSelection = await functionRetrieveUserSelection(); + const redisKey = decrpytUserSelection?.redisKey; + if (redisKey === "default") { return { selectionList: [], activeSelection: {} } } + const result = await redis.get(`${redisKey}`); + if (!result) { return { selectionList: [], activeSelection: {} } } + const parsedResult = JSON.parse(result); + if (!parsedResult.selection) { return { selectionList: [], activeSelection: {} } } + return parsedResult.selection; + } catch (error) { return { selectionList: [], activeSelection: {} } } +} + +const setSelectionToRedis = async (selectionObject: ClientSelection) => { + try { + const decrpytUserSelection = await functionRetrieveUserSelection() + if (!decrpytUserSelection) throw new AuthError("No user selection found"); + + const redisKey = decrpytUserSelection?.redisKey; + if (!redisKey) throw new AuthError("No redis key found"); + + if (!selectionObject) throw new AuthError("No selection object provided"); + + const oldData = await getCompleteFromRedis(); + if (!oldData) throw new AuthError("No old data found in redis"); + + await setCompleteToRedis({ ...oldData, selection: selectionObject }) + return true; + } catch (error) { + // Re-throw AuthError instances, wrap other errors as AuthError + if (error instanceof AuthError) { + throw error; + } else { + throw new AuthError(error instanceof Error ? error.message : "Unknown error"); + } + } +} + +export { getSelectionFromRedis, setSelectionToRedis }; diff --git a/ServicesWeb/customer/src/fetchers/custom/context/dash/settings/fetch.tsx b/ServicesWeb/customer/src/fetchers/custom/context/dash/settings/fetch.tsx new file mode 100644 index 0000000..0d0f0fc --- /dev/null +++ b/ServicesWeb/customer/src/fetchers/custom/context/dash/settings/fetch.tsx @@ -0,0 +1,40 @@ +"use server"; +import { redis } from "@/lib/redis"; +import { functionRetrieveUserSelection } from "@/fetchers/fecther"; +import { ClientSettings, AuthError } from "@/fetchers/types/context"; +import { getCompleteFromRedis, setCompleteToRedis } from "@/fetchers/custom/context/complete/fetch"; + +const getSettingsFromRedis = async (): Promise => { + try { + const decrpytUserSelection = await functionRetrieveUserSelection() + const redisKey = decrpytUserSelection?.redisKey; + if (!redisKey) throw new AuthError("No redis key found"); + const result = await redis.get(`${redisKey}`); + if (!result) throw new AuthError("No data found in redis"); + const parsedResult = JSON.parse(result); + if (!parsedResult.settings) throw new AuthError("No settings found in redis"); + return parsedResult.settings; + } catch (error) { + if (error instanceof AuthError) { throw error } + throw new AuthError(error instanceof Error ? error.message : "Unknown error"); + } +} + +const setSettingsToRedis = async (settingsObject: ClientSettings) => { + try { + const decrpytUserSelection = await functionRetrieveUserSelection() + if (!decrpytUserSelection) throw new AuthError("No user selection found"); + const redisKey = decrpytUserSelection?.redisKey; + if (!redisKey) throw new AuthError("No redis key found"); + if (!settingsObject) throw new AuthError("No settings object provided"); + const oldData = await getCompleteFromRedis(); + if (!oldData) throw new AuthError("No old data found in redis"); + await setCompleteToRedis({ ...oldData, settings: settingsObject }) + return true; + } catch (error) { + if (error instanceof AuthError) { throw error } + throw new AuthError(error instanceof Error ? error.message : "Unknown error"); + } +} + +export { getSettingsFromRedis, setSettingsToRedis }; diff --git a/ServicesWeb/customer/src/fetchers/custom/context/dash/user/fetch.tsx b/ServicesWeb/customer/src/fetchers/custom/context/dash/user/fetch.tsx new file mode 100644 index 0000000..4399ab8 --- /dev/null +++ b/ServicesWeb/customer/src/fetchers/custom/context/dash/user/fetch.tsx @@ -0,0 +1,40 @@ +"use server"; +import { redis } from "@/lib/redis"; +import { functionRetrieveUserSelection } from "@/fetchers/fecther"; +import { ClientUser, AuthError } from "@/fetchers/types/context"; +import { getCompleteFromRedis, setCompleteToRedis } from "@/fetchers/custom/context/complete/fetch"; + +const getUserFromRedis = async (): Promise => { + try { + const decrpytUserSelection = await functionRetrieveUserSelection() + const redisKey = decrpytUserSelection?.redisKey; + if (!redisKey) throw new AuthError("No redis key found"); + const result = await redis.get(`${redisKey}`); + if (!result) throw new AuthError("No data found in redis"); + const parsedResult = JSON.parse(result); + if (!parsedResult.user) throw new AuthError("No user found in redis"); + return parsedResult.user; + } catch (error) { + if (error instanceof AuthError) { throw error } + throw new AuthError(error instanceof Error ? error.message : "Unknown error"); + } +} + +const setUserToRedis = async (userObject: ClientUser) => { + try { + const decrpytUserSelection = await functionRetrieveUserSelection() + if (!decrpytUserSelection) throw new AuthError("No user selection found"); + const redisKey = decrpytUserSelection?.redisKey; + if (!redisKey) throw new AuthError("No redis key found"); + if (!userObject) throw new AuthError("No user object provided"); + const oldData = await getCompleteFromRedis(); + if (!oldData) throw new AuthError("No old data found in redis"); + await setCompleteToRedis({ ...oldData, user: userObject }); + return true; + } catch (error) { + if (error instanceof AuthError) { throw error } + throw new AuthError(error instanceof Error ? error.message : "Unknown error"); + } +} + +export { getUserFromRedis, setUserToRedis }; diff --git a/ServicesWeb/customer/src/fetchers/custom/context/page/config/fetch.tsx b/ServicesWeb/customer/src/fetchers/custom/context/page/config/fetch.tsx new file mode 100644 index 0000000..f84900e --- /dev/null +++ b/ServicesWeb/customer/src/fetchers/custom/context/page/config/fetch.tsx @@ -0,0 +1,40 @@ +"use server"; +import { redis } from "@/lib/redis"; +import { functionRetrieveUserSelection } from "@/fetchers/fecther"; +import { ClientSettings, AuthError } from "@/fetchers/types/context"; +import { getCompleteFromRedis, setCompleteToRedis } from "@/fetchers/custom/context/complete/fetch"; + +const getConfigFromRedis = async (): Promise => { + try { + const decrpytUserSelection = await functionRetrieveUserSelection() + const redisKey = decrpytUserSelection?.redisKey; + if (!redisKey) throw new AuthError("No redis key found"); + const result = await redis.get(`${redisKey}`); + if (!result) throw new AuthError("No data found in redis"); + const parsedResult = JSON.parse(result); + if (!parsedResult.settings) throw new AuthError("No settings found in redis"); + return parsedResult.settings; + } catch (error) { + if (error instanceof AuthError) { throw error } + throw new AuthError(error instanceof Error ? error.message : "Unknown error"); + } +} + +const setConfigToRedis = async (settingsObject: ClientSettings) => { + try { + const decrpytUserSelection = await functionRetrieveUserSelection() + if (!decrpytUserSelection) throw new AuthError("No user selection found"); + const redisKey = decrpytUserSelection?.redisKey; + if (!redisKey) throw new AuthError("No redis key found"); + if (!settingsObject) throw new AuthError("No settings object provided"); + const oldData = await getCompleteFromRedis(); + if (!oldData) throw new AuthError("No old data found in redis"); + await setCompleteToRedis({ ...oldData, settings: settingsObject }); + return true; + } catch (error) { + if (error instanceof AuthError) { throw error } + throw new AuthError(error instanceof Error ? error.message : "Unknown error"); + } +} + +export { getConfigFromRedis, setConfigToRedis }; diff --git a/ServicesWeb/customer/src/fetchers/custom/context/page/menu/fetch.tsx b/ServicesWeb/customer/src/fetchers/custom/context/page/menu/fetch.tsx new file mode 100644 index 0000000..491ea2a --- /dev/null +++ b/ServicesWeb/customer/src/fetchers/custom/context/page/menu/fetch.tsx @@ -0,0 +1,34 @@ +"use server"; +import { redis } from "@/lib/redis"; +import { functionRetrieveUserSelection } from "@/fetchers/fecther"; +import { ClientMenu, AuthError } from "@/fetchers/types/context"; +import { getCompleteFromRedis, setCompleteToRedis } from "@/fetchers/custom/context/complete/fetch"; + +const getMenuFromRedis = async (): Promise => { + try { + const decrpytUserSelection = await functionRetrieveUserSelection() + const redisKey = decrpytUserSelection?.redisKey; + if (!redisKey) throw new AuthError("No redis key found"); + const result = await redis.get(`${redisKey}`); + if (!result) throw new AuthError("No data found in redis"); + const parsedResult = JSON.parse(result); + if (!parsedResult.menu) throw new AuthError("No menu found in redis"); + return parsedResult.menu; + } catch (error) { if (error instanceof AuthError) { throw error } else { throw new AuthError(error instanceof Error ? error.message : "Unknown error") } } +} + +const setMenuToRedis = async (menuObject: ClientMenu) => { + try { + const decrpytUserSelection = await functionRetrieveUserSelection() + if (!decrpytUserSelection) throw new AuthError("No user selection found"); + const redisKey = decrpytUserSelection?.redisKey; + if (!redisKey) throw new AuthError("No redis key found"); + if (!menuObject) throw new AuthError("No menu object provided"); + const oldData = await getCompleteFromRedis(); + if (!oldData) throw new AuthError("No old data found in redis"); + await setCompleteToRedis({ ...oldData, menu: menuObject }); + return true; + } catch (error) { if (error instanceof AuthError) { throw error } else { throw new AuthError(error instanceof Error ? error.message : "Unknown error") } } +} + +export { getMenuFromRedis, setMenuToRedis }; diff --git a/ServicesWeb/customer/src/fetchers/custom/context/page/online/fetch.tsx b/ServicesWeb/customer/src/fetchers/custom/context/page/online/fetch.tsx new file mode 100644 index 0000000..15889ad --- /dev/null +++ b/ServicesWeb/customer/src/fetchers/custom/context/page/online/fetch.tsx @@ -0,0 +1,61 @@ +"use server"; +import { redis } from "@/lib/redis"; +import { functionRetrieveUserSelection } from "@/fetchers/fecther"; +import { ClientOnline, AuthError } from "@/fetchers/types/context"; +import { getCompleteFromRedis, setCompleteToRedis } from "@/fetchers/custom/context/complete/fetch"; +import { REDIS_TIMEOUT } from "@/fetchers/base"; + +/** + * Gets the online state from Redis + * @returns The online state object + */ +const getOnlineFromRedis = async (): Promise => { + try { + let result; + let decrpytUserSelection; + try { decrpytUserSelection = await functionRetrieveUserSelection() } catch (error) { throw new AuthError('Failed to retrieve user selection') } + const redisKey = decrpytUserSelection?.redisKey; + if (!redisKey) { throw new AuthError('No redis key found') } + if (redisKey === "default") { throw new AuthError('Invalid redis key') } + try { + const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error('Redis operation timed out')), REDIS_TIMEOUT) }); + result = await Promise.race([redis.get(`${redisKey}`), timeoutPromise]) as string | null; + } catch (redisError) { throw new AuthError('Failed to access Redis data') } + + if (!result) { throw new AuthError('No data found in redis') } + + try { + const parsedResult = JSON.parse(result); + if (!parsedResult.online) { throw new AuthError('No online data found in redis') } + return parsedResult.online; + } catch (parseError) { throw new AuthError('Invalid data format in redis') } + } catch (error) { if (error instanceof AuthError) { throw error } else { throw new AuthError(error instanceof Error ? error.message : 'Unknown error') } } +} + +/** + * Sets the online state in Redis + * @param onlineObject The online state to set + * @returns True if successful, false otherwise + */ +const setOnlineToRedis = async (onlineObject: ClientOnline): Promise => { + try { + if (!onlineObject) { throw new AuthError('No online object provided') } + + let decrpytUserSelection; + let oldData; + + try { decrpytUserSelection = await functionRetrieveUserSelection() } catch (error) { throw new AuthError('Failed to retrieve user selection') } + if (!decrpytUserSelection) { throw new AuthError('No user selection found') } + const redisKey = decrpytUserSelection?.redisKey; + if (!redisKey) { throw new AuthError('No redis key found') } + try { oldData = await getCompleteFromRedis() } catch (error) { throw new AuthError('Failed to retrieve existing data from Redis') } + if (!oldData) { throw new AuthError('No old data found in redis') } + try { + const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error('Redis operation timed out')), REDIS_TIMEOUT) }); + await Promise.race([setCompleteToRedis({ ...oldData, online: onlineObject }), timeoutPromise]); + return true; + } catch (redisError) { throw new AuthError('Failed to update Redis data') } + } catch (error) { if (error instanceof AuthError) throw error; throw new AuthError(error instanceof Error ? error.message : 'Unknown error') } +} + +export { getOnlineFromRedis, setOnlineToRedis }; diff --git a/ServicesWeb/customer/src/fetchers/fecther.ts b/ServicesWeb/customer/src/fetchers/fecther.ts index 5d7409f..f6b16ee 100644 --- a/ServicesWeb/customer/src/fetchers/fecther.ts +++ b/ServicesWeb/customer/src/fetchers/fecther.ts @@ -1,7 +1,75 @@ "use server"; -import { DEFAULT_RESPONSE, defaultHeaders, DEFAULT_TIMEOUT } from "./base"; +import { + DEFAULT_RESPONSE, + defaultHeaders, + DEFAULT_TIMEOUT, + nextCrypto, +} from "./base"; import { FetchOptions, HttpMethod, ApiResponse } from "./types"; import { retrieveAccessToken } from "./mutual/cookies/token"; +import { cookies } from "next/headers"; +import { cookieObject } from "@/fetchers/base"; + +/** + * Retrieves user selection from cookies with graceful fallback + * @returns User selection object or default selection if not found + */ +const functionRetrieveUserSelection = async () => { + try { + const cookieStore = await cookies(); + const encrpytUserSelection = cookieStore.get("eys-sel")?.value || ""; + if (!encrpytUserSelection) { + return { + redisKey: "default", + uuid: "", + timestamp: new Date().toISOString(), + }; + } + try { + const decrpytUserSelection = await nextCrypto.decrypt( + encrpytUserSelection + ); + if (!decrpytUserSelection) { + return { + redisKey: "default", + uuid: "", + timestamp: new Date().toISOString(), + }; + } + return JSON.parse(decrpytUserSelection); + } catch (decryptError) { + return { + redisKey: "default", + uuid: "", + timestamp: new Date().toISOString(), + }; + } + } catch (error) { + return { + redisKey: "default", + uuid: "", + timestamp: new Date().toISOString(), + }; + } +}; + +const functionSetUserSelection = async (userSelection: any) => { + const cookieStore = await cookies(); + const encrpytUserSelection = await nextCrypto.encrypt( + JSON.stringify(userSelection) + ); + if (!encrpytUserSelection) throw new Error("No user selection found"); + cookieStore.set({ + name: "eys-sel", + value: encrpytUserSelection, + ...cookieObject, + } as any); +}; + +const functionRemoveUserSelection = async () => { + const cookieStore = await cookies(); + cookieStore.delete("eys-sel"); +}; /** * Creates a promise that rejects after a specified timeout @@ -124,4 +192,11 @@ async function updateDataWithToken( ); } -export { fetchData, fetchDataWithToken, updateDataWithToken }; +export { + fetchData, + fetchDataWithToken, + updateDataWithToken, + functionRetrieveUserSelection, + functionSetUserSelection, + functionRemoveUserSelection, +}; diff --git a/ServicesWeb/customer/src/fetchers/types/context/complete/validations.ts b/ServicesWeb/customer/src/fetchers/types/context/complete/validations.ts new file mode 100644 index 0000000..118b204 --- /dev/null +++ b/ServicesWeb/customer/src/fetchers/types/context/complete/validations.ts @@ -0,0 +1,43 @@ +import { ClientRedisOnline } from "@/fetchers/types/context/online/validations"; +import { ClientPageConfig } from "@/fetchers/types/context/pageConfig/validations"; +import { ClientMenu } from "@/fetchers/types/context/menu/validations"; +import { ClientHeader } from "@/fetchers/types/context/header/validations"; +import { ClientSelection } from "@/fetchers/types/context/selection/validations"; +import { ClientUser } from "@/fetchers/types/context/user/validations"; +import { ClientSettings } from "@/fetchers/types/context/settings/validations"; +import { defaultValuesOnline } from "@/fetchers/types/context/online/validations"; +import { defaultValuesPageConfig } from "@/fetchers/types/context/pageConfig/validations"; +import { defaultValuesMenu } from "@/fetchers/types/context/menu/validations"; +import { defaultValuesHeader } from "@/fetchers/types/context/header/validations"; +import { defaultValuesSelection } from "@/fetchers/types/context/selection/validations"; +import { defaultValuesUser } from "@/fetchers/types/context/user/validations"; +import { defaultValuesSettings } from "@/fetchers/types/context/settings/validations"; + +interface ClientRedisToken { + online: ClientRedisOnline; + pageConfig: ClientPageConfig; + menu: ClientMenu; + header: ClientHeader; + selection: ClientSelection; + user: ClientUser; + settings: ClientSettings; + chatRoom: string[]; + notifications: string[]; + messages: string[]; +} + +const defaultClientRedisToken: ClientRedisToken = { + online: defaultValuesOnline, + pageConfig: defaultValuesPageConfig, + menu: defaultValuesMenu, + header: defaultValuesHeader, + selection: defaultValuesSelection, + user: defaultValuesUser, + settings: defaultValuesSettings, + chatRoom: [], + notifications: [], + messages: [], +}; + +export type { ClientRedisToken }; +export { defaultClientRedisToken }; diff --git a/ServicesWeb/customer/src/fetchers/types/context/header/validations.ts b/ServicesWeb/customer/src/fetchers/types/context/header/validations.ts new file mode 100644 index 0000000..74fab2d --- /dev/null +++ b/ServicesWeb/customer/src/fetchers/types/context/header/validations.ts @@ -0,0 +1,16 @@ +interface ClientHeader { + header: string[]; + activeDomain: string; + listOfDomains: string[]; + connections: string[]; +} + +const defaultValuesHeader: ClientHeader = { + header: [], + activeDomain: "", + listOfDomains: [], + connections: [], +}; + +export type { ClientHeader }; +export { defaultValuesHeader }; diff --git a/ServicesWeb/customer/src/fetchers/types/context/index.ts b/ServicesWeb/customer/src/fetchers/types/context/index.ts new file mode 100644 index 0000000..a68eae5 --- /dev/null +++ b/ServicesWeb/customer/src/fetchers/types/context/index.ts @@ -0,0 +1,62 @@ +import { + ClientRedisToken, + defaultClientRedisToken, +} from "@/fetchers/types/context/complete/validations"; +import { + ClientPageConfig, + defaultValuesPageConfig, +} from "@/fetchers/types/context/pageConfig/validations"; +import { + ClientMenu, + defaultValuesMenu, +} from "@/fetchers/types/context/menu/validations"; +import { + ClientHeader, + defaultValuesHeader, +} from "@/fetchers/types/context/header/validations"; +import { + ClientSelection, + defaultValuesSelection, +} from "@/fetchers/types/context/selection/validations"; +import { + ClientUser, + defaultValuesUser, +} from "@/fetchers/types/context/user/validations"; +import { + ClientSettings, + defaultValuesSettings, +} from "@/fetchers/types/context/settings/validations"; +import { + ClientOnline, + defaultValuesOnline, +} from "@/fetchers/types/context/online/validations"; + +class AuthError extends Error { + constructor(message: string) { + super(message); + this.name = "AuthError"; + } +} + +export type { + ClientRedisToken, + ClientOnline, + ClientPageConfig, + ClientMenu, + ClientHeader, + ClientSelection, + ClientUser, + ClientSettings, +}; + +export { + defaultClientRedisToken, + defaultValuesOnline, + defaultValuesPageConfig, + defaultValuesMenu, + defaultValuesHeader, + defaultValuesSelection, + defaultValuesUser, + defaultValuesSettings, + AuthError, +}; diff --git a/ServicesWeb/customer/src/fetchers/types/context/menu/validations.ts b/ServicesWeb/customer/src/fetchers/types/context/menu/validations.ts new file mode 100644 index 0000000..7cc25b7 --- /dev/null +++ b/ServicesWeb/customer/src/fetchers/types/context/menu/validations.ts @@ -0,0 +1,12 @@ +interface ClientMenu { + selectionList: string[]; + activeSelection: string; +} + +const defaultValuesMenu: ClientMenu = { + selectionList: ["/dashboard"], + activeSelection: "/dashboard", +}; + +export type { ClientMenu }; +export { defaultValuesMenu }; diff --git a/ServicesWeb/customer/src/fetchers/types/context/online/validations.ts b/ServicesWeb/customer/src/fetchers/types/context/online/validations.ts new file mode 100644 index 0000000..b2ec0b5 --- /dev/null +++ b/ServicesWeb/customer/src/fetchers/types/context/online/validations.ts @@ -0,0 +1,22 @@ +interface ClientOnline { + lastLogin: Date; + lastLogout: Date; + lastAction: Date; + lastPage: string; + userType: string; + lang: string; + timezone: string; +} + +const defaultValuesOnline: ClientOnline = { + lastLogin: new Date(), + lastLogout: new Date(), + lastAction: new Date(), + lastPage: "/dashboard", + userType: "employee", + lang: "tr", + timezone: "GMT+3", +}; + +export type { ClientOnline }; +export { defaultValuesOnline }; diff --git a/ServicesWeb/customer/src/fetchers/types/context/pageConfig/validations.ts b/ServicesWeb/customer/src/fetchers/types/context/pageConfig/validations.ts new file mode 100644 index 0000000..4b2dbf5 --- /dev/null +++ b/ServicesWeb/customer/src/fetchers/types/context/pageConfig/validations.ts @@ -0,0 +1,14 @@ +interface ClientPageConfig { + mode: string; + textFont: number; + theme: string; +} + +const defaultValuesPageConfig: ClientPageConfig = { + mode: "light", + textFont: 14, + theme: "default", +}; + +export type { ClientPageConfig }; +export { defaultValuesPageConfig }; diff --git a/ServicesWeb/customer/src/fetchers/types/context/selection/validations.ts b/ServicesWeb/customer/src/fetchers/types/context/selection/validations.ts new file mode 100644 index 0000000..d06627b --- /dev/null +++ b/ServicesWeb/customer/src/fetchers/types/context/selection/validations.ts @@ -0,0 +1,12 @@ +interface ClientSelection { + selectionList: string[]; + activeSelection: Record; +} + +const defaultValuesSelection: ClientSelection = { + selectionList: ["/dashboard"], + activeSelection: {}, +}; + +export type { ClientSelection }; +export { defaultValuesSelection }; diff --git a/ServicesWeb/customer/src/fetchers/types/context/settings/validations.ts b/ServicesWeb/customer/src/fetchers/types/context/settings/validations.ts new file mode 100644 index 0000000..35ac81f --- /dev/null +++ b/ServicesWeb/customer/src/fetchers/types/context/settings/validations.ts @@ -0,0 +1,12 @@ +interface ClientSettings { + lastOnline: Date; + token: string; +} + +const defaultValuesSettings: ClientSettings = { + lastOnline: new Date(), + token: "", +}; + +export type { ClientSettings }; +export { defaultValuesSettings }; diff --git a/ServicesWeb/customer/src/fetchers/types/context/user/validations.ts b/ServicesWeb/customer/src/fetchers/types/context/user/validations.ts new file mode 100644 index 0000000..8ccc85a --- /dev/null +++ b/ServicesWeb/customer/src/fetchers/types/context/user/validations.ts @@ -0,0 +1,44 @@ +interface Person { + uuid: string; + firstname: string; + surname: string; + middle_name: string; + sex_code: string; + person_tag: string; + country_code: string; + birth_date: string; +} + +interface ClientUser { + uuid: string; + avatar: string; + email: string; + phone_number: string; + user_tag: string; + password_expiry_begins: string; + person: Person; +} + +const defaultValuesPerson: Person = { + uuid: "", + firstname: "", + surname: "", + middle_name: "", + sex_code: "", + person_tag: "", + country_code: "", + birth_date: "", +}; + +const defaultValuesUser: ClientUser = { + uuid: "", + avatar: "", + email: "", + phone_number: "", + user_tag: "", + password_expiry_begins: new Date().toISOString(), + person: defaultValuesPerson, +}; + +export type { ClientUser }; +export { defaultValuesUser }; diff --git a/ServicesWeb/customer/src/validations/mutual/fecther/validations.ts b/ServicesWeb/customer/src/fetchers/types/fecther/validations.ts similarity index 100% rename from ServicesWeb/customer/src/validations/mutual/fecther/validations.ts rename to ServicesWeb/customer/src/fetchers/types/fecther/validations.ts diff --git a/ServicesWeb/customer/src/validations/mutual/context/validations.ts b/ServicesWeb/customer/src/validations/mutual/context/validations.ts deleted file mode 100644 index 3d1cfe3..0000000 --- a/ServicesWeb/customer/src/validations/mutual/context/validations.ts +++ /dev/null @@ -1,193 +0,0 @@ -// From Redis objects -export class AuthError extends Error { - constructor(message: string) { - super(message); - this.name = 'AuthError'; - } -} - -interface ClientOnline { - lastLogin: Date; - lastLogout: Date; - lastAction: Date; - lastPage: string; - userType: string; - lang: string; - timezone: string; -} - -interface ClientPageConfig { - mode: string; - textFont: number; - theme: string; -} - -interface ClientHeader { - header: any[]; - activeDomain: string; - listOfDomains: string[]; - connections: any[]; -} - -interface ClientMenu { - selectionList: string[]; - activeSelection: string; -} - -interface ClientSelection { - selectionList: any[]; - activeSelection: Record; -} - -interface ClientUser { - uuid: string; - avatar: string; - email: string; - phone_number: string; - user_tag: string; - password_expiry_begins: string; - person: { - uuid: string; - firstname: string; - surname: string; - middle_name: string; - sex_code: string; - person_tag: string; - country_code: string; - birth_date: string; - }; -} - -interface ClientSettings { - lastOnline: Date; - token: string; -} - -interface LinkList { - linkList: any[]; -} - -interface ClientRedisToken { - online: ClientOnline; - pageConfig: ClientPageConfig; - menu: ClientMenu; - header: ClientHeader; - selection: ClientSelection; - user: ClientUser; - settings: ClientSettings; - chatRoom: LinkList; - notifications: LinkList; - messages: LinkList; -} - -const defaultClientOnline: ClientOnline = { - lastLogin: new Date(), - lastLogout: new Date(), - lastAction: new Date(), - lastPage: "/dashboard", - userType: "employee", - lang: "tr", - timezone: "GMT+3", -}; - -const defaultClientPageConfig: ClientPageConfig = { - mode: "light", - textFont: 14, - theme: "default", -}; - -const defaultClientMenu: ClientMenu = { - selectionList: [], - activeSelection: "/dashboard", -}; - -const defaultClientHeader: ClientHeader = { - header: [], - activeDomain: "", - listOfDomains: [], - connections: [], -}; - -const defaultClientSelection: ClientSelection = { - selectionList: [], - activeSelection: {}, -}; - -const defaultClientUser: ClientUser = { - uuid: "", - avatar: "", - email: "", - phone_number: "", - user_tag: "", - password_expiry_begins: new Date().toISOString(), - person: { - uuid: "", - firstname: "", - surname: "", - middle_name: "", - sex_code: "", - person_tag: "", - country_code: "", - birth_date: "", - }, -}; - -const defaultClientSettings: ClientSettings = { - lastOnline: new Date(), - token: "", -}; - -const defaultLinkList: LinkList = { - linkList: [], -}; - -const defaultClientRedisToken: ClientRedisToken = { - online: defaultClientOnline, - pageConfig: defaultClientPageConfig, - menu: defaultClientMenu, - header: defaultClientHeader, - selection: defaultClientSelection, - user: defaultClientUser, - settings: defaultClientSettings, - chatRoom: defaultLinkList, - notifications: defaultLinkList, - messages: defaultLinkList, -}; - -const generateRedisKey = (userType: string, uuId: string) => { - const userTypeToUpper = userType.toUpperCase(); - if (!userTypeToUpper || !uuId) throw new Error("Invalid user type or uuId"); - return `CLIENT:${userTypeToUpper}:${uuId}`; -}; - -const readRedisKey = (redisKey: string) => { - if (redisKey.split(":").length !== 2) throw new Error("Invalid redis key"); - const userTypeToUpper = redisKey.split(":")[1]; - const uuId = redisKey.split(":")[2]; - if (!userTypeToUpper || !uuId) throw new Error("Invalid user type or uuId"); - return { userType: userTypeToUpper.toLowerCase(), uuId }; -}; - -export { - defaultClientOnline, - defaultClientPageConfig, - defaultClientMenu, - defaultClientHeader, - defaultClientSelection, - defaultClientUser, - defaultClientSettings, - defaultLinkList, - defaultClientRedisToken, -}; -export type { - ClientOnline, - ClientPageConfig, - ClientMenu, - ClientHeader, - ClientSelection, - ClientUser, - ClientSettings, - LinkList, - ClientRedisToken, -}; -export { generateRedisKey, readRedisKey }; diff --git a/ServicesWeb/management/src/fetchers/base.ts b/ServicesWeb/management/src/fetchers/base.ts index 6a24785..f32899a 100644 --- a/ServicesWeb/management/src/fetchers/base.ts +++ b/ServicesWeb/management/src/fetchers/base.ts @@ -3,8 +3,8 @@ import NextCrypto from "next-crypto"; const tokenSecretEnv = process.env.TOKENSECRET_90; const tokenSecret = tokenSecretEnv || "e781d1b0-9418-40b3-9940-385abf81a0b7"; +const REDIS_TIMEOUT = 5000; // Redis operation timeout (5 seconds) const nextCrypto = new NextCrypto(tokenSecret); - const cookieObject: CookieObject = { httpOnly: true, path: "/", @@ -13,7 +13,6 @@ const cookieObject: CookieObject = { maxAge: 3600, priority: "high", }; - const DEFAULT_TIMEOUT: number = 10000; // 10 seconds const defaultHeaders: Record = { accept: "application/json", @@ -31,6 +30,7 @@ const DEFAULT_RESPONSE: ApiResponse = { export { DEFAULT_TIMEOUT, DEFAULT_RESPONSE, + REDIS_TIMEOUT, defaultHeaders, tokenSecret, cookieObject, diff --git a/ServicesWeb/management/src/fetchers/custom/context/complete/fetch.tsx b/ServicesWeb/management/src/fetchers/custom/context/complete/fetch.tsx new file mode 100644 index 0000000..d522dff --- /dev/null +++ b/ServicesWeb/management/src/fetchers/custom/context/complete/fetch.tsx @@ -0,0 +1,69 @@ +"use server"; +import { redis } from "@/lib/redis"; +import { functionRetrieveUserSelection } from "@/fetchers/fecther"; +import { ClientRedisToken, defaultClientRedisToken } from "@/fetchers/types/context"; +import { REDIS_TIMEOUT } from "@/fetchers/base"; + +/** + * Gets the complete data from Redis with improved error handling and timeouts + * @returns The complete Redis data or default values if there's an error + */ +const getCompleteFromRedis = async (): Promise => { + try { + let decrpytUserSelection; + try { decrpytUserSelection = await functionRetrieveUserSelection() } catch (error) { return defaultClientRedisToken } + const redisKey = decrpytUserSelection?.redisKey; + if (!redisKey) { return defaultClientRedisToken } + if (redisKey === "default") { return defaultClientRedisToken } + + try { + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => reject(new Error('Redis operation timed out')), REDIS_TIMEOUT); + }); + const result = await Promise.race([redis.get(`${redisKey}`), timeoutPromise]); + if (!result) { return defaultClientRedisToken } + try { const parsedResult = JSON.parse(result); return parsedResult } catch (parseError) { return defaultClientRedisToken } + } catch (redisError) { return defaultClientRedisToken } + } catch (error) { return defaultClientRedisToken } +} + +/** + * Sets the complete data in Redis with improved error handling and timeouts + * @param completeObject The complete data to set in Redis + * @returns True if successful, false otherwise + */ +const setCompleteToRedis = async (completeObject: ClientRedisToken): Promise => { + try { + if (!completeObject) { return false } + let decrpytUserSelection; + try { decrpytUserSelection = await functionRetrieveUserSelection() } catch (error) { return false } + if (!decrpytUserSelection) { return false } + const redisKey = decrpytUserSelection?.redisKey; + if (!redisKey) { return false } + try { + const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error('Redis operation timed out')), REDIS_TIMEOUT) }); + await Promise.race([redis.set(redisKey, JSON.stringify(completeObject)), timeoutPromise]); + return true; + } catch (redisError) { return false } + } catch (error) { return false } +} + +/** + * Sets new complete data in Redis with a specific key with improved error handling and timeouts + * @param completeObject The complete data to set in Redis + * @param redisKey The specific Redis key to use + * @returns True if successful, false otherwise + */ +const setNewCompleteToRedis = async (completeObject: ClientRedisToken, redisKey: string): Promise => { + try { + if (!redisKey) { return false } + if (!completeObject) { return false } + try { + const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error('Redis operation timed out')), REDIS_TIMEOUT) }); + await Promise.race([redis.set(redisKey, JSON.stringify(completeObject)), timeoutPromise]) + return true; + } catch (redisError) { return false } + } catch (error) { return false } +} + +export { getCompleteFromRedis, setCompleteToRedis, setNewCompleteToRedis }; diff --git a/ServicesWeb/management/src/fetchers/custom/context/dash/selection/fetch.tsx b/ServicesWeb/management/src/fetchers/custom/context/dash/selection/fetch.tsx new file mode 100644 index 0000000..558473e --- /dev/null +++ b/ServicesWeb/management/src/fetchers/custom/context/dash/selection/fetch.tsx @@ -0,0 +1,45 @@ +"use server"; +import { redis } from "@/lib/redis"; +import { functionRetrieveUserSelection } from "@/fetchers/fecther"; +import { ClientSelection, AuthError } from "@/fetchers/types/context"; +import { getCompleteFromRedis, setCompleteToRedis } from "@/fetchers/custom/context/complete/fetch"; + +const getSelectionFromRedis = async (): Promise => { + try { + const decrpytUserSelection = await functionRetrieveUserSelection(); + const redisKey = decrpytUserSelection?.redisKey; + if (redisKey === "default") { return { selectionList: [], activeSelection: {} } } + const result = await redis.get(`${redisKey}`); + if (!result) { return { selectionList: [], activeSelection: {} } } + const parsedResult = JSON.parse(result); + if (!parsedResult.selection) { return { selectionList: [], activeSelection: {} } } + return parsedResult.selection; + } catch (error) { return { selectionList: [], activeSelection: {} } } +} + +const setSelectionToRedis = async (selectionObject: ClientSelection) => { + try { + const decrpytUserSelection = await functionRetrieveUserSelection() + if (!decrpytUserSelection) throw new AuthError("No user selection found"); + + const redisKey = decrpytUserSelection?.redisKey; + if (!redisKey) throw new AuthError("No redis key found"); + + if (!selectionObject) throw new AuthError("No selection object provided"); + + const oldData = await getCompleteFromRedis(); + if (!oldData) throw new AuthError("No old data found in redis"); + + await setCompleteToRedis({ ...oldData, selection: selectionObject }) + return true; + } catch (error) { + // Re-throw AuthError instances, wrap other errors as AuthError + if (error instanceof AuthError) { + throw error; + } else { + throw new AuthError(error instanceof Error ? error.message : "Unknown error"); + } + } +} + +export { getSelectionFromRedis, setSelectionToRedis }; diff --git a/ServicesWeb/management/src/fetchers/custom/context/dash/settings/fetch.tsx b/ServicesWeb/management/src/fetchers/custom/context/dash/settings/fetch.tsx new file mode 100644 index 0000000..0d0f0fc --- /dev/null +++ b/ServicesWeb/management/src/fetchers/custom/context/dash/settings/fetch.tsx @@ -0,0 +1,40 @@ +"use server"; +import { redis } from "@/lib/redis"; +import { functionRetrieveUserSelection } from "@/fetchers/fecther"; +import { ClientSettings, AuthError } from "@/fetchers/types/context"; +import { getCompleteFromRedis, setCompleteToRedis } from "@/fetchers/custom/context/complete/fetch"; + +const getSettingsFromRedis = async (): Promise => { + try { + const decrpytUserSelection = await functionRetrieveUserSelection() + const redisKey = decrpytUserSelection?.redisKey; + if (!redisKey) throw new AuthError("No redis key found"); + const result = await redis.get(`${redisKey}`); + if (!result) throw new AuthError("No data found in redis"); + const parsedResult = JSON.parse(result); + if (!parsedResult.settings) throw new AuthError("No settings found in redis"); + return parsedResult.settings; + } catch (error) { + if (error instanceof AuthError) { throw error } + throw new AuthError(error instanceof Error ? error.message : "Unknown error"); + } +} + +const setSettingsToRedis = async (settingsObject: ClientSettings) => { + try { + const decrpytUserSelection = await functionRetrieveUserSelection() + if (!decrpytUserSelection) throw new AuthError("No user selection found"); + const redisKey = decrpytUserSelection?.redisKey; + if (!redisKey) throw new AuthError("No redis key found"); + if (!settingsObject) throw new AuthError("No settings object provided"); + const oldData = await getCompleteFromRedis(); + if (!oldData) throw new AuthError("No old data found in redis"); + await setCompleteToRedis({ ...oldData, settings: settingsObject }) + return true; + } catch (error) { + if (error instanceof AuthError) { throw error } + throw new AuthError(error instanceof Error ? error.message : "Unknown error"); + } +} + +export { getSettingsFromRedis, setSettingsToRedis }; diff --git a/ServicesWeb/management/src/fetchers/custom/context/dash/user/fetch.tsx b/ServicesWeb/management/src/fetchers/custom/context/dash/user/fetch.tsx new file mode 100644 index 0000000..4399ab8 --- /dev/null +++ b/ServicesWeb/management/src/fetchers/custom/context/dash/user/fetch.tsx @@ -0,0 +1,40 @@ +"use server"; +import { redis } from "@/lib/redis"; +import { functionRetrieveUserSelection } from "@/fetchers/fecther"; +import { ClientUser, AuthError } from "@/fetchers/types/context"; +import { getCompleteFromRedis, setCompleteToRedis } from "@/fetchers/custom/context/complete/fetch"; + +const getUserFromRedis = async (): Promise => { + try { + const decrpytUserSelection = await functionRetrieveUserSelection() + const redisKey = decrpytUserSelection?.redisKey; + if (!redisKey) throw new AuthError("No redis key found"); + const result = await redis.get(`${redisKey}`); + if (!result) throw new AuthError("No data found in redis"); + const parsedResult = JSON.parse(result); + if (!parsedResult.user) throw new AuthError("No user found in redis"); + return parsedResult.user; + } catch (error) { + if (error instanceof AuthError) { throw error } + throw new AuthError(error instanceof Error ? error.message : "Unknown error"); + } +} + +const setUserToRedis = async (userObject: ClientUser) => { + try { + const decrpytUserSelection = await functionRetrieveUserSelection() + if (!decrpytUserSelection) throw new AuthError("No user selection found"); + const redisKey = decrpytUserSelection?.redisKey; + if (!redisKey) throw new AuthError("No redis key found"); + if (!userObject) throw new AuthError("No user object provided"); + const oldData = await getCompleteFromRedis(); + if (!oldData) throw new AuthError("No old data found in redis"); + await setCompleteToRedis({ ...oldData, user: userObject }); + return true; + } catch (error) { + if (error instanceof AuthError) { throw error } + throw new AuthError(error instanceof Error ? error.message : "Unknown error"); + } +} + +export { getUserFromRedis, setUserToRedis }; diff --git a/ServicesWeb/management/src/fetchers/custom/context/page/config/fetch.tsx b/ServicesWeb/management/src/fetchers/custom/context/page/config/fetch.tsx new file mode 100644 index 0000000..f84900e --- /dev/null +++ b/ServicesWeb/management/src/fetchers/custom/context/page/config/fetch.tsx @@ -0,0 +1,40 @@ +"use server"; +import { redis } from "@/lib/redis"; +import { functionRetrieveUserSelection } from "@/fetchers/fecther"; +import { ClientSettings, AuthError } from "@/fetchers/types/context"; +import { getCompleteFromRedis, setCompleteToRedis } from "@/fetchers/custom/context/complete/fetch"; + +const getConfigFromRedis = async (): Promise => { + try { + const decrpytUserSelection = await functionRetrieveUserSelection() + const redisKey = decrpytUserSelection?.redisKey; + if (!redisKey) throw new AuthError("No redis key found"); + const result = await redis.get(`${redisKey}`); + if (!result) throw new AuthError("No data found in redis"); + const parsedResult = JSON.parse(result); + if (!parsedResult.settings) throw new AuthError("No settings found in redis"); + return parsedResult.settings; + } catch (error) { + if (error instanceof AuthError) { throw error } + throw new AuthError(error instanceof Error ? error.message : "Unknown error"); + } +} + +const setConfigToRedis = async (settingsObject: ClientSettings) => { + try { + const decrpytUserSelection = await functionRetrieveUserSelection() + if (!decrpytUserSelection) throw new AuthError("No user selection found"); + const redisKey = decrpytUserSelection?.redisKey; + if (!redisKey) throw new AuthError("No redis key found"); + if (!settingsObject) throw new AuthError("No settings object provided"); + const oldData = await getCompleteFromRedis(); + if (!oldData) throw new AuthError("No old data found in redis"); + await setCompleteToRedis({ ...oldData, settings: settingsObject }); + return true; + } catch (error) { + if (error instanceof AuthError) { throw error } + throw new AuthError(error instanceof Error ? error.message : "Unknown error"); + } +} + +export { getConfigFromRedis, setConfigToRedis }; diff --git a/ServicesWeb/management/src/fetchers/custom/context/page/menu/fetch.tsx b/ServicesWeb/management/src/fetchers/custom/context/page/menu/fetch.tsx new file mode 100644 index 0000000..491ea2a --- /dev/null +++ b/ServicesWeb/management/src/fetchers/custom/context/page/menu/fetch.tsx @@ -0,0 +1,34 @@ +"use server"; +import { redis } from "@/lib/redis"; +import { functionRetrieveUserSelection } from "@/fetchers/fecther"; +import { ClientMenu, AuthError } from "@/fetchers/types/context"; +import { getCompleteFromRedis, setCompleteToRedis } from "@/fetchers/custom/context/complete/fetch"; + +const getMenuFromRedis = async (): Promise => { + try { + const decrpytUserSelection = await functionRetrieveUserSelection() + const redisKey = decrpytUserSelection?.redisKey; + if (!redisKey) throw new AuthError("No redis key found"); + const result = await redis.get(`${redisKey}`); + if (!result) throw new AuthError("No data found in redis"); + const parsedResult = JSON.parse(result); + if (!parsedResult.menu) throw new AuthError("No menu found in redis"); + return parsedResult.menu; + } catch (error) { if (error instanceof AuthError) { throw error } else { throw new AuthError(error instanceof Error ? error.message : "Unknown error") } } +} + +const setMenuToRedis = async (menuObject: ClientMenu) => { + try { + const decrpytUserSelection = await functionRetrieveUserSelection() + if (!decrpytUserSelection) throw new AuthError("No user selection found"); + const redisKey = decrpytUserSelection?.redisKey; + if (!redisKey) throw new AuthError("No redis key found"); + if (!menuObject) throw new AuthError("No menu object provided"); + const oldData = await getCompleteFromRedis(); + if (!oldData) throw new AuthError("No old data found in redis"); + await setCompleteToRedis({ ...oldData, menu: menuObject }); + return true; + } catch (error) { if (error instanceof AuthError) { throw error } else { throw new AuthError(error instanceof Error ? error.message : "Unknown error") } } +} + +export { getMenuFromRedis, setMenuToRedis }; diff --git a/ServicesWeb/management/src/fetchers/custom/context/page/online/fetch.tsx b/ServicesWeb/management/src/fetchers/custom/context/page/online/fetch.tsx new file mode 100644 index 0000000..15889ad --- /dev/null +++ b/ServicesWeb/management/src/fetchers/custom/context/page/online/fetch.tsx @@ -0,0 +1,61 @@ +"use server"; +import { redis } from "@/lib/redis"; +import { functionRetrieveUserSelection } from "@/fetchers/fecther"; +import { ClientOnline, AuthError } from "@/fetchers/types/context"; +import { getCompleteFromRedis, setCompleteToRedis } from "@/fetchers/custom/context/complete/fetch"; +import { REDIS_TIMEOUT } from "@/fetchers/base"; + +/** + * Gets the online state from Redis + * @returns The online state object + */ +const getOnlineFromRedis = async (): Promise => { + try { + let result; + let decrpytUserSelection; + try { decrpytUserSelection = await functionRetrieveUserSelection() } catch (error) { throw new AuthError('Failed to retrieve user selection') } + const redisKey = decrpytUserSelection?.redisKey; + if (!redisKey) { throw new AuthError('No redis key found') } + if (redisKey === "default") { throw new AuthError('Invalid redis key') } + try { + const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error('Redis operation timed out')), REDIS_TIMEOUT) }); + result = await Promise.race([redis.get(`${redisKey}`), timeoutPromise]) as string | null; + } catch (redisError) { throw new AuthError('Failed to access Redis data') } + + if (!result) { throw new AuthError('No data found in redis') } + + try { + const parsedResult = JSON.parse(result); + if (!parsedResult.online) { throw new AuthError('No online data found in redis') } + return parsedResult.online; + } catch (parseError) { throw new AuthError('Invalid data format in redis') } + } catch (error) { if (error instanceof AuthError) { throw error } else { throw new AuthError(error instanceof Error ? error.message : 'Unknown error') } } +} + +/** + * Sets the online state in Redis + * @param onlineObject The online state to set + * @returns True if successful, false otherwise + */ +const setOnlineToRedis = async (onlineObject: ClientOnline): Promise => { + try { + if (!onlineObject) { throw new AuthError('No online object provided') } + + let decrpytUserSelection; + let oldData; + + try { decrpytUserSelection = await functionRetrieveUserSelection() } catch (error) { throw new AuthError('Failed to retrieve user selection') } + if (!decrpytUserSelection) { throw new AuthError('No user selection found') } + const redisKey = decrpytUserSelection?.redisKey; + if (!redisKey) { throw new AuthError('No redis key found') } + try { oldData = await getCompleteFromRedis() } catch (error) { throw new AuthError('Failed to retrieve existing data from Redis') } + if (!oldData) { throw new AuthError('No old data found in redis') } + try { + const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error('Redis operation timed out')), REDIS_TIMEOUT) }); + await Promise.race([setCompleteToRedis({ ...oldData, online: onlineObject }), timeoutPromise]); + return true; + } catch (redisError) { throw new AuthError('Failed to update Redis data') } + } catch (error) { if (error instanceof AuthError) throw error; throw new AuthError(error instanceof Error ? error.message : 'Unknown error') } +} + +export { getOnlineFromRedis, setOnlineToRedis }; diff --git a/ServicesWeb/management/src/fetchers/fecther.ts b/ServicesWeb/management/src/fetchers/fecther.ts index 5d7409f..f6b16ee 100644 --- a/ServicesWeb/management/src/fetchers/fecther.ts +++ b/ServicesWeb/management/src/fetchers/fecther.ts @@ -1,7 +1,75 @@ "use server"; -import { DEFAULT_RESPONSE, defaultHeaders, DEFAULT_TIMEOUT } from "./base"; +import { + DEFAULT_RESPONSE, + defaultHeaders, + DEFAULT_TIMEOUT, + nextCrypto, +} from "./base"; import { FetchOptions, HttpMethod, ApiResponse } from "./types"; import { retrieveAccessToken } from "./mutual/cookies/token"; +import { cookies } from "next/headers"; +import { cookieObject } from "@/fetchers/base"; + +/** + * Retrieves user selection from cookies with graceful fallback + * @returns User selection object or default selection if not found + */ +const functionRetrieveUserSelection = async () => { + try { + const cookieStore = await cookies(); + const encrpytUserSelection = cookieStore.get("eys-sel")?.value || ""; + if (!encrpytUserSelection) { + return { + redisKey: "default", + uuid: "", + timestamp: new Date().toISOString(), + }; + } + try { + const decrpytUserSelection = await nextCrypto.decrypt( + encrpytUserSelection + ); + if (!decrpytUserSelection) { + return { + redisKey: "default", + uuid: "", + timestamp: new Date().toISOString(), + }; + } + return JSON.parse(decrpytUserSelection); + } catch (decryptError) { + return { + redisKey: "default", + uuid: "", + timestamp: new Date().toISOString(), + }; + } + } catch (error) { + return { + redisKey: "default", + uuid: "", + timestamp: new Date().toISOString(), + }; + } +}; + +const functionSetUserSelection = async (userSelection: any) => { + const cookieStore = await cookies(); + const encrpytUserSelection = await nextCrypto.encrypt( + JSON.stringify(userSelection) + ); + if (!encrpytUserSelection) throw new Error("No user selection found"); + cookieStore.set({ + name: "eys-sel", + value: encrpytUserSelection, + ...cookieObject, + } as any); +}; + +const functionRemoveUserSelection = async () => { + const cookieStore = await cookies(); + cookieStore.delete("eys-sel"); +}; /** * Creates a promise that rejects after a specified timeout @@ -124,4 +192,11 @@ async function updateDataWithToken( ); } -export { fetchData, fetchDataWithToken, updateDataWithToken }; +export { + fetchData, + fetchDataWithToken, + updateDataWithToken, + functionRetrieveUserSelection, + functionSetUserSelection, + functionRemoveUserSelection, +}; diff --git a/ServicesWeb/management/src/fetchers/types/context/complete/validations.ts b/ServicesWeb/management/src/fetchers/types/context/complete/validations.ts new file mode 100644 index 0000000..118b204 --- /dev/null +++ b/ServicesWeb/management/src/fetchers/types/context/complete/validations.ts @@ -0,0 +1,43 @@ +import { ClientRedisOnline } from "@/fetchers/types/context/online/validations"; +import { ClientPageConfig } from "@/fetchers/types/context/pageConfig/validations"; +import { ClientMenu } from "@/fetchers/types/context/menu/validations"; +import { ClientHeader } from "@/fetchers/types/context/header/validations"; +import { ClientSelection } from "@/fetchers/types/context/selection/validations"; +import { ClientUser } from "@/fetchers/types/context/user/validations"; +import { ClientSettings } from "@/fetchers/types/context/settings/validations"; +import { defaultValuesOnline } from "@/fetchers/types/context/online/validations"; +import { defaultValuesPageConfig } from "@/fetchers/types/context/pageConfig/validations"; +import { defaultValuesMenu } from "@/fetchers/types/context/menu/validations"; +import { defaultValuesHeader } from "@/fetchers/types/context/header/validations"; +import { defaultValuesSelection } from "@/fetchers/types/context/selection/validations"; +import { defaultValuesUser } from "@/fetchers/types/context/user/validations"; +import { defaultValuesSettings } from "@/fetchers/types/context/settings/validations"; + +interface ClientRedisToken { + online: ClientRedisOnline; + pageConfig: ClientPageConfig; + menu: ClientMenu; + header: ClientHeader; + selection: ClientSelection; + user: ClientUser; + settings: ClientSettings; + chatRoom: string[]; + notifications: string[]; + messages: string[]; +} + +const defaultClientRedisToken: ClientRedisToken = { + online: defaultValuesOnline, + pageConfig: defaultValuesPageConfig, + menu: defaultValuesMenu, + header: defaultValuesHeader, + selection: defaultValuesSelection, + user: defaultValuesUser, + settings: defaultValuesSettings, + chatRoom: [], + notifications: [], + messages: [], +}; + +export type { ClientRedisToken }; +export { defaultClientRedisToken }; diff --git a/ServicesWeb/management/src/fetchers/types/context/header/validations.ts b/ServicesWeb/management/src/fetchers/types/context/header/validations.ts new file mode 100644 index 0000000..74fab2d --- /dev/null +++ b/ServicesWeb/management/src/fetchers/types/context/header/validations.ts @@ -0,0 +1,16 @@ +interface ClientHeader { + header: string[]; + activeDomain: string; + listOfDomains: string[]; + connections: string[]; +} + +const defaultValuesHeader: ClientHeader = { + header: [], + activeDomain: "", + listOfDomains: [], + connections: [], +}; + +export type { ClientHeader }; +export { defaultValuesHeader }; diff --git a/ServicesWeb/management/src/fetchers/types/context/index.ts b/ServicesWeb/management/src/fetchers/types/context/index.ts new file mode 100644 index 0000000..a68eae5 --- /dev/null +++ b/ServicesWeb/management/src/fetchers/types/context/index.ts @@ -0,0 +1,62 @@ +import { + ClientRedisToken, + defaultClientRedisToken, +} from "@/fetchers/types/context/complete/validations"; +import { + ClientPageConfig, + defaultValuesPageConfig, +} from "@/fetchers/types/context/pageConfig/validations"; +import { + ClientMenu, + defaultValuesMenu, +} from "@/fetchers/types/context/menu/validations"; +import { + ClientHeader, + defaultValuesHeader, +} from "@/fetchers/types/context/header/validations"; +import { + ClientSelection, + defaultValuesSelection, +} from "@/fetchers/types/context/selection/validations"; +import { + ClientUser, + defaultValuesUser, +} from "@/fetchers/types/context/user/validations"; +import { + ClientSettings, + defaultValuesSettings, +} from "@/fetchers/types/context/settings/validations"; +import { + ClientOnline, + defaultValuesOnline, +} from "@/fetchers/types/context/online/validations"; + +class AuthError extends Error { + constructor(message: string) { + super(message); + this.name = "AuthError"; + } +} + +export type { + ClientRedisToken, + ClientOnline, + ClientPageConfig, + ClientMenu, + ClientHeader, + ClientSelection, + ClientUser, + ClientSettings, +}; + +export { + defaultClientRedisToken, + defaultValuesOnline, + defaultValuesPageConfig, + defaultValuesMenu, + defaultValuesHeader, + defaultValuesSelection, + defaultValuesUser, + defaultValuesSettings, + AuthError, +}; diff --git a/ServicesWeb/management/src/fetchers/types/context/menu/validations.ts b/ServicesWeb/management/src/fetchers/types/context/menu/validations.ts new file mode 100644 index 0000000..7cc25b7 --- /dev/null +++ b/ServicesWeb/management/src/fetchers/types/context/menu/validations.ts @@ -0,0 +1,12 @@ +interface ClientMenu { + selectionList: string[]; + activeSelection: string; +} + +const defaultValuesMenu: ClientMenu = { + selectionList: ["/dashboard"], + activeSelection: "/dashboard", +}; + +export type { ClientMenu }; +export { defaultValuesMenu }; diff --git a/ServicesWeb/management/src/fetchers/types/context/online/validations.ts b/ServicesWeb/management/src/fetchers/types/context/online/validations.ts new file mode 100644 index 0000000..b2ec0b5 --- /dev/null +++ b/ServicesWeb/management/src/fetchers/types/context/online/validations.ts @@ -0,0 +1,22 @@ +interface ClientOnline { + lastLogin: Date; + lastLogout: Date; + lastAction: Date; + lastPage: string; + userType: string; + lang: string; + timezone: string; +} + +const defaultValuesOnline: ClientOnline = { + lastLogin: new Date(), + lastLogout: new Date(), + lastAction: new Date(), + lastPage: "/dashboard", + userType: "employee", + lang: "tr", + timezone: "GMT+3", +}; + +export type { ClientOnline }; +export { defaultValuesOnline }; diff --git a/ServicesWeb/management/src/fetchers/types/context/pageConfig/validations.ts b/ServicesWeb/management/src/fetchers/types/context/pageConfig/validations.ts new file mode 100644 index 0000000..4b2dbf5 --- /dev/null +++ b/ServicesWeb/management/src/fetchers/types/context/pageConfig/validations.ts @@ -0,0 +1,14 @@ +interface ClientPageConfig { + mode: string; + textFont: number; + theme: string; +} + +const defaultValuesPageConfig: ClientPageConfig = { + mode: "light", + textFont: 14, + theme: "default", +}; + +export type { ClientPageConfig }; +export { defaultValuesPageConfig }; diff --git a/ServicesWeb/management/src/fetchers/types/context/selection/validations.ts b/ServicesWeb/management/src/fetchers/types/context/selection/validations.ts new file mode 100644 index 0000000..d06627b --- /dev/null +++ b/ServicesWeb/management/src/fetchers/types/context/selection/validations.ts @@ -0,0 +1,12 @@ +interface ClientSelection { + selectionList: string[]; + activeSelection: Record; +} + +const defaultValuesSelection: ClientSelection = { + selectionList: ["/dashboard"], + activeSelection: {}, +}; + +export type { ClientSelection }; +export { defaultValuesSelection }; diff --git a/ServicesWeb/management/src/fetchers/types/context/settings/validations.ts b/ServicesWeb/management/src/fetchers/types/context/settings/validations.ts new file mode 100644 index 0000000..35ac81f --- /dev/null +++ b/ServicesWeb/management/src/fetchers/types/context/settings/validations.ts @@ -0,0 +1,12 @@ +interface ClientSettings { + lastOnline: Date; + token: string; +} + +const defaultValuesSettings: ClientSettings = { + lastOnline: new Date(), + token: "", +}; + +export type { ClientSettings }; +export { defaultValuesSettings }; diff --git a/ServicesWeb/management/src/fetchers/types/context/user/validations.ts b/ServicesWeb/management/src/fetchers/types/context/user/validations.ts new file mode 100644 index 0000000..8ccc85a --- /dev/null +++ b/ServicesWeb/management/src/fetchers/types/context/user/validations.ts @@ -0,0 +1,44 @@ +interface Person { + uuid: string; + firstname: string; + surname: string; + middle_name: string; + sex_code: string; + person_tag: string; + country_code: string; + birth_date: string; +} + +interface ClientUser { + uuid: string; + avatar: string; + email: string; + phone_number: string; + user_tag: string; + password_expiry_begins: string; + person: Person; +} + +const defaultValuesPerson: Person = { + uuid: "", + firstname: "", + surname: "", + middle_name: "", + sex_code: "", + person_tag: "", + country_code: "", + birth_date: "", +}; + +const defaultValuesUser: ClientUser = { + uuid: "", + avatar: "", + email: "", + phone_number: "", + user_tag: "", + password_expiry_begins: new Date().toISOString(), + person: defaultValuesPerson, +}; + +export type { ClientUser }; +export { defaultValuesUser }; diff --git a/ServicesWeb/management/src/fetchers/types/fecther/validations.ts b/ServicesWeb/management/src/fetchers/types/fecther/validations.ts new file mode 100644 index 0000000..394dd2d --- /dev/null +++ b/ServicesWeb/management/src/fetchers/types/fecther/validations.ts @@ -0,0 +1,44 @@ +interface FetcherRequest { + url: string; + isNoCache: boolean; +} + +interface PostFetcherRequest extends FetcherRequest { + body: Record; +} + +interface GetFetcherRequest extends FetcherRequest { + url: string; +} + +interface DeleteFetcherRequest extends GetFetcherRequest {} +interface PutFetcherRequest extends PostFetcherRequest {} +interface PatchFetcherRequest extends PostFetcherRequest {} + +interface FetcherRespose { + success: boolean; +} +interface PaginationResponse { + onPage: number; + onPageCount: number; + totalPage: number; + totalCount: number; + next: boolean; + back: boolean; +} + +interface FetcherDataResponse extends FetcherRespose { + data: Record | null; + pagination?: PaginationResponse; +} + +export type { + FetcherRequest, + PostFetcherRequest, + GetFetcherRequest, + DeleteFetcherRequest, + PutFetcherRequest, + PatchFetcherRequest, + FetcherRespose, + FetcherDataResponse, +}; diff --git a/ServicesWeb/management/src/validations/custom/a.txt b/ServicesWeb/management/src/validations/custom/a.txt new file mode 100644 index 0000000..e69de29