Skip to content

Commit

Permalink
Merge pull request #27466 from nextcloud/feature/dav-scheduling-avail…
Browse files Browse the repository at this point in the history
…ability-settings

Add scheduling availability settings
  • Loading branch information
ChristophWurst authored Oct 18, 2021
2 parents 4eb421e + f432dd2 commit a4368ad
Show file tree
Hide file tree
Showing 21 changed files with 2,714 additions and 3 deletions.
1 change: 1 addition & 0 deletions apps/dav/appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@

<settings>
<admin>OCA\DAV\Settings\CalDAVSettings</admin>
<personal>OCA\DAV\Settings\AvailabilitySettings</personal>
</settings>

<activity>
Expand Down
1 change: 1 addition & 0 deletions apps/dav/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@
'OCA\\DAV\\Search\\EventsSearchProvider' => $baseDir . '/../lib/Search/EventsSearchProvider.php',
'OCA\\DAV\\Search\\TasksSearchProvider' => $baseDir . '/../lib/Search/TasksSearchProvider.php',
'OCA\\DAV\\Server' => $baseDir . '/../lib/Server.php',
'OCA\\DAV\\Settings\\AvailabilitySettings' => $baseDir . '/../lib/Settings/AvailabilitySettings.php',
'OCA\\DAV\\Settings\\CalDAVSettings' => $baseDir . '/../lib/Settings/CalDAVSettings.php',
'OCA\\DAV\\Storage\\PublicOwnerWrapper' => $baseDir . '/../lib/Storage/PublicOwnerWrapper.php',
'OCA\\DAV\\SystemTag\\SystemTagMappingNode' => $baseDir . '/../lib/SystemTag/SystemTagMappingNode.php',
Expand Down
1 change: 1 addition & 0 deletions apps/dav/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ class ComposerStaticInitDAV
'OCA\\DAV\\Search\\EventsSearchProvider' => __DIR__ . '/..' . '/../lib/Search/EventsSearchProvider.php',
'OCA\\DAV\\Search\\TasksSearchProvider' => __DIR__ . '/..' . '/../lib/Search/TasksSearchProvider.php',
'OCA\\DAV\\Server' => __DIR__ . '/..' . '/../lib/Server.php',
'OCA\\DAV\\Settings\\AvailabilitySettings' => __DIR__ . '/..' . '/../lib/Settings/AvailabilitySettings.php',
'OCA\\DAV\\Settings\\CalDAVSettings' => __DIR__ . '/..' . '/../lib/Settings/CalDAVSettings.php',
'OCA\\DAV\\Storage\\PublicOwnerWrapper' => __DIR__ . '/..' . '/../lib/Storage/PublicOwnerWrapper.php',
'OCA\\DAV\\SystemTag\\SystemTagMappingNode' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagMappingNode.php',
Expand Down
4 changes: 2 additions & 2 deletions apps/dav/js/settings-admin-caldav.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion apps/dav/js/settings-admin-caldav.js.map

Large diffs are not rendered by default.

2,122 changes: 2,122 additions & 0 deletions apps/dav/js/settings-personal-availability.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions apps/dav/js/settings-personal-availability.js.map

Large diffs are not rendered by default.

44 changes: 44 additions & 0 deletions apps/dav/lib/Settings/AvailabilitySettings.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

/*
* @copyright 2021 Christoph Wurst <[email protected]>
*
* @author 2021 Christoph Wurst <[email protected]>
*
* @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/>.
*/

namespace OCA\DAV\Settings;

use OCA\DAV\AppInfo\Application;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\Settings\ISettings;

class AvailabilitySettings implements ISettings {
public function getForm(): TemplateResponse {
return new TemplateResponse(Application::APP_ID, 'settings-personal-availability');
}

public function getSection(): string {
return 'groupware';
}

public function getPriority(): int {
return 10;
}
}
39 changes: 39 additions & 0 deletions apps/dav/src/dav/client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* @copyright 2021 Christoph Wurst <[email protected]>
*
* @author 2021 Christoph Wurst <[email protected]>
*
* @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 * as webdav from 'webdav'
import axios from '@nextcloud/axios'
import memoize from 'lodash/fp/memoize'
import { generateRemoteUrl } from '@nextcloud/router'
import { getCurrentUser } from '@nextcloud/auth'

export const getClient = memoize((service) => {
// Add this so the server knows it is an request from the browser
axios.defaults.headers['X-Requested-With'] = 'XMLHttpRequest'

// force our axios
const patcher = webdav.getPatcher()
patcher.patch('request', axios)

return webdav.createClient(
generateRemoteUrl(`dav/${service}/${getCurrentUser().uid}`)
)
})
147 changes: 147 additions & 0 deletions apps/dav/src/service/CalendarService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/**
* @copyright 2021 Christoph Wurst <[email protected]>
*
* @author 2021 Christoph Wurst <[email protected]>
*
* @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>`
})
}
28 changes: 28 additions & 0 deletions apps/dav/src/service/logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* @copyright 2021 Christoph Wurst <[email protected]>
*
* @author 2021 Christoph Wurst <[email protected]>
*
* @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
9 changes: 9 additions & 0 deletions apps/dav/src/settings-personal-availability.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Vue from 'vue'
import { translate } from '@nextcloud/l10n'
import Availability from './views/Availability'

Vue.prototype.$t = translate

const View = Vue.extend(Availability);

(new View({})).$mount('#settings-personal-availability')
Loading

0 comments on commit a4368ad

Please sign in to comment.