server: core bootstrap — config, db clients, shared utilities
Add env config (zod validation), game balance constants, Prisma and Redis client singletons, auth guard middleware, error helpers, request validators, Fastify JWT type augmentation, and app/server entry points. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import { errors } from './errors.js';
|
||||
|
||||
/** preHandler che richiede un JWT valido; popola request.user con { sub, playerId }. */
|
||||
export async function authGuard(request: FastifyRequest, _reply: FastifyReply): Promise<void> {
|
||||
try {
|
||||
await request.jwtVerify();
|
||||
} catch {
|
||||
throw errors.unauthorized('Token mancante o non valido');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/** Errore applicativo con status HTTP e codice macchina-leggibile. */
|
||||
export class AppError extends Error {
|
||||
constructor(
|
||||
public readonly statusCode: number,
|
||||
public readonly code: string,
|
||||
message: string,
|
||||
) {
|
||||
super(message);
|
||||
this.name = 'AppError';
|
||||
}
|
||||
}
|
||||
|
||||
export const errors = {
|
||||
badRequest: (message: string) => new AppError(400, 'BAD_REQUEST', message),
|
||||
validation: (message: string) => new AppError(400, 'VALIDATION_ERROR', message),
|
||||
unauthorized: (message = 'Non autenticato') => new AppError(401, 'UNAUTHORIZED', message),
|
||||
forbidden: (message = 'Operazione non consentita') => new AppError(403, 'FORBIDDEN', message),
|
||||
notFound: (message = 'Risorsa non trovata') => new AppError(404, 'NOT_FOUND', message),
|
||||
conflict: (message: string) => new AppError(409, 'CONFLICT', message),
|
||||
insufficientFunds: () => new AppError(400, 'INSUFFICIENT_FUNDS', 'Denaro insufficiente'),
|
||||
insufficientItems: () =>
|
||||
new AppError(400, 'INSUFFICIENT_ITEMS', 'Quantità insufficiente in inventario'),
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import type { ZodTypeAny, output } from 'zod';
|
||||
import { AppError } from './errors.js';
|
||||
|
||||
/** Valida `data` con lo schema Zod; lancia AppError 400 con i dettagli in caso di errore. */
|
||||
export function parseOrThrow<S extends ZodTypeAny>(schema: S, data: unknown): output<S> {
|
||||
const result = schema.safeParse(data);
|
||||
if (!result.success) {
|
||||
const detail = result.error.issues
|
||||
.map((issue) => `${issue.path.join('.') || 'body'}: ${issue.message}`)
|
||||
.join('; ');
|
||||
throw new AppError(400, 'VALIDATION_ERROR', detail);
|
||||
}
|
||||
return result.data;
|
||||
}
|
||||
|
||||
export function clamp(value: number, min: number, max: number): number {
|
||||
return Math.min(max, Math.max(min, value));
|
||||
}
|
||||
|
||||
export function randomInt(min: number, max: number): number {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
export function randomFloat(min: number, max: number): number {
|
||||
return Math.random() * (max - min) + min;
|
||||
}
|
||||
|
||||
export function pickRandom<T>(values: readonly T[]): T {
|
||||
return values[Math.floor(Math.random() * values.length)]!;
|
||||
}
|
||||
Reference in New Issue
Block a user