Skip to content

Commit 1d4aa2d

Browse files
committed
Handle remote calendar connection errors in public availability endpoint
1 parent b06ed0d commit 1d4aa2d

File tree

4 files changed

+61
-44
lines changed

4 files changed

+61
-44
lines changed

backend/src/appointment/controller/calendar.py

Lines changed: 53 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -938,58 +938,68 @@ def existing_events_for_schedule(
938938
if external_connection is None or external_connection.token is None:
939939
raise RemoteCalendarConnectionError()
940940

941-
# Create a single connector for this batch of calendars
942-
con = GoogleConnector(
943-
db=db,
944-
redis_instance=redis,
945-
google_client=google_client,
946-
remote_calendar_id=google_calendars[0].user, # This isn't used for get_busy_time but is still needed.
947-
calendar_id=google_calendars[0].id, # This isn't used for get_busy_time but is still needed.
948-
subscriber_id=subscriber.id,
949-
google_tkn=external_connection.token,
950-
)
951-
952-
# Batch all calendar IDs for this connection into a single API call
953-
calendar_ids = [calendar.user for calendar in google_calendars]
954-
existing_events.extend(
955-
[
956-
schemas.Event(start=busy.get('start'), end=busy.get('end'), title='Busy')
957-
for busy in con.get_busy_time(calendar_ids, start.strftime(DATEFMT), end.strftime(DATEFMT))
958-
]
959-
)
960-
961-
# Process CalDAV calendars individually (no batching support)
962-
for calendar in caldav_calendars:
963-
con = CalDavConnector(
964-
db=db,
965-
redis_instance=redis,
966-
url=calendar.url,
967-
user=calendar.user,
968-
password=calendar.password,
969-
subscriber_id=subscriber.id,
970-
calendar_id=calendar.id,
971-
)
972-
973941
try:
942+
# Create a single connector for this batch of calendars
943+
con = GoogleConnector(
944+
db=db,
945+
redis_instance=redis,
946+
google_client=google_client,
947+
remote_calendar_id=google_calendars[0].user, # Not used for get_busy_time but needed.
948+
calendar_id=google_calendars[0].id, # Not used for get_busy_time but needed.
949+
subscriber_id=subscriber.id,
950+
google_tkn=external_connection.token,
951+
)
952+
953+
# Batch all calendar IDs for this connection into a single API call
954+
calendar_ids = [calendar.user for calendar in google_calendars]
974955
existing_events.extend(
975956
[
976957
schemas.Event(start=busy.get('start'), end=busy.get('end'), title='Busy')
977-
for busy in con.get_busy_time([calendar.url], start.strftime(DATEFMT), end.strftime(DATEFMT))
958+
for busy in con.get_busy_time(calendar_ids, start.strftime(DATEFMT), end.strftime(DATEFMT))
978959
]
979960
)
961+
except RemoteCalendarConnectionError:
962+
raise
963+
except Exception as e:
964+
logging.warning(f'[Tools.existing_events_for_schedule] Google Calendar connection error: {e}')
965+
raise RemoteCalendarConnectionError()
980966

981-
# We're good here, continue along the loop
982-
continue
983-
except caldav.lib.error.ReportError:
984-
logging.debug('[Tools.existing_events_for_schedule] CalDAV server does not support FreeBusy API.')
985-
pass
986-
987-
# Okay maybe this server doesn't support freebusy, try the old way
967+
# Process CalDAV calendars individually (no batching support)
968+
for calendar in caldav_calendars:
988969
try:
970+
con = CalDavConnector(
971+
db=db,
972+
redis_instance=redis,
973+
url=calendar.url,
974+
user=calendar.user,
975+
password=calendar.password,
976+
subscriber_id=subscriber.id,
977+
calendar_id=calendar.id,
978+
)
979+
980+
try:
981+
busy_times = con.get_busy_time(
982+
[calendar.url], start.strftime(DATEFMT), end.strftime(DATEFMT)
983+
)
984+
existing_events.extend(
985+
[
986+
schemas.Event(start=busy.get('start'), end=busy.get('end'), title='Busy')
987+
for busy in busy_times
988+
]
989+
)
990+
991+
# We're good here, continue along the loop
992+
continue
993+
except caldav.lib.error.ReportError:
994+
logging.debug('[Tools.existing_events_for_schedule] CalDAV server does not support FreeBusy API.')
995+
996+
# Okay maybe this server doesn't support freebusy, try the old way
989997
existing_events.extend(con.list_events(start.strftime(DATEFMT), end.strftime(DATEFMT)))
990-
except requests.exceptions.ConnectionError:
991-
# Connection error with remote caldav calendar, don't crash this route.
992-
pass
998+
except RemoteCalendarConnectionError:
999+
raise
1000+
except Exception as e:
1001+
logging.warning(f'[Tools.existing_events_for_schedule] CalDAV connection error: {e}')
1002+
raise RemoteCalendarConnectionError()
9931003

9941004
# handle already requested time slots
9951005
for slot in schedule.slots:

frontend/src/locales/de.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"authenticationRequired": "Entschuldigung, um diese Seite zu sehen ist eine Anmeldung erforderlich.",
2020
"bookingCancelError": "Buchung konnte nicht abgebrochen werden.",
2121
"calendarConnectError": "Es gab ein Problem mit der Kalender-Verbindung.",
22+
"calendarConnectionUnavailable": "Die Kalenderverbindung ist derzeit nicht verfügbar. Bitte kontaktiere den Kalenderbesitzer direkt.",
2223
"credentialsIncomplete": "Bitte gib deine Zugangsdaten ein.",
2324
"dataSourceIsEmpty": "{name} konnte nicht gefunden werden.",
2425
"externalAccountHasNoCalendars": "Dein {external}-Konto enthält keine Kalender. Bitte verbinde ein anderes Konto.",

frontend/src/locales/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"authenticationRequired": "Sorry, this page requires you to be logged in.",
2020
"bookingCancelError": "Booking couldn't be cancelled.",
2121
"calendarConnectError": "There was a problem with the calendar connection.",
22+
"calendarConnectionUnavailable": "The calendar connection is currently not available. Please contact the calendar owner directly.",
2223
"credentialsIncomplete": "Please provide login credentials.",
2324
"dataSourceIsEmpty": "No {name} could be found.",
2425
"externalAccountHasNoCalendars": "Your {external} account contains no calendars. Please connect a different account.",

frontend/src/views/BookerView/index.vue

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { inject, onMounted, ref } from 'vue';
44
import { storeToRefs } from 'pinia';
55
import { useBookingViewStore } from '@/stores/booking-view-store';
66
import { dayjsKey, callKey } from '@/keys';
7+
import { useI18n } from 'vue-i18n';
78
import {
89
Appointment, Slot, Exception, ExceptionDetail, AppointmentResponse
910
} from '@/models';
@@ -15,6 +16,7 @@ import BookingViewError from './components/BookingViewError.vue';
1516
// component constants
1617
const dj = inject(dayjsKey);
1718
const call = inject(callKey);
19+
const { t } = useI18n();
1820
const bookingViewStore = useBookingViewStore();
1921
2022
const errorHeading = ref<string>(null);
@@ -74,9 +76,12 @@ const handleError = (data: Exception) => {
7476
if (errorDetail?.id === 'SCHEDULE_NOT_ACTIVE') {
7577
errorHeading.value = '';
7678
errorBody.value = errorDetail.message;
77-
} else if (errorDetail.id === 'RATE_LIMIT_EXCEEDED') {
79+
} else if (errorDetail?.id === 'RATE_LIMIT_EXCEEDED') {
7880
errorHeading.value = '';
7981
errorBody.value = errorDetail.message;
82+
} else if (errorDetail?.id === 'REMOTE_CALENDAR_CONNECTION_ERROR') {
83+
errorHeading.value = '';
84+
errorBody.value = t('error.calendarConnectionUnavailable');
8085
}
8186
};
8287

0 commit comments

Comments
 (0)