From 1757e4a5d8bf13b976a1649fe26c393fefbd45d7 Mon Sep 17 00:00:00 2001 From: Janis Wehen Date: Mon, 16 Mar 2026 17:51:28 +0100 Subject: [PATCH 1/3] remove invalid documents if the id field is not given --- .../services/yust_database_service_dart.dart | 7 ++ .../yust_database_service_flutter.dart | 113 +++++++++--------- 2 files changed, 64 insertions(+), 56 deletions(-) diff --git a/lib/src/services/yust_database_service_dart.dart b/lib/src/services/yust_database_service_dart.dart index 9ac86d61..166db640 100644 --- a/lib/src/services/yust_database_service_dart.dart +++ b/lib/src/services/yust_database_service_dart.dart @@ -1504,6 +1504,13 @@ class YustDatabaseService implements IYustDatabaseService { return null; } + if (json['id'] is! String || json['id'].isEmpty) { + print( + '[[WARNING]] Error Transforming JSON. Document has no id: Collection ${docSetup.collectionName}, Name ${document.name}', + ); + return null; + } + try { final doc = docSetup.fromJson(json); doc.clearUpdateMask(); diff --git a/lib/src/services/yust_database_service_flutter.dart b/lib/src/services/yust_database_service_flutter.dart index 19dac26c..5953f14c 100644 --- a/lib/src/services/yust_database_service_flutter.dart +++ b/lib/src/services/yust_database_service_flutter.dart @@ -35,36 +35,35 @@ class YustDatabaseService implements IYustDatabaseService { final Yust _yust; YustDatabaseService({required Yust yust, String? emulatorAddress}) - : _yust = yust, - envCollectionName = yust.envCollectionName, - useSubcollections = yust.useSubcollections, - _fireStore = FirebaseFirestore.instance { - dbLogCallback = - ( - DatabaseLogAction action, - String documentPath, - int count, { - String? id, - List? updateMask, - num? aggregationResult, - }) { - statistics.dbStatisticsCallback( - action, - documentPath, - count, - id: id, - updateMask: updateMask, - aggregationResult: aggregationResult, - ); - yust.dbLogCallback?.call( - action, - documentPath, - count, - id: id, - updateMask: updateMask, - aggregationResult: aggregationResult, - ); - }; + : _yust = yust, + envCollectionName = yust.envCollectionName, + useSubcollections = yust.useSubcollections, + _fireStore = FirebaseFirestore.instance { + dbLogCallback = ( + DatabaseLogAction action, + String documentPath, + int count, { + String? id, + List? updateMask, + num? aggregationResult, + }) { + statistics.dbStatisticsCallback( + action, + documentPath, + count, + id: id, + updateMask: updateMask, + aggregationResult: aggregationResult, + ); + yust.dbLogCallback?.call( + action, + documentPath, + count, + id: id, + updateMask: updateMask, + aggregationResult: aggregationResult, + ); + }; } /// Represents the collection name for the tenants. @@ -76,10 +75,10 @@ class YustDatabaseService implements IYustDatabaseService { final bool useSubcollections; YustDatabaseService.mocked({required Yust yust, String? emulatorAddress}) - : _yust = yust, - envCollectionName = yust.envCollectionName, - useSubcollections = yust.useSubcollections, - dbLogCallback = yust.dbLogCallback { + : _yust = yust, + envCollectionName = yust.envCollectionName, + useSubcollections = yust.useSubcollections, + dbLogCallback = yust.dbLogCallback { throw UnsupportedError('Not supported in Flutter Environment'); } @@ -97,12 +96,12 @@ class YustDatabaseService implements IYustDatabaseService { .get(GetOptions(source: Source.serverAndCache)) .then((docSnapshot) => _transformDoc(docSetup, docSnapshot)) .catchError((e) { - if (e is FirebaseException && e.code == 'permission-denied') { - print('Permission denied for doc: ${docSetup.collectionName}/$id'); - return null; - } - throw e; - }); + if (e is FirebaseException && e.code == 'permission-denied') { + print('Permission denied for doc: ${docSetup.collectionName}/$id'); + return null; + } + throw e; + }); dbLogCallback?.call( DatabaseLogAction.get, _getCollectionPath(docSetup), @@ -167,13 +166,13 @@ class YustDatabaseService implements IYustDatabaseService { .doc(id) .snapshots() .map((docSnapshot) { - dbLogCallback?.call( - DatabaseLogActionExtension.fromSnapshot(docSnapshot), - docSnapshot.reference.parent.path, - docSnapshot.exists ? 1 : 0, - ); - return _transformDoc(docSetup, docSnapshot); - }); + dbLogCallback?.call( + DatabaseLogActionExtension.fromSnapshot(docSnapshot), + docSnapshot.reference.parent.path, + docSnapshot.exists ? 1 : 0, + ); + return _transformDoc(docSetup, docSnapshot); + }); } @override @@ -419,9 +418,8 @@ class YustDatabaseService implements IYustDatabaseService { int? limit, }) async { var query = getQuery(docSetup, filters: filters); - final snapshot = await query - .aggregate(cf.average(fieldPath), cf.count()) - .get(); + final snapshot = + await query.aggregate(cf.average(fieldPath), cf.count()).get(); return (count: snapshot.count ?? 0, result: snapshot.getAverage(fieldPath)); } @@ -704,9 +702,8 @@ class YustDatabaseService implements IYustDatabaseService { YustDocSetup docSetup, T doc, ) async { - final docRef = _fireStore - .collection(_getCollectionPath(docSetup)) - .doc(doc.id); + final docRef = + _fireStore.collection(_getCollectionPath(docSetup)).doc(doc.id); await docRef.delete(); dbLogCallback?.call( DatabaseLogAction.delete, @@ -721,9 +718,8 @@ class YustDatabaseService implements IYustDatabaseService { YustDocSetup docSetup, String docId, ) async { - final docRef = _fireStore - .collection(_getCollectionPath(docSetup)) - .doc(docId); + final docRef = + _fireStore.collection(_getCollectionPath(docSetup)).doc(docId); await docRef.delete(); dbLogCallback?.call( DatabaseLogAction.delete, @@ -956,6 +952,11 @@ class YustDatabaseService implements IYustDatabaseService { } final data = snapshot.data(); if (data is Map) { + if (data['id'] is! String || data['id'].isEmpty) { + print( + '[[WARNING]] Error Transforming JSON. Document has no id: ${snapshot.reference.path}'); + return null; + } // Convert Timestamps to ISOStrings final modifiedData = TraverseObject.traverseObject(data, (currentNode) { // Convert Timestamp to Iso8601-String, as this is the format json_serializable expects From 1e4dcce4bc5acce3b12e4af9b0afe5ecbff1ffbf Mon Sep 17 00:00:00 2001 From: Janis Wehen Date: Mon, 16 Mar 2026 18:31:04 +0100 Subject: [PATCH 2/3] always generate id in doc constructor --- lib/src/models/yust_doc.dart | 11 +++++++---- lib/src/services/yust_database_service_dart.dart | 6 +----- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/src/models/yust_doc.dart b/lib/src/models/yust_doc.dart index 6111e50c..15bc86a9 100644 --- a/lib/src/models/yust_doc.dart +++ b/lib/src/models/yust_doc.dart @@ -87,7 +87,7 @@ abstract class YustDoc { bool get hasChanges => _updateMask.isNotEmpty; YustDoc({ - String id = '', + String? id, DateTime? createdAt, String? createdBy, DateTime? modifiedAt, @@ -95,7 +95,7 @@ abstract class YustDoc { String? userId, String? envId, DateTime? expiresAt, - }) : _id = id, + }) : _id = id ?? createDocumentId(), _createdAt = createdAt, _createdBy = createdBy, _modifiedAt = modifiedAt, @@ -104,8 +104,6 @@ abstract class YustDoc { _envId = envId, _expiresAt = expiresAt; - YustDoc.fromJson(Map json) : _id = ''; - Map toJson(); Map toExportJson() { @@ -125,6 +123,11 @@ abstract class YustDoc { return GoogleCloudHelpers.convertTimestamp(value); } + /// Creates a new document ID. + static String createDocumentId() { + return Yust.helpers.randomString(length: 20); + } + /// clear the update mask void clearUpdateMask() => updateMask.clear(); } diff --git a/lib/src/services/yust_database_service_dart.dart b/lib/src/services/yust_database_service_dart.dart index 166db640..c669a16f 100644 --- a/lib/src/services/yust_database_service_dart.dart +++ b/lib/src/services/yust_database_service_dart.dart @@ -107,7 +107,7 @@ class YustDatabaseService implements IYustDatabaseService { /// assigned a new id becoming a new document if it had an id previously. @override T initDoc(YustDocSetup docSetup, [T? doc]) { - final id = _createDocumentId(); + final id = YustDoc.createDocumentId(); return doInitDoc(docSetup, id, doc); } @@ -1622,10 +1622,6 @@ class YustDatabaseService implements IYustDatabaseService { } } - String _createDocumentId() { - return Yust.helpers.randomString(length: 20); - } - /// Retries the given function if a TlsException, ClientException or YustBadGatewayException occurs. /// Those are network errors that can occur when the firestore is rate-limiting. Future _retryOnException( From 538f8cfb661bc25ae9a896cdf6314cfb6088dde0 Mon Sep 17 00:00:00 2001 From: Janis Wehen Date: Mon, 16 Mar 2026 18:43:31 +0100 Subject: [PATCH 3/3] ensure all fields are set in constructor of yust user --- lib/src/models/yust_user.dart | 8 ++++++++ lib/src/models/yust_user.g.dart | 28 ++++++++++++++-------------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/lib/src/models/yust_user.dart b/lib/src/models/yust_user.dart index 4bb9225d..6eaea53b 100644 --- a/lib/src/models/yust_user.dart +++ b/lib/src/models/yust_user.dart @@ -186,6 +186,14 @@ class YustUser extends YustDoc { Map get userAttributes => Map.unmodifiable(_userAttributes); YustUser({ + super.id, + super.createdAt, + super.createdBy, + super.modifiedAt, + super.modifiedBy, + super.userId, + super.envId, + super.expiresAt, required String email, required String firstName, required String lastName, diff --git a/lib/src/models/yust_user.g.dart b/lib/src/models/yust_user.g.dart index ab77acc6..5aa58b54 100644 --- a/lib/src/models/yust_user.g.dart +++ b/lib/src/models/yust_user.g.dart @@ -8,6 +8,20 @@ part of 'yust_user.dart'; YustUser _$YustUserFromJson(Map json) => YustUser( + id: json['id'] as String?, + createdAt: json['createdAt'] == null + ? null + : DateTime.parse(json['createdAt'] as String), + createdBy: json['createdBy'] as String?, + modifiedAt: json['modifiedAt'] == null + ? null + : DateTime.parse(json['modifiedAt'] as String), + modifiedBy: json['modifiedBy'] as String?, + userId: json['userId'] as String?, + envId: json['envId'] as String?, + expiresAt: json['expiresAt'] == null + ? null + : DateTime.parse(json['expiresAt'] as String), email: json['email'] as String, firstName: json['firstName'] as String, lastName: json['lastName'] as String, @@ -19,20 +33,6 @@ YustUser _$YustUserFromJson(Map json) => authId: json['authId'] as String?, locale: json['locale'] as String?, ) - ..id = json['id'] as String - ..createdAt = json['createdAt'] == null - ? null - : DateTime.parse(json['createdAt'] as String) - ..createdBy = json['createdBy'] as String? - ..modifiedAt = json['modifiedAt'] == null - ? null - : DateTime.parse(json['modifiedAt'] as String) - ..modifiedBy = json['modifiedBy'] as String? - ..userId = json['userId'] as String? - ..envId = json['envId'] as String? - ..expiresAt = json['expiresAt'] == null - ? null - : DateTime.parse(json['expiresAt'] as String) .._envIds = (json['envIds'] as Map?)?.map( (k, e) => MapEntry(k as String, e as bool?),