feat(inscription): payload enfant/photo et limite JSON 15 Mo côté API

- Express/Nest : body parser json et urlencoded à 15 Mo pour les photos base64.
- Front : envoi d’inscription parent (authService, payload, étape 3).

Made-with: Cursor
This commit is contained in:
2026-04-11 16:36:15 +02:00
parent d7ccbfbe35
commit 4969be5809
5 changed files with 155 additions and 37 deletions
@@ -1,5 +1,4 @@
class ApiConfig {
// static const String baseUrl = 'http://localhost:3000/api/v1/';
static const String baseUrl = 'https://app.ptits-pas.fr/api/v1';
// Auth endpoints
+102 -16
View File
@@ -185,7 +185,7 @@ class AuthService {
return;
}
final decoded = response.body.isNotEmpty ? jsonDecode(response.body) : null;
final decoded = _tryDecodeJsonMap(response.body);
final message = _extractErrorMessage(decoded, response.statusCode);
throw Exception(message);
}
@@ -199,37 +199,123 @@ class AuthService {
}
final body = ParentRegistrationPayload.toJson(data);
final String encodedBody;
try {
encodedBody = jsonEncode(body);
} catch (_) {
throw Exception(
'Impossible de préparer lenvoi (données trop volumineuses ou invalides). '
'Réessayez avec une photo plus légère ou sans caractères inhabituels.',
);
}
final response = await http.post(
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.registerParent}'),
headers: ApiConfig.headers,
body: jsonEncode(body),
);
late final http.Response response;
try {
response = await http.post(
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.registerParent}'),
headers: ApiConfig.headers,
body: encodedBody,
);
} on http.ClientException {
throw Exception(
'Connexion à ${ApiConfig.baseUrl} impossible. Vérifiez votre réseau, '
'un pare-feu ou un bloqueur, puis réessayez.',
);
}
if (response.statusCode == 200 || response.statusCode == 201) {
return;
}
final decoded = response.body.isNotEmpty ? jsonDecode(response.body) : null;
final message = _extractErrorMessage(decoded, response.statusCode);
final decoded = _tryDecodeJsonMap(response.body);
var message = _extractErrorMessage(decoded, response.statusCode);
message = _humanizeParentRegistrationHttpError(message, response.statusCode);
throw Exception(message);
}
static Map<String, dynamic>? _tryDecodeJsonMap(String body) {
if (body.isEmpty) return null;
try {
final decoded = jsonDecode(body);
if (decoded is Map) {
return Map<String, dynamic>.from(decoded);
}
return null;
} catch (_) {
return null;
}
}
/// Aplatit `message` (string, liste, ou objet Nest / AllExceptionsFilter).
static String? _flattenApiMessageField(dynamic field) {
if (field == null) return null;
if (field is String) {
final t = field.trim();
return t.isEmpty ? null : t;
}
if (field is List) {
final parts = field
.map(_flattenApiMessageField)
.whereType<String>()
.where((s) => s.isNotEmpty)
.toList();
if (parts.isEmpty) return null;
return parts.join('. ');
}
if (field is Map) {
if (field['message'] != null) {
final inner = _flattenApiMessageField(field['message']);
if (inner != null) return inner;
}
final err = field['error'];
if (err is String && err.trim().isNotEmpty) return err.trim();
}
return null;
}
/// Extrait le message d'erreur des réponses NestJS (message string, array, ou objet).
static String _extractErrorMessage(dynamic decoded, int statusCode) {
const fallback = 'Erreur lors de l\'inscription';
if (decoded == null || decoded is! Map) return '$fallback ($statusCode)';
final msg = decoded['message'];
if (msg == null) {
final err = decoded['error'];
return (err is String ? err : err?.toString()) ?? '$fallback ($statusCode)';
if (decoded == null || decoded is! Map) {
return '$fallback ($statusCode)';
}
if (msg is String) return msg;
if (msg is List) return msg.map((e) => e.toString()).join('. ').trim();
if (msg is Map && msg['message'] != null) return msg['message'].toString();
final map = decoded;
final fromMessage = _flattenApiMessageField(map['message']);
if (fromMessage != null) return fromMessage;
final err = map['error'];
if (err is String && err.trim().isNotEmpty) return err.trim();
return '$fallback ($statusCode)';
}
/// Remplace « Internal server error » (souvent contrainte SQL / 500) par un texte utile à linscription.
static String _humanizeParentRegistrationHttpError(String msg, int statusCode) {
if (statusCode == 409) return msg;
final lower = msg.toLowerCase().trim();
final looksLikeGenericServerError = lower == 'internal server error' ||
lower == 'internal server error.' ||
lower.contains('internal server error');
if (statusCode >= 500 && looksLikeGenericServerError) {
return 'Impossible d\'enregistrer votre demande pour le moment. '
'Souvent, cela signifie que cette adresse e-mail est déjà utilisée : '
'connectez-vous ou utilisez une autre adresse pour le parent principal '
'(et pour le co-parent si vous en indiquez un). '
'Si le problème continue, réessayez plus tard ou contactez le support.';
}
if (statusCode >= 500) {
if (lower.contains('duplicate') ||
lower.contains('unique constraint') ||
lower.contains('23505') ||
lower.contains('already exist')) {
return 'Cette adresse e-mail semble déjà enregistrée. '
'Essayez de vous connecter ou modifiez l\'adresse du parent ou du co-parent.';
}
}
return msg;
}
/// Rafraîchit le profil utilisateur depuis l'API
static Future<AppUser?> refreshCurrentUser() async {
final token = await TokenService.getToken();