import 'dart:convert'; import '../../application/application.dart'; import '../../domain/domain.dart'; import 'http_api_client.dart'; final class HttpRemoteShareApi implements RemoteShareApi { const HttpRemoteShareApi(this.client); final HttpApiClient client; @override Future sendShare({ required ShareResourceType resourceType, required Map payload, required List recipientEmails, required String token, }) async { final response = await client.postJson( '/shares', bearerToken: token, body: { 'resourceType': _shareResourceTypeToWire(resourceType), 'payload': payload, 'recipientEmails': recipientEmails, }, expectedStatuses: const {201}, ); return RemoteShareSendResult( shareId: _requiredString(response, 'shareId'), recipientUserIds: _stringList(response, 'recipientUserIds'), unresolvedEmails: _stringList(response, 'unresolvedEmails'), ); } @override Future> fetchInbox(String token) async { final response = await client.getJson('/shares/inbox', bearerToken: token); return _list( response, 'items', ).map((item) => _inboxItemFromJson(_map(item))).toList(); } @override Future acceptShare(String shareId, String token) async { final response = await client.postJson( '/shares/$shareId/accept', bearerToken: token, ); return _syncedItemFromJson(_map(response['createdResource'])); } @override Future declineShare(String shareId, String token) { return client.postEmpty('/shares/$shareId/decline', bearerToken: token); } @override Future revokeShare(String shareId, String token) { return client.postEmpty('/shares/$shareId/revoke', bearerToken: token); } } ShareInboxItem _inboxItemFromJson(Map json) { return ShareInboxItem( shareId: _requiredString(json, 'shareId'), senderUserId: _requiredString(json, 'senderUserId'), resourceType: _shareResourceTypeFromWire( _requiredString(json, 'resourceType'), ), payloadJson: _jsonObjectString(json['payload']), status: _shareInboxStatusFromWire(_requiredString(json, 'status')), createdAt: _requiredDateTime(json, 'createdAt'), respondedAt: _optionalDateTime(json, 'respondedAt'), ); } RemoteSyncedItem _syncedItemFromJson(Map json) { return RemoteSyncedItem( resourceType: _syncResourceTypeFromWire( _requiredString(json, 'resourceType'), ), clientId: _requiredString(json, 'clientId'), serverId: _requiredString(json, 'serverId'), schemaVersion: _requiredInt(json, 'schemaVersion'), clientUpdatedAt: _requiredDateTime(json, 'clientUpdatedAt'), serverUpdatedAt: _requiredDateTime(json, 'serverUpdatedAt'), deletedAt: _optionalDateTime(json, 'deletedAt'), payload: _map(json['payload']), ); } String _shareResourceTypeToWire(ShareResourceType type) => switch (type) { ShareResourceType.program => 'program', ShareResourceType.workoutTemplate => 'workoutTemplate', }; ShareResourceType _shareResourceTypeFromWire(String value) => switch (value) { 'program' => ShareResourceType.program, 'workoutTemplate' => ShareResourceType.workoutTemplate, _ => throw RemoteAuthException( RemoteAuthFailure.unknown, 'Unknown share resource type: $value', ), }; ShareInboxStatus _shareInboxStatusFromWire(String value) => switch (value) { 'pending' => ShareInboxStatus.pending, 'accepted' => ShareInboxStatus.accepted, 'declined' => ShareInboxStatus.declined, 'revoked' => ShareInboxStatus.revoked, _ => throw RemoteAuthException( RemoteAuthFailure.unknown, 'Unknown share status: $value', ), }; SyncResourceType _syncResourceTypeFromWire(String value) => switch (value) { 'exercise' => SyncResourceType.exercise, 'program' => SyncResourceType.program, 'workoutTemplate' => SyncResourceType.workoutTemplate, 'workoutHistory' => SyncResourceType.workoutHistory, 'mediaAsset' => SyncResourceType.mediaAsset, _ => throw RemoteAuthException( RemoteAuthFailure.unknown, 'Unknown sync resource type: $value', ), }; List _list(Map json, String key) { final value = json[key]; if (value is List) { return value.cast(); } return const []; } List _stringList(Map json, String key) { return _list(json, key).whereType().toList(growable: false); } Map _map(Object? value) { if (value is Map) { return Map.from(value); } throw const RemoteAuthException( RemoteAuthFailure.unknown, 'Expected JSON object.', ); } String _requiredString(Map json, String key) { final value = json[key]; if (value is String && value.trim().isNotEmpty) { return value; } throw RemoteAuthException( RemoteAuthFailure.unknown, '$key is missing from share response.', ); } int _requiredInt(Map json, String key) { final value = json[key]; if (value is int) { return value; } throw RemoteAuthException( RemoteAuthFailure.unknown, '$key is missing from share response.', ); } DateTime _requiredDateTime(Map json, String key) { return DateTime.parse(_requiredString(json, key)).toUtc(); } DateTime? _optionalDateTime(Map json, String key) { final value = json[key]; return value is String && value.trim().isNotEmpty ? DateTime.parse(value).toUtc() : null; } String _jsonObjectString(Object? value) { return jsonEncode(_map(value)); }