@@ -9,7 +9,7 @@ import '../utils/constants.dart';
99
1010const String _label = 'Cloud Database' ;
1111
12- const List <String > _privateDocumentFields = [
12+ const List <String > privateDocumentFields = [
1313 SDKConstants .id,
1414 SDKConstants .createdAt,
1515 SDKConstants .updatedAt,
@@ -289,15 +289,23 @@ class FirestoreCloudDatabase extends CloudDatabase {
289289 bool skipCreationIfDocumentExists = true ,
290290 required Map <String , dynamic > value,
291291 }) async {
292- // filter data for private fields.
293- value = sanitizeCloudDataToSend (value);
294-
292+ // Auto generate id if desired.
295293 if (autoGenerateId) {
296294 // if autoGenerateId is true, then skipCreationIfDocumentExists and docId is ignored.
297295 final document = await rootRef.collection (path).add (value);
298296 logger.log (_label, 'Document added: ${document .path }' );
297+
298+ // Sanitize data afterwards because we didn't have the docId before.
299+ value = sanitizeCloudDataToSend (value, docId: document.id);
300+ await document.set (value);
301+
299302 return true ;
300303 }
304+
305+ // Since we're not auto-generating ids at this point, we can sanitize
306+ // the data before we do anything, ahead of time.
307+ value = sanitizeCloudDataToSend (value, docId: documentId);
308+
301309 // Get doc reference.
302310 final DocumentReference docRef = getDocPath (path, documentId);
303311
@@ -330,7 +338,7 @@ class FirestoreCloudDatabase extends CloudDatabase {
330338 // }
331339
332340 // Sanitize data.
333- value = sanitizeCloudDataToSend (value);
341+ value = sanitizeCloudDataToSend (value, docId : documentId );
334342
335343 // TODO: Should we do update instead of set?
336344 await docRef.set (value, SetOptions (merge: true ));
@@ -353,7 +361,8 @@ class FirestoreCloudDatabase extends CloudDatabase {
353361 String path, String documentId) async {
354362 final docRef = getDocPath (path, documentId);
355363 final snapshot = await docRef.get ();
356- return snapshot.data () ?? {};
364+ final data = snapshot.data () ?? {};
365+ return sanitizeCloudDataForUse (data, docId: snapshot.id);
357366 }
358367
359368 @override
@@ -385,7 +394,7 @@ class FirestoreCloudDatabase extends CloudDatabase {
385394 logger.log (_label,
386395 'Updating variable ${variable .value .name } with success state.' );
387396
388- data = sanitizeCloudDataForUse (data);
397+ data = sanitizeCloudDataForUse (data, docId : documentId );
389398
390399 // Set the variable with success state.
391400 variable.set (
@@ -455,8 +464,9 @@ class FirestoreCloudDatabase extends CloudDatabase {
455464 // Listen to the stream and update the variable.
456465 final subscription = stream.listen (
457466 (snapshot) {
458- final docs =
459- snapshot.docs.map ((doc) => {...doc.data (), 'id' : doc.id}).toList ();
467+ final docs = snapshot.docs
468+ .map ((doc) => sanitizeCloudDataForUse (doc.data (), docId: doc.id))
469+ .toList ();
460470 logger.log (_label, 'Document stream update from cloud storage: $path ' );
461471 logger.log (_label,
462472 'Updating variable ${variable .value .name } with success state.' );
@@ -484,53 +494,85 @@ class FirestoreCloudDatabase extends CloudDatabase {
484494
485495/// Returns a sanitized version of the given [data] to be used in the SDK
486496/// and variables.
487- Map <String , dynamic > sanitizeCloudDataForUse (Map <String , dynamic > data,
488- {String ? docId}) {
497+ /// Updates the [SDKConstants.createdAt] and [SDKConstants.updatedAt] fields
498+ /// with the current time.
499+ Map <String , dynamic > sanitizeCloudDataForUse (
500+ Map <String , dynamic > data, {
501+ required String docId,
502+ }) {
489503 if (data.isEmpty) return data;
490504 // breaks reference and allows to modify the data.
491505 data = {...data};
492506
507+ // Late because it can potentially be unused;
508+ late final DateTime now = DateTime .now ();
509+
493510 data[SDKConstants .createdAt] =
494- getSanitizedDate (data[SDKConstants .createdAt]) ??
495- DateTime .now ().toUtc ().toIso8601String ();
511+ deserializeCosmicValue (data[SDKConstants .createdAt] ?? now);
496512 data[SDKConstants .updatedAt] =
497- getSanitizedDate (data[SDKConstants .updatedAt]) ??
498- DateTime .now ().toUtc ().toIso8601String ();
499-
500- if (docId != null ) data[SDKConstants .id] = docId;
513+ deserializeCosmicValue (data[SDKConstants .updatedAt] ?? now);
514+ data[SDKConstants .id] = docId;
515+
516+ // Sort private fields to the bottom.
517+ for (final field in privateDocumentFields) {
518+ if (data.containsKey (field)) {
519+ final value = data.remove (field);
520+ data[field] = value;
521+ }
522+ }
501523
502524 return data;
503525}
504526
505527/// Returns a sanitized version of the given [data] to be sent to the cloud
506528/// storage.
507- Map <String , dynamic > sanitizeCloudDataToSend (Map <String , dynamic > data) {
529+ /// Updates the [SDKConstants.createdAt] and [SDKConstants.updatedAt] fields
530+ /// with the current time.
531+ Map <String , dynamic > sanitizeCloudDataToSend (
532+ Map <String , dynamic > data, {
533+ required String ? docId,
534+ bool hidePrivateFields = false ,
535+ }) {
508536 if (data.isEmpty) return data;
509- // breaks reference and allows to modify the data.
537+
538+ // Breaks reference and allows to modify the data.
510539 data = {...data};
511540
512- final createdAt = getSanitizedDate (data[SDKConstants .createdAt]) ??
513- DateTime .now ().toUtc ().toIso8601String ();
541+ // Remove private fields.
542+ if (hidePrivateFields) {
543+ for (final field in privateDocumentFields) {
544+ data.remove (field);
545+ }
546+ } else {
547+ // Late because it can potentially be unused;
548+ late final DateTime now = DateTime .now ();
514549
515- final updatedAt = DateTime .now ().toUtc ().toIso8601String ();
550+ data[SDKConstants .createdAt] =
551+ serializedCosmicValue (data[SDKConstants .createdAt] ?? now);
552+ data[SDKConstants .updatedAt] = serializedCosmicValue (now);
516553
517- // Remove private fields.
518- data.removeWhere ((key, value) => _privateDocumentFields.contains (key));
554+ if (docId != null ) {
555+ data[SDKConstants .id] = docId;
556+ }
519557
520- // put back private fields.
521- data[SDKConstants .createdAt] = createdAt;
522- data[SDKConstants .updatedAt] = updatedAt;
558+ // Sort private fields to the bottom.
559+ for (final field in privateDocumentFields) {
560+ if (data.containsKey (field)) {
561+ final value = data.remove (field);
562+ data[field] = value;
563+ }
564+ }
565+ }
523566
524567 return data;
525568}
526569
527570/// Returns a sanitized date string from the given [value] .
528571/// Converts different representation of date to a string representation.
529- String ? getSanitizedDate (Object ? value) {
572+ String ? serializedCosmicValue (Object ? value) {
530573 return switch (value) {
531574 Timestamp timestamp => timestamp.toDate ().toUtc ().toIso8601String (),
532575 DateTime dateTime => dateTime.toUtc ().toIso8601String (),
533- // TODO: maybe we can avoid this since it is already a string representation of date.
534576 String string => DateTime .tryParse (string)? .toUtc ().toIso8601String (),
535577 int millisecondsSinceEpoch =>
536578 DateTime .fromMillisecondsSinceEpoch (millisecondsSinceEpoch)
@@ -539,3 +581,14 @@ String? getSanitizedDate(Object? value) {
539581 _ => null ,
540582 };
541583}
584+
585+ DateTime ? deserializeCosmicValue (Object ? value) {
586+ return switch (value) {
587+ Timestamp timestamp => timestamp.toDate ().toUtc (),
588+ DateTime dateTime => dateTime.toUtc (),
589+ String string => DateTime .tryParse (string)? .toUtc (),
590+ int millisecondsSinceEpoch =>
591+ DateTime .fromMillisecondsSinceEpoch (millisecondsSinceEpoch).toUtc (),
592+ _ => null ,
593+ };
594+ }
0 commit comments