Skip to content

Commit

Permalink
Merge pull request #229 from ProximaEPFL/recursive-deletion
Browse files Browse the repository at this point in the history
Recursive post deletion
  • Loading branch information
Aderfish authored May 9, 2024
2 parents e1dfd3f + c31fa83 commit 238e371
Show file tree
Hide file tree
Showing 5 changed files with 199 additions and 24 deletions.
98 changes: 78 additions & 20 deletions lib/services/database/comment_repository_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import "package:proxima/models/database/post/post_data.dart";
import "package:proxima/models/database/post/post_firestore.dart";
import "package:proxima/models/database/post/post_id_firestore.dart";
import "package:proxima/services/database/firestore_service.dart";
import "package:proxima/services/database/upvote_repository_service.dart";

/// This class is a service that allows to interact with the comments
/// of the posts in the firestore database.
Expand Down Expand Up @@ -88,27 +89,84 @@ class CommentRepositoryService {
Future<void> deleteComment(
PostIdFirestore parentPostId,
CommentIdFirestore commentId,
) =>
_firestore.runTransaction(
(transaction) async {
final commentRef =
_commentsSubCollection(parentPostId).doc(commentId.value);

final commentDoc = await transaction.get(commentRef);

if (!commentDoc.exists) {
throw Exception("Comment does not exist");
}

transaction.delete(commentRef);

final postDocRef = _postDocument(parentPostId);
transaction.update(
postDocRef,
{PostData.commentCountField: FieldValue.increment(-1)},
);
},
) async {
final batch = _firestore.batch();

final commentUpvoteRepository =
UpvoteRepositoryService.commentUpvoteRepository(
_firestore,
parentPostId,
);

await _deleteCommentNoCountUpdate(
parentPostId,
commentId,
batch,
commentUpvoteRepository,
);

batch.update(
_postDocument(parentPostId),
{PostData.commentCountField: FieldValue.increment(-1)},
);

await batch.commit();
}

/// This method will delete all the comments of the post with id [parentPostId].
/// Helper method to delete a post. Adds all the deletions to the batch [batch].
Future<void> deleteAllComments(
PostIdFirestore parentPostId,
WriteBatch batch,
) async {
final commentsRef = _commentsSubCollection(parentPostId);
final comments = await commentsRef.get();
final commentUpvoteRepository =
UpvoteRepositoryService.commentUpvoteRepository(
_firestore,
parentPostId,
);

for (final comment in comments.docs) {
await _deleteCommentNoCountUpdate(
parentPostId,
CommentIdFirestore(value: comment.id),
batch,
commentUpvoteRepository,
checkExists: false,
);
}

batch.update(_postDocument(parentPostId), {PostData.commentCountField: 0});
}

/// Helper method to delete a comment. Adds all the deletions to the batch [batch].
/// Does not update the comment count of the post. That should be done separately.
/// If [checkExists] is true, the method will check if the comment exists before
/// deleting it. If it does not exist, the method will throw an error.
Future<void> _deleteCommentNoCountUpdate(
PostIdFirestore parentPostId,
CommentIdFirestore commentId,
WriteBatch batch,
UpvoteRepositoryService<CommentIdFirestore> commentUpvoteRepository, {
bool checkExists = true,
}) async {
final commentRef =
_commentsSubCollection(parentPostId).doc(commentId.value);

if (checkExists) {
final comment = await commentRef.get();
if (!comment.exists) {
throw Exception("Comment does not exist");
}
}

await commentUpvoteRepository.deleteAllUpvotes(
commentId,
batch,
);
batch.delete(commentRef);
}
}

final commentRepositoryProvider = Provider<CommentRepositoryService>(
Expand Down
25 changes: 21 additions & 4 deletions lib/services/database/post_repository_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,24 @@ import "package:proxima/models/database/post/post_firestore.dart";
import "package:proxima/models/database/post/post_id_firestore.dart";
import "package:proxima/models/database/post/post_location_firestore.dart";
import "package:proxima/models/database/user/user_id_firestore.dart";
import "package:proxima/services/database/comment_repository_service.dart";
import "package:proxima/services/database/firestore_service.dart";
import "package:proxima/services/database/upvote_repository_service.dart";

/// This repository service is responsible for managing the posts in the database
class PostRepositoryService {
final CollectionReference _collectionRef;
final FirebaseFirestore _firestore;
final CollectionReference<Map<String, dynamic>> _collectionRef;
final CommentRepositoryService _commentRepository;
final UpvoteRepositoryService<PostIdFirestore> _upvoteRepository;

PostRepositoryService({
required FirebaseFirestore firestore,
}) : _collectionRef = firestore.collection(PostFirestore.collectionName);
}) : _firestore = firestore,
_collectionRef = firestore.collection(PostFirestore.collectionName),
_commentRepository = CommentRepositoryService(firestore: firestore),
_upvoteRepository =
UpvoteRepositoryService.postUpvoteRepository(firestore);

/// This method creates a new post that has for data [postData]
/// and that is located at [position] and adds it to the database
Expand All @@ -29,9 +38,17 @@ class PostRepositoryService {
return PostIdFirestore(value: reference.id);
}

/// This method deletes the post with id [postId] from the database
/// This method deletes the post with id [postId] from the database.
/// This means removing the corresponding document, and all the
/// subcollections.
Future<void> deletePost(PostIdFirestore postId) async {
await _collectionRef.doc(postId.value).delete();
final batch = _firestore.batch();

await _commentRepository.deleteAllComments(postId, batch);
await _upvoteRepository.deleteAllUpvotes(postId, batch);
batch.delete(_collectionRef.doc(postId.value));

await batch.commit();
}

/// This method returns true if the post with id [postId] exists in the database
Expand Down
14 changes: 14 additions & 0 deletions lib/services/database/upvote_repository_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -133,4 +133,18 @@ class UpvoteRepositoryService<ParentIdFirestore extends IdFirestore> {
);
});
}

/// Deletes all the upvotes of the parent with id [parentId]. Should not be
/// used on its own but rather as part of the deletion of the parent. Adds all
/// the deletions to the batch [batch].
Future<void> deleteAllUpvotes(
ParentIdFirestore parentId,
WriteBatch batch,
) async {
final upvotes = await _votersCollection(parentId).get();
for (final upvote in upvotes.docs) {
batch.delete(upvote.reference);
}
batch.update(_parentDocument(parentId), {_voteScoreField: 0});
}
}
44 changes: 44 additions & 0 deletions test/services/database/comment_repository_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ import "package:proxima/models/database/comment/comment_id_firestore.dart";
import "package:proxima/models/database/post/post_data.dart";
import "package:proxima/models/database/post/post_firestore.dart";
import "package:proxima/models/database/post/post_id_firestore.dart";
import "package:proxima/models/database/vote/upvote_state.dart";
import "package:proxima/services/database/comment_repository_service.dart";
import "package:proxima/services/database/firestore_service.dart";
import "package:proxima/services/database/upvote_repository_service.dart";

import "../../mocks/data/comment_data.dart";
import "../../mocks/data/firestore_comment.dart";
import "../../mocks/data/firestore_post.dart";
import "../../mocks/data/firestore_user.dart";
import "../../mocks/data/geopoint.dart";

void main() {
Expand All @@ -26,6 +29,8 @@ void main() {
late CommentFirestoreGenerator commentGenerator;
late CommentDataGenerator commentDataGenerator;

late UpvoteRepositoryService<CommentIdFirestore> commentUpvoteRepository;

/// The setup add a single post with no comments
setUp(() async {
fakeFirestore = FakeFirebaseFirestore();
Expand Down Expand Up @@ -57,6 +62,11 @@ void main() {

commentGenerator = CommentFirestoreGenerator();
commentDataGenerator = CommentDataGenerator();

commentUpvoteRepository = UpvoteRepositoryService.commentUpvoteRepository(
fakeFirestore,
postId,
);
});

group("getting comments", () {
Expand Down Expand Up @@ -180,12 +190,26 @@ void main() {

final commentId = comment.first.id;

// Add an upvote to the comment to later check that it is deleted
commentUpvoteRepository.setUpvoteState(
testingUserFirestoreId,
commentId,
UpvoteState.upvoted,
);

await commentRepository.deleteComment(postId, commentId);

// Check that the comment was deleted
final actualComments = await commentRepository.getComments(postId);
expect(actualComments, isEmpty);

// Check that the upvote was deleted
final upvote = await commentUpvoteRepository.getUpvoteState(
testingUserFirestoreId,
commentId,
);
expect(upvote, UpvoteState.none);

// Check that the comment count was updated correctly
final postDoc = await postDocument.get();
final post = PostFirestore.fromDb(postDoc);
Expand Down Expand Up @@ -248,6 +272,26 @@ void main() {

expect(post.data.commentCount, equals(5));
});

test("can delete all comments", () async {
await commentGenerator.addComments(
5,
postId,
commentRepository,
);

final batch = fakeFirestore.batch();
await commentRepository.deleteAllComments(postId, batch);
await batch.commit();

final actualComments = await commentRepository.getComments(postId);
expect(actualComments, isEmpty);

final postDoc = await postDocument.get();
final post = PostFirestore.fromDb(postDoc);

expect(post.data.commentCount, equals(0));
});
});
});
}
42 changes: 42 additions & 0 deletions test/services/database/post_repository_test.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import "package:fake_cloud_firestore/fake_cloud_firestore.dart";
import "package:flutter_test/flutter_test.dart";
import "package:geoflutterfire_plus/geoflutterfire_plus.dart";
import "package:proxima/models/database/comment/comment_firestore.dart";
import "package:proxima/models/database/post/post_firestore.dart";
import "package:proxima/models/database/post/post_id_firestore.dart";
import "package:proxima/models/database/post/post_location_firestore.dart";
import "package:proxima/models/database/user/user_id_firestore.dart";
import "package:proxima/models/database/vote/upvote_state.dart";
import "package:proxima/models/database/vote/vote_firestore.dart";
import "package:proxima/services/database/comment_repository_service.dart";
import "package:proxima/services/database/post_repository_service.dart";
import "package:proxima/services/database/upvote_repository_service.dart";

import "../../mocks/data/firestore_comment.dart";
import "../../mocks/data/firestore_post.dart";
import "../../mocks/data/firestore_user.dart";
import "../../mocks/data/geopoint.dart";
import "../../mocks/data/post_data.dart";

Expand Down Expand Up @@ -47,6 +54,41 @@ void main() {
expect(actualPost.exists, false);
});

test("Delete method deletes subcollections", () async {
await setPostFirestore(post, firestore);
final postRef =
firestore.collection(PostFirestore.collectionName).doc(post.id.value);
// Check that the post is in the db
final dbPost = await postRef.get();
expect(dbPost.exists, true);

final commentRepository = CommentRepositoryService(firestore: firestore);
await CommentFirestoreGenerator()
.addComments(4, post.id, commentRepository);

final upvoteRepository = UpvoteRepositoryService.postUpvoteRepository(
firestore,
);

await upvoteRepository.setUpvoteState(
testingUserFirestoreId,
post.id,
UpvoteState.upvoted,
);

await postRepository.deletePost(post.id);
final actualPost = await postRef.get();
expect(actualPost.exists, false);

final actualComments =
await postRef.collection(CommentFirestore.subCollectionName).get();
expect(actualComments.docs.length, 0);

final actualVoters =
await postRef.collection(VoteFirestore.votersSubCollectionName).get();
expect(actualVoters.size, 0);
});

test("Get post correctly", () async {
await setPostFirestore(post, firestore);

Expand Down

0 comments on commit 238e371

Please sign in to comment.