import '../../application/application.dart'; import 'http_api_client.dart'; final class HttpRemoteSyncApi implements RemoteSyncApi { const HttpRemoteSyncApi(this.client); final HttpApiClient client; @override Future push({ required String deviceId, required List items, required String token, }) async { final response = await client.postJson( '/sync/push', bearerToken: token, body: { 'deviceId': deviceId, 'items': items.map(_pushItemToJson).toList(), }, ); return RemoteSyncPushResult( serverCursor: _optionalString(response, 'serverCursor'), results: (_list( response, 'results', )).map((item) => _pushItemResultFromJson(_map(item))).toList(), ); } @override Future pull({ required String? since, required String token, }) async { final response = await client.getJson( '/sync/pull', bearerToken: token, queryParameters: {'since': since}, ); return RemoteSyncPullResult( serverCursor: _optionalString(response, 'serverCursor'), items: (_list( response, 'items', )).map((item) => _syncedItemFromJson(_map(item))).toList(), ); } Map _pushItemToJson(RemoteSyncPushItem item) => { 'resourceType': _resourceTypeToWire(item.resourceType), 'clientId': item.clientId, 'schemaVersion': item.schemaVersion, 'clientUpdatedAt': item.clientUpdatedAt.toUtc().toIso8601String(), 'deletedAt': item.deletedAt?.toUtc().toIso8601String(), 'payload': item.payload, }; RemoteSyncPushItemResult _pushItemResultFromJson(Map json) { return RemoteSyncPushItemResult( resourceType: _resourceTypeFromWire( _requiredString(json, 'resourceType'), ), clientId: _requiredString(json, 'clientId'), serverId: _optionalString(json, 'serverId'), status: _pushStatusFromWire(_requiredString(json, 'status')), serverUpdatedAt: _optionalDateTime(json, 'serverUpdatedAt'), errorMessage: _optionalString(json, 'message'), ); } RemoteSyncedItem _syncedItemFromJson(Map json) { return RemoteSyncedItem( resourceType: _resourceTypeFromWire( _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 _resourceTypeToWire(SyncResourceType type) => switch (type) { SyncResourceType.exercise => 'exercise', SyncResourceType.program => 'program', SyncResourceType.workoutTemplate => 'workoutTemplate', SyncResourceType.workoutHistory => 'workoutHistory', SyncResourceType.mediaAsset => 'mediaAsset', }; SyncResourceType _resourceTypeFromWire(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', ), }; RemoteSyncPushStatus _pushStatusFromWire(String value) => switch (value) { 'accepted' => RemoteSyncPushStatus.accepted, 'ignoredOlder' => RemoteSyncPushStatus.ignoredOlder, 'error' => RemoteSyncPushStatus.error, _ => throw RemoteAuthException( RemoteAuthFailure.unknown, 'Unknown sync push status: $value', ), }; List _list(Map json, String key) { final value = json[key]; if (value is List) { return value.cast(); } return const []; } 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 sync response.', ); } String? _optionalString(Map json, String key) { final value = json[key]; return value is String && value.trim().isNotEmpty ? value : null; } int _requiredInt(Map json, String key) { final value = json[key]; if (value is int) { return value; } throw RemoteAuthException( RemoteAuthFailure.unknown, '$key is missing from sync response.', ); } DateTime _requiredDateTime(Map json, String key) { return DateTime.parse(_requiredString(json, key)).toUtc(); } DateTime? _optionalDateTime(Map json, String key) { final value = _optionalString(json, key); return value == null ? null : DateTime.parse(value).toUtc(); }