chore(wip): consolidation intermédiaire multi-tickets (sprints Statistiques, UI, Bug resolution, Serveur-client)
Regroupe l'état de travail en cours réalisé dans un même worktree sur plusieurs tickets/sprints (#85, #136, #145, #155-160, #162-164), mélangeant des tickets QA et inProgress. Ne constitue pas une feature terminée : commit de sauvegarde avant triage/split par ticket en branches feature/* dédiées. Exclut les dossiers d'environnement de build locaux et le heap dump parasite (.gitignore mis à jour). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -30,14 +30,21 @@ final class ShareApi {
|
||||
}
|
||||
try {
|
||||
final body = await _readJsonObject(request);
|
||||
final shareKind = _optionalString(body, 'shareKind') ?? 'single';
|
||||
final result = await createShare.execute(
|
||||
senderUserId: authenticated.user.id,
|
||||
resourceType: _requiredString(body, 'resourceType'),
|
||||
payloadJson: _requiredObject(body, 'payload'),
|
||||
shareKind: shareKind,
|
||||
packName: _optionalString(body, 'packName'),
|
||||
resourceType: _optionalString(body, 'resourceType'),
|
||||
payloadJson: _optionalObject(body, 'payload'),
|
||||
packItems: shareKind == ShareKind.pack.wireName
|
||||
? _packItems(body['items'])
|
||||
: const [],
|
||||
recipientEmails: _requiredStringList(body, 'recipientEmails'),
|
||||
);
|
||||
return _jsonResponse(201, {
|
||||
'shareId': result.share.id,
|
||||
'shareKind': result.share.kind.wireName,
|
||||
'recipientUserIds': result.recipientUserIds,
|
||||
'unresolvedEmails': result.unresolvedEmails,
|
||||
});
|
||||
@ -62,7 +69,9 @@ final class ShareApi {
|
||||
{
|
||||
'shareId': item.share.id,
|
||||
'senderUserId': item.share.senderUserId,
|
||||
'resourceType': item.share.resourceType.wireName,
|
||||
'shareKind': item.share.kind.wireName,
|
||||
'packName': item.share.packName,
|
||||
'resourceType': item.share.resourceType?.wireName,
|
||||
'payload': item.share.payloadJson,
|
||||
'status': item.recipient.status.wireName,
|
||||
'createdAt': item.share.createdAt.toIso8601String(),
|
||||
@ -83,7 +92,12 @@ final class ShareApi {
|
||||
recipientUserId: authenticated.user.id,
|
||||
);
|
||||
return _jsonResponse(200, {
|
||||
'createdResource': _resourceJson(result.createdResource),
|
||||
if (result.createdResources.length == 1)
|
||||
'createdResource': _resourceJson(result.createdResource),
|
||||
'createdResources': [
|
||||
for (final resource in result.createdResources)
|
||||
_resourceJson(resource),
|
||||
],
|
||||
});
|
||||
} on ShareNotFoundException catch (error) {
|
||||
return _errorResponse(404, error.message);
|
||||
@ -140,6 +154,26 @@ Map<String, Object?> _resourceJson(SyncedResource resource) {
|
||||
};
|
||||
}
|
||||
|
||||
List<SharePackItemInput> _packItems(Object? rawItems) {
|
||||
if (rawItems is! List) {
|
||||
throw const FormatException('items must be an array.');
|
||||
}
|
||||
return [
|
||||
for (final rawItem in rawItems)
|
||||
if (rawItem is Map)
|
||||
_packItem(Map<String, Object?>.from(rawItem))
|
||||
else
|
||||
throw const FormatException('items must contain only objects.'),
|
||||
];
|
||||
}
|
||||
|
||||
SharePackItemInput _packItem(Map<String, Object?> item) {
|
||||
return SharePackItemInput(
|
||||
resourceType: _requiredString(item, 'resourceType'),
|
||||
payloadJson: _requiredObject(item, 'payload'),
|
||||
);
|
||||
}
|
||||
|
||||
Future<Map<String, Object?>> _readJsonObject(Request request) async {
|
||||
final raw = await request.readAsString();
|
||||
final decoded = jsonDecode(raw);
|
||||
@ -157,6 +191,18 @@ String _requiredString(Map<String, Object?> body, String key) {
|
||||
return value;
|
||||
}
|
||||
|
||||
String? _optionalString(Map<String, Object?> body, String key) {
|
||||
final value = body[key];
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value is! String) {
|
||||
throw FormatException('$key must be a string.');
|
||||
}
|
||||
final trimmed = value.trim();
|
||||
return trimmed.isEmpty ? null : trimmed;
|
||||
}
|
||||
|
||||
Map<String, Object?> _requiredObject(Map<String, Object?> body, String key) {
|
||||
final value = body[key];
|
||||
if (value is Map<String, Object?>) {
|
||||
@ -168,6 +214,13 @@ Map<String, Object?> _requiredObject(Map<String, Object?> body, String key) {
|
||||
throw FormatException('$key must be a JSON object.');
|
||||
}
|
||||
|
||||
Map<String, Object?>? _optionalObject(Map<String, Object?> body, String key) {
|
||||
if (!body.containsKey(key)) {
|
||||
return null;
|
||||
}
|
||||
return _requiredObject(body, key);
|
||||
}
|
||||
|
||||
List<String> _requiredStringList(Map<String, Object?> body, String key) {
|
||||
final value = body[key];
|
||||
if (value is! List) {
|
||||
|
||||
Reference in New Issue
Block a user