Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 53 additions & 43 deletions backend/src/appointment/controller/calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -938,58 +938,68 @@ def existing_events_for_schedule(
if external_connection is None or external_connection.token is None:
raise RemoteCalendarConnectionError()

# Create a single connector for this batch of calendars
con = GoogleConnector(
db=db,
redis_instance=redis,
google_client=google_client,
remote_calendar_id=google_calendars[0].user, # This isn't used for get_busy_time but is still needed.
calendar_id=google_calendars[0].id, # This isn't used for get_busy_time but is still needed.
subscriber_id=subscriber.id,
google_tkn=external_connection.token,
)

# Batch all calendar IDs for this connection into a single API call
calendar_ids = [calendar.user for calendar in google_calendars]
existing_events.extend(
[
schemas.Event(start=busy.get('start'), end=busy.get('end'), title='Busy')
for busy in con.get_busy_time(calendar_ids, start.strftime(DATEFMT), end.strftime(DATEFMT))
]
)

# Process CalDAV calendars individually (no batching support)
for calendar in caldav_calendars:
con = CalDavConnector(
db=db,
redis_instance=redis,
url=calendar.url,
user=calendar.user,
password=calendar.password,
subscriber_id=subscriber.id,
calendar_id=calendar.id,
)

try:
# Create a single connector for this batch of calendars
con = GoogleConnector(
db=db,
redis_instance=redis,
google_client=google_client,
remote_calendar_id=google_calendars[0].user, # Not used for get_busy_time but needed.
calendar_id=google_calendars[0].id, # Not used for get_busy_time but needed.
subscriber_id=subscriber.id,
google_tkn=external_connection.token,
)

# Batch all calendar IDs for this connection into a single API call
calendar_ids = [calendar.user for calendar in google_calendars]
existing_events.extend(
[
schemas.Event(start=busy.get('start'), end=busy.get('end'), title='Busy')
for busy in con.get_busy_time([calendar.url], start.strftime(DATEFMT), end.strftime(DATEFMT))
for busy in con.get_busy_time(calendar_ids, start.strftime(DATEFMT), end.strftime(DATEFMT))
]
)
except RemoteCalendarConnectionError:
raise
except Exception as e:
logging.warning(f'[Tools.existing_events_for_schedule] Google Calendar connection error: {e}')
raise RemoteCalendarConnectionError()

# We're good here, continue along the loop
continue
except caldav.lib.error.ReportError:
logging.debug('[Tools.existing_events_for_schedule] CalDAV server does not support FreeBusy API.')
pass

# Okay maybe this server doesn't support freebusy, try the old way
# Process CalDAV calendars individually (no batching support)
for calendar in caldav_calendars:
try:
con = CalDavConnector(
db=db,
redis_instance=redis,
url=calendar.url,
user=calendar.user,
password=calendar.password,
subscriber_id=subscriber.id,
calendar_id=calendar.id,
)

try:
busy_times = con.get_busy_time(
[calendar.url], start.strftime(DATEFMT), end.strftime(DATEFMT)
)
existing_events.extend(
[
schemas.Event(start=busy.get('start'), end=busy.get('end'), title='Busy')
for busy in busy_times
]
)

# We're good here, continue along the loop
continue
except caldav.lib.error.ReportError:
logging.debug('[Tools.existing_events_for_schedule] CalDAV server does not support FreeBusy API.')

# Okay maybe this server doesn't support freebusy, try the old way
existing_events.extend(con.list_events(start.strftime(DATEFMT), end.strftime(DATEFMT)))
except requests.exceptions.ConnectionError:
# Connection error with remote caldav calendar, don't crash this route.
pass
except RemoteCalendarConnectionError:
raise
except Exception as e:
logging.warning(f'[Tools.existing_events_for_schedule] CalDAV connection error: {e}')
raise RemoteCalendarConnectionError()

# handle already requested time slots
for slot in schedule.slots:
Expand Down
1 change: 1 addition & 0 deletions frontend/src/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"authenticationRequired": "Entschuldigung, um diese Seite zu sehen ist eine Anmeldung erforderlich.",
"bookingCancelError": "Buchung konnte nicht abgebrochen werden.",
"calendarConnectError": "Es gab ein Problem mit der Kalender-Verbindung.",
"calendarConnectionUnavailable": "Die Kalenderverbindung ist derzeit nicht verfügbar. Bitte kontaktiere den Kalenderbesitzer direkt.",
"credentialsIncomplete": "Bitte gib deine Zugangsdaten ein.",
"dataSourceIsEmpty": "{name} konnte nicht gefunden werden.",
"externalAccountHasNoCalendars": "Dein {external}-Konto enthält keine Kalender. Bitte verbinde ein anderes Konto.",
Expand Down
1 change: 1 addition & 0 deletions frontend/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"authenticationRequired": "Sorry, this page requires you to be logged in.",
"bookingCancelError": "Booking couldn't be cancelled.",
"calendarConnectError": "There was a problem with the calendar connection.",
"calendarConnectionUnavailable": "The calendar connection is currently not available. Please contact the calendar owner directly.",
"credentialsIncomplete": "Please provide login credentials.",
"dataSourceIsEmpty": "No {name} could be found.",
"externalAccountHasNoCalendars": "Your {external} account contains no calendars. Please connect a different account.",
Expand Down
22 changes: 16 additions & 6 deletions frontend/src/views/BookerView/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { inject, onMounted, ref } from 'vue';
import { storeToRefs } from 'pinia';
import { useBookingViewStore } from '@/stores/booking-view-store';
import { dayjsKey, callKey } from '@/keys';
import { useI18n } from 'vue-i18n';
import {
Appointment, Slot, Exception, ExceptionDetail, AppointmentResponse
} from '@/models';
Expand All @@ -15,6 +16,7 @@ import BookingViewError from './components/BookingViewError.vue';
// component constants
const dj = inject(dayjsKey);
const call = inject(callKey);
const { t } = useI18n();
const bookingViewStore = useBookingViewStore();

const errorHeading = ref<string>(null);
Expand Down Expand Up @@ -71,12 +73,20 @@ const handleError = (data: Exception) => {

const errorDetail = data?.detail as ExceptionDetail;

if (errorDetail?.id === 'SCHEDULE_NOT_ACTIVE') {
errorHeading.value = '';
errorBody.value = errorDetail.message;
} else if (errorDetail.id === 'RATE_LIMIT_EXCEEDED') {
errorHeading.value = '';
errorBody.value = errorDetail.message;
switch (errorDetail?.id) {
case 'SCHEDULE_NOT_ACTIVE':
case 'RATE_LIMIT_EXCEEDED':
errorHeading.value = '';
errorBody.value = errorDetail.message;
break;
case 'REMOTE_CALENDAR_CONNECTION_ERROR':
errorHeading.value = '';
errorBody.value = t('error.calendarConnectionUnavailable');
break;
default:
errorHeading.value = '';
errorBody.value = t('error.generalBookingError');
break;
}
};

Expand Down