first action update user pages .

2-update driver pages [fixed image display and notyfi]
3-update car pages [fixed image]
4-build tripe pages crud
5-build some role pages
6-build audit page
7-update ui compounants
8-update saidebar and topbar
9-cheange view to be ar view
10-cheange stractcher to be app,src
11-add validation layer by yup
12-add api image-proxy to broke image corse bloken
This commit is contained in:
m7amedez5511
2026-06-23 15:54:07 +03:00
parent c1b77f89cd
commit db64b79fe3
140 changed files with 9266 additions and 2449 deletions

View File

@@ -1,4 +1,4 @@
// app/api/auth/clear-cookie/route.ts
// Clears the HttpOnly auth cookie on logout.
// Only the server can delete an HttpOnly cookie.

View File

@@ -0,0 +1,35 @@
import { NextRequest, NextResponse } from "next/server";
const BACKEND_BASE = "https://logiapi.slash.sa";
export async function GET(
_req: NextRequest,
{ params }: { params: Promise<{ filename: string }> },
) {
const { filename } = await params;
try {
const upstream = await fetch(
`${BACKEND_BASE}/public/uploads/driver-photos/${filename}`,
{ cache: "no-store" },
);
if (!upstream.ok) {
return new NextResponse(null, { status: upstream.status });
}
const buffer = await upstream.arrayBuffer();
const contentType =
upstream.headers.get("Content-Type") ?? "image/jpeg";
return new NextResponse(buffer, {
status: 200,
headers: {
"Content-Type": contentType,
"Cache-Control": "public, max-age=3600, stale-while-revalidate=86400",
},
});
} catch {
return new NextResponse(null, { status: 502 });
}
}

View File

@@ -10,51 +10,54 @@ async function proxy(
const path = params.path?.join("/") ?? "";
const targetUrl = `${BACKEND_BASE_URL}/${path}${request.nextUrl.search}`;
// Read the HttpOnly cookie server-side — this is the ONLY place the raw
// token value is accessible after the Issue 3 refactor.
const cookieStore = await cookies();
const token = cookieStore.get("auth_token")?.value;
const headers = new Headers();
// Forward all safe request headers.
request.headers.forEach((value, key) => {
if (key === "host" || key === "content-length" || key === "cookie") return;
headers.set(key, value);
});
// Inject the Authorization header using the server-side cookie value.
// The client never had access to this token value.
if (token) {
headers.set("Authorization", `Bearer ${decodeURIComponent(token)}`);
}
if (!headers.has("Content-Type")) {
// NOTE: do NOT force a default Content-Type here when there's no body —
// for multipart/form-data requests, the browser sets Content-Type itself
// (including the multipart boundary). We only need a fallback for bodied
// requests that didn't already specify one (e.g. raw JSON fetches).
const isBodyless = request.method === "GET" || request.method === "HEAD";
// CRITICAL FIX: read the body as raw bytes (ArrayBuffer), never as text.
// request.text() decodes the body as UTF-8, which corrupts any binary
// payload (images inside multipart/form-data) — invalid UTF-8 byte
// sequences get silently replaced, mangling the uploaded file before it
// ever reaches the backend. ArrayBuffer passes bytes through untouched,
// and works equally well for JSON bodies.
const body = isBodyless ? undefined : await request.arrayBuffer();
if (!isBodyless && !headers.has("Content-Type")) {
headers.set("Content-Type", "application/json");
}
const isBodyless = request.method === "GET" || request.method === "HEAD";
const body = isBodyless ? undefined : await request.text();
const upstream = await fetch(targetUrl, {
method: request.method,
headers,
body,
cache: "no-store",
// @ts-expect-error - duplex is required by undici when streaming a body
// but isn't yet in the official RequestInit types in this Next.js version.
duplex: body ? "half" : undefined,
});
const contentType =
upstream.headers.get("content-type") || "application/json";
// Responses with these statuses MUST NOT carry a body (per the Fetch/HTTP
// spec). Constructing a NextResponse with a body alongside one of these
// statuses throws at runtime, which Next.js then surfaces as a 500 to the
// client — even though the upstream call itself succeeded. This is exactly
// what happens on DELETE endpoints that correctly return 204 No Content.
const isNoBodyStatus = [204, 205, 304].includes(upstream.status);
if (isNoBodyStatus) {
// Drain the upstream body (should be empty anyway) without parsing it.
await upstream.text().catch(() => null);
return new NextResponse(null, {
status: upstream.status,
@@ -62,6 +65,19 @@ async function proxy(
});
}
// Pass binary responses (e.g. images) through untouched too, just in case
// any proxied endpoint ever returns one.
if (!contentType.includes("application/json") && !contentType.includes("text")) {
const buffer = await upstream.arrayBuffer();
return new NextResponse(buffer, {
status: upstream.status,
headers: {
"content-type": contentType,
"cache-control": "no-store",
},
});
}
const responseBody = contentType.includes("application/json")
? await upstream.json().catch(() => null)
: await upstream.text();