diff options
Diffstat (limited to 'apps/dav/src/service')
-rw-r--r-- | apps/dav/src/service/CalendarService.js | 147 | ||||
-rw-r--r-- | apps/dav/src/service/logger.js | 28 |
2 files changed, 175 insertions, 0 deletions
diff --git a/apps/dav/src/service/CalendarService.js b/apps/dav/src/service/CalendarService.js new file mode 100644 index 00000000000..ff47be59959 --- /dev/null +++ b/apps/dav/src/service/CalendarService.js @@ -0,0 +1,147 @@ +/** + * @copyright 2021 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @author 2021 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +import { getClient } from '../dav/client' +import ICAL from 'ical.js' +import logger from './logger' +import { parseXML } from 'webdav/dist/node/tools/dav' +import { v4 as uuidv4 } from 'uuid' + +export function getEmptySlots() { + return { + MO: [], + TU: [], + WE: [], + TH: [], + FR: [], + SA: [], + SU: [], + } +} + +export async function findScheduleInboxAvailability() { + const client = getClient('calendars') + + const response = await client.customRequest('inbox', { + method: 'PROPFIND', + data: `<?xml version="1.0"?> + <x0:propfind xmlns:x0="DAV:"> + <x0:prop> + <x1:calendar-availability xmlns:x1="urn:ietf:params:xml:ns:caldav"/> + </x0:prop> + </x0:propfind>` + }) + + const xml = await parseXML(response.data) + + if (!xml) { + return undefined + } + + const availability = xml?.multistatus?.response[0]?.propstat?.prop['calendar-availability'] + if (!availability) { + return undefined + } + + const parsedIcal = ICAL.parse(availability) + + const vcalendarComp = new ICAL.Component(parsedIcal) + const vavailabilityComp = vcalendarComp.getFirstSubcomponent('vavailability') + const availableComps = vavailabilityComp.getAllSubcomponents('available') + + // Combine all AVAILABLE blocks into a week of slots + const slots = getEmptySlots() + availableComps.forEach((availableComp) => { + const start = availableComp.getFirstProperty('dtstart').getFirstValue().toJSDate() + const end = availableComp.getFirstProperty('dtend').getFirstValue().toJSDate() + const rrule = availableComp.getFirstProperty('rrule') + + if (rrule.getFirstValue().freq !== 'WEEKLY') { + logger.warn('rrule not supported', { + rrule: rrule.toICALString(), + }) + return + } + + rrule.getFirstValue().getComponent('BYDAY').forEach(day => { + slots[day].push({ + start, + end, + }) + }) + }) + + return { + slots, + } +} + +export async function saveScheduleInboxAvailability(slots, timezoneId) { + const all = [...Object.keys(slots).flatMap(dayId => slots[dayId].map(slot => ({ + ...slot, + day: dayId, + })))] + + const vavailabilityComp = new ICAL.Component('vavailability') + // TODO: deduplicate slots that occur on more than one day + all.map(slot => { + const availableComp = new ICAL.Component('available') + + // Define DTSTART and DTEND + // TODO: tz? moment.tz(dateTime, timezone).toDate() + const startTimeProp = availableComp.addPropertyWithValue('dtstart', ICAL.Time.fromJSDate(slot.start, false)) + startTimeProp.setParameter('tzid', timezoneId) + const endTimeProp = availableComp.addPropertyWithValue('dtend', ICAL.Time.fromJSDate(slot.end, false)) + endTimeProp.setParameter('tzid', timezoneId) + + // Add mandatory UID + availableComp.addPropertyWithValue('uid', uuidv4()) + + // TODO: add optional summary + + // Define RRULE + availableComp.addPropertyWithValue('rrule', { + freq: 'WEEKLY', + byday: slot.day, + }) + + return availableComp + }).map(vavailabilityComp.addSubcomponent.bind(vavailabilityComp)) + + const vcalendarComp = new ICAL.Component('vcalendar') + vcalendarComp.addSubcomponent(vavailabilityComp) + logger.debug('New availability ical created', { + asObject: vcalendarComp, + asString: vcalendarComp.toString(), + }) + + const client = getClient('calendars') + await client.customRequest('inbox', { + method: 'PROPPATCH', + data: `<?xml version="1.0"?> + <x0:propertyupdate xmlns:x0="DAV:"> + <x0:set> + <x0:prop> + <x1:calendar-availability xmlns:x1="urn:ietf:params:xml:ns:caldav">${vcalendarComp.toString()}</x1:calendar-availability> + </x0:prop> + </x0:set> + </x0:propertyupdate>` + }) +} diff --git a/apps/dav/src/service/logger.js b/apps/dav/src/service/logger.js new file mode 100644 index 00000000000..8941d8d8adc --- /dev/null +++ b/apps/dav/src/service/logger.js @@ -0,0 +1,28 @@ +/* + * @copyright 2021 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @author 2021 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +import { getLoggerBuilder } from '@nextcloud/logger' + +const logger = getLoggerBuilder() + .setApp('dav') + .detectUser() + .build() + +export default logger |