feat(#132): upload photo à la création d'enfant (admin).
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http_parser/http_parser.dart';
|
||||
import 'package:p_tits_pas/models/user.dart';
|
||||
import 'package:p_tits_pas/models/enfant_admin_model.dart';
|
||||
import 'package:p_tits_pas/models/parent_model.dart';
|
||||
@@ -57,6 +58,33 @@ class UserService {
|
||||
return v.toString();
|
||||
}
|
||||
|
||||
/// MIME pour upload photo enfant (filtre Nest : jpg/jpeg/png/gif).
|
||||
static MediaType _imageMediaType(String filename, List<int> bytes) {
|
||||
if (bytes.length >= 3 &&
|
||||
bytes[0] == 0xFF &&
|
||||
bytes[1] == 0xD8 &&
|
||||
bytes[2] == 0xFF) {
|
||||
return MediaType('image', 'jpeg');
|
||||
}
|
||||
if (bytes.length >= 8 &&
|
||||
bytes[0] == 0x89 &&
|
||||
bytes[1] == 0x50 &&
|
||||
bytes[2] == 0x4E &&
|
||||
bytes[3] == 0x47) {
|
||||
return MediaType('image', 'png');
|
||||
}
|
||||
if (bytes.length >= 6 &&
|
||||
bytes[0] == 0x47 &&
|
||||
bytes[1] == 0x49 &&
|
||||
bytes[2] == 0x46) {
|
||||
return MediaType('image', 'gif');
|
||||
}
|
||||
final lower = filename.toLowerCase();
|
||||
if (lower.endsWith('.png')) return MediaType('image', 'png');
|
||||
if (lower.endsWith('.gif')) return MediaType('image', 'gif');
|
||||
return MediaType('image', 'jpeg');
|
||||
}
|
||||
|
||||
static String _errMessage(dynamic err) {
|
||||
if (err == null) return 'Erreur inconnue';
|
||||
if (err is String) return err;
|
||||
@@ -517,19 +545,55 @@ class UserService {
|
||||
|
||||
/// Création enfant côté staff (#132) — nécessite `POST /enfants` étendu (voir mini-spec).
|
||||
/// [parentUserId] : parent pivot du foyer (`parent_user_id`).
|
||||
/// Avec [photoBytes] : `multipart/form-data` (champ fichier `photo`), sinon JSON.
|
||||
static Future<EnfantAdminModel> createEnfant({
|
||||
required String parentUserId,
|
||||
required Map<String, dynamic> body,
|
||||
List<int>? photoBytes,
|
||||
String? photoFilename,
|
||||
}) async {
|
||||
final payload = <String, dynamic>{
|
||||
...body,
|
||||
'parent_user_id': parentUserId,
|
||||
};
|
||||
final response = await http.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.enfants}'),
|
||||
headers: await _headers(),
|
||||
body: jsonEncode(payload),
|
||||
);
|
||||
final hasPhoto = photoBytes != null && photoBytes.isNotEmpty;
|
||||
final http.Response response;
|
||||
if (hasPhoto) {
|
||||
final token = await TokenService.getToken();
|
||||
final req = http.MultipartRequest(
|
||||
'POST',
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.enfants}'),
|
||||
);
|
||||
req.headers['Accept'] = 'application/json';
|
||||
if (token != null) {
|
||||
req.headers['Authorization'] = 'Bearer $token';
|
||||
}
|
||||
body.forEach((key, value) {
|
||||
if (value == null) return;
|
||||
req.fields[key] = value is bool
|
||||
? (value ? 'true' : 'false')
|
||||
: value.toString();
|
||||
});
|
||||
req.fields['parent_user_id'] = parentUserId;
|
||||
final name = (photoFilename ?? '').trim();
|
||||
final filename = name.isNotEmpty ? name : 'photo.jpg';
|
||||
req.files.add(
|
||||
http.MultipartFile.fromBytes(
|
||||
'photo',
|
||||
photoBytes,
|
||||
filename: filename,
|
||||
contentType: _imageMediaType(filename, photoBytes),
|
||||
),
|
||||
);
|
||||
final streamed = await req.send();
|
||||
response = await http.Response.fromStream(streamed);
|
||||
} else {
|
||||
final payload = <String, dynamic>{
|
||||
...body,
|
||||
'parent_user_id': parentUserId,
|
||||
};
|
||||
response = await http.post(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.enfants}'),
|
||||
headers: await _headers(),
|
||||
body: jsonEncode(payload),
|
||||
);
|
||||
}
|
||||
if (response.statusCode != 200 && response.statusCode != 201) {
|
||||
throw Exception(
|
||||
_extractErrorMessage(response.body, 'Erreur création enfant'),
|
||||
|
||||
Reference in New Issue
Block a user