feat(unionflow): ajout Spec-Kit, constitution, mission mutuelles

- Config Spec-Kit pour Spec-Driven Development
- CONSTITUTION.md + .specify/memory/constitution.md
- Commandes Cursor /speckit.*, règles projet
- Mission: associations + mutuelles d'épargne et de financement
- .gitignore: versionner config spec-kit unionflow

Made-with: Cursor
This commit is contained in:
dahoud
2026-02-27 14:41:07 +00:00
parent 144b68f8e7
commit b1957c1c81
631 changed files with 104070 additions and 0 deletions

View File

@@ -0,0 +1,192 @@
/// Gestionnaire d'erreurs global pour l'application
library error_handler;
import 'package:dio/dio.dart';
/// Classe utilitaire pour gérer les erreurs de manière centralisée
class ErrorHandler {
/// Convertit une erreur en message utilisateur lisible
static String getErrorMessage(dynamic error) {
if (error is DioException) {
return _handleDioError(error);
} else if (error is String) {
return error;
} else if (error is Exception) {
return error.toString().replaceAll('Exception: ', '');
}
return 'Une erreur inattendue s\'est produite.';
}
/// Gère les erreurs Dio spécifiques
static String _handleDioError(DioException error) {
switch (error.type) {
case DioExceptionType.connectionTimeout:
return 'Délai de connexion dépassé.\nVérifiez votre connexion internet.';
case DioExceptionType.sendTimeout:
return 'Délai d\'envoi dépassé.\nVérifiez votre connexion internet.';
case DioExceptionType.receiveTimeout:
return 'Délai de réception dépassé.\nLe serveur met trop de temps à répondre.';
case DioExceptionType.badResponse:
return _handleBadResponse(error.response);
case DioExceptionType.cancel:
return 'Requête annulée.';
case DioExceptionType.connectionError:
return 'Erreur de connexion.\nVérifiez votre connexion internet.';
case DioExceptionType.badCertificate:
return 'Erreur de certificat SSL.\nLa connexion n\'est pas sécurisée.';
case DioExceptionType.unknown:
default:
if (error.message?.contains('SocketException') ?? false) {
return 'Impossible de se connecter au serveur.\nVérifiez votre connexion internet.';
}
return 'Erreur de connexion.\nVeuillez réessayer.';
}
}
/// Gère les réponses HTTP avec erreur
static String _handleBadResponse(Response? response) {
if (response == null) {
return 'Erreur serveur inconnue.';
}
// Essayer d'extraire le message d'erreur du body
String? errorMessage;
if (response.data is Map) {
errorMessage = response.data['message'] ??
response.data['error'] ??
response.data['details'];
}
switch (response.statusCode) {
case 400:
return errorMessage ?? 'Requête invalide.\nVérifiez les données saisies.';
case 401:
return errorMessage ?? 'Non authentifié.\nVeuillez vous reconnecter.';
case 403:
return errorMessage ?? 'Accès refusé.\nVous n\'avez pas les permissions nécessaires.';
case 404:
return errorMessage ?? 'Ressource non trouvée.';
case 409:
return errorMessage ?? 'Conflit.\nCette ressource existe déjà.';
case 422:
return errorMessage ?? 'Données invalides.\nVérifiez les informations saisies.';
case 429:
return 'Trop de requêtes.\nVeuillez patienter quelques instants.';
case 500:
return errorMessage ?? 'Erreur serveur interne.\nVeuillez réessayer plus tard.';
case 502:
return 'Passerelle incorrecte.\nLe serveur est temporairement indisponible.';
case 503:
return 'Service temporairement indisponible.\nVeuillez réessayer plus tard.';
case 504:
return 'Délai d\'attente de la passerelle dépassé.\nLe serveur met trop de temps à répondre.';
default:
return errorMessage ?? 'Erreur serveur (${response.statusCode}).\nVeuillez réessayer.';
}
}
/// Détermine si l'erreur est une erreur réseau
static bool isNetworkError(dynamic error) {
if (error is DioException) {
return error.type == DioExceptionType.connectionTimeout ||
error.type == DioExceptionType.sendTimeout ||
error.type == DioExceptionType.receiveTimeout ||
error.type == DioExceptionType.connectionError ||
(error.message?.contains('SocketException') ?? false);
}
return false;
}
/// Détermine si l'erreur est une erreur d'authentification
static bool isAuthError(dynamic error) {
if (error is DioException && error.response != null) {
return error.response!.statusCode == 401;
}
return false;
}
/// Détermine si l'erreur est une erreur de permissions
static bool isPermissionError(dynamic error) {
if (error is DioException && error.response != null) {
return error.response!.statusCode == 403;
}
return false;
}
/// Détermine si l'erreur est une erreur de validation
static bool isValidationError(dynamic error) {
if (error is DioException && error.response != null) {
return error.response!.statusCode == 400 ||
error.response!.statusCode == 422;
}
return false;
}
/// Détermine si l'erreur est une erreur serveur
static bool isServerError(dynamic error) {
if (error is DioException && error.response != null) {
final statusCode = error.response!.statusCode ?? 0;
return statusCode >= 500 && statusCode < 600;
}
return false;
}
/// Extrait les détails de validation d'une erreur
static Map<String, dynamic>? getValidationErrors(dynamic error) {
if (error is DioException &&
error.response != null &&
error.response!.data is Map) {
final data = error.response!.data as Map;
if (data.containsKey('errors')) {
return data['errors'] as Map<String, dynamic>?;
}
if (data.containsKey('validationErrors')) {
return data['validationErrors'] as Map<String, dynamic>?;
}
}
return null;
}
}
/// Extension pour faciliter l'utilisation de ErrorHandler
extension ErrorHandlerExtension on Object {
/// Convertit l'objet en message d'erreur lisible
String toErrorMessage() => ErrorHandler.getErrorMessage(this);
/// Vérifie si c'est une erreur réseau
bool get isNetworkError => ErrorHandler.isNetworkError(this);
/// Vérifie si c'est une erreur d'authentification
bool get isAuthError => ErrorHandler.isAuthError(this);
/// Vérifie si c'est une erreur de permissions
bool get isPermissionError => ErrorHandler.isPermissionError(this);
/// Vérifie si c'est une erreur de validation
bool get isValidationError => ErrorHandler.isValidationError(this);
/// Vérifie si c'est une erreur serveur
bool get isServerError => ErrorHandler.isServerError(this);
/// Récupère les erreurs de validation
Map<String, dynamic>? get validationErrors => ErrorHandler.getValidationErrors(this);
}

View File

@@ -0,0 +1,50 @@
/// Exception de base pour l'application
abstract class AppException implements Exception {
final String message;
final String? code;
const AppException(this.message, [this.code]);
@override
String toString() => 'AppException: $message${code != null ? ' (Code: $code)' : ''}';
}
/// Exception serveur
class ServerException extends AppException {
const ServerException(super.message, [super.code]);
@override
String toString() => 'ServerException: $message${code != null ? ' (Code: $code)' : ''}';
}
/// Exception de cache
class CacheException extends AppException {
const CacheException(super.message, [super.code]);
@override
String toString() => 'CacheException: $message${code != null ? ' (Code: $code)' : ''}';
}
/// Exception de réseau
class NetworkException extends AppException {
const NetworkException(super.message, [super.code]);
@override
String toString() => 'NetworkException: $message${code != null ? ' (Code: $code)' : ''}';
}
/// Exception d'authentification
class AuthException extends AppException {
const AuthException(super.message, [super.code]);
@override
String toString() => 'AuthException: $message${code != null ? ' (Code: $code)' : ''}';
}
/// Exception de validation
class ValidationException extends AppException {
const ValidationException(super.message, [super.code]);
@override
String toString() => 'ValidationException: $message${code != null ? ' (Code: $code)' : ''}';
}

View File

@@ -0,0 +1,71 @@
import 'package:equatable/equatable.dart';
/// Classe de base pour tous les échecs
abstract class Failure extends Equatable {
final String message;
final String? code;
const Failure(this.message, [this.code]);
@override
List<Object?> get props => [message, code];
@override
String toString() => 'Failure: $message${code != null ? ' (Code: $code)' : ''}';
}
/// Échec serveur
class ServerFailure extends Failure {
const ServerFailure(super.message, [super.code]);
@override
String toString() => 'ServerFailure: $message${code != null ? ' (Code: $code)' : ''}';
}
/// Échec de cache
class CacheFailure extends Failure {
const CacheFailure(super.message, [super.code]);
@override
String toString() => 'CacheFailure: $message${code != null ? ' (Code: $code)' : ''}';
}
/// Échec de réseau
class NetworkFailure extends Failure {
const NetworkFailure(super.message, [super.code]);
@override
String toString() => 'NetworkFailure: $message${code != null ? ' (Code: $code)' : ''}';
}
/// Échec d'authentification
class AuthFailure extends Failure {
const AuthFailure(super.message, [super.code]);
@override
String toString() => 'AuthFailure: $message${code != null ? ' (Code: $code)' : ''}';
}
/// Échec de validation
class ValidationFailure extends Failure {
const ValidationFailure(super.message, [super.code]);
@override
String toString() => 'ValidationFailure: $message${code != null ? ' (Code: $code)' : ''}';
}
/// Échec de permission
class PermissionFailure extends Failure {
const PermissionFailure(super.message, [super.code]);
@override
String toString() => 'PermissionFailure: $message${code != null ? ' (Code: $code)' : ''}';
}
/// Échec de données non trouvées
class NotFoundFailure extends Failure {
const NotFoundFailure(super.message, [super.code]);
@override
String toString() => 'NotFoundFailure: $message${code != null ? ' (Code: $code)' : ''}';
}