mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-12 08:45:38 +02:00
## Description <!-- Briefly describe the purpose and scope of your changes --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Upgraded API and authentication services with dynamic scaling, enhanced load balancing, and real-time interaction endpoints. - Introduced new commands to streamline local development and container builds. - Added new endpoints for retrieving Steam account information and managing connections. - Implemented a QR code authentication interface for Steam, enhancing user login experiences. - **Database Updates** - Rolled out comprehensive schema migrations that improve data integrity and indexing. - Introduced new tables for managing Steam user credentials and machine information. - **UI Enhancements** - Added refreshed animated assets and an improved QR code login flow for a more engaging experience. - Introduced new styled components for displaying friends and games. - **Maintenance** - Completed extensive refactoring and configuration updates to optimize performance and development workflows. - Updated logging configurations and improved error handling mechanisms. - Streamlined resource definitions in the configuration files. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
246 lines
6.6 KiB
TypeScript
246 lines
6.6 KiB
TypeScript
import { z, ZodSchema } from "zod";
|
|
import {type Hook } from "./types/hook";
|
|
import { ErrorCodes, ErrorResponse } from "@nestri/core/error";
|
|
import type { MiddlewareHandler, ValidationTargets } from "hono";
|
|
import { resolver, validator as zodValidator } from "hono-openapi/zod";
|
|
|
|
export function Result<T extends z.ZodTypeAny>(schema: T) {
|
|
return resolver(
|
|
z.object({
|
|
data: schema,
|
|
}),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Custom validator wrapper around hono-openapi/zod validator that formats errors
|
|
* according to our standard API error format
|
|
*/
|
|
export const validator = <
|
|
T extends ZodSchema,
|
|
Target extends keyof ValidationTargets
|
|
>(
|
|
target: Target,
|
|
schema: T
|
|
): MiddlewareHandler<
|
|
any,
|
|
string,
|
|
{
|
|
in: {
|
|
[K in Target]: z.input<T>;
|
|
};
|
|
out: {
|
|
[K in Target]: z.output<T>;
|
|
};
|
|
}
|
|
> => {
|
|
// Create a custom error handler that formats errors according to our standards
|
|
// const standardErrorHandler: Parameters<typeof zodValidator>[2] = (
|
|
const standardErrorHandler: Hook<z.infer<T>, any, any, Target> = (
|
|
result,
|
|
c,
|
|
) => {
|
|
if (!result.success) {
|
|
// Get the validation issues
|
|
const issues = result.error.issues || result.error.errors || [];
|
|
if (issues.length === 0) {
|
|
// If there are no issues, return a generic error
|
|
return c.json(
|
|
{
|
|
type: "validation",
|
|
code: ErrorCodes.Validation.INVALID_PARAMETER,
|
|
message: "Invalid request data",
|
|
},
|
|
400,
|
|
);
|
|
}
|
|
|
|
// Get the first error for the main response
|
|
const firstIssue = issues[0]!;
|
|
const fieldPath = firstIssue.path
|
|
? Array.isArray(firstIssue.path)
|
|
? firstIssue.path.join(".")
|
|
: firstIssue.path
|
|
: undefined;
|
|
|
|
// Map Zod error codes to our standard error codes
|
|
let errorCode = ErrorCodes.Validation.INVALID_PARAMETER;
|
|
if (
|
|
firstIssue.code === "invalid_type" &&
|
|
firstIssue.received === "undefined"
|
|
) {
|
|
errorCode = ErrorCodes.Validation.MISSING_REQUIRED_FIELD;
|
|
} else if (
|
|
["invalid_string", "invalid_date", "invalid_regex"].includes(
|
|
firstIssue.code,
|
|
)
|
|
) {
|
|
errorCode = ErrorCodes.Validation.INVALID_FORMAT;
|
|
}
|
|
|
|
// Create our standardized error response
|
|
const response = {
|
|
type: "validation",
|
|
code: errorCode,
|
|
message: firstIssue.message,
|
|
param: fieldPath,
|
|
details: undefined as any,
|
|
};
|
|
|
|
// Add details if we have multiple issues
|
|
if (issues.length > 0) {
|
|
response.details = {
|
|
issues: issues.map((issue) => ({
|
|
path: issue.path
|
|
? Array.isArray(issue.path)
|
|
? issue.path.join(".")
|
|
: issue.path
|
|
: undefined,
|
|
code: issue.code,
|
|
message: issue.message,
|
|
// @ts-expect-error
|
|
expected: issue.expected,
|
|
// @ts-expect-error
|
|
received: issue.received,
|
|
})),
|
|
};
|
|
}
|
|
|
|
console.log("Validation error in validator:", response);
|
|
return c.json(response, 400);
|
|
}
|
|
};
|
|
|
|
// Use the original validator with our custom error handler
|
|
return zodValidator(target, schema, standardErrorHandler);
|
|
};
|
|
|
|
/**
|
|
* Standard error responses for OpenAPI documentation
|
|
*/
|
|
export const ErrorResponses = {
|
|
400: {
|
|
content: {
|
|
"application/json": {
|
|
schema: resolver(
|
|
ErrorResponse.openapi({
|
|
description: "Validation error",
|
|
example: {
|
|
type: "validation",
|
|
code: "invalid_parameter",
|
|
message: "The request was invalid",
|
|
param: "email",
|
|
},
|
|
}),
|
|
),
|
|
},
|
|
},
|
|
description:
|
|
"Bad Request - The request could not be understood or was missing required parameters.",
|
|
},
|
|
401: {
|
|
content: {
|
|
"application/json": {
|
|
schema: resolver(
|
|
ErrorResponse.openapi({
|
|
description: "Authentication error",
|
|
example: {
|
|
type: "authentication",
|
|
code: "unauthorized",
|
|
message: "Authentication required",
|
|
},
|
|
}),
|
|
),
|
|
},
|
|
},
|
|
description:
|
|
"Unauthorized - Authentication is required and has failed or has not been provided.",
|
|
},
|
|
403: {
|
|
content: {
|
|
"application/json": {
|
|
schema: resolver(
|
|
ErrorResponse.openapi({
|
|
description: "Permission error",
|
|
example: {
|
|
type: "forbidden",
|
|
code: "permission_denied",
|
|
message: "You do not have permission to access this resource",
|
|
},
|
|
}),
|
|
),
|
|
},
|
|
},
|
|
description:
|
|
"Forbidden - You do not have permission to access this resource.",
|
|
},
|
|
404: {
|
|
content: {
|
|
"application/json": {
|
|
schema: resolver(
|
|
ErrorResponse.openapi({
|
|
description: "Not found error",
|
|
example: {
|
|
type: "not_found",
|
|
code: "resource_not_found",
|
|
message: "The requested resource could not be found",
|
|
},
|
|
}),
|
|
),
|
|
},
|
|
},
|
|
description: "Not Found - The requested resource does not exist.",
|
|
},
|
|
409: {
|
|
content: {
|
|
"application/json": {
|
|
schema: resolver(
|
|
ErrorResponse.openapi({
|
|
description: "Conflict Error",
|
|
example: {
|
|
type: "already_exists",
|
|
code: "resource_already_exists",
|
|
message: "The resource could not be created because it already exists",
|
|
},
|
|
}),
|
|
),
|
|
},
|
|
},
|
|
description: "Conflict - The resource could not be created because it already exists.",
|
|
},
|
|
429: {
|
|
content: {
|
|
"application/json": {
|
|
schema: resolver(
|
|
ErrorResponse.openapi({
|
|
description: "Rate limit error",
|
|
example: {
|
|
type: "rate_limit",
|
|
code: "too_many_requests",
|
|
message: "Rate limit exceeded",
|
|
},
|
|
}),
|
|
),
|
|
},
|
|
},
|
|
description:
|
|
"Too Many Requests - You have made too many requests in a short period of time.",
|
|
},
|
|
500: {
|
|
content: {
|
|
"application/json": {
|
|
schema: resolver(
|
|
ErrorResponse.openapi({
|
|
description: "Server error",
|
|
example: {
|
|
type: "internal",
|
|
code: "internal_error",
|
|
message: "Internal server error",
|
|
},
|
|
}),
|
|
),
|
|
},
|
|
},
|
|
description: "Internal Server Error - Something went wrong on our end.",
|
|
},
|
|
}; |