updated last web service

This commit is contained in:
2025-06-02 21:11:15 +03:00
parent df3f59bd8e
commit 0cd0eb0f22
106 changed files with 1061 additions and 50 deletions

View File

View File

@@ -0,0 +1,26 @@
import { NextResponse } from "next/server";
import { fetchTest } from "@/fetchers/custom/test/fetch";
export async function GET() {
try {
return NextResponse.json({ status: 200, data: { message: "Test" } });
} catch (error) {
return NextResponse.json({ status: 500, message: "No data is found" });
}
}
export async function POST(request: Request) {
const body = await request.json();
try {
const data = await fetchTest({
page: body.page,
size: body.size,
orderField: body.orderField,
orderType: body.orderType,
query: body.query,
});
return NextResponse.json({ status: 200, data });
} catch (error) {
return NextResponse.json({ status: 500, message: "No data is found" });
}
}

View File

@@ -0,0 +1,169 @@
'use client';
import React, { useState } from "react";
import { apiPostFetcher, apiGetFetcher } from "@/lib/fetcher"
import { API_BASE_URL } from "@/config/config"
import { Button } from "@/components/mutual/ui/button"
import { Input } from "@/components/mutual/ui/input";
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/mutual/ui/form"
import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import * as z from "zod"
// Define the form schema with Zod
const formSchema = z.object({
page: z.number().min(1, "Page must be at least 1"),
size: z.number().min(1, "Size must be at least 1"),
orderField: z.array(z.string()),
orderType: z.array(z.string()),
query: z.any()
});
// Define the type for our form values
type FormValues = z.infer<typeof formSchema>;
export default function TestPage() {
const [testPostResult, setTestPostResult] = useState({});
const [testGetResult, setTestGetResult] = useState({});
// Initialize the form
const form = useForm<FormValues>({
resolver: zodResolver(formSchema),
defaultValues: {
page: 1,
size: 10,
orderField: [],
orderType: [],
query: {}
}
});
const testPost = async (values: FormValues) => {
const result = await apiPostFetcher({
url: `${API_BASE_URL}/test`,
body: values,
isNoCache: true
})
setTestPostResult(result);
}
const testGet = async () => {
const result = await apiGetFetcher({
url: `${API_BASE_URL}/test`,
isNoCache: true
})
setTestGetResult(result);
}
return (
<div className="container flex flex-col gap-4 mx-auto my-10">
<Form {...form}>
<form onSubmit={form.handleSubmit(testPost)} className="flex flex-col gap-4">
<FormField
control={form.control}
name="page"
render={({ field }) => (
<FormItem>
<FormLabel>Page</FormLabel>
<FormControl>
<Input type="number" placeholder="Page" {...field} onChange={e => field.onChange(Number(e.target.value))} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="size"
render={({ field }) => (
<FormItem>
<FormLabel>Size</FormLabel>
<FormControl>
<Input type="number" placeholder="Size" {...field} onChange={e => field.onChange(Number(e.target.value))} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="orderField"
render={({ field }) => (
<FormItem>
<FormLabel>Order Field</FormLabel>
<FormControl>
<Input
type="text"
placeholder="Order Field"
value={field.value.join(",")}
onChange={e => field.onChange(e.target.value.split(","))}
/>
</FormControl>
<FormDescription>Comma-separated list of fields</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="orderType"
render={({ field }) => (
<FormItem>
<FormLabel>Order Type</FormLabel>
<FormControl>
<Input
type="text"
placeholder="Order Type"
value={field.value.join(",")}
onChange={e => field.onChange(e.target.value.split(","))}
/>
</FormControl>
<FormDescription>Comma-separated list of types (asc/desc)</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="query"
render={({ field }) => (
<FormItem>
<FormLabel>Query</FormLabel>
<FormControl>
<Input
type="text"
placeholder="Query"
value={JSON.stringify(field.value)}
onChange={e => {
try {
field.onChange(JSON.parse(e.target.value))
} catch (error) {
// Handle JSON parse error
}
}}
/>
</FormControl>
<FormDescription>JSON format</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Test Post</Button>
</form>
</Form>
<Button onClick={testGet}>Test Get</Button>
{/* <div>{JSON.stringify(testPostResult?.data?.data || [])}</div> */}
<div className="grid grid-cols-4 gap-4">
{(testPostResult?.data?.data?.map((item: any) => (
<div className="flex flex-col gap-2" key={item.uu_id}>
<span key={`${item.uu_id}-uu_id`} >UUID:{item.uu_id}</span>
<span key={`${item.uu_id}-name`}>Name:{item.process_name}</span>
<span key={`${item.uu_id}-bank_date`}>Bank Date:{item.bank_date}</span>
<span key={`${item.uu_id}-currency_value`}>Currency Value:{item.currency_value}</span>
</div>
))
)}
</div>
</div>
);
}

View File

@@ -4,7 +4,7 @@ import * as React from "react"
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"
import { buttonVariants } from "@/components/mutual/ui/button"
function AlertDialog({
...props

View File

@@ -5,7 +5,7 @@ import { ChevronLeft, ChevronRight } from "lucide-react"
import { DayPicker } from "react-day-picker"
import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"
import { buttonVariants } from "@/components/mutual/ui/button"
function Calendar({
className,

View File

@@ -11,7 +11,7 @@ import {
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
} from "@/components/mutual/ui/dialog"
function Command({
className,

View File

@@ -14,7 +14,7 @@ import {
} from "react-hook-form"
import { cn } from "@/lib/utils"
import { Label } from "@/components/ui/label"
import { Label } from "@/components/mutual/ui/label"
const Form = FormProvider

View File

@@ -0,0 +1,29 @@
"use server";
import { fetchData } from "@/fetchers/fecther";
import { urlTesterList } from "@/fetchers/index";
export const fetchTest = async ({
page,
size,
orderField,
orderType,
query,
}: {
page?: number;
size?: number;
orderField?: string[];
orderType?: string[];
query?: any;
}) => {
try {
const response = await fetchData(urlTesterList, { method: "POST", cache: false, payload: { page, size, orderField, orderType, query } });
return response.data;
} catch (error) {
console.error(error);
return {
completed: false,
data: null,
message: "No data is found",
};
}
};

View File

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

View File

@@ -2,7 +2,7 @@ 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 { AuthError } from "@/fetchers/types/context";
import { fetchResponseStatus } from "@/fetchers/utils";
async function checkAccessTokenIsValid() {

View File

@@ -0,0 +1,165 @@
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(
request: NextRequest,
body: any,
listFunction: ListFunction
) {
const page = body.page || 1;
const size = body.size || 10;
const orderField = body.orderField || ["uu_id"];
const orderType = body.orderType || ["asc"];
const query = body.query || {};
const response = await listFunction({
page,
size,
orderField,
orderType,
query,
} as PaginationParams);
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(
body: any,
createFunction?: CreateFunction,
requiredFields: string[] = []
) {
if (requiredFields.length > 0) {
const validation = validateRequiredFields(body, requiredFields);
if (!validation.valid) {
return errorResponse(validation.error as string, 400);
}
}
if (createFunction) {
const result = await createFunction(body);
return createResponse(result);
}
return createResponse({
uuid: 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
*/
export async function handleUpdateOperation(
request: NextRequest,
body: any,
updateFunction?: UpdateFunction
) {
const uuid = request.nextUrl.searchParams.get("uuid");
if (!uuid) {
return errorResponse("UUID not found", 400);
}
if (updateFunction) {
const result = await updateFunction(body, uuid);
return updateResponse(result);
}
return updateResponse(body);
}
/**
* Generic delete operation handler
* @param request NextRequest object
* @param deleteFunction The function to call to delete the item
*/
export async function handleDeleteOperation(
request: NextRequest,
deleteFunction?: DeleteFunction
) {
const uuid = request.nextUrl.searchParams.get("uuid");
if (!uuid) {
return errorResponse("UUID not found", 400);
}
if (deleteFunction) {
await deleteFunction(uuid);
}
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: NextRequest, body: any) =>
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[] = []
) {
// This handler only takes the body parameter, not the request
return withErrorHandling((body: any) => {
// Ensure we're only passing the actual body data to the create function
if (body && typeof body === 'object' && body.body) {
return handleCreateOperation(body.body, createFunction, requiredFields);
}
return handleCreateOperation(body, createFunction, requiredFields);
});
}
/**
* Create a wrapped update handler with error handling
* @param updateFunction The function to call to update the item
*/
export function createUpdateHandler(updateFunction?: UpdateFunction) {
return withErrorHandling((request: NextRequest, body: any) =>
handleUpdateOperation(request, body, updateFunction)
);
}
/**
* 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: NextRequest) =>
handleDeleteOperation(request, deleteFunction)
);
}

View File

@@ -0,0 +1,15 @@
export {
successResponse,
errorResponse,
paginationResponse,
createResponse,
updateResponse,
deleteResponse,
} from "./responseHandlers";
export { withErrorHandling, validateRequiredFields } from "./requestHandlers";
export {
createListHandler,
createCreateHandler,
createUpdateHandler,
createDeleteHandler,
} from "./apiOperations";

View File

@@ -0,0 +1,70 @@
import { NextRequest } from "next/server";
import { errorResponse } from "./responseHandlers";
import { ValidationResult, ApiHandler, ApiHandlerBodyOnly, ApiHandlerWithRequest } 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);
}
// Check handler parameter count to determine if it needs request object
// If handler has only 1 parameter, it's likely a create operation that only needs body
if (handler.length === 1) {
// Cast to the appropriate handler type
return await (handler as ApiHandlerBodyOnly)(body);
} else {
// Otherwise pass both request and body (for list, update, delete operations)
return await (handler as ApiHandlerWithRequest)(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 };
}

View File

@@ -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);
}

View File

@@ -0,0 +1,119 @@
/**
* 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;
}
export const defaultPaginationResponse: PaginationResponse = {
page: 1,
size: 10,
totalCount: 0,
totalItems: 0,
totalPages: 0,
pageCount: 0,
orderField: ["uu_id"],
orderType: ["asc"],
query: {},
next: false,
back: false,
};
/**
* 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;
}
export const collectPaginationFromApiResponse = (
response: PaginatedApiResponse<any>
): PaginationResponse => {
return {
page: response.pagination?.page || 1,
size: response.pagination?.size || 10,
totalCount: response.pagination?.totalCount || 0,
totalItems: response.pagination?.totalItems || 0,
totalPages: response.pagination?.totalPages || 0,
pageCount: response.pagination?.pageCount || 0,
orderField: response.pagination?.orderField || ["uu_id"],
orderType: response.pagination?.orderType || ["asc"],
query: response.pagination?.query || {},
next: response.pagination?.next || false,
back: response.pagination?.back || false,
};
};
/**
* API handler function types
*/
export type ApiHandlerWithRequest = (request: NextRequest, body: any) => Promise<Response>;
export type ApiHandlerBodyOnly = (body: any) => Promise<Response>;
export type ApiHandler = ApiHandlerWithRequest | ApiHandlerBodyOnly;
/**
* 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>;

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

@@ -4,7 +4,7 @@ import * as React from "react"
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"
import { buttonVariants } from "@/components/mutual/ui/button"
function AlertDialog({
...props

View File

@@ -5,7 +5,7 @@ import { ChevronLeft, ChevronRight } from "lucide-react"
import { DayPicker } from "react-day-picker"
import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"
import { buttonVariants } from "@/components/mutual/ui/button"
function Calendar({
className,

View File

@@ -11,7 +11,7 @@ import {
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
} from "@/components/mutual/ui/dialog"
function Command({
className,

View File

@@ -14,7 +14,7 @@ import {
} from "react-hook-form"
import { cn } from "@/lib/utils"
import { Label } from "@/components/ui/label"
import { Label } from "@/components/mutual/ui/label"
const Form = FormProvider