Basic Architecture of Sport Web

This commit is contained in:
Nông Đức Huy
2026-08-11 13:37:25 +07:00
commit 8032fff6ac
262 changed files with 20348 additions and 0 deletions
@@ -0,0 +1,48 @@
import { Injectable, type PipeTransform } from '@nestjs/common';
import type { ZodType } from 'zod';
import type { ApiFieldErrors } from '@sport/types';
import { AppException } from '../errors/app.exception';
/**
* Validates a request payload with a Zod schema from @sport/validation and
* returns the *parsed* value (with coercions and defaults applied).
*
* Chosen over class-validator so that exactly one schema governs both the API
* and the frontend forms. See ADR-0006.
*
* @Post()
* create(@Body(new ZodValidationPipe(createProductSchema)) body: CreateProductInput) {}
*/
@Injectable()
export class ZodValidationPipe<TSchema extends ZodType> implements PipeTransform {
constructor(private readonly schema: TSchema) {}
transform(value: unknown): unknown {
const result = this.schema.safeParse(value);
if (!result.success) {
throw AppException.validation(toFieldErrors(result.error.issues));
}
return result.data;
}
}
interface ZodIssueLike {
path: PropertyKey[];
message: string;
}
/** `items.0.quantity` → ["Must be at least 1"] — directly consumable by forms. */
function toFieldErrors(issues: readonly ZodIssueLike[]): ApiFieldErrors {
const fields: Record<string, string[]> = {};
for (const issue of issues) {
const key = issue.path.map(String).join('.') || '_';
(fields[key] ??= []).push(issue.message);
}
return fields;
}