Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Delete Account and Data #931

Merged
merged 5 commits into from
Sep 28, 2024
Merged

Delete Account and Data #931

merged 5 commits into from
Sep 28, 2024

Conversation

mdmohsin7
Copy link
Collaborator

@mdmohsin7 mdmohsin7 commented Sep 27, 2024

screen-20240927-183921.mp4

Summary by Entelligence.AI

  • New Feature: Introduced a dedicated DeleteAccount screen in the settings, enhancing user experience and control over account deletion.
  • Refactor: Updated the ProfilePage class to navigate to the new DeleteAccount screen instead of showing a dialog.
  • Refactor: Modified DeviceSettings to use IntercomManager.instance.displayChargingArticle for better article management.
  • New Feature: Enhanced the MixpanelManager class with functions to handle account deletion events for improved analytics.
  • Refactor: Updated the getDialog function in dialog.dart to allow dynamic setting of cancel button text.
  • New Feature: Added backend functionality to delete user data across various collections before deleting the user account, ensuring clean removal of user data.
  • Refactor: Improved error handling during the account deletion process to catch and log exceptions.

Copy link

github-actions bot commented Sep 27, 2024

Image description Entelligence.AI

Walkthrough

This change introduces a new feature allowing users to delete their accounts. It includes UI updates, analytics tracking, and backend changes for data deletion. The account deletion process is handled securely with Firebase Auth and SharedPreferences. Additionally, the code has been refactored to replace certain dialogs with screens and update Intercom article display methods.

Changes

Files Summary
app/lib/pages/settings/delete_account.dart, app/lib/pages/settings/profile.dart Introduced a new Flutter widget DeleteAccount for handling user account deletions. Replaced the dialog with a new screen in the ProfilePage class for deleting an account.
app/lib/pages/settings/device_settings.dart Replaced a call to Intercom.instance.displayArticle with IntercomManager.instance.displayChargingArticle.
app/lib/utils/analytics/mixpanel.dart, app/lib/widgets/dialog.dart Added functions for tracking account deletion actions in Mixpanel. Enhanced the getDialog function with a new optional parameter cancelButtonText for customizing the cancel button text.
backend/database/users.py, backend/routers/users.py, backend/utils/other/endpoints.py Added a new function delete_user_data to delete user-related collections and the user itself. Updated the delete_account function to handle user data and account deletion from Firebase. Added exception handling during the deletion process.

🎉 Code changes are done, the pull request is spun,
Account deletion is no fun, but it's necessary when we're done.
From UI to Firebase, every trace,
We ensure to erase, leaving no trace. 🚀


Uplevel your code reviews with Entelligence.AI Pro

Entelligence.AI Pro

If you like this project, please support us by purchasing the Pro version. The Pro version has advanced context, superior noise reduction and several proprietary improvements compared to the open source version. Moreover, Entelligence.AI Pro is free for open source projects.

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description Entelligence.AI

Commits Files that changed from the base of the PR and between c3c6b20 and 094fba5 commits.
Files selected (8)
  • app/lib/pages/settings/delete_account.dart (1)
  • app/lib/pages/settings/device_settings.dart (2)
  • app/lib/pages/settings/profile.dart (2)
  • app/lib/utils/analytics/mixpanel.dart (2)
  • app/lib/widgets/dialog.dart (2)
  • backend/database/users.py (1)
  • backend/routers/users.py (1)
  • backend/utils/other/endpoints.py (1)
Review comments generated (18)
  • Review: 12
  • LGTM: 6

Tips

Chat with Image description Entelligence.AI Bot (@Entelligence.AI)

  • Reply on review comments left by this bot to ask follow-up questions. A review comment is a comment on a diff or a file.
  • Invite the bot into a review comment chain by tagging @Entelligence.AI in a reply.

Code suggestions

  • The bot may make code suggestions, but please review them carefully before committing since the line number ranges may be misaligned.
  • You can edit the comment made by the bot and manually tweak the suggestion if it is slightly off.

Pausing incremental reviews

  • Add @Entelligence.AI: ignore anywhere in the PR description to pause further reviews from the bot.

@@ -23,7 +24,7 @@ getDialog(
: [
TextButton(
onPressed: () => onCancel(),
child: const Text('Cancel', style: TextStyle(color: Colors.white)),
child: Text(cancelButtonText, style: TextStyle(color: Colors.white)),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description Entelligence.AI

The change is good as it allows customization of the cancel button text. However, consider handling the case where cancelButtonText is null to avoid potential null reference exceptions.

- child: Text(cancelButtonText, style: TextStyle(color: Colors.white)),
+ child: Text(cancelButtonText ?? 'Cancel', style: TextStyle(color: Colors.white)),

This way, if cancelButtonText is not provided, it will default to 'Cancel'.

Comment on lines +280 to +287

void deleteAccountClicked() => track('Delete Account Clicked');

void deleteAccountConfirmed() => track('Delete Account Confirmed');

void deleteAccountCancelled() => track('Delete Account Cancelled');

void deleteUser() => _mixpanel?.getPeople().deleteUser();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description Entelligence.AI

The new analytics tracking functions for account deletion events look good. However, I would suggest adding some error handling around the deleteUser() function. If the Mixpanel API call fails for any reason (network issues, API changes, etc.), it could cause an unhandled exception and crash the app.

- void deleteUser() => _mixpanel?.getPeople().deleteUser();
+ void deleteUser() {
+   try {
+     _mixpanel?.getPeople().deleteUser();
+   } catch (e) {
+     print('Failed to delete user from Mixpanel: $e');
+   }
+ }

This way, if the deletion fails, the error will be logged and the app will continue running.

Comment on lines 10 to 12
import 'package:friend_private/utils/analytics/intercom.dart';
import 'package:friend_private/utils/analytics/mixpanel.dart';
import 'package:gradient_borders/gradient_borders.dart';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description Entelligence.AI

The import statements are not properly formatted. According to the Dart style guide, Dart imports should come before other imports. Also, there's an extra space at the beginning of lines 11 and 12.

- import 'package:friend_private/utils/analytics/intercom.dart';
- import 'package:friend_private/utils/analytics/mixpanel.dart';
- import 'package:gradient_borders/gradient_borders.dart';
+ import 'package:friend_private/utils/analytics/intercom.dart';
+ import 'package:friend_private/utils/analytics/mixpanel.dart';
+ import 'package:gradient_borders/gradient_borders.dart';

Comment on lines +89 to +93


def delete_account(uid: str):
auth.delete_user(uid)
return {"message": "User deleted"}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description Entelligence.AI

The delete_account function deletes the user from Firebase Auth but it doesn't seem to delete the user's data from other collections. This could lead to orphaned data in your database. It would be better to call the delete_user_data function before deleting the user account to ensure all related data is cleaned up.

+ from .user_data import delete_user_data

def delete_account(uid: str):
+    delete_user_data(uid)
    auth.delete_user(uid)
    return {"message": "User deleted"}

Also, consider adding error handling around auth.delete_user(uid) to handle any exceptions that might occur during the deletion process.

Comment on lines +20 to +22
delete_user_data(uid)
# delete user from firebase auth
auth.delete_account(uid)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description Entelligence.AI

The delete_user_data(uid) and auth.delete_account(uid) functions are called without any error handling. If an exception occurs in either of these functions, the user will receive a 500 error response, which is not very informative. It would be better to catch exceptions separately for each function call and return more specific error messages.

-    try:
-        delete_user_data(uid)
-        # delete user from firebase auth
-        auth.delete_account(uid)
-    except Exception as e:
-        print('delete_account', str(e))
-        raise HTTPException(status_code=500, detail=str(e))

+    try:
+        delete_user_data(uid)
+    except Exception as e:
+        print('delete_user_data', str(e))
+        raise HTTPException(status_code=500, detail="Failed to delete user data: " + str(e))
+
+    try:
+        auth.delete_account(uid)
+    except Exception as e:
+        print('delete_account', str(e))
+        raise HTTPException(status_code=500, detail="Failed to delete user account: " + str(e))

Comment on lines +48 to +73
def delete_user_data(uid: str):
user_ref = db.collection('users').document(uid)
memories_ref = user_ref.collection('memories')
# delete all memories
batch = db.batch()
for doc in memories_ref.stream():
batch.delete(doc.reference)
batch.commit()
# delete chat messages
messages_ref = user_ref.collection('messages')
batch = db.batch()
for doc in messages_ref.stream():
batch.delete(doc.reference)
batch.commit()
# delete facts
batch = db.batch()
facts_ref = user_ref.collection('facts')
for doc in facts_ref.stream():
batch.delete(doc.reference)
batch.commit()
# delete processing memories
processing_memories_ref = user_ref.collection('processing_memories')
batch = db.batch()
for doc in processing_memories_ref.stream():
batch.delete(doc.reference)
batch.commit()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description Entelligence.AI

The current approach to deleting user data involves multiple batch operations, each of which is committed separately. This could lead to partial deletion if an error occurs during one of the batch commits. To ensure atomicity, consider using a single batch operation for all deletions.

-    # delete all memories
-    batch = db.batch()
-    for doc in memories_ref.stream():
-        batch.delete(doc.reference)
-    batch.commit()
-    # delete chat messages
-    messages_ref = user_ref.collection('messages')
-    batch = db.batch()
-    for doc in messages_ref.stream():
-        batch.delete(doc.reference)
-    batch.commit()
-    # delete facts
-    batch = db.batch()
-    facts_ref = user_ref.collection('facts')
-    for doc in facts_ref.stream():
-        batch.delete(doc.reference)
-    batch.commit()
-    # delete processing memories
-    processing_memories_ref = user_ref.collection('processing_memories')
-    batch = db.batch()
-    for doc in processing_memories_ref.stream():
-        batch.delete(doc.reference)
-    batch.commit()

+    # Initialize a single batch operation
+    batch = db.batch()
+
+    # Delete all memories
+    for doc in memories_ref.stream():
+        batch.delete(doc.reference)
+
+    # Delete chat messages
+    messages_ref = user_ref.collection('messages')
+    for doc in messages_ref.stream():
+        batch.delete(doc.reference)
+
+    # Delete facts
+    facts_ref = user_ref.collection('facts')
+    for doc in facts_ref.stream():
+        batch.delete(doc.reference)
+
+    # Delete processing memories
+    processing_memories_ref = user_ref.collection('processing_memories')
+    for doc in processing_memories_ref.stream():
+        batch.delete(doc.reference)
+
+    # Commit all deletions in a single batch operation
+    batch.commit()

Comment on lines +74 to +75
# delete user
user_ref.delete()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description Entelligence.AI

Deleting the user document from Firestore before ensuring that all associated data has been deleted could lead to orphaned data if an error occurs during the deletion of associated data. Consider moving the user document deletion after all associated data has been successfully deleted.

-    # delete user
-    user_ref.delete()

+    # Delete user after all associated data has been deleted
+    user_ref.delete()


class _DeleteAccountState extends State<DeleteAccount> {
bool checkboxValue = false;
bool isDeleteing = false;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description Entelligence.AI

There's a typo in the variable name isDeleteing. It should be isDeleting.

- bool isDeleteing = false;
+ bool isDeleting = false;

Comment on lines +28 to +41
Future deleteAccountNow() async {
setState(() {
isDeleteing = true;
});
MixpanelManager().deleteAccountConfirmed();
MixpanelManager().deleteUser();
SharedPreferencesUtil().clear();
await deleteAccount();
await FirebaseAuth.instance.signOut();
setState(() {
isDeleteing = false;
});
routeToPage(context, const DeciderWidget(), replace: true);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description Entelligence.AI

The deleteAccountNow function does not handle exceptions. If any of the operations fail (like deleting the account, signing out, or routing to another page), the application could crash or end up in an inconsistent state. Consider adding error handling to this function.

- Future deleteAccountNow() async {
-   setState(() {
-     isDeleteing = true;
-   });
-   MixpanelManager().deleteAccountConfirmed();
-   MixpanelManager().deleteUser();
-   SharedPreferencesUtil().clear();
-   await deleteAccount();
-   await FirebaseAuth.instance.signOut();
-   setState(() {
-     isDeleteing = false;
-   });
-   routeToPage(context, const DeciderWidget(), replace: true);
- }
+ Future deleteAccountNow() async {
+   try {
+     setState(() {
+       isDeleting = true;
+     });
+     MixpanelManager().deleteAccountConfirmed();
+     MixpanelManager().deleteUser();
+     SharedPreferencesUtil().clear();
+     await deleteAccount();
+     await FirebaseAuth.instance.signOut();
+   } catch (e) {
+     // Log the exception and show an error message to the user
+     print(e);
+     ScaffoldMessenger.of(context).showSnackBar(
+       SnackBar(content: Text('An error occurred while deleting your account. Please try again.')),
+     );
+   } finally {
+     setState(() {
+       isDeleting = false;
+     });
+   }
+   if (!isDeleting) {
+     routeToPage(context, const DeciderWidget(), replace: true);
+   }
+ }

Comment on lines +114 to +177
isDeleteing
? const CircularProgressIndicator(
color: Colors.white,
)
: Container(
decoration: BoxDecoration(
border: const GradientBoxBorder(
gradient: LinearGradient(colors: [
Color.fromARGB(127, 208, 208, 208),
Color.fromARGB(127, 188, 99, 121),
Color.fromARGB(127, 86, 101, 182),
Color.fromARGB(127, 126, 190, 236)
]),
width: 2,
),
borderRadius: BorderRadius.circular(12),
),
child: ElevatedButton(
onPressed: () {
if (checkboxValue) {
showDialog(
context: context,
builder: (c) {
return getDialog(context, () {
MixpanelManager().deleteAccountCancelled();
Navigator.of(context).pop();
}, () {
deleteAccountNow();
Navigator.of(context).pop();
}, "Are you sure?\n",
"This action is irreversible and will permanently delete your account and all associated data. Are you sure you want to proceed?",
okButtonText: 'Delete Now', cancelButtonText: 'Go Back');
});
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Check the box to confirm you understand that deleting your account is permanent and irreversible.'),
),
);
}
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.transparent,
shadowColor: const Color.fromARGB(255, 17, 17, 17),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Container(
width: double.infinity,
height: 45,
alignment: Alignment.center,
child: const Text(
'Delete Account',
style: TextStyle(
fontWeight: FontWeight.w400,
fontSize: 18,
color: Color.fromARGB(255, 255, 255, 255),
),
),
),
),
),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description Entelligence.AI

The UI code for the delete button is quite complex and could be simplified by extracting it into a separate widget. This would improve readability and maintainability.

- isDeleteing
-     ? const CircularProgressIndicator(
-         color: Colors.white,
-       )
-     : Container(
-         decoration: BoxDecoration(
-           border: const GradientBoxBorder(
-             gradient: LinearGradient(colors: [
-               Color.fromARGB(127, 208, 208, 208),
-               Color.fromARGB(127, 188, 99, 121),
-               Color.fromARGB(127, 86, 101, 182),
-               Color.fromARGB(127, 126, 190, 236)
-             ]),
-             width: 2,
-           ),
-           borderRadius: BorderRadius.circular(12),
-         ),
-         child: ElevatedButton(
-           onPressed: () {
-             if (checkboxValue) {
-               showDialog(
-                   context: context,
-                   builder: (c) {
-                     return getDialog(context, () {
-                       MixpanelManager().deleteAccountCancelled();
-                       Navigator.of(context).pop();
-                     }, () {
-                       deleteAccountNow();
-                       Navigator.of(context).pop();
-                     }, "Are you sure?\n",
-                         "This action is irreversible and will permanently delete your account and all associated data. Are you sure you want to proceed?",
-                         okButtonText: 'Delete Now', cancelButtonText: 'Go Back');
-                   });
-             } else {
-               ScaffoldMessenger.of(context).showSnackBar(
-                 const SnackBar(
-                   content: Text(
-                       'Check the box to confirm you understand that deleting your account is permanent and irreversible.'),
-                 ),
-               );
-             }
-           },
-           style: ElevatedButton.styleFrom(
-             backgroundColor: Colors.transparent,
-             shadowColor: const Color.fromARGB(255, 17, 17, 17),
-             shape: RoundedRectangleBorder(
-               borderRadius: BorderRadius.circular(12),
-             ),
-           ),
-           child: Container(
-             width: double.infinity,
-             height: 45,
-             alignment: Alignment.center,
-             child: const Text(
-               'Delete Account',
-               style: TextStyle(
-                 fontWeight: FontWeight.w400,
-                 fontSize: 18,
-                 color: Color.fromARGB(255, 255, 255, 255),
-               ),
-             ),
-           ),
-         ),
-       ),
+ DeleteAccountButton(isDeleting: isDeleting, checkboxValue: checkboxValue, deleteAccountNow: deleteAccountNow)

In the above diff, DeleteAccountButton is a new widget that encapsulates all the UI logic for the delete button. You would need to create this new widget and move the corresponding code there.

@josancamon19 josancamon19 merged commit 1c22cc0 into main Sep 28, 2024
1 check passed
@josancamon19 josancamon19 deleted the delete-account branch September 28, 2024 06:35
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description Entelligence.AI

Commits Files that changed from the base of the PR and between 094fba5 and b5ccf01 commits.
Files selected (1)
  • app/lib/pages/settings/profile.dart (2)
Review comments generated (2)
  • Review: 1
  • LGTM: 1

Tips

Chat with Image description Entelligence.AI Bot (@Entelligence.AI)

  • Reply on review comments left by this bot to ask follow-up questions. A review comment is a comment on a diff or a file.
  • Invite the bot into a review comment chain by tagging @Entelligence.AI in a reply.

Code suggestions

  • The bot may make code suggestions, but please review them carefully before committing since the line number ranges may be misaligned.
  • You can edit the comment made by the bot and manually tweak the suggestion if it is slightly off.

Pausing incremental reviews

  • Add @Entelligence.AI: ignore anywhere in the PR description to pause further reviews from the bot.

singleButton: false,
);
});
Navigator.push(context, MaterialPageRoute(builder: (context) => const DeleteAccount()));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description Entelligence.AI

The change from a dialog to a new page for account deletion is a significant UX change. It's important to ensure that the DeleteAccount page provides clear instructions and warnings to the user about the implications of deleting their account. Also, it would be beneficial to add error handling in case the navigation to the DeleteAccount page fails.

- Navigator.push(context, MaterialPageRoute(builder: (context) => const DeleteAccount()));
+ try {
+   Navigator.push(context, MaterialPageRoute(builder: (context) => const DeleteAccount()));
+ } catch (e) {
+   // Handle navigation error here
+ }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants