Skip to content

Commit

Permalink
Merge pull request #43 from ProximaEPFL/firestore-repositories
Browse files Browse the repository at this point in the history
Add repositories for user and posts
  • Loading branch information
yoannLafore authored Apr 2, 2024
2 parents b1d98ad + 423fd77 commit 8d86802
Show file tree
Hide file tree
Showing 29 changed files with 1,263 additions and 62 deletions.
78 changes: 78 additions & 0 deletions lib/models/database/post/post_data.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import "package:cloud_firestore/cloud_firestore.dart";
import "package:flutter/foundation.dart";
import "package:proxima/models/database/user/user_id_firestore.dart";

@immutable
class PostData {
final UserIdFirestore ownerId;
static const String ownerIdField = "ownerId";

final String title;
static const String titleField = "title";

final String description;
static const String descriptionField = "description";

final Timestamp publicationTime;
static const String publicationTimeField = "publicationTime";

final int voteScore;
static const String voteScoreField = "voteScore";

const PostData({
required this.ownerId,
required this.title,
required this.description,
required this.publicationTime,
required this.voteScore,
});

/// This method will create an instance of [PostData] from the
/// data map [data] that comes from firestore
factory PostData.fromDbData(Map<String, dynamic> data) {
try {
return PostData(
ownerId: UserIdFirestore(value: data[ownerIdField]),
title: data[titleField],
description: data[descriptionField],
publicationTime: data[publicationTimeField],
voteScore: data[voteScoreField],
);
} catch (e) {
if (e is TypeError) {
throw FormatException("Cannot parse post document: ${e.toString()}");
} else {
rethrow;
}
}
}

/// This method will create a map from the current instance of [PostData]
/// to be stored in firestore
Map<String, dynamic> toDbData() {
return {
ownerIdField: ownerId.value,
titleField: title,
descriptionField: description,
publicationTimeField: publicationTime,
voteScoreField: voteScore,
};
}

@override
bool operator ==(Object other) {
if (identical(this, other)) return true;

return other is PostData &&
other.ownerId == ownerId &&
other.title == title &&
other.description == description &&
other.publicationTime == publicationTime &&
other.voteScore == voteScore;
}

@override
int get hashCode {
return Object.hash(ownerId, title, description, publicationTime, voteScore);
}
}
68 changes: 68 additions & 0 deletions lib/models/database/post/post_firestore.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import "package:cloud_firestore/cloud_firestore.dart";
import "package:flutter/foundation.dart";
import "package:proxima/models/database/post/post_data.dart";
import "package:proxima/models/database/post/post_id_firestore.dart";
import "package:proxima/models/database/post/post_location_firestore.dart";

@immutable
class PostFirestore {
/// This is the collection in which the posts are stored
static const collectionName = "posts";

/// The id is not stored in a field because it already
/// corresponds to the document id on firestore
final PostIdFirestore id;

/// The post location is not stored in the [PostData] because it
/// is exclusively managed by the repository (in particular, it is the
/// responsability of the repository to create it when adding a post)
final PostLocationFirestore location;
static const String locationField = "location";

final PostData data;

const PostFirestore({
required this.id,
required this.location,
required this.data,
});

/// This method will create an instance of [PostFirestore] from the
/// document snapshot [docSnap] that comes from firestore
factory PostFirestore.fromDb(DocumentSnapshot docSnap) {
if (!docSnap.exists) {
throw Exception("Post document does not exist");
}

try {
final data = docSnap.data() as Map<String, dynamic>;

return PostFirestore(
id: PostIdFirestore(value: docSnap.id),
location: PostLocationFirestore.fromDbData(data[locationField]),
data: PostData.fromDbData(data),
);
} catch (e) {
if (e is TypeError) {
throw FormatException("Cannot parse post document: ${e.toString()}");
} else {
rethrow;
}
}
}

@override
bool operator ==(Object other) {
if (identical(this, other)) return true;

return other is PostFirestore &&
other.id == id &&
other.location == location &&
other.data == data;
}

@override
int get hashCode {
return Object.hash(id, location, data);
}
}
17 changes: 17 additions & 0 deletions lib/models/database/post/post_id_firestore.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import "package:flutter/foundation.dart";

/// The id are strong typed to avoid misuse
@immutable
class PostIdFirestore {
final String value;

const PostIdFirestore({required this.value});

@override
bool operator ==(Object other) {
return other is PostIdFirestore && other.value == value;
}

@override
int get hashCode => value.hashCode;
}
52 changes: 52 additions & 0 deletions lib/models/database/post/post_location_firestore.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import "package:cloud_firestore/cloud_firestore.dart";
import "package:flutter/foundation.dart";

@immutable
class PostLocationFirestore {
final GeoPoint geoPoint;
final String geohash;

/// Do not change !
/// The [GeoFlutterFire] library that is used to perform the geo queries uses
/// hardcoded field name values in its implementation and does not provide
/// methods to automatically parse the point data. Thus we must manually specify
/// the field name for the geo point and the geo hash
static const String geoPointField = "geopoint"; // Do not change
static const String geohashField = "geohash"; // Do not change

const PostLocationFirestore({
required this.geoPoint,
required this.geohash,
});

/// This method will create an instance of [PostLocationFirestore] from the
/// data map [data] that comes from firestore
factory PostLocationFirestore.fromDbData(Map<String, dynamic> data) {
try {
return PostLocationFirestore(
geoPoint: data[geoPointField],
geohash: data[geohashField],
);
} catch (e) {
if (e is TypeError) {
throw FormatException(
"Cannot parse post location document: ${e.toString()}",
);
} else {
rethrow;
}
}
}

@override
bool operator ==(Object other) {
if (identical(this, other)) return true;

return other is PostLocationFirestore &&
other.geoPoint == geoPoint &&
other.geohash == geohash;
}

@override
int get hashCode => Object.hash(geoPoint, geohash);
}
61 changes: 61 additions & 0 deletions lib/models/database/user/user_data.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import "package:cloud_firestore/cloud_firestore.dart";
import "package:flutter/foundation.dart";

@immutable
class UserData {
final String username;
static const String usernameField = "username";

final String displayName;
static const String displayNameField = "displayName";

final Timestamp joinTime;
static const joinTimeField = "joinTime";

const UserData({
required this.username,
required this.displayName,
required this.joinTime,
});

/// This method will create an instance of [UserData] from the
/// data map [data] that comes from firestore
factory UserData.fromDbData(Map<String, dynamic> data) {
try {
return UserData(
username: data[usernameField],
displayName: data[displayNameField],
joinTime: data[joinTimeField],
);
} catch (e) {
if (e is TypeError) {
throw FormatException(
"Cannot parse user data document: ${e.toString()}",
);
} else {
rethrow;
}
}
}

/// This method will create a map from the current instance of [UserData]
/// to be stored in firestore
Map<String, dynamic> toDbData() {
return {
usernameField: username,
displayNameField: displayName,
joinTimeField: joinTime,
};
}

@override
bool operator ==(Object other) {
return other is UserData &&
other.username == username &&
other.displayName == displayName &&
other.joinTime == joinTime;
}

@override
int get hashCode => Object.hash(username, displayName, joinTime);
}
49 changes: 49 additions & 0 deletions lib/models/database/user/user_firestore.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import "package:cloud_firestore/cloud_firestore.dart";
import "package:flutter/foundation.dart";
import "package:proxima/models/database/user/user_data.dart";
import "package:proxima/models/database/user/user_id_firestore.dart";

@immutable
class UserFirestore {
// This is the collection where the users are stored
static const collectionName = "users";

/// The uid is not stored in a field because it already
/// corresponds to the document id on firestore
final UserIdFirestore uid;

final UserData data;

const UserFirestore({required this.uid, required this.data});

/// This method will create an instance of [UserFirestore] from the
/// document snapshot [docSnap] that comes from firestore
factory UserFirestore.fromDb(DocumentSnapshot docSnap) {
if (!docSnap.exists) {
throw Exception("User document does not exist");
}

try {
final data = docSnap.data() as Map<String, dynamic>;

return UserFirestore(
uid: UserIdFirestore(value: docSnap.id),
data: UserData.fromDbData(data),
);
} catch (e) {
if (e is TypeError) {
throw FormatException("Cannot parse user document : ${e.toString()}");
} else {
rethrow;
}
}
}

@override
bool operator ==(Object other) {
return other is UserFirestore && other.uid == uid && other.data == data;
}

@override
int get hashCode => Object.hash(uid, data);
}
17 changes: 17 additions & 0 deletions lib/models/database/user/user_id_firestore.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import "package:flutter/foundation.dart";

/// The id are strong typed to avoid misuse
@immutable
class UserIdFirestore {
final String value;

const UserIdFirestore({required this.value});

@override
bool operator ==(Object other) {
return other is UserIdFirestore && other.value == value;
}

@override
int get hashCode => value.hashCode;
}
7 changes: 7 additions & 0 deletions lib/services/database/firestore_service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import "package:cloud_firestore/cloud_firestore.dart";
import "package:hooks_riverpod/hooks_riverpod.dart";

/// Static singleton [FirebaseFirestore] instance provider
final firestoreProvider = Provider<FirebaseFirestore>(
(ref) => FirebaseFirestore.instance,
);
Loading

0 comments on commit 8d86802

Please sign in to comment.