updated fetchers

This commit is contained in:
Berkay 2025-06-02 12:06:47 +03:00
parent f8184246d9
commit a8ff968962
17 changed files with 846 additions and 5 deletions

View File

@ -0,0 +1,38 @@
import { ApiResponse, CookieObject } from "./types";
import NextCrypto from "next-crypto";
const tokenSecretEnv = process.env.TOKENSECRET_90;
const tokenSecret = tokenSecretEnv || "e781d1b0-9418-40b3-9940-385abf81a0b7";
const nextCrypto = new NextCrypto(tokenSecret);
const cookieObject: CookieObject = {
httpOnly: true,
path: "/",
sameSite: "none",
secure: true,
maxAge: 3600,
priority: "high",
};
const DEFAULT_TIMEOUT: number = 10000; // 10 seconds
const defaultHeaders: Record<string, string> = {
accept: "application/json",
language: "tr",
domain: "evyos.com.tr",
tz: "GMT+3",
"Content-type": "application/json",
};
const DEFAULT_RESPONSE: ApiResponse = {
error: "Hata tipi belirtilmedi",
status: 500,
data: {},
};
export {
DEFAULT_TIMEOUT,
DEFAULT_RESPONSE,
defaultHeaders,
tokenSecret,
cookieObject,
nextCrypto,
};

View File

@ -0,0 +1,127 @@
"use server";
import { DEFAULT_RESPONSE, defaultHeaders, DEFAULT_TIMEOUT } from "./base";
import { FetchOptions, HttpMethod, ApiResponse } from "./types";
import { retrieveAccessToken } from "./mutual/cookies/token";
/**
* Creates a promise that rejects after a specified timeout
* @param ms Timeout in milliseconds
* @param controller AbortController to abort the fetch request
* @returns A promise that rejects after the timeout
*/
const createTimeoutPromise = (
ms: number,
controller: AbortController
): Promise<never> => {
return new Promise((_, reject) => {
setTimeout(() => {
controller.abort();
reject(new Error(`Request timed out after ${ms}ms`));
}, ms);
});
};
/**
* Core fetch function with timeout and error handling
* @param url The URL to fetch
* @param options Fetch options
* @param headers Request headers
* @param payload Request payload
* @returns API response
*/
async function coreFetch<T>(
url: string,
options: FetchOptions = {},
headers: Record<string, string> = defaultHeaders,
payload?: any
): Promise<ApiResponse<T>> {
const { method = "POST", cache = false, timeout = DEFAULT_TIMEOUT } = options;
try {
const controller = new AbortController();
const fetchOptions: RequestInit = {
method,
headers,
cache: cache ? "force-cache" : "no-cache",
signal: controller.signal,
};
if (method !== "GET" && payload) {
fetchOptions.body = JSON.stringify(
payload.payload ? payload.payload : payload
);
}
const timeoutPromise = createTimeoutPromise(timeout, controller);
const response = await Promise.race([
fetch(url, fetchOptions),
timeoutPromise,
]);
const responseData = await response.json();
return {
status: response.status,
data: responseData || ({} as T),
};
} catch (error) {
console.error(`API Error (${url}):`, error);
return {
...DEFAULT_RESPONSE,
error: error instanceof Error ? error.message : "Network error",
} as ApiResponse<T>;
}
}
/**
* Fetch data without authentication
*/
async function fetchData<T>(
endpoint: string,
payload?: any,
method: HttpMethod = "POST",
cache: boolean = false,
timeout: number = DEFAULT_TIMEOUT
): Promise<ApiResponse<T>> {
return coreFetch<T>(
endpoint,
{ method, cache, timeout },
defaultHeaders,
payload
);
}
/**
* Fetch data with authentication token
*/
async function fetchDataWithToken<T>(
endpoint: string,
payload?: any,
method: HttpMethod = "POST",
cache: boolean = false,
timeout: number = DEFAULT_TIMEOUT
): Promise<ApiResponse<T>> {
const accessToken = await retrieveAccessToken();
const headers = { ...defaultHeaders, "eys-acs-tkn": accessToken };
return coreFetch<T>(endpoint, { method, cache, timeout }, headers, payload);
}
/**
* Update data with authentication token and UUID
*/
async function updateDataWithToken<T>(
endpoint: string,
uuid: string,
payload?: any,
method: HttpMethod = "POST",
cache: boolean = false,
timeout: number = DEFAULT_TIMEOUT
): Promise<ApiResponse<T>> {
const accessToken = await retrieveAccessToken();
const headers = { ...defaultHeaders, "eys-acs-tkn": accessToken };
return coreFetch<T>(
`${endpoint}/${uuid}`,
{ method, cache, timeout },
headers,
payload
);
}
export { fetchData, fetchDataWithToken, updateDataWithToken };

View File

@ -0,0 +1,36 @@
import { formatServiceUrl } from "./utils";
const baseUrlAuth = formatServiceUrl(
process.env.NEXT_PUBLIC_AUTH_SERVICE_URL || "auth_service:8001"
);
const baseUrlRestriction = formatServiceUrl(
process.env.NEXT_PUBLIC_RESTRICTION_SERVICE_URL || "restriction_service:8002"
);
const baseUrlApplication = formatServiceUrl(
process.env.NEXT_PUBLIC_MANAGEMENT_SERVICE_URL || "management_service:8003"
);
const baseUrlAccount = formatServiceUrl(
process.env.NEXT_PUBLIC_ACCOUNT_SERVICE_URL || "account_service:8004"
);
const baseUrlBuilding = formatServiceUrl(
process.env.NEXT_PUBLIC_BUILDING_SERVICE_URL || "building_service:8006"
);
const baseUrlPeople = formatServiceUrl(
process.env.NEXT_PUBLIC_VALIDATION_SERVICE_URL || "validation_service:8009"
);
const urlCheckToken = `${baseUrlAuth}/authentication/token/check`;
const urlPageValid = `${baseUrlRestriction}/restrictions/page/valid`;
const urlSiteUrls = `${baseUrlRestriction}/restrictions/sites/list`;
export {
baseUrlAuth,
baseUrlPeople,
baseUrlApplication,
baseUrlAccount,
baseUrlBuilding,
baseUrlRestriction,
urlCheckToken,
urlPageValid,
urlSiteUrls,
};

View File

@ -0,0 +1,18 @@
'use server';
import { cookies } from "next/headers";
import { cookieObject } from "@/fetchers/base";
import { ResponseCookie } from "next/dist/compiled/@edge-runtime/cookies";
/**
* Server action to delete all access cookies at once
* This is a direct server action that can be called from server components
*/
export async function deleteAllCookies() {
try {
const cookieStore = await cookies();
if (cookieStore.has("eys-zzz")) { cookieStore.delete({ name: "eys-zzz", ...cookieObject } as ResponseCookie); }
if (cookieStore.has("eys-yyy")) { cookieStore.delete({ name: "eys-yyy", ...cookieObject } as ResponseCookie); }
if (cookieStore.has("eys-sel")) { cookieStore.delete({ name: "eys-sel", ...cookieObject } as ResponseCookie); }
return true;
} catch (error) { console.error("Error in deleteAllCookies:", error); return false }
}

View File

@ -0,0 +1,49 @@
import { cookies } from "next/headers";
import { fetchDataWithToken } from "@/fetchers/fecther";
import { urlCheckToken, urlPageValid, urlSiteUrls } from "@/fetchers/index";
import { nextCrypto } from "@/fetchers/base";
import { AuthError } from "@/validations/mutual/context/validations";
import { fetchResponseStatus } from "@/fetchers/utils";
async function checkAccessTokenIsValid() {
try {
const response = await fetchDataWithToken(urlCheckToken, {}, "GET", false);
return fetchResponseStatus(response);
} catch (error) { throw new AuthError("No access token found") }
}
async function retrievePageList() {
const response: any = await fetchDataWithToken(urlSiteUrls, {}, "GET", false);
return fetchResponseStatus(response) ? response.data?.sites : null;
}
async function retrieveApplicationbyUrl(pageUrl: string) {
const response: any = await fetchDataWithToken(urlPageValid, { page_url: pageUrl }, "POST", false);
return fetchResponseStatus(response) ? response.data?.application : null;
}
async function retrieveAccessToken() {
const cookieStore = await cookies();
try {
const encrpytAccessToken = cookieStore.get("eys-zzz")?.value || "";
return await nextCrypto.decrypt(encrpytAccessToken) || "";
}
catch (error) { throw new AuthError("No access token found") }
}
async function retrieveAccessObjects() {
const cookieStore = await cookies();
try {
const encrpytAccessObject = cookieStore.get("eys-yyy")?.value || "";
return await nextCrypto.decrypt(encrpytAccessObject) || "";
}
catch (error) { throw new AuthError("No access objects found") }
}
export {
checkAccessTokenIsValid,
retrieveAccessToken,
retrieveAccessObjects,
retrieveApplicationbyUrl,
retrievePageList,
};

View File

@ -0,0 +1,24 @@
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
interface ApiResponse<T = any> {
status: number;
data: T;
error?: string;
}
interface FetchOptions {
method?: HttpMethod;
cache?: boolean;
timeout?: number;
}
interface CookieObject {
httpOnly: boolean;
path: string;
sameSite: string;
secure: boolean;
maxAge: number;
priority: string;
}
export type { HttpMethod, ApiResponse, FetchOptions, CookieObject };

View File

@ -0,0 +1,10 @@
const formatServiceUrl = (url: string) => {
if (!url) return "";
return url.startsWith("http") ? url : `http://${url}`;
};
function fetchResponseStatus(response: any) {
return 199 < response?.status && response?.status < 300;
}
export { formatServiceUrl, fetchResponseStatus };

View File

@ -0,0 +1,193 @@
// 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<string, any>;
}
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 };

View File

@ -0,0 +1,44 @@
interface FetcherRequest {
url: string;
isNoCache: boolean;
}
interface PostFetcherRequest<T> extends FetcherRequest {
body: Record<string, T>;
}
interface GetFetcherRequest extends FetcherRequest {
url: string;
}
interface DeleteFetcherRequest extends GetFetcherRequest {}
interface PutFetcherRequest<T> extends PostFetcherRequest<T> {}
interface PatchFetcherRequest<T> extends PostFetcherRequest<T> {}
interface FetcherRespose {
success: boolean;
}
interface PaginationResponse {
onPage: number;
onPageCount: number;
totalPage: number;
totalCount: number;
next: boolean;
back: boolean;
}
interface FetcherDataResponse<T> extends FetcherRespose {
data: Record<string, T> | null;
pagination?: PaginationResponse;
}
export type {
FetcherRequest,
PostFetcherRequest,
GetFetcherRequest,
DeleteFetcherRequest,
PutFetcherRequest,
PatchFetcherRequest,
FetcherRespose,
FetcherDataResponse,
};

View File

@ -0,0 +1,38 @@
import { ApiResponse, CookieObject } from "./types";
import NextCrypto from "next-crypto";
const tokenSecretEnv = process.env.TOKENSECRET_90;
const tokenSecret = tokenSecretEnv || "e781d1b0-9418-40b3-9940-385abf81a0b7";
const nextCrypto = new NextCrypto(tokenSecret);
const cookieObject: CookieObject = {
httpOnly: true,
path: "/",
sameSite: "none",
secure: true,
maxAge: 3600,
priority: "high",
};
const DEFAULT_TIMEOUT: number = 10000; // 10 seconds
const defaultHeaders: Record<string, string> = {
accept: "application/json",
language: "tr",
domain: "evyos.com.tr",
tz: "GMT+3",
"Content-type": "application/json",
};
const DEFAULT_RESPONSE: ApiResponse = {
error: "Hata tipi belirtilmedi",
status: 500,
data: {},
};
export {
DEFAULT_TIMEOUT,
DEFAULT_RESPONSE,
defaultHeaders,
tokenSecret,
cookieObject,
nextCrypto,
};

View File

@ -0,0 +1,127 @@
"use server";
import { DEFAULT_RESPONSE, defaultHeaders, DEFAULT_TIMEOUT } from "./base";
import { FetchOptions, HttpMethod, ApiResponse } from "./types";
import { retrieveAccessToken } from "./mutual/cookies/token";
/**
* Creates a promise that rejects after a specified timeout
* @param ms Timeout in milliseconds
* @param controller AbortController to abort the fetch request
* @returns A promise that rejects after the timeout
*/
const createTimeoutPromise = (
ms: number,
controller: AbortController
): Promise<never> => {
return new Promise((_, reject) => {
setTimeout(() => {
controller.abort();
reject(new Error(`Request timed out after ${ms}ms`));
}, ms);
});
};
/**
* Core fetch function with timeout and error handling
* @param url The URL to fetch
* @param options Fetch options
* @param headers Request headers
* @param payload Request payload
* @returns API response
*/
async function coreFetch<T>(
url: string,
options: FetchOptions = {},
headers: Record<string, string> = defaultHeaders,
payload?: any
): Promise<ApiResponse<T>> {
const { method = "POST", cache = false, timeout = DEFAULT_TIMEOUT } = options;
try {
const controller = new AbortController();
const fetchOptions: RequestInit = {
method,
headers,
cache: cache ? "force-cache" : "no-cache",
signal: controller.signal,
};
if (method !== "GET" && payload) {
fetchOptions.body = JSON.stringify(
payload.payload ? payload.payload : payload
);
}
const timeoutPromise = createTimeoutPromise(timeout, controller);
const response = await Promise.race([
fetch(url, fetchOptions),
timeoutPromise,
]);
const responseData = await response.json();
return {
status: response.status,
data: responseData || ({} as T),
};
} catch (error) {
console.error(`API Error (${url}):`, error);
return {
...DEFAULT_RESPONSE,
error: error instanceof Error ? error.message : "Network error",
} as ApiResponse<T>;
}
}
/**
* Fetch data without authentication
*/
async function fetchData<T>(
endpoint: string,
payload?: any,
method: HttpMethod = "POST",
cache: boolean = false,
timeout: number = DEFAULT_TIMEOUT
): Promise<ApiResponse<T>> {
return coreFetch<T>(
endpoint,
{ method, cache, timeout },
defaultHeaders,
payload
);
}
/**
* Fetch data with authentication token
*/
async function fetchDataWithToken<T>(
endpoint: string,
payload?: any,
method: HttpMethod = "POST",
cache: boolean = false,
timeout: number = DEFAULT_TIMEOUT
): Promise<ApiResponse<T>> {
const accessToken = await retrieveAccessToken();
const headers = { ...defaultHeaders, "eys-acs-tkn": accessToken };
return coreFetch<T>(endpoint, { method, cache, timeout }, headers, payload);
}
/**
* Update data with authentication token and UUID
*/
async function updateDataWithToken<T>(
endpoint: string,
uuid: string,
payload?: any,
method: HttpMethod = "POST",
cache: boolean = false,
timeout: number = DEFAULT_TIMEOUT
): Promise<ApiResponse<T>> {
const accessToken = await retrieveAccessToken();
const headers = { ...defaultHeaders, "eys-acs-tkn": accessToken };
return coreFetch<T>(
`${endpoint}/${uuid}`,
{ method, cache, timeout },
headers,
payload
);
}
export { fetchData, fetchDataWithToken, updateDataWithToken };

View File

@ -0,0 +1,36 @@
import { formatServiceUrl } from "./utils";
const baseUrlAuth = formatServiceUrl(
process.env.NEXT_PUBLIC_AUTH_SERVICE_URL || "auth_service:8001"
);
const baseUrlRestriction = formatServiceUrl(
process.env.NEXT_PUBLIC_RESTRICTION_SERVICE_URL || "restriction_service:8002"
);
const baseUrlApplication = formatServiceUrl(
process.env.NEXT_PUBLIC_MANAGEMENT_SERVICE_URL || "management_service:8003"
);
const baseUrlAccount = formatServiceUrl(
process.env.NEXT_PUBLIC_ACCOUNT_SERVICE_URL || "account_service:8004"
);
const baseUrlBuilding = formatServiceUrl(
process.env.NEXT_PUBLIC_BUILDING_SERVICE_URL || "building_service:8006"
);
const baseUrlPeople = formatServiceUrl(
process.env.NEXT_PUBLIC_VALIDATION_SERVICE_URL || "validation_service:8009"
);
const urlCheckToken = `${baseUrlAuth}/authentication/token/check`;
const urlPageValid = `${baseUrlRestriction}/restrictions/page/valid`;
const urlSiteUrls = `${baseUrlRestriction}/restrictions/sites/list`;
export {
baseUrlAuth,
baseUrlPeople,
baseUrlApplication,
baseUrlAccount,
baseUrlBuilding,
baseUrlRestriction,
urlCheckToken,
urlPageValid,
urlSiteUrls,
};

View File

@ -0,0 +1,18 @@
'use server';
import { cookies } from "next/headers";
import { cookieObject } from "@/fetchers/base";
import { ResponseCookie } from "next/dist/compiled/@edge-runtime/cookies";
/**
* Server action to delete all access cookies at once
* This is a direct server action that can be called from server components
*/
export async function deleteAllCookies() {
try {
const cookieStore = await cookies();
if (cookieStore.has("eys-zzz")) { cookieStore.delete({ name: "eys-zzz", ...cookieObject } as ResponseCookie); }
if (cookieStore.has("eys-yyy")) { cookieStore.delete({ name: "eys-yyy", ...cookieObject } as ResponseCookie); }
if (cookieStore.has("eys-sel")) { cookieStore.delete({ name: "eys-sel", ...cookieObject } as ResponseCookie); }
return true;
} catch (error) { console.error("Error in deleteAllCookies:", error); return false }
}

View File

@ -0,0 +1,49 @@
import { cookies } from "next/headers";
import { fetchDataWithToken } from "@/fetchers/fecther";
import { urlCheckToken, urlPageValid, urlSiteUrls } from "@/fetchers/index";
import { nextCrypto } from "@/fetchers/base";
import { AuthError } from "@/validations/mutual/context/validations";
import { fetchResponseStatus } from "@/fetchers/utils";
async function checkAccessTokenIsValid() {
try {
const response = await fetchDataWithToken(urlCheckToken, {}, "GET", false);
return fetchResponseStatus(response);
} catch (error) { throw new AuthError("No access token found") }
}
async function retrievePageList() {
const response: any = await fetchDataWithToken(urlSiteUrls, {}, "GET", false);
return fetchResponseStatus(response) ? response.data?.sites : null;
}
async function retrieveApplicationbyUrl(pageUrl: string) {
const response: any = await fetchDataWithToken(urlPageValid, { page_url: pageUrl }, "POST", false);
return fetchResponseStatus(response) ? response.data?.application : null;
}
async function retrieveAccessToken() {
const cookieStore = await cookies();
try {
const encrpytAccessToken = cookieStore.get("eys-zzz")?.value || "";
return await nextCrypto.decrypt(encrpytAccessToken) || "";
}
catch (error) { throw new AuthError("No access token found") }
}
async function retrieveAccessObjects() {
const cookieStore = await cookies();
try {
const encrpytAccessObject = cookieStore.get("eys-yyy")?.value || "";
return await nextCrypto.decrypt(encrpytAccessObject) || "";
}
catch (error) { throw new AuthError("No access objects found") }
}
export {
checkAccessTokenIsValid,
retrieveAccessToken,
retrieveAccessObjects,
retrieveApplicationbyUrl,
retrievePageList,
};

View File

@ -0,0 +1,24 @@
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
interface ApiResponse<T = any> {
status: number;
data: T;
error?: string;
}
interface FetchOptions {
method?: HttpMethod;
cache?: boolean;
timeout?: number;
}
interface CookieObject {
httpOnly: boolean;
path: string;
sameSite: string;
secure: boolean;
maxAge: number;
priority: string;
}
export type { HttpMethod, ApiResponse, FetchOptions, CookieObject };

View File

@ -0,0 +1,10 @@
const formatServiceUrl = (url: string) => {
if (!url) return "";
return url.startsWith("http") ? url : `http://${url}`;
};
function fetchResponseStatus(response: any) {
return 199 < response?.status && response?.status < 300;
}
export { formatServiceUrl, fetchResponseStatus };

View File

@ -2,11 +2,11 @@
## Contexts ## Contexts
- [Component Contexts](./docs/contexts/component_contexts.md) - [Component Contexts](./contexts/component_contexts.md)
- [Component Menu](./docs/contexts/component_menu.md) - [Component Menu](./contexts/component_menu.md)
- [Component User](./docs/contexts/component_user.md) - [Component User](./contexts/component_user.md)
- [Component Online](./docs/contexts/component_online.md) - [Component Online](./contexts/component_online.md)
- [Component Selection](./docs/contexts/component_selection.md) - [Component Selection](./contexts/component_selection.md)
## Language Selection ## Language Selection