From 476dfeeb0861d8cbe2f537aed379d7464987b0ae Mon Sep 17 00:00:00 2001 From: Hermann_Kitio Date: Fri, 17 Apr 2026 17:24:21 +0300 Subject: [PATCH] =?UTF-8?q?feat(shared):=20types=20API,=20logger=20structu?= =?UTF-8?q?r=C3=A9,=20validation=20env.ts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/config/env.ts | 22 ++++++++++++++++++++++ src/shared/lib/logger.ts | 18 ++++++++++++++++++ src/shared/types/api.ts | 23 +++++++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 src/shared/config/env.ts create mode 100644 src/shared/lib/logger.ts create mode 100644 src/shared/types/api.ts diff --git a/src/shared/config/env.ts b/src/shared/config/env.ts new file mode 100644 index 0000000..573abff --- /dev/null +++ b/src/shared/config/env.ts @@ -0,0 +1,22 @@ +import { z } from 'zod' + +const envSchema = z.object({ + VITE_API_URL: z.string().url(), + VITE_SUPABASE_URL: z.string().url(), + VITE_SUPABASE_ANON_KEY: z.string().min(1), + VITE_ENABLE_T2_LIVE: z.enum(['true', 'false']).optional(), + VITE_SENTRY_DSN: z.string().url().optional(), +}) + +const parsed = envSchema.safeParse(import.meta.env) + +if (!parsed.success) { + const issues = parsed.error.issues + .map((i) => ` - ${i.path.join('.')}: ${i.message}`) + .join('\n') + throw new Error( + `Variables d'environnement invalides ou manquantes :\n${issues}\n\nVérifier .env.example`, + ) +} + +export const env = parsed.data diff --git a/src/shared/lib/logger.ts b/src/shared/lib/logger.ts new file mode 100644 index 0000000..ba61ea6 --- /dev/null +++ b/src/shared/lib/logger.ts @@ -0,0 +1,18 @@ +type LogLevel = 'debug' | 'info' | 'warn' | 'error' +type LogContext = Record + +function log(level: LogLevel, message: string, context?: LogContext): void { + if (import.meta.env.DEV) { + console[level](`[${level.toUpperCase()}] ${message}`, context ?? '') + return + } + const entry = { level, message, timestamp: new Date().toISOString(), ...context } + console[level](JSON.stringify(entry)) +} + +export const logger = { + debug: (msg: string, ctx?: LogContext) => log('debug', msg, ctx), + info: (msg: string, ctx?: LogContext) => log('info', msg, ctx), + warn: (msg: string, ctx?: LogContext) => log('warn', msg, ctx), + error: (msg: string, ctx?: LogContext) => log('error', msg, ctx), +} diff --git a/src/shared/types/api.ts b/src/shared/types/api.ts new file mode 100644 index 0000000..8d7a7fb --- /dev/null +++ b/src/shared/types/api.ts @@ -0,0 +1,23 @@ +// SOURCE OF TRUTH: expria-backend — format réel des erreurs, confirmé par audit 2026-04-17 +// Les modifications doivent être synchronisées avec le backend. + +export interface ApiError { + error: true + code: ApiErrorCode + message: string + status?: number +} + +export type ApiErrorCode = + | 'AUTH_REQUIRED' + | 'PLAN_INSUFFICIENT' + | 'QUOTA_REACHED' + | 'VALIDATION_ERROR' + | 'INVALID_BODY' + | 'INVALID_PLAN' + | 'NO_ACTIVE_SUBSCRIPTION' + | 'SIMULATION_NOT_FOUND' + | 'STRIPE_WEBHOOK_INVALID' + | 'INTERNAL_ERROR' + +export type FrontendErrorCode = 'TIMEOUT' | 'NETWORK_ERROR' | 'PARSE_ERROR'