updated Identity and managment service
This commit is contained in:
177
WebServices/management-frontend/src/apicalls/api-fetcher.ts
Normal file
177
WebServices/management-frontend/src/apicalls/api-fetcher.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
"use server";
|
||||
import { retrieveAccessToken } from "@/apicalls/cookies/token";
|
||||
import {
|
||||
DEFAULT_RESPONSE,
|
||||
defaultHeaders,
|
||||
FetchOptions,
|
||||
HttpMethod,
|
||||
ApiResponse,
|
||||
DEFAULT_TIMEOUT,
|
||||
} from "./basics";
|
||||
|
||||
/**
|
||||
* 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);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Prepares a standardized API response
|
||||
* @param response The response data
|
||||
* @param statusCode HTTP status code
|
||||
* @returns Standardized API response
|
||||
*/
|
||||
const prepareResponse = <T>(
|
||||
response: T,
|
||||
statusCode: number
|
||||
): ApiResponse<T> => {
|
||||
try {
|
||||
return {
|
||||
status: statusCode,
|
||||
data: response || ({} as T),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error preparing response:", error);
|
||||
return {
|
||||
...DEFAULT_RESPONSE,
|
||||
error: "Response parsing error",
|
||||
} as ApiResponse<T>;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 signal = controller.signal;
|
||||
|
||||
const fetchOptions: RequestInit = {
|
||||
method,
|
||||
headers,
|
||||
cache: cache ? "force-cache" : "no-cache",
|
||||
signal,
|
||||
};
|
||||
|
||||
// Add body if needed
|
||||
if (method !== "GET" && payload) {
|
||||
fetchOptions.body = JSON.stringify(
|
||||
// Handle special case for updateDataWithToken
|
||||
payload.payload ? payload.payload : payload
|
||||
);
|
||||
}
|
||||
|
||||
// Create timeout promise
|
||||
const timeoutPromise = createTimeoutPromise(timeout, controller);
|
||||
|
||||
// Race between fetch and timeout
|
||||
const response = (await Promise.race([
|
||||
fetch(url, fetchOptions),
|
||||
timeoutPromise,
|
||||
])) as Response;
|
||||
|
||||
const responseJson = await response.json();
|
||||
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
console.log("Fetching:", url, fetchOptions);
|
||||
console.log("Response:", responseJson);
|
||||
}
|
||||
|
||||
return prepareResponse(responseJson, response.status);
|
||||
} catch (error) {
|
||||
console.error(`Fetch 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 };
|
||||
@@ -1,144 +0,0 @@
|
||||
"use server";
|
||||
import { retrieveAccessToken } from "@/apicalls/cookies/token";
|
||||
|
||||
const defaultHeaders = {
|
||||
accept: "application/json",
|
||||
language: "tr",
|
||||
domain: "management.com.tr",
|
||||
tz: "GMT+3",
|
||||
"Content-type": "application/json",
|
||||
};
|
||||
|
||||
const DefaultResponse = {
|
||||
error: "Hata tipi belirtilmedi",
|
||||
status: "500",
|
||||
data: {},
|
||||
};
|
||||
|
||||
const cacheList = ["no-cache", "no-store", "force-cache", "only-if-cached"];
|
||||
|
||||
const prepareResponse = (response: any, statusCode: number) => {
|
||||
try {
|
||||
return {
|
||||
status: statusCode,
|
||||
data: response || {},
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error preparing response:", error);
|
||||
return {
|
||||
...DefaultResponse,
|
||||
error: "Response parsing error",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const fetchData = async (
|
||||
endpoint: string,
|
||||
payload: any,
|
||||
method: string = "POST",
|
||||
cache: boolean = false
|
||||
) => {
|
||||
try {
|
||||
const headers = {
|
||||
...defaultHeaders,
|
||||
};
|
||||
const fetchOptions: RequestInit = {
|
||||
method,
|
||||
headers,
|
||||
cache: cache ? "force-cache" : "no-cache",
|
||||
};
|
||||
|
||||
if (method === "POST" && payload) {
|
||||
fetchOptions.body = JSON.stringify(payload);
|
||||
}
|
||||
|
||||
const response = await fetch(endpoint, fetchOptions);
|
||||
const responseJson = await response.json();
|
||||
console.log("Fetching:", endpoint, fetchOptions);
|
||||
console.log("Response:", responseJson);
|
||||
return prepareResponse(responseJson, response.status);
|
||||
} catch (error) {
|
||||
console.error("Fetch error:", error);
|
||||
return {
|
||||
...DefaultResponse,
|
||||
error: error instanceof Error ? error.message : "Network error",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const updateDataWithToken = async (
|
||||
endpoint: string,
|
||||
uuid: string,
|
||||
payload: any,
|
||||
method: string = "POST",
|
||||
cache: boolean = false
|
||||
) => {
|
||||
const accessToken = (await retrieveAccessToken()) || "";
|
||||
const headers = {
|
||||
...defaultHeaders,
|
||||
"eys-acs-tkn": accessToken,
|
||||
};
|
||||
|
||||
try {
|
||||
const fetchOptions: RequestInit = {
|
||||
method,
|
||||
headers,
|
||||
cache: cache ? "force-cache" : "no-cache",
|
||||
};
|
||||
|
||||
if (method !== "GET" && payload) {
|
||||
fetchOptions.body = JSON.stringify(payload.payload);
|
||||
}
|
||||
|
||||
const response = await fetch(`${endpoint}/${uuid}`, fetchOptions);
|
||||
const responseJson = await response.json();
|
||||
console.log("Fetching:", `${endpoint}/${uuid}`, fetchOptions);
|
||||
console.log("Response:", responseJson);
|
||||
return prepareResponse(responseJson, response.status);
|
||||
} catch (error) {
|
||||
console.error("Update error:", error);
|
||||
return {
|
||||
...DefaultResponse,
|
||||
error: error instanceof Error ? error.message : "Network error",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const fetchDataWithToken = async (
|
||||
endpoint: string,
|
||||
payload: any,
|
||||
method: string = "POST",
|
||||
cache: boolean = false
|
||||
) => {
|
||||
const accessToken = (await retrieveAccessToken()) || "";
|
||||
const headers = {
|
||||
...defaultHeaders,
|
||||
"eys-acs-tkn": accessToken,
|
||||
};
|
||||
|
||||
try {
|
||||
const fetchOptions: RequestInit = {
|
||||
method,
|
||||
headers,
|
||||
cache: cache ? "force-cache" : "no-cache",
|
||||
};
|
||||
|
||||
if (method === "POST" && payload) {
|
||||
fetchOptions.body = JSON.stringify(payload);
|
||||
}
|
||||
|
||||
const response = await fetch(endpoint, fetchOptions);
|
||||
const responseJson = await response.json();
|
||||
console.log("Fetching:", endpoint, fetchOptions);
|
||||
console.log("Response:", responseJson);
|
||||
return prepareResponse(responseJson, response.status);
|
||||
} catch (error) {
|
||||
console.error("Fetch with token error:", error);
|
||||
return {
|
||||
...DefaultResponse,
|
||||
error: error instanceof Error ? error.message : "Network error",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export { fetchData, fetchDataWithToken, updateDataWithToken };
|
||||
@@ -3,22 +3,34 @@ const formatServiceUrl = (url: string) => {
|
||||
return url.startsWith("http") ? url : `http://${url}`;
|
||||
};
|
||||
|
||||
export const baseUrlAuth = formatServiceUrl(
|
||||
const baseUrlAuth = formatServiceUrl(
|
||||
process.env.NEXT_PUBLIC_AUTH_SERVICE_URL || "auth_service:8001"
|
||||
);
|
||||
export const baseUrlPeople = formatServiceUrl(
|
||||
const baseUrlPeople = formatServiceUrl(
|
||||
process.env.NEXT_PUBLIC_VALIDATION_SERVICE_URL || "identity_service:8002"
|
||||
);
|
||||
export const baseUrlApplication = formatServiceUrl(
|
||||
const baseUrlApplication = formatServiceUrl(
|
||||
process.env.NEXT_PUBLIC_MANAGEMENT_SERVICE_URL || "management_service:8004"
|
||||
);
|
||||
|
||||
// export const baseUrlEvent = formatServiceUrl(
|
||||
// process.env.NEXT_PUBLIC_EVENT_SERVICE_URL || "eventservice:8888"
|
||||
// );
|
||||
export const tokenSecret = process.env.TOKENSECRET_90 || "";
|
||||
// Types for better type safety
|
||||
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
||||
|
||||
export const cookieObject: any = {
|
||||
interface ApiResponse<T = any> {
|
||||
status: number;
|
||||
data: T;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
interface FetchOptions {
|
||||
method?: HttpMethod;
|
||||
cache?: boolean;
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
const tokenSecret = process.env.TOKENSECRET_90 || "";
|
||||
|
||||
const cookieObject: any = {
|
||||
httpOnly: true,
|
||||
path: "/",
|
||||
sameSite: "none",
|
||||
@@ -75,5 +87,36 @@ class FilterList {
|
||||
|
||||
const defaultFilterList = new FilterList({});
|
||||
|
||||
export { FilterList, defaultFilterList };
|
||||
export type { FilterListInterface };
|
||||
// Constants
|
||||
const DEFAULT_TIMEOUT = 10000; // 10 seconds
|
||||
const defaultHeaders = {
|
||||
accept: "application/json",
|
||||
language: "tr",
|
||||
domain: "management.com.tr",
|
||||
tz: "GMT+3",
|
||||
"Content-type": "application/json",
|
||||
};
|
||||
const DEFAULT_RESPONSE: ApiResponse = {
|
||||
error: "Hata tipi belirtilmedi",
|
||||
status: 500,
|
||||
data: {},
|
||||
};
|
||||
|
||||
export type {
|
||||
FilterList,
|
||||
FilterListInterface,
|
||||
HttpMethod,
|
||||
ApiResponse,
|
||||
FetchOptions,
|
||||
};
|
||||
export {
|
||||
DEFAULT_TIMEOUT,
|
||||
DEFAULT_RESPONSE,
|
||||
defaultHeaders,
|
||||
defaultFilterList,
|
||||
baseUrlAuth,
|
||||
baseUrlPeople,
|
||||
baseUrlApplication,
|
||||
tokenSecret,
|
||||
cookieObject,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { NextRequest } from "next/server";
|
||||
// Import the createApplication function when it's available
|
||||
// import { createApplication } from "@/apicalls/application/application";
|
||||
import { createCreateHandler } from "../../utils";
|
||||
|
||||
// Create a handler for creating applications using our utility function
|
||||
// When createApplication is available, pass it as the first argument
|
||||
// No need for field validation as it's already handled by Zod at the page level
|
||||
export const POST = createCreateHandler();
|
||||
@@ -0,0 +1,6 @@
|
||||
import { NextRequest } from "next/server";
|
||||
import { listApplications } from "@/apicalls/application/application";
|
||||
import { createListHandler } from "../../utils";
|
||||
|
||||
// Create a handler for listing applications using our utility function
|
||||
export const POST = createListHandler(listApplications);
|
||||
@@ -1,81 +0,0 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { listApplications } from "@/apicalls/application/application";
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const requestBody = await request.json();
|
||||
|
||||
// Check if this is a list request or a create request
|
||||
// If action is 'list' or if pagination parameters are present, treat as a list request
|
||||
if (requestBody.action === 'list' || requestBody.page !== undefined) {
|
||||
// Extract pagination parameters with defaults
|
||||
const page = requestBody.page || 1;
|
||||
const size = requestBody.size || 10;
|
||||
|
||||
// Extract sorting parameters with defaults
|
||||
const orderField = requestBody.orderField || ["name"];
|
||||
const orderType = requestBody.orderType || ["asc"];
|
||||
|
||||
// Extract query filters
|
||||
const query = requestBody.query || {};
|
||||
|
||||
// Call the actual API function for listing
|
||||
const response = await listApplications({
|
||||
page,
|
||||
size,
|
||||
orderField,
|
||||
orderType,
|
||||
query,
|
||||
});
|
||||
|
||||
// Return the list response
|
||||
return NextResponse.json({
|
||||
data: response.data || [],
|
||||
pagination: response.pagination || {
|
||||
page,
|
||||
size,
|
||||
totalCount: 0,
|
||||
totalItems: 0,
|
||||
totalPages: 0,
|
||||
pageCount: 0,
|
||||
orderField,
|
||||
orderType,
|
||||
query,
|
||||
next: false,
|
||||
back: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
// If action is 'create' or no action is specified (assuming it's a create request)
|
||||
else if (requestBody.action === 'create' || !requestBody.action) {
|
||||
// Here you would call your actual API function to create a new application
|
||||
// For example: const result = await createApplication(requestBody.data);
|
||||
|
||||
// For now, we'll return a mock response
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: true,
|
||||
data: {
|
||||
id: Math.floor(Math.random() * 1000),
|
||||
...requestBody.data,
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
},
|
||||
{ status: 201 }
|
||||
);
|
||||
}
|
||||
// If the action is not recognized
|
||||
else {
|
||||
return NextResponse.json(
|
||||
{ error: "Invalid action specified" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("API error:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Internal Server Error" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { NextRequest } from "next/server";
|
||||
// Import the updateApplication function when it's available
|
||||
// import { updateApplication } from "@/apicalls/application/application";
|
||||
import { createUpdateHandler } from "../../utils";
|
||||
|
||||
// Create a handler for updating applications using our utility function
|
||||
// When updateApplication is available, pass it as the first argument
|
||||
// No need for field validation as it's already handled by Zod at the page level
|
||||
// We only need to ensure 'id' is present for updates
|
||||
export const POST = createUpdateHandler();
|
||||
@@ -0,0 +1,192 @@
|
||||
import { NextRequest } from "next/server";
|
||||
import {
|
||||
successResponse,
|
||||
errorResponse,
|
||||
paginationResponse,
|
||||
createResponse,
|
||||
updateResponse,
|
||||
deleteResponse
|
||||
} from "./responseHandlers";
|
||||
import { withErrorHandling, validateRequiredFields } from "./requestHandlers";
|
||||
import {
|
||||
ApiHandler,
|
||||
PaginationParams,
|
||||
ListFunction,
|
||||
CreateFunction,
|
||||
UpdateFunction,
|
||||
DeleteFunction
|
||||
} from "./types";
|
||||
|
||||
/**
|
||||
* Generic list operation handler
|
||||
* @param request NextRequest object
|
||||
* @param body Request body
|
||||
* @param listFunction The function to call to get the list data
|
||||
*/
|
||||
export async function handleListOperation<T>(
|
||||
request: NextRequest,
|
||||
body: any,
|
||||
listFunction: ListFunction
|
||||
) {
|
||||
// Extract pagination parameters with defaults
|
||||
const page = body.page || 1;
|
||||
const size = body.size || 10;
|
||||
|
||||
// Extract sorting parameters with defaults
|
||||
const orderField = body.orderField || ["name"];
|
||||
const orderType = body.orderType || ["asc"];
|
||||
|
||||
// Extract query filters
|
||||
const query = body.query || {};
|
||||
|
||||
// Call the actual API function for listing
|
||||
const response = await listFunction({
|
||||
page,
|
||||
size,
|
||||
orderField,
|
||||
orderType,
|
||||
query,
|
||||
} as PaginationParams);
|
||||
|
||||
// Return the list response
|
||||
return paginationResponse(response.data, response.pagination);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic create operation handler
|
||||
* @param request NextRequest object
|
||||
* @param body Request body
|
||||
* @param createFunction The function to call to create the item
|
||||
* @param requiredFields Array of required field names
|
||||
*/
|
||||
export async function handleCreateOperation<T>(
|
||||
request: NextRequest,
|
||||
body: any,
|
||||
createFunction?: CreateFunction,
|
||||
requiredFields: string[] = []
|
||||
) {
|
||||
// Validate required fields if specified
|
||||
if (requiredFields.length > 0) {
|
||||
const validation = validateRequiredFields(body, requiredFields);
|
||||
if (!validation.valid) {
|
||||
return errorResponse(validation.error as string, 400);
|
||||
}
|
||||
}
|
||||
|
||||
// If a create function is provided, call it
|
||||
if (createFunction) {
|
||||
const result = await createFunction(body);
|
||||
return createResponse(result);
|
||||
}
|
||||
|
||||
// Otherwise return a mock response
|
||||
return createResponse({
|
||||
id: Math.floor(Math.random() * 1000),
|
||||
...body,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic update operation handler
|
||||
* @param request NextRequest object
|
||||
* @param body Request body
|
||||
* @param updateFunction The function to call to update the item
|
||||
* @param requiredFields Array of required field names
|
||||
*/
|
||||
export async function handleUpdateOperation<T>(
|
||||
request: NextRequest,
|
||||
body: any,
|
||||
updateFunction?: UpdateFunction,
|
||||
requiredFields: string[] = ['id']
|
||||
) {
|
||||
// Validate required fields
|
||||
const validation = validateRequiredFields(body, requiredFields);
|
||||
if (!validation.valid) {
|
||||
return errorResponse(validation.error as string, 400);
|
||||
}
|
||||
|
||||
// If an update function is provided, call it
|
||||
if (updateFunction) {
|
||||
const result = await updateFunction(body.id, body);
|
||||
return updateResponse(result);
|
||||
}
|
||||
|
||||
// Otherwise return a mock response
|
||||
return updateResponse(body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic delete operation handler
|
||||
* @param request NextRequest object
|
||||
* @param body Request body
|
||||
* @param deleteFunction The function to call to delete the item
|
||||
*/
|
||||
export async function handleDeleteOperation(
|
||||
request: NextRequest,
|
||||
body: any,
|
||||
deleteFunction?: DeleteFunction
|
||||
) {
|
||||
// Validate that ID is provided
|
||||
const validation = validateRequiredFields(body, ['id']);
|
||||
if (!validation.valid) {
|
||||
return errorResponse(validation.error as string, 400);
|
||||
}
|
||||
|
||||
// If a delete function is provided, call it
|
||||
if (deleteFunction) {
|
||||
await deleteFunction(body.id);
|
||||
}
|
||||
|
||||
// Return a success response
|
||||
return deleteResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a wrapped list handler with error handling
|
||||
* @param listFunction The function to call to get the list data
|
||||
*/
|
||||
export function createListHandler(listFunction: ListFunction) {
|
||||
return withErrorHandling((request, body) =>
|
||||
handleListOperation(request, body, listFunction)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a wrapped create handler with error handling
|
||||
* @param createFunction The function to call to create the item
|
||||
* @param requiredFields Array of required field names
|
||||
*/
|
||||
export function createCreateHandler(
|
||||
createFunction?: CreateFunction,
|
||||
requiredFields: string[] = []
|
||||
) {
|
||||
return withErrorHandling((request, body) =>
|
||||
handleCreateOperation(request, body, createFunction, requiredFields)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a wrapped update handler with error handling
|
||||
* @param updateFunction The function to call to update the item
|
||||
* @param requiredFields Array of required field names
|
||||
*/
|
||||
export function createUpdateHandler(
|
||||
updateFunction?: UpdateFunction,
|
||||
requiredFields: string[] = ['id']
|
||||
) {
|
||||
return withErrorHandling((request, body) =>
|
||||
handleUpdateOperation(request, body, updateFunction, requiredFields)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a wrapped delete handler with error handling
|
||||
* @param deleteFunction The function to call to delete the item
|
||||
*/
|
||||
export function createDeleteHandler(
|
||||
deleteFunction?: DeleteFunction
|
||||
) {
|
||||
return withErrorHandling((request, body) =>
|
||||
handleDeleteOperation(request, body, deleteFunction)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// Export all utility functions from a single entry point
|
||||
export * from './responseHandlers';
|
||||
export * from './requestHandlers';
|
||||
export * from './apiOperations';
|
||||
@@ -0,0 +1,62 @@
|
||||
import { NextRequest } from "next/server";
|
||||
import { errorResponse } from "./responseHandlers";
|
||||
import { ValidationResult, ApiHandler } from "./types";
|
||||
|
||||
/**
|
||||
* Safely parse JSON request body with error handling
|
||||
* @param request NextRequest object
|
||||
* @returns Parsed request body or null if parsing fails
|
||||
*/
|
||||
export async function parseRequestBody(request: NextRequest) {
|
||||
try {
|
||||
return await request.json();
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for API route handlers with built-in error handling
|
||||
* @param handler The handler function to wrap
|
||||
*/
|
||||
export function withErrorHandling(
|
||||
handler: ApiHandler
|
||||
) {
|
||||
return async (request: NextRequest) => {
|
||||
try {
|
||||
const body = await parseRequestBody(request);
|
||||
|
||||
if (body === null) {
|
||||
return errorResponse("Invalid request body", 400);
|
||||
}
|
||||
|
||||
return await handler(request, body);
|
||||
} catch (error: any) {
|
||||
return errorResponse(
|
||||
error.message || "Internal Server Error",
|
||||
error.status || 500
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that required fields are present in the request body
|
||||
* @param body Request body
|
||||
* @param requiredFields Array of required field names
|
||||
* @returns Object with validation result and error message if validation fails
|
||||
*/
|
||||
export function validateRequiredFields(body: any, requiredFields: string[]): ValidationResult {
|
||||
const missingFields = requiredFields.filter(field =>
|
||||
body[field] === undefined || body[field] === null || body[field] === ''
|
||||
);
|
||||
|
||||
if (missingFields.length > 0) {
|
||||
return {
|
||||
valid: false,
|
||||
error: `Missing required fields: ${missingFields.join(', ')}`
|
||||
};
|
||||
}
|
||||
|
||||
return { valid: true };
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { ApiResponse, PaginationResponse, PaginatedApiResponse } from "./types";
|
||||
|
||||
/**
|
||||
* Standard success response handler
|
||||
* @param data The data to return in the response
|
||||
* @param status HTTP status code (default: 200)
|
||||
*/
|
||||
export function successResponse<T>(data: T, status: number = 200) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: true,
|
||||
data,
|
||||
} as ApiResponse<T>,
|
||||
{ status }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard error response handler
|
||||
* @param message Error message
|
||||
* @param status HTTP status code (default: 500)
|
||||
*/
|
||||
export function errorResponse(message: string, status: number = 500) {
|
||||
console.error(`API error: ${message}`);
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: message
|
||||
} as ApiResponse<never>,
|
||||
{ status }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard pagination response format
|
||||
* @param data Array of items to return
|
||||
* @param pagination Pagination information
|
||||
*/
|
||||
export function paginationResponse<T>(data: T[], pagination: PaginationResponse | null) {
|
||||
return NextResponse.json({
|
||||
data: data || [],
|
||||
pagination: pagination || {
|
||||
page: 1,
|
||||
size: 10,
|
||||
totalCount: 0,
|
||||
totalItems: 0,
|
||||
totalPages: 0,
|
||||
pageCount: 0,
|
||||
orderField: ["name"],
|
||||
orderType: ["asc"],
|
||||
query: {},
|
||||
next: false,
|
||||
back: false,
|
||||
},
|
||||
} as PaginatedApiResponse<T>);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create response handler
|
||||
* @param data The created item data
|
||||
*/
|
||||
export function createResponse<T>(data: T) {
|
||||
return successResponse(
|
||||
{
|
||||
...data as any,
|
||||
createdAt: new Date().toISOString(),
|
||||
} as T,
|
||||
201
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update response handler
|
||||
* @param data The updated item data
|
||||
*/
|
||||
export function updateResponse<T>(data: T) {
|
||||
return successResponse(
|
||||
{
|
||||
...data as any,
|
||||
updatedAt: new Date().toISOString(),
|
||||
} as T
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete response handler
|
||||
*/
|
||||
export function deleteResponse() {
|
||||
return successResponse({ message: "Item deleted successfully" }, 204);
|
||||
}
|
||||
83
WebServices/management-frontend/src/app/api/utils/types.ts
Normal file
83
WebServices/management-frontend/src/app/api/utils/types.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Type definitions for API utilities
|
||||
*/
|
||||
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
/**
|
||||
* Validation result interface
|
||||
*/
|
||||
export interface ValidationResult {
|
||||
valid: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pagination parameters interface
|
||||
*/
|
||||
export interface PaginationParams {
|
||||
page: number;
|
||||
size: number;
|
||||
orderField: string[];
|
||||
orderType: string[];
|
||||
query: Record<string, any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pagination response interface
|
||||
*/
|
||||
export interface PaginationResponse {
|
||||
page: number;
|
||||
size: number;
|
||||
totalCount: number;
|
||||
totalItems: number;
|
||||
totalPages: number;
|
||||
pageCount: number;
|
||||
orderField: string[];
|
||||
orderType: string[];
|
||||
query: Record<string, any>;
|
||||
next: boolean;
|
||||
back: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* API response interface
|
||||
*/
|
||||
export interface ApiResponse<T> {
|
||||
success: boolean;
|
||||
data?: T;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Paginated API response interface
|
||||
*/
|
||||
export interface PaginatedApiResponse<T> {
|
||||
data: T[];
|
||||
pagination: PaginationResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* API handler function type
|
||||
*/
|
||||
export type ApiHandler = (request: NextRequest, body: any) => Promise<Response>;
|
||||
|
||||
/**
|
||||
* List function type
|
||||
*/
|
||||
export type ListFunction = (params: PaginationParams) => Promise<PaginatedApiResponse<any>>;
|
||||
|
||||
/**
|
||||
* Create function type
|
||||
*/
|
||||
export type CreateFunction = (data: any) => Promise<any>;
|
||||
|
||||
/**
|
||||
* Update function type
|
||||
*/
|
||||
export type UpdateFunction = (id: any, data: any) => Promise<any>;
|
||||
|
||||
/**
|
||||
* Delete function type
|
||||
*/
|
||||
export type DeleteFunction = (id: any) => Promise<any>;
|
||||
@@ -9,7 +9,7 @@ import { Textarea } from "@/components/ui/textarea";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useForm, SubmitHandler } from "react-hook-form";
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
import { AlertCircle } from "lucide-react";
|
||||
|
||||
@@ -21,6 +21,7 @@ export function CreateComponent<T>({
|
||||
lang,
|
||||
translations,
|
||||
formProps = {},
|
||||
apiUrl,
|
||||
}: CreateComponentProps<T>) {
|
||||
const t = translations[lang as keyof typeof translations] || {};
|
||||
|
||||
@@ -63,7 +64,7 @@ export function CreateComponent<T>({
|
||||
formState: { errors },
|
||||
setValue,
|
||||
watch,
|
||||
} = useForm({
|
||||
} = useForm<Record<string, any>>({
|
||||
defaultValues,
|
||||
resolver: validationSchema ? zodResolver(validationSchema) : undefined,
|
||||
});
|
||||
@@ -71,14 +72,30 @@ export function CreateComponent<T>({
|
||||
const formValues = watch();
|
||||
|
||||
// Handle form submission
|
||||
const onSubmit = async (data: Record<string, any>) => {
|
||||
const onSubmit: SubmitHandler<Record<string, any>> = async (data) => {
|
||||
try {
|
||||
console.log("Form data to save:", data);
|
||||
|
||||
// Here you would make an API call to save the data
|
||||
// For example: await createApplication(data);
|
||||
// Make an API call to save the data if apiUrl is provided
|
||||
if (apiUrl) {
|
||||
// Use the create endpoint by appending '/create' to the apiUrl
|
||||
const createUrl = `${apiUrl}/create`;
|
||||
const response = await fetch(createUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`API error: ${response.status}`);
|
||||
}
|
||||
|
||||
// Optional: get the created item from response
|
||||
// const createdItem = await response.json();
|
||||
}
|
||||
|
||||
// Mock API call success
|
||||
if (refetch) refetch();
|
||||
setMode("list");
|
||||
setSelectedItem(null);
|
||||
@@ -219,7 +236,7 @@ export function CreateComponent<T>({
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(() => console.log("Form data to save:"))}>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<Card className="w-full mb-6">
|
||||
<CardHeader>
|
||||
<CardTitle>{t.create || "Create"}</CardTitle>
|
||||
|
||||
@@ -15,6 +15,7 @@ export function FormDisplay<T>({
|
||||
lang,
|
||||
translations,
|
||||
formProps = {},
|
||||
apiUrl,
|
||||
}: FormDisplayProps<T>) {
|
||||
const [enhancedFormProps, setEnhancedFormProps] = useState(formProps);
|
||||
|
||||
@@ -84,6 +85,7 @@ export function FormDisplay<T>({
|
||||
lang={lang}
|
||||
translations={translations}
|
||||
formProps={enhancedFormProps}
|
||||
apiUrl={apiUrl}
|
||||
/>
|
||||
);
|
||||
case "update":
|
||||
|
||||
@@ -24,6 +24,7 @@ export interface BaseFormProps<T> {
|
||||
lang: string;
|
||||
translations: Record<string, Record<string, string>>;
|
||||
formProps?: Record<string, any>;
|
||||
apiUrl?: string;
|
||||
}
|
||||
|
||||
export interface CreateComponentProps<T> extends BaseFormProps<T> {}
|
||||
@@ -46,4 +47,5 @@ export interface FormDisplayProps<T> {
|
||||
lang: string;
|
||||
translations: Record<string, Record<string, string>>;
|
||||
formProps?: Record<string, any>;
|
||||
apiUrl?: string;
|
||||
}
|
||||
|
||||
@@ -16,9 +16,8 @@ export function useApiData<T>(
|
||||
params: RequestParams
|
||||
): Promise<ApiResponse<T>> => {
|
||||
try {
|
||||
// Prepare the request body with action and all params
|
||||
// Prepare the request body with pagination parameters
|
||||
const requestBody = {
|
||||
action: "list",
|
||||
page: params.page,
|
||||
size: params.size,
|
||||
orderField: params.orderField,
|
||||
@@ -26,8 +25,11 @@ export function useApiData<T>(
|
||||
query: params.query,
|
||||
};
|
||||
|
||||
// Construct the list endpoint URL
|
||||
const listEndpoint = `${endpoint}/list`;
|
||||
|
||||
// Make the API request using POST
|
||||
const response = await fetch(endpoint, {
|
||||
const response = await fetch(listEndpoint, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
"use client";
|
||||
import React, { useState } from "react";
|
||||
import * as schema from "./schema";
|
||||
import { translations } from "./language";
|
||||
import type { FormMode } from "@/components/common/FormDisplay/types";
|
||||
|
||||
import { PageProps } from "@/validations/translations/translation";
|
||||
import { useApiData } from "@/components/common/hooks/useApiData";
|
||||
import { translations } from "./language";
|
||||
import * as schema from "./schema";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Building, Filter, User } from "lucide-react";
|
||||
import { TextQueryModifier, SelectQueryModifier, TypeQueryModifier } from "@/components/common/QueryModifiers";
|
||||
import { CreateButton } from "@/components/common/ActionButtonsDisplay/CreateButton";
|
||||
@@ -14,12 +15,11 @@ import { CardDisplay } from "@/components/common/CardDisplay";
|
||||
import { FormDisplay } from "@/components/common/FormDisplay/FormDisplay";
|
||||
import { GridSelectionComponent, GridSize } from "@/components/common/HeaderSelections/GridSelectionComponent";
|
||||
import { LanguageSelectionComponent, Language } from "@/components/common/HeaderSelections/LanguageSelectionComponent";
|
||||
import type { FormMode } from "@/components/common/FormDisplay/types";
|
||||
|
||||
const ApplicationPage: React.FC<PageProps> = ({ lang: initialLang = "en" }) => {
|
||||
// Add local state for language to ensure it persists when changed
|
||||
const [lang, setLang] = useState<Language>(initialLang as Language);
|
||||
|
||||
|
||||
// Use the API data hook directly
|
||||
const {
|
||||
data,
|
||||
@@ -136,16 +136,16 @@ const ApplicationPage: React.FC<PageProps> = ({ lang: initialLang = "en" }) => {
|
||||
<h1 className="text-2xl font-bold">{translations[lang].applicationTitle || "Applications"}</h1>
|
||||
<div className="flex space-x-4">
|
||||
{/* Grid Selection */}
|
||||
<GridSelectionComponent
|
||||
gridCols={gridCols}
|
||||
setGridCols={setGridCols}
|
||||
<GridSelectionComponent
|
||||
gridCols={gridCols}
|
||||
setGridCols={setGridCols}
|
||||
/>
|
||||
|
||||
|
||||
{/* Language Selection */}
|
||||
<LanguageSelectionComponent
|
||||
lang={lang}
|
||||
<LanguageSelectionComponent
|
||||
lang={lang}
|
||||
translations={translations}
|
||||
setLang={setLang}
|
||||
setLang={setLang}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -280,6 +280,7 @@ const ApplicationPage: React.FC<PageProps> = ({ lang: initialLang = "en" }) => {
|
||||
onCancel={handleCancel}
|
||||
lang={lang}
|
||||
translations={translations}
|
||||
apiUrl='/api/applications'
|
||||
formProps={{
|
||||
fieldDefinitions: mode === 'create' ? schema.createFieldDefinitions :
|
||||
mode === 'update' ? schema.updateFieldDefinitions :
|
||||
|
||||
Reference in New Issue
Block a user