Created
November 23, 2020 22:47
-
-
Save Jonas-Sander/4ad27f11aaeeeccb1ed0fef39777d6b6 to your computer and use it in GitHub Desktop.
Fixed googleapis Firestore Query
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:convert'; | |
// ignore: import_of_legacy_library_into_null_safe | |
import 'package:googleapis/firestore/v1.dart'; | |
// ignore: import_of_legacy_library_into_null_safe | |
import 'package:http/http.dart' as http; | |
extension FirestoreRunQueryFixedExtension | |
on ProjectsDatabasesDocumentsResourceApi { | |
/// Runs a query. | |
/// | |
/// [request] - The metadata request object. | |
/// | |
/// Request parameters: | |
/// | |
/// [parent] - The parent resource name. In the format: | |
/// `projects/{project_id}/databases/{database_id}/documents` or | |
/// `projects/{project_id}/databases/{database_id}/documents/{document_path}`. | |
/// For example: | |
/// `projects/my-project/databases/my-database/documents` or | |
/// `projects/my-project/databases/my-database/documents/chatrooms/my-chatroom` | |
/// Value must have pattern | |
/// "^projects/[^/]+/databases/[^/]+/documents/[^/]+/.+$". | |
/// | |
/// [$fields] - Selector specifying which fields to include in a partial | |
/// response. | |
/// | |
/// Completes with a [RunQueryResponse]. | |
/// | |
/// Completes with a [commons.ApiRequestError] if the API endpoint returned an | |
/// error. | |
/// | |
/// If the used [http.Client] completes with an error when making a REST call, | |
/// this method will complete with the same error. | |
Future<List<Document>> runQueryFixed(RunQueryRequest request, | |
{required http.Client client, required String? parent}) async { | |
final urlParentAddition = parent != null ? '/$parent' : ''; | |
final url = | |
'https://firestore.googleapis.com/v1/projects/sharezone-debug/databases/(default)/documents$urlParentAddition:runQuery'; | |
final body = json.encode(request.toJson()); | |
final response = await client.post(url, body: body); | |
final resBody = response.body; | |
final List<dynamic> decoded = json.decode(resBody) as List; | |
if (decoded[0]['error'] != null) { | |
throw Exception('Firestore Error: $resBody!'); | |
} | |
final docs = [ | |
for (final docJson in decoded) | |
if (docJson['document'] != null) | |
Document.fromJson(docJson['document'] as Map) | |
]; | |
return docs; | |
} | |
} | |
Future<List<Document>> runQuery({ | |
required String fieldPath, | |
required String operation, | |
required dynamic value, | |
required String collectionId, | |
required String? parentPath, | |
required ProjectsDatabasesDocumentsResourceApi api, | |
required http.Client client, | |
int? limit, | |
}) async { | |
final chatFieldReference = FieldReference()..fieldPath = fieldPath; | |
final fstoreValue = '$value'.toFirestoreValue(); | |
final fieldFilter = FieldFilter() | |
..field = chatFieldReference | |
..op = operation | |
..value = fstoreValue; | |
final filter = Filter()..fieldFilter = fieldFilter; | |
final structuredQuery = StructuredQuery() | |
..where = filter | |
..from = [CollectionSelector()..collectionId = collectionId]; | |
if (limit != null) { | |
structuredQuery.limit = limit; | |
} | |
final runQueryRequest = RunQueryRequest()..structuredQuery = structuredQuery; | |
final documents = await api.runQueryFixed(runQueryRequest, | |
client: client, parent: parentPath); | |
return documents; | |
} | |
Future<List<Document>> runMultiConditionQuery({ | |
required List<Condition> conditions, | |
required String collectionId, | |
required String? parentPath, | |
required ProjectsDatabasesDocumentsResourceApi api, | |
required http.Client client, | |
}) async { | |
final filters = conditions.map((condition) { | |
final chatFieldReference = FieldReference() | |
..fieldPath = condition.fieldPath; | |
final fstoreValue = '${condition.value}'.toFirestoreValue(); | |
final fieldFilter = FieldFilter() | |
..field = chatFieldReference | |
..op = condition.operation | |
..value = fstoreValue; | |
return Filter()..fieldFilter = fieldFilter; | |
}).toList(); | |
final compositeFilter = CompositeFilter() | |
..filters = filters | |
..op = 'AND'; | |
final filter = Filter()..compositeFilter = compositeFilter; | |
final structuredQuery = StructuredQuery() | |
..where = filter | |
..from = [CollectionSelector()..collectionId = collectionId]; | |
final runQueryRequest = RunQueryRequest()..structuredQuery = structuredQuery; | |
final documents = await api.runQueryFixed(runQueryRequest, | |
client: client, parent: parentPath); | |
return documents; | |
} | |
class Condition { | |
final String fieldPath; | |
final String operation; | |
final dynamic value; | |
Condition({ | |
required this.fieldPath, | |
required this.operation, | |
required this.value, | |
}); | |
} | |
extension MapToFirestoreMap on Map<String, dynamic> { | |
Map<String, Value> toFirestoreMap() { | |
return map((key, value) { | |
return MapEntry(key, _toFirestoreValue(value)); | |
}); | |
} | |
} | |
extension ToFirestoreValueList<T> on Iterable<T> { | |
Value toFirestoreValue() { | |
final arrVal = ArrayValue() | |
..values = map<Value>((e) => (e as dynamic).toFirestoreValue() as Value) | |
.toList(); | |
return Value()..arrayValue = arrVal; | |
} | |
} | |
Value _toFirestoreValue(dynamic val) { | |
if (val is String) { | |
return Value()..stringValue = val; | |
} | |
if (val is int) { | |
return Value()..integerValue = val.toString(); | |
} | |
if (val is bool) { | |
return Value()..booleanValue = val; | |
} | |
if (val is DateTime) { | |
return Value()..timestampValue = val.toUtcIso8601String(); | |
} | |
if (val is double) { | |
return Value()..doubleValue = val; | |
} | |
if (val is List) { | |
final arrVal = ArrayValue() | |
..values = val.map<Value>(_toFirestoreValue).toList(); | |
return Value()..arrayValue = arrVal; | |
} | |
if (val is Map<String, dynamic>) { | |
final mapVals = val.toFirestoreMap(); | |
final mapVal = MapValue()..fields = mapVals; | |
return Value()..mapValue = mapVal; | |
} | |
if (val is Set) { | |
return Value()..arrayValue = _toFirestoreValue(val.toList()).arrayValue; | |
} | |
throw ArgumentError( | |
'Cant convert ${val.runtimeType} to a Firestore Value.', | |
); | |
} | |
extension ToFirestoreValue on Object { | |
Value toFirestoreValue() => _toFirestoreValue(this); | |
} | |
extension FirestoreMapToMap on Map<String, Value> { | |
Map<String, dynamic> toPrimitives() { | |
return map((key, value) => MapEntry(key, value.toPrimitive())); | |
} | |
} | |
extension ValueToPrimitive on Value { | |
dynamic toPrimitive() { | |
if (arrayValue != null) { | |
return [ | |
for (final val in arrayValue.values) val.toPrimitive(), | |
]; | |
} | |
if (booleanValue != null) { | |
return booleanValue; | |
} | |
if (bytesValue != null) { | |
return bytesValueAsBytes; | |
} | |
if (doubleValue != null) { | |
return doubleValue; | |
} | |
if (geoPointValue != null) { | |
return geoPointValue; | |
} | |
if (integerValue != null) { | |
return int.parse(integerValue); | |
} | |
if (mapValue != null) { | |
return mapValue.fields | |
.map((key, value) => MapEntry(key, value.toPrimitive())); | |
} | |
if (nullValue != null) { | |
return null; | |
} | |
if (referenceValue != null) { | |
return referenceValue; | |
} | |
if (stringValue != null) { | |
return stringValue; | |
} | |
if (timestampValue != null) { | |
return DateTime.parse(timestampValue); | |
} | |
throw UnimplementedError('Cant convert to primtive ${toJson()}'); | |
} | |
} | |
extension DateTimeToUtcIso8601StringExtension on DateTime { | |
String toUtcIso8601String() { | |
if (!isUtc) { | |
return toUtc().toIso8601String(); | |
} | |
return toIso8601String(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment