From 56b86509bad002fbcbf0c18dcad57d6adeedbe0c Mon Sep 17 00:00:00 2001 From: Mel <97147377+MelissaAutumn@users.noreply.github.com> Date: Wed, 8 May 2024 12:49:28 -0700 Subject: [PATCH] Allow a user to disconnect their google account (#393) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Disconnect Google: * Add a new "Connected Accounts" section in Settings. * Adds a `google/disconnect` route which removes connection details and calendars. * Make Settings mobile responsive. * Adjust styling for SecondaryButton on dark mode. * Adjust styling for Settings page titles. * Fix Settings page internal routing. * 🌐 Update German lang strings * PR suggestions --------- Co-authored-by: Andreas Müller --- .../src/appointment/database/repo/calendar.py | 10 ++ .../src/appointment/dependencies/google.py | 2 +- backend/src/appointment/routes/google.py | 32 +++- frontend/src/components/SettingsAccount.vue | 45 +---- frontend/src/components/SettingsCalendar.vue | 2 +- .../src/components/SettingsConnections.vue | 161 ++++++++++++++++++ frontend/src/components/SettingsGeneral.vue | 2 +- frontend/src/definitions.js | 6 +- frontend/src/elements/SecondaryButton.vue | 2 +- frontend/src/locales/de.json | 36 ++++ frontend/src/locales/en.json | 36 ++++ .../src/stores/external-connections-store.js | 9 +- frontend/src/views/SettingsView.vue | 38 +---- 13 files changed, 292 insertions(+), 89 deletions(-) create mode 100644 frontend/src/components/SettingsConnections.vue diff --git a/backend/src/appointment/database/repo/calendar.py b/backend/src/appointment/database/repo/calendar.py index 7ead589e5..f5ab6cba1 100644 --- a/backend/src/appointment/database/repo/calendar.py +++ b/backend/src/appointment/database/repo/calendar.py @@ -133,3 +133,13 @@ def delete_by_subscriber(db: Session, subscriber_id: int): for calendar in calendars: delete(db, calendar_id=calendar.id) return True + + +def delete_by_subscriber_and_provider(db: Session, subscriber_id: int, provider: models.CalendarProvider): + """Delete all subscriber's calendar by a provider""" + calendars = get_by_subscriber(db, subscriber_id=subscriber_id) + for calendar in calendars: + if calendar.provider == provider: + delete(db, calendar_id=calendar.id) + + return True diff --git a/backend/src/appointment/dependencies/google.py b/backend/src/appointment/dependencies/google.py index 95c37ac79..b26439444 100644 --- a/backend/src/appointment/dependencies/google.py +++ b/backend/src/appointment/dependencies/google.py @@ -12,7 +12,7 @@ ) -def get_google_client(): +def get_google_client() -> 'GoogleClient': """Returns the google client instance""" try: _google_client.setup() diff --git a/backend/src/appointment/routes/google.py b/backend/src/appointment/routes/google.py index cbf92632f..49f8abe71 100644 --- a/backend/src/appointment/routes/google.py +++ b/backend/src/appointment/routes/google.py @@ -1,18 +1,16 @@ -import json import os -from datetime import datetime from fastapi import APIRouter, Depends, Request from fastapi.responses import RedirectResponse from ..controller.apis.google_client import GoogleClient -from ..database import repo, schemas +from ..database import repo, schemas, models from sqlalchemy.orm import Session from ..dependencies.auth import get_subscriber from ..dependencies.database import get_db -from ..database.models import Subscriber, ExternalConnectionType, ExternalConnections +from ..database.models import Subscriber, ExternalConnectionType from ..dependencies.google import get_google_client from ..exceptions.google_api import GoogleInvalidCredentials from ..exceptions.google_api import GoogleScopeChanged @@ -76,10 +74,12 @@ def google_callback( if google_id is None: return google_callback_error(l10n('google-auth-fail')) - external_connection = repo.external_connection.get_by_type(db, subscriber.id, ExternalConnectionType.google, google_id) + external_connection = repo.external_connection.get_by_type(db, subscriber.id, ExternalConnectionType.google, + google_id) # Create an artificial limit of one google account per account, mainly because we didn't plan for multiple accounts! - remainder = list(filter(lambda ec: ec.type_id != google_id, repo.external_connection.get_by_type(db, subscriber.id, ExternalConnectionType.google))) + remainder = list(filter(lambda ec: ec.type_id != google_id, + repo.external_connection.get_by_type(db, subscriber.id, ExternalConnectionType.google))) if len(remainder) > 0: return google_callback_error(l10n('google-only-one')) @@ -97,7 +97,7 @@ def google_callback( repo.external_connection.create(db, external_connection_schema) else: repo.external_connection.update_token(db, creds.to_json(), subscriber.id, - ExternalConnectionType.google, google_id) + ExternalConnectionType.google, google_id) error_occurred = google_client.sync_calendars(db, subscriber_id=subscriber.id, token=creds) @@ -111,3 +111,21 @@ def google_callback( def google_callback_error(error: str): """Call if you encounter an unrecoverable error with the Google callback function""" return RedirectResponse(f"{os.getenv('FRONTEND_URL', 'http://localhost:8080')}/settings/calendar?error={error}") + + +@router.post("/disconnect") +def disconnect_account( + db: Session = Depends(get_db), + subscriber: Subscriber = Depends(get_subscriber), +): + """Disconnects a google account. Removes associated data from our services and deletes the connection details.""" + google_connection = subscriber.get_external_connection(ExternalConnectionType.google) + + # Remove all of their google calendars (We only support one connection so this should be good for now) + repo.calendar.delete_by_subscriber_and_provider(db, subscriber.id, provider=models.CalendarProvider.google) + + # Remove their account details + repo.external_connection.delete_by_type(db, subscriber.id, google_connection.type, google_connection.type_id) + + + return True diff --git a/frontend/src/components/SettingsAccount.vue b/frontend/src/components/SettingsAccount.vue index 83b431719..78f910534 100644 --- a/frontend/src/components/SettingsAccount.vue +++ b/frontend/src/components/SettingsAccount.vue @@ -1,6 +1,6 @@