2019-02-27 19:09:49 -06:00
|
|
|
import 'dart:async';
|
|
|
|
|
|
2019-02-22 00:13:06 -06:00
|
|
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
|
|
|
import 'package:rxdart/rxdart.dart';
|
|
|
|
|
|
|
|
|
|
import '../models/event_model.dart';
|
|
|
|
|
import '../models/user_model.dart';
|
2019-03-02 23:05:35 -06:00
|
|
|
import '../models/summary_model.dart';
|
2019-02-22 00:13:06 -06:00
|
|
|
import '../models/task_model.dart';
|
|
|
|
|
|
|
|
|
|
/// A connection to the Cloud Firestore database
|
2019-02-23 15:04:40 -06:00
|
|
|
///
|
|
|
|
|
/// Implempents CRUD operations for users, tasks and events.
|
2019-02-22 00:13:06 -06:00
|
|
|
class FirestoreProvider {
|
2019-02-26 20:25:19 -06:00
|
|
|
final Firestore _firestore;
|
2019-02-22 00:13:06 -06:00
|
|
|
|
2019-02-26 20:25:19 -06:00
|
|
|
FirestoreProvider([Firestore firestore])
|
|
|
|
|
: _firestore = firestore ?? Firestore.instance {
|
2019-03-08 22:00:21 -06:00
|
|
|
_firestore.settings(timestampsInSnapshotsEnabled: true);
|
2019-02-22 00:13:06 -06:00
|
|
|
}
|
2019-02-23 16:51:27 -06:00
|
|
|
//-----------------------User related operations------------------------------
|
2019-02-22 00:13:06 -06:00
|
|
|
|
|
|
|
|
/// Returns a stream of [UserModel].
|
2019-04-01 00:34:36 -06:00
|
|
|
Observable<UserModel> getUserObservable(String username) {
|
2019-02-26 20:25:19 -06:00
|
|
|
final mappedStream = _firestore
|
2019-02-22 00:13:06 -06:00
|
|
|
.collection('users')
|
|
|
|
|
.where('username', isEqualTo: username)
|
|
|
|
|
.snapshots()
|
|
|
|
|
.map(
|
|
|
|
|
(QuerySnapshot snapshot) {
|
|
|
|
|
if (snapshot.documents.isEmpty) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
final userSnapshot = snapshot.documents.first;
|
|
|
|
|
return UserModel.fromFirestore(
|
2019-02-23 15:04:40 -06:00
|
|
|
userSnapshot.data,
|
|
|
|
|
id: userSnapshot.documentID,
|
|
|
|
|
);
|
2019-02-22 00:13:06 -06:00
|
|
|
},
|
|
|
|
|
);
|
2019-02-23 16:51:27 -06:00
|
|
|
|
|
|
|
|
return Observable(mappedStream);
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-01 00:34:36 -06:00
|
|
|
//TODO: add tests for this method.
|
|
|
|
|
|
|
|
|
|
/// Returns a [UserModel].
|
|
|
|
|
/// Only one out of id or username can be provided, if both are provided id
|
|
|
|
|
/// will have higher priority.
|
|
|
|
|
Future<UserModel> getUser({String id, String username}) async {
|
|
|
|
|
DocumentSnapshot documentSnapshot;
|
|
|
|
|
if (id != null) {
|
|
|
|
|
documentSnapshot = await _firestore.document('users/$id').get();
|
|
|
|
|
} else {
|
|
|
|
|
final querySnapshot = await _firestore
|
|
|
|
|
.collection('users')
|
|
|
|
|
.where('username', isEqualTo: username)
|
|
|
|
|
.getDocuments();
|
|
|
|
|
documentSnapshot = querySnapshot.documents.first;
|
|
|
|
|
}
|
|
|
|
|
return UserModel.fromFirestore(
|
|
|
|
|
documentSnapshot.data,
|
|
|
|
|
id: documentSnapshot.documentID,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-02 23:05:35 -06:00
|
|
|
/// Creates a new instance of a user in Firestore.
|
2019-03-05 20:06:05 -06:00
|
|
|
Future<void> createUser(UserModel user, String uid) async {
|
2019-03-02 19:08:25 -06:00
|
|
|
try {
|
|
|
|
|
final dataMap = user.toFirestoreMap();
|
2019-03-05 20:06:05 -06:00
|
|
|
final documentReference = _firestore.collection('users').document(uid);
|
|
|
|
|
await documentReference.setData(dataMap);
|
2019-03-02 19:08:25 -06:00
|
|
|
} catch (e) {
|
|
|
|
|
print('Error creating user: $e');
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-03-02 18:59:24 -06:00
|
|
|
|
2019-03-02 23:05:35 -06:00
|
|
|
/// Verifies if a user with the given username is already in the database.
|
2019-03-02 20:40:33 -06:00
|
|
|
Future<bool> userExists(String username) async {
|
|
|
|
|
final querySnapshot = await _firestore
|
|
|
|
|
.collection('users')
|
|
|
|
|
.where('username', isEqualTo: username)
|
|
|
|
|
.getDocuments();
|
|
|
|
|
return querySnapshot.documents.length > 0;
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-02 23:41:11 -06:00
|
|
|
/// Updates a user's data in Firestore.
|
|
|
|
|
///
|
|
|
|
|
/// Updates are only pushed if at least one property is provided.
|
2019-03-02 23:05:35 -06:00
|
|
|
Future<void> updateUser(
|
|
|
|
|
String id, {
|
|
|
|
|
List<String> tasks,
|
2019-04-01 00:34:36 -06:00
|
|
|
List<String> events,
|
2019-03-02 23:05:35 -06:00
|
|
|
SummaryModel summary,
|
|
|
|
|
int pendingHigh,
|
|
|
|
|
int pendingMedium,
|
|
|
|
|
int pendingLow,
|
|
|
|
|
}) async {
|
|
|
|
|
final newData = <String, dynamic>{
|
|
|
|
|
'tasks': tasks,
|
2019-04-01 00:34:36 -06:00
|
|
|
'events': events,
|
2019-03-02 23:05:35 -06:00
|
|
|
'summary': summary,
|
|
|
|
|
'pendingHigh': pendingHigh,
|
|
|
|
|
'pendingMedium': pendingMedium,
|
|
|
|
|
'pendingLow': pendingLow,
|
|
|
|
|
};
|
|
|
|
|
newData.removeWhere((key, value) => value == null);
|
|
|
|
|
|
|
|
|
|
if (newData.isEmpty) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
final documentReference = _firestore.collection('users').document(id);
|
|
|
|
|
await documentReference.setData(newData, merge: true);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
print('Error trying to update user data: $e');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-23 15:04:40 -06:00
|
|
|
//-------------------------Task related operations----------------------------
|
2019-02-23 16:51:27 -06:00
|
|
|
|
2019-02-23 16:51:27 -06:00
|
|
|
/// Adds a task to firestore.
|
2019-02-23 15:22:24 -06:00
|
|
|
Future<void> addTask(TaskModel task) async {
|
2019-02-23 16:51:27 -06:00
|
|
|
try {
|
|
|
|
|
final dataMap = task.toFirestoreMap();
|
2019-02-26 20:25:19 -06:00
|
|
|
await _firestore.collection('tasks').add(dataMap);
|
2019-02-23 16:51:27 -06:00
|
|
|
} catch (e) {
|
|
|
|
|
print('Error adding task to firestore: $e');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Returns a Stream of a single task from an id.
|
|
|
|
|
Observable<TaskModel> getTask(String id) {
|
|
|
|
|
final mappedStream =
|
2019-02-26 20:25:19 -06:00
|
|
|
_firestore.collection('tasks').document(id).snapshots().map(
|
2019-02-23 16:51:27 -06:00
|
|
|
(DocumentSnapshot snapshot) {
|
|
|
|
|
return TaskModel.fromFirestore(
|
|
|
|
|
snapshot.data,
|
|
|
|
|
id: snapshot.documentID,
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return Observable(mappedStream);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Deletes a task from firestore.
|
|
|
|
|
Future<void> deleteTask(String id) async {
|
|
|
|
|
try {
|
2019-02-26 20:25:19 -06:00
|
|
|
final documentReference = _firestore.collection('tasks').document(id);
|
2019-02-23 16:51:27 -06:00
|
|
|
await documentReference.delete();
|
|
|
|
|
} catch (e) {
|
|
|
|
|
print('Error deleting task from firestore: $e');
|
|
|
|
|
}
|
2019-02-23 15:22:24 -06:00
|
|
|
}
|
|
|
|
|
|
2019-02-23 16:51:27 -06:00
|
|
|
/// Updates a task in firestore.
|
|
|
|
|
///
|
|
|
|
|
/// Only the [text], [priority] and [done] attributes can be update.
|
2019-02-23 19:28:31 -06:00
|
|
|
/// Provide at least one of these values or this operation won't have any
|
|
|
|
|
/// effect.
|
2019-02-23 16:51:27 -06:00
|
|
|
Future<void> updateTask(
|
|
|
|
|
String id, {
|
|
|
|
|
String text,
|
|
|
|
|
int priority,
|
|
|
|
|
bool done,
|
|
|
|
|
}) async {
|
|
|
|
|
final newData = <String, dynamic>{
|
|
|
|
|
'text': text,
|
|
|
|
|
'priority': priority,
|
|
|
|
|
'done': done,
|
|
|
|
|
};
|
|
|
|
|
newData.removeWhere((key, value) => value == null);
|
2019-02-23 19:28:31 -06:00
|
|
|
|
|
|
|
|
// No need to call firestore if there's no new data to update.
|
|
|
|
|
if (newData.isEmpty) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-23 16:51:27 -06:00
|
|
|
try {
|
2019-02-26 20:25:19 -06:00
|
|
|
final documentReference = _firestore.collection('tasks').document(id);
|
2019-02-23 16:51:27 -06:00
|
|
|
await documentReference.setData(newData, merge: true);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
print('Error updating task in firestore: $e');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Returns a stream of [List<Task>] that correspond to a particular user.
|
2019-02-22 00:13:06 -06:00
|
|
|
///
|
2019-02-23 15:22:24 -06:00
|
|
|
/// The [event] parameter is used to query tasks that are part of a certain
|
|
|
|
|
/// event.
|
2019-02-22 00:13:06 -06:00
|
|
|
Observable<List<TaskModel>> getUserTasks(String username, {String event}) {
|
2019-02-26 20:25:19 -06:00
|
|
|
Query query = _firestore
|
2019-02-22 00:13:06 -06:00
|
|
|
.collection('tasks')
|
|
|
|
|
.where('ownerUsername', isEqualTo: username)
|
|
|
|
|
.where('done', isEqualTo: false);
|
|
|
|
|
|
|
|
|
|
if (event != null) {
|
|
|
|
|
query = query.where('event', isEqualTo: event);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final mappedStream = query.snapshots().map(
|
|
|
|
|
(QuerySnapshot snapshot) {
|
|
|
|
|
return snapshot.documents.map(
|
|
|
|
|
(DocumentSnapshot document) {
|
2019-02-23 15:04:40 -06:00
|
|
|
return TaskModel.fromFirestore(
|
|
|
|
|
document.data,
|
|
|
|
|
id: document.documentID,
|
|
|
|
|
);
|
2019-02-22 00:13:06 -06:00
|
|
|
},
|
|
|
|
|
).toList();
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return Observable(mappedStream);
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-23 15:04:40 -06:00
|
|
|
//-----------------------Event related operations-----------------------------
|
2019-02-23 19:12:24 -06:00
|
|
|
// TODO: Change the Events collection name to 'events' in forestore.
|
2019-02-23 15:04:40 -06:00
|
|
|
|
2019-02-23 19:12:24 -06:00
|
|
|
/// Adds an event to firestore.
|
|
|
|
|
Future<void> addEvent(String userId, EventModel event) async {
|
|
|
|
|
try {
|
|
|
|
|
final dataMap = event.toFirestoreMap();
|
2019-02-26 20:25:19 -06:00
|
|
|
await _firestore.collection('users/$userId/Events').add(dataMap);
|
2019-04-01 00:34:36 -06:00
|
|
|
// After the event was added successfully we have to update the events a
|
|
|
|
|
// user has.
|
|
|
|
|
final user = await getUser(id: userId);
|
|
|
|
|
final newEventsArray = user.events..add(event.name);
|
|
|
|
|
await updateUser(userId, events: newEventsArray);
|
2019-02-23 19:12:24 -06:00
|
|
|
} catch (e) {
|
|
|
|
|
print('Error adding Event to firestore: $e');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Returns a Stream of a single event.
|
|
|
|
|
Observable<EventModel> getEvent(String userId, String eventId) {
|
2019-02-26 20:25:19 -06:00
|
|
|
final mappedStream = _firestore
|
2019-02-23 19:12:24 -06:00
|
|
|
.collection('users/$userId/Events')
|
|
|
|
|
.document(eventId)
|
|
|
|
|
.snapshots()
|
|
|
|
|
.map(
|
|
|
|
|
(DocumentSnapshot snapshot) {
|
|
|
|
|
return EventModel.fromFirestore(
|
|
|
|
|
snapshot.data,
|
|
|
|
|
id: snapshot.documentID,
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return Observable(mappedStream);
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-23 19:28:31 -06:00
|
|
|
/// Deletes an event from firestore.
|
|
|
|
|
///
|
|
|
|
|
/// It does not delete all the dependent items, this includes tasks and media.
|
2019-02-23 19:12:24 -06:00
|
|
|
Future<void> deleteEvent(String userId, String eventId) async {
|
|
|
|
|
try {
|
|
|
|
|
final documentReference =
|
2019-02-26 20:25:19 -06:00
|
|
|
_firestore.document('users/$userId/Events/$eventId');
|
2019-02-23 19:12:24 -06:00
|
|
|
await documentReference.delete();
|
|
|
|
|
} catch (e) {
|
|
|
|
|
print('Error deleting event in firestore: $e');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-23 19:28:31 -06:00
|
|
|
/// Updates and event with the provided data.
|
|
|
|
|
///
|
|
|
|
|
/// At least one of the following has to be provided (otherwise this
|
|
|
|
|
/// operation has no effect): [name], [pendingTasks], [meida], [tasks],
|
|
|
|
|
/// [highPriority], [mediumPriority] or [lowPriority].
|
2019-02-23 19:12:24 -06:00
|
|
|
Future<void> updateEvent(
|
|
|
|
|
String userId,
|
|
|
|
|
String eventId, {
|
|
|
|
|
String name,
|
|
|
|
|
int pendingtasks,
|
|
|
|
|
List<int> media,
|
|
|
|
|
List<String> tasks,
|
|
|
|
|
int highPriority,
|
|
|
|
|
int mediumPriority,
|
|
|
|
|
int lowPriority,
|
|
|
|
|
}) async {
|
|
|
|
|
final newData = <String, dynamic>{
|
|
|
|
|
'name': name,
|
|
|
|
|
'pendingtasks': pendingtasks,
|
|
|
|
|
'media': media,
|
|
|
|
|
'tasks': tasks,
|
|
|
|
|
'highPriority': highPriority,
|
|
|
|
|
'mediumPriority': mediumPriority,
|
|
|
|
|
'lowPriority': lowPriority,
|
|
|
|
|
};
|
|
|
|
|
newData.removeWhere((_, value) => value == null);
|
|
|
|
|
|
2019-02-23 19:28:31 -06:00
|
|
|
// No need to call firestore if there's no new data to update.
|
|
|
|
|
if (newData.isEmpty) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-23 19:12:24 -06:00
|
|
|
try {
|
|
|
|
|
final documentReference =
|
2019-02-26 20:25:19 -06:00
|
|
|
_firestore.document('users/$userId/Events/$eventId');
|
2019-02-23 19:12:24 -06:00
|
|
|
await documentReference.setData(newData, merge: true);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
print('Error while updating Event in Firestore: $e');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Returns a stream of [List<EventModel] that correspond to
|
|
|
|
|
/// a particular user.
|
2019-03-05 21:07:14 -06:00
|
|
|
Observable<List<EventModel>> getUserEvents(String userId) {
|
2019-02-24 21:38:12 -06:00
|
|
|
final mappedStream =
|
2019-03-05 21:07:14 -06:00
|
|
|
_firestore.collection('users/$userId/Events').snapshots().map(
|
2019-02-22 00:13:06 -06:00
|
|
|
(QuerySnapshot snapshot) {
|
|
|
|
|
return snapshot.documents.map((DocumentSnapshot documentSnapshot) {
|
|
|
|
|
return EventModel.fromFirestore(
|
2019-02-23 15:04:40 -06:00
|
|
|
documentSnapshot.data,
|
|
|
|
|
id: documentSnapshot.documentID,
|
|
|
|
|
);
|
2019-02-22 00:13:06 -06:00
|
|
|
}).toList();
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return Observable(mappedStream);
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-03-08 23:46:24 -06:00
|
|
|
|
|
|
|
|
final firestoreProvider = FirestoreProvider();
|