mirror of
https://github.com/yusufipk/OpenFrame.git
synced 2026-09-11 09:36:08 +00:00
Merge pull request #34 from yusufipk/fix/bigint-safe-success-response
fix(api): serialize BigInt in all API success responses
This commit is contained in:
@@ -2,7 +2,6 @@ import { NextRequest } from 'next/server';
|
|||||||
import { db } from '@/lib/db';
|
import { db } from '@/lib/db';
|
||||||
import { auth, checkProjectAccess } from '@/lib/auth';
|
import { auth, checkProjectAccess } from '@/lib/auth';
|
||||||
import { validateUrl, validateOptionalUrlOrAppPath } from '@/lib/validation';
|
import { validateUrl, validateOptionalUrlOrAppPath } from '@/lib/validation';
|
||||||
import { toJsonSafe } from '@/lib/json-serialize';
|
|
||||||
import { rateLimit } from '@/lib/rate-limit';
|
import { rateLimit } from '@/lib/rate-limit';
|
||||||
import { notifyProjectOwner } from '@/lib/notifications';
|
import { notifyProjectOwner } from '@/lib/notifications';
|
||||||
import { apiErrors, successResponse, withCacheControl } from '@/lib/api-response';
|
import { apiErrors, successResponse, withCacheControl } from '@/lib/api-response';
|
||||||
@@ -264,7 +263,7 @@ export async function POST(request: NextRequest, { params }: RouteParams) {
|
|||||||
}).catch((err) => logError('Notification failed:', err));
|
}).catch((err) => logError('Notification failed:', err));
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = successResponse(toJsonSafe(version), 201);
|
const response = successResponse(version, 201);
|
||||||
return withCacheControl(response, 'private, no-store');
|
return withCacheControl(response, 'private, no-store');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError('Error creating version:', error);
|
logError('Error creating version:', error);
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { NextRequest } from 'next/server';
|
|||||||
import { db } from '@/lib/db';
|
import { db } from '@/lib/db';
|
||||||
import { auth, checkProjectAccess } from '@/lib/auth';
|
import { auth, checkProjectAccess } from '@/lib/auth';
|
||||||
import { validateUrl, validateOptionalUrlOrAppPath } from '@/lib/validation';
|
import { validateUrl, validateOptionalUrlOrAppPath } from '@/lib/validation';
|
||||||
import { toJsonSafe } from '@/lib/json-serialize';
|
|
||||||
import { rateLimit } from '@/lib/rate-limit';
|
import { rateLimit } from '@/lib/rate-limit';
|
||||||
import { notifyProjectOwner } from '@/lib/notifications';
|
import { notifyProjectOwner } from '@/lib/notifications';
|
||||||
import { apiErrors, successResponse, withCacheControl } from '@/lib/api-response';
|
import { apiErrors, successResponse, withCacheControl } from '@/lib/api-response';
|
||||||
@@ -269,7 +268,7 @@ export async function POST(request: NextRequest, { params }: RouteParams) {
|
|||||||
}).catch((err) => logError('Notification failed:', err));
|
}).catch((err) => logError('Notification failed:', err));
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = successResponse(toJsonSafe(video), 201);
|
const response = successResponse(video, 201);
|
||||||
return withCacheControl(response, 'private, no-store');
|
return withCacheControl(response, 'private, no-store');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError('Error creating video:', error);
|
logError('Error creating video:', error);
|
||||||
|
|||||||
+10
-1
@@ -1,4 +1,5 @@
|
|||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from 'next/server';
|
||||||
|
import { bigIntReplacer } from '@/lib/json-serialize';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Standardized API error response format
|
* Standardized API error response format
|
||||||
@@ -113,6 +114,11 @@ export function errorResponse(
|
|||||||
* @param status - HTTP status code (default: 200)
|
* @param status - HTTP status code (default: 200)
|
||||||
* @param meta - Pagination or other metadata (optional)
|
* @param meta - Pagination or other metadata (optional)
|
||||||
*
|
*
|
||||||
|
* Serialized with bigIntReplacer rather than NextResponse.json(), because
|
||||||
|
* JSON.stringify throws on BigInt and Prisma returns BigInt for sizeBytes.
|
||||||
|
* Any payload carrying a VideoVersion or VideoAsset row would otherwise 500
|
||||||
|
* after its write had already committed. BigInt values render as strings.
|
||||||
|
*
|
||||||
* @example
|
* @example
|
||||||
* ```ts
|
* ```ts
|
||||||
* return successResponse({ projects: [] });
|
* return successResponse({ projects: [] });
|
||||||
@@ -127,7 +133,10 @@ export function successResponse<T>(
|
|||||||
const body: ApiSuccessResponse<T> = { data };
|
const body: ApiSuccessResponse<T> = { data };
|
||||||
if (meta) body.meta = meta;
|
if (meta) body.meta = meta;
|
||||||
|
|
||||||
return NextResponse.json(body, { status });
|
return new NextResponse(JSON.stringify(body, bigIntReplacer), {
|
||||||
|
status,
|
||||||
|
headers: { 'content-type': 'application/json' },
|
||||||
|
}) as NextResponse<ApiSuccessResponse<T>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function withCacheControl(response: Response, value: string): Response {
|
export function withCacheControl(response: Response, value: string): Response {
|
||||||
|
|||||||
+13
-3
@@ -1,8 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* JSON.stringify replacer that renders Prisma BigInt columns (sizeBytes) as
|
||||||
|
* strings. Without it, JSON.stringify throws on any payload carrying one.
|
||||||
|
*/
|
||||||
|
export function bigIntReplacer(_key: string, value: unknown): unknown {
|
||||||
|
return typeof value === 'bigint' ? value.toString() : value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts values for JSON responses (e.g. Prisma BigInt fields).
|
* Converts values for JSON responses (e.g. Prisma BigInt fields).
|
||||||
|
*
|
||||||
|
* API routes do not need this — successResponse() serializes BigInt already.
|
||||||
|
* Use it for payloads that bypass that helper, such as data handed from a
|
||||||
|
* server component to a client component.
|
||||||
*/
|
*/
|
||||||
export function toJsonSafe<T>(value: T): T {
|
export function toJsonSafe<T>(value: T): T {
|
||||||
return JSON.parse(
|
return JSON.parse(JSON.stringify(value, bigIntReplacer)) as T;
|
||||||
JSON.stringify(value, (_key, val) => (typeof val === 'bigint' ? val.toString() : val))
|
|
||||||
) as T;
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user