Skip to content

Commit

Permalink
feat(dav): implement personal absence settings
Browse files Browse the repository at this point in the history
Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
  • Loading branch information
st3iny committed Oct 25, 2023
1 parent 107bafd commit c557c4a
Show file tree
Hide file tree
Showing 25 changed files with 792 additions and 319 deletions.
2 changes: 1 addition & 1 deletion apps/dav/appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<name>WebDAV</name>
<summary>WebDAV endpoint</summary>
<description>WebDAV endpoint</description>
<version>1.28.0</version>
<version>1.29.0</version>
<licence>agpl</licence>
<author>owncloud.org</author>
<namespace>DAV</namespace>
Expand Down
5 changes: 4 additions & 1 deletion apps/dav/appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*
* @author Georg Ehrke <oc.list@georgehrke.com>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Richard Steinmetz <richard@steinmetz.cloud>
*
* @license GNU AGPL version 3 or any later version
*
Expand All @@ -28,7 +29,9 @@
['name' => 'invitation_response#accept', 'url' => '/invitation/accept/{token}', 'verb' => 'GET'],
['name' => 'invitation_response#decline', 'url' => '/invitation/decline/{token}', 'verb' => 'GET'],
['name' => 'invitation_response#options', 'url' => '/invitation/moreOptions/{token}', 'verb' => 'GET'],
['name' => 'invitation_response#processMoreOptionsResult', 'url' => '/invitation/moreOptions/{token}', 'verb' => 'POST']
['name' => 'invitation_response#processMoreOptionsResult', 'url' => '/invitation/moreOptions/{token}', 'verb' => 'POST'],
['name' => 'availability_settings#updateAbsence', 'url' => '/settings/absence', 'verb' => 'POST'],
['name' => 'availability_settings#clearAbsence', 'url' => '/settings/absence', 'verb' => 'DELETE'],
],
'ocs' => [
['name' => 'direct#getUrl', 'url' => '/api/v1/direct', 'verb' => 'POST'],
Expand Down
5 changes: 5 additions & 0 deletions apps/dav/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@
'OCA\\DAV\\Connector\\Sabre\\SharesPlugin' => $baseDir . '/../lib/Connector/Sabre/SharesPlugin.php',
'OCA\\DAV\\Connector\\Sabre\\TagList' => $baseDir . '/../lib/Connector/Sabre/TagList.php',
'OCA\\DAV\\Connector\\Sabre\\TagsPlugin' => $baseDir . '/../lib/Connector/Sabre/TagsPlugin.php',
'OCA\\DAV\\Controller\\AvailabilitySettingsController' => $baseDir . '/../lib/Controller/AvailabilitySettingsController.php',
'OCA\\DAV\\Controller\\BirthdayCalendarController' => $baseDir . '/../lib/Controller/BirthdayCalendarController.php',
'OCA\\DAV\\Controller\\DirectController' => $baseDir . '/../lib/Controller/DirectController.php',
'OCA\\DAV\\Controller\\InvitationResponseController' => $baseDir . '/../lib/Controller/InvitationResponseController.php',
Expand All @@ -201,6 +202,8 @@
'OCA\\DAV\\DAV\\Sharing\\Xml\\ShareRequest' => $baseDir . '/../lib/DAV/Sharing/Xml/ShareRequest.php',
'OCA\\DAV\\DAV\\SystemPrincipalBackend' => $baseDir . '/../lib/DAV/SystemPrincipalBackend.php',
'OCA\\DAV\\DAV\\ViewOnlyPlugin' => $baseDir . '/../lib/DAV/ViewOnlyPlugin.php',
'OCA\\DAV\\Db\\Absence' => $baseDir . '/../lib/Db/Absence.php',
'OCA\\DAV\\Db\\AbsenceMapper' => $baseDir . '/../lib/Db/AbsenceMapper.php',
'OCA\\DAV\\Db\\Direct' => $baseDir . '/../lib/Db/Direct.php',
'OCA\\DAV\\Db\\DirectMapper' => $baseDir . '/../lib/Db/DirectMapper.php',
'OCA\\DAV\\Direct\\DirectFile' => $baseDir . '/../lib/Direct/DirectFile.php',
Expand Down Expand Up @@ -297,6 +300,7 @@
'OCA\\DAV\\Migration\\Version1018Date20210312100735' => $baseDir . '/../lib/Migration/Version1018Date20210312100735.php',
'OCA\\DAV\\Migration\\Version1024Date20211221144219' => $baseDir . '/../lib/Migration/Version1024Date20211221144219.php',
'OCA\\DAV\\Migration\\Version1027Date20230504122946' => $baseDir . '/../lib/Migration/Version1027Date20230504122946.php',
'OCA\\DAV\\Migration\\Version1029Date20231004091403' => $baseDir . '/../lib/Migration/Version1029Date20231004091403.php',
'OCA\\DAV\\Profiler\\ProfilerPlugin' => $baseDir . '/../lib/Profiler/ProfilerPlugin.php',
'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningNode' => $baseDir . '/../lib/Provisioning/Apple/AppleProvisioningNode.php',
'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningPlugin' => $baseDir . '/../lib/Provisioning/Apple/AppleProvisioningPlugin.php',
Expand All @@ -306,6 +310,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\\Service\\AbsenceService' => $baseDir . '/../lib/Service/AbsenceService.php',
'OCA\\DAV\\Settings\\AvailabilitySettings' => $baseDir . '/../lib/Settings/AvailabilitySettings.php',
'OCA\\DAV\\Settings\\CalDAVSettings' => $baseDir . '/../lib/Settings/CalDAVSettings.php',
'OCA\\DAV\\SetupChecks\\NeedsSystemAddressBookSync' => $baseDir . '/../lib/SetupChecks/NeedsSystemAddressBookSync.php',
Expand Down
5 changes: 5 additions & 0 deletions apps/dav/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ class ComposerStaticInitDAV
'OCA\\DAV\\Connector\\Sabre\\SharesPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/SharesPlugin.php',
'OCA\\DAV\\Connector\\Sabre\\TagList' => __DIR__ . '/..' . '/../lib/Connector/Sabre/TagList.php',
'OCA\\DAV\\Connector\\Sabre\\TagsPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/TagsPlugin.php',
'OCA\\DAV\\Controller\\AvailabilitySettingsController' => __DIR__ . '/..' . '/../lib/Controller/AvailabilitySettingsController.php',
'OCA\\DAV\\Controller\\BirthdayCalendarController' => __DIR__ . '/..' . '/../lib/Controller/BirthdayCalendarController.php',
'OCA\\DAV\\Controller\\DirectController' => __DIR__ . '/..' . '/../lib/Controller/DirectController.php',
'OCA\\DAV\\Controller\\InvitationResponseController' => __DIR__ . '/..' . '/../lib/Controller/InvitationResponseController.php',
Expand All @@ -216,6 +217,8 @@ class ComposerStaticInitDAV
'OCA\\DAV\\DAV\\Sharing\\Xml\\ShareRequest' => __DIR__ . '/..' . '/../lib/DAV/Sharing/Xml/ShareRequest.php',
'OCA\\DAV\\DAV\\SystemPrincipalBackend' => __DIR__ . '/..' . '/../lib/DAV/SystemPrincipalBackend.php',
'OCA\\DAV\\DAV\\ViewOnlyPlugin' => __DIR__ . '/..' . '/../lib/DAV/ViewOnlyPlugin.php',
'OCA\\DAV\\Db\\Absence' => __DIR__ . '/..' . '/../lib/Db/Absence.php',
'OCA\\DAV\\Db\\AbsenceMapper' => __DIR__ . '/..' . '/../lib/Db/AbsenceMapper.php',
'OCA\\DAV\\Db\\Direct' => __DIR__ . '/..' . '/../lib/Db/Direct.php',
'OCA\\DAV\\Db\\DirectMapper' => __DIR__ . '/..' . '/../lib/Db/DirectMapper.php',
'OCA\\DAV\\Direct\\DirectFile' => __DIR__ . '/..' . '/../lib/Direct/DirectFile.php',
Expand Down Expand Up @@ -312,6 +315,7 @@ class ComposerStaticInitDAV
'OCA\\DAV\\Migration\\Version1018Date20210312100735' => __DIR__ . '/..' . '/../lib/Migration/Version1018Date20210312100735.php',
'OCA\\DAV\\Migration\\Version1024Date20211221144219' => __DIR__ . '/..' . '/../lib/Migration/Version1024Date20211221144219.php',
'OCA\\DAV\\Migration\\Version1027Date20230504122946' => __DIR__ . '/..' . '/../lib/Migration/Version1027Date20230504122946.php',
'OCA\\DAV\\Migration\\Version1029Date20231004091403' => __DIR__ . '/..' . '/../lib/Migration/Version1029Date20231004091403.php',
'OCA\\DAV\\Profiler\\ProfilerPlugin' => __DIR__ . '/..' . '/../lib/Profiler/ProfilerPlugin.php',
'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningNode' => __DIR__ . '/..' . '/../lib/Provisioning/Apple/AppleProvisioningNode.php',
'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningPlugin' => __DIR__ . '/..' . '/../lib/Provisioning/Apple/AppleProvisioningPlugin.php',
Expand All @@ -321,6 +325,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\\Service\\AbsenceService' => __DIR__ . '/..' . '/../lib/Service/AbsenceService.php',
'OCA\\DAV\\Settings\\AvailabilitySettings' => __DIR__ . '/..' . '/../lib/Settings/AvailabilitySettings.php',
'OCA\\DAV\\Settings\\CalDAVSettings' => __DIR__ . '/..' . '/../lib/Settings/CalDAVSettings.php',
'OCA\\DAV\\SetupChecks\\NeedsSystemAddressBookSync' => __DIR__ . '/..' . '/../lib/SetupChecks/NeedsSystemAddressBookSync.php',
Expand Down
94 changes: 94 additions & 0 deletions apps/dav/lib/Controller/AvailabilitySettingsController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php

declare(strict_types=1);

/**
* @copyright Copyright (c) 2023 Richard Steinmetz <richard@steinmetz.cloud>
*
* @author Richard Steinmetz <richard@steinmetz.cloud>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\DAV\Controller;

use DateTimeImmutable;
use OCA\DAV\AppInfo\Application;
use OCA\DAV\Service\AbsenceService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\Response;
use OCP\IRequest;

class AvailabilitySettingsController extends Controller {
public function __construct(
IRequest $request,
private ?string $userId,
private AbsenceService $absenceService,

Check failure on line 43 in apps/dav/lib/Controller/AvailabilitySettingsController.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

UndefinedClass

apps/dav/lib/Controller/AvailabilitySettingsController.php:43:3: UndefinedClass: Class, interface or enum named OCA\DAV\Service\AbsenceService does not exist (see https://psalm.dev/019)

Check failure

Code scanning / Psalm

UndefinedClass Error

Class, interface or enum named OCA\DAV\Service\AbsenceService does not exist
) {
parent::__construct(Application::APP_ID, $request);
}

/**
* @throws \OCP\DB\Exception
* @throws \Exception
*/
#[NoAdminRequired]
public function updateAbsence(
string $firstDay,
string $lastDay,
string $status,
string $message,
): Response {
$userId = $this->userId;
if ($userId === null) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}

$parsedFirstDay = new DateTimeImmutable($firstDay);
$parsedLastDay = new DateTimeImmutable($lastDay);
if ($parsedFirstDay->getTimestamp() >= $parsedLastDay->getTimestamp()) {
throw new \Exception('First day is on or after last day');
}

$absence = $this->absenceService->createOrUpdateAbsence(

Check failure on line 70 in apps/dav/lib/Controller/AvailabilitySettingsController.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

UndefinedClass

apps/dav/lib/Controller/AvailabilitySettingsController.php:70:14: UndefinedClass: Class, interface or enum named OCA\DAV\Service\AbsenceService does not exist (see https://psalm.dev/019)

Check failure

Code scanning / Psalm

UndefinedClass Error

Class, interface or enum named OCA\DAV\Service\AbsenceService does not exist
$userId,
$firstDay,
$lastDay,
$status,
$message,
);
return new JSONResponse($absence);
}

/**
* @throws \OCP\DB\Exception
*/
#[NoAdminRequired]
public function clearAbsence(): Response {
$userId = $this->userId;
if ($userId === null) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}

$this->absenceService->clearAbsence($userId);

Check failure on line 90 in apps/dav/lib/Controller/AvailabilitySettingsController.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

UndefinedClass

apps/dav/lib/Controller/AvailabilitySettingsController.php:90:3: UndefinedClass: Class, interface or enum named OCA\DAV\Service\AbsenceService does not exist (see https://psalm.dev/019)

Check failure

Code scanning / Psalm

UndefinedClass Error

Class, interface or enum named OCA\DAV\Service\AbsenceService does not exist
return new JSONResponse([]);
}

}
19 changes: 18 additions & 1 deletion apps/dav/lib/Settings/AvailabilitySettings.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* @copyright 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Richard Steinmetz <richard@steinmetz.cloud>
*
* @license GNU AGPL version 3 or any later version
*
Expand All @@ -26,6 +27,8 @@
namespace OCA\DAV\Settings;

use OCA\DAV\AppInfo\Application;
use OCA\DAV\Db\AbsenceMapper;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Services\IInitialState;
use OCP\IConfig;
Expand All @@ -38,7 +41,8 @@ class AvailabilitySettings implements ISettings {

public function __construct(IConfig $config,
IInitialState $initialState,
?string $userId) {
?string $userId,
private AbsenceMapper $absenceMapper) {

Check failure on line 45 in apps/dav/lib/Settings/AvailabilitySettings.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

UndefinedClass

apps/dav/lib/Settings/AvailabilitySettings.php:45:9: UndefinedClass: Class, interface or enum named OCA\DAV\Db\AbsenceMapper does not exist (see https://psalm.dev/019)

Check failure

Code scanning / Psalm

UndefinedClass Error

Class, interface or enum named OCA\DAV\Db\AbsenceMapper does not exist
$this->config = $config;
$this->initialState = $initialState;
$this->userId = $userId;
Expand All @@ -54,6 +58,19 @@ public function getForm(): TemplateResponse {
'no'
)
);
$hideAbsenceSettings = $this->config->getAppValue(
Application::APP_ID,
'hide_absence_settings',
'yes',
) === 'yes';
$this->initialState->provideInitialState('hide_absence_settings', $hideAbsenceSettings);
if (!$hideAbsenceSettings) {
try {
$absence = $this->absenceMapper->findByUserId($this->userId);

Check failure on line 69 in apps/dav/lib/Settings/AvailabilitySettings.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

UndefinedClass

apps/dav/lib/Settings/AvailabilitySettings.php:69:16: UndefinedClass: Class, interface or enum named OCA\DAV\Db\AbsenceMapper does not exist (see https://psalm.dev/019)

Check failure

Code scanning / Psalm

UndefinedClass Error

Class, interface or enum named OCA\DAV\Db\AbsenceMapper does not exist
$this->initialState->provideInitialState('absence', $absence);
} catch (DoesNotExistException|\OCP\DB\Exception) {
}
}

return new TemplateResponse(Application::APP_ID, 'settings-personal-availability');
}
Expand Down
160 changes: 160 additions & 0 deletions apps/dav/src/components/AbsenceForm.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
<!--
- @copyright Copyright (c) 2023 Richard Steinmetz <richard@steinmetz.cloud>
-
- @author Richard Steinmetz <richard@steinmetz.cloud>
-
- @license AGPL-3.0-or-later
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU 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 General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->

<template>
<div class="absence">
<div class="absence__dates">
<NcDateTimePickerNative id="absence-first-day"
v-model="firstDay"
:label="$t('dav', 'First day')"
class="absence__dates__picker" />
<NcDateTimePickerNative id="absence-last-day"
v-model="lastDay"
:label="$t('dav', 'Last day (inclusive)')"
class="absence__dates__picker" />
</div>
<NcTextField :value.sync="status" :label="$t('dav', 'Short absence status')" />
<NcTextArea :value.sync="message" :label="$t('dav', 'Long absence Message')" />

<div class="absence__buttons">
<NcButton :disabled="loading"
type="primary"
@click="saveForm">
{{ $t('dav', 'Save') }}
</NcButton>
<NcButton :disabled="loading"
type="error"
@click="clearAbsence">
{{ $t('dav', 'Disable absence') }}
</NcButton>
</div>
</div>
</template>

<script>
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
import NcTextArea from '@nextcloud/vue/dist/Components/NcTextArea.js'
import NcDateTimePickerNative from '@nextcloud/vue/dist/Components/NcDateTimePickerNative.js'
import { generateUrl } from '@nextcloud/router'
import axios from '@nextcloud/axios'
import { formatDateAsYMD } from '../utils/date.js'
import { loadState } from '@nextcloud/initial-state'
import { showError } from '@nextcloud/dialogs'

export default {
name: 'AbsenceForm',
components: {
NcButton,
NcTextField,
NcTextArea,
NcDateTimePickerNative,
},
data() {
const { firstDay, lastDay, status, message } = loadState('dav', 'absence', {})

return {
loading: false,
status: status ?? '',
message: message ?? '',
firstDay: firstDay ? new Date(firstDay) : new Date(),
lastDay: lastDay ? new Date(lastDay) : null,
}
},
computed: {
/**
* @return {boolean}
*/
valid() {
return !!this.firstDay
&& !!this.lastDay
&& !!this.status
&& this.lastDay > this.firstDay
},
},
methods: {
resetForm() {
this.status = ''
this.message = ''
this.firstDay = new Date()
this.lastDay = null
},
async saveForm() {
if (!this.valid) {
return
}

this.loading = true
try {
await axios.post(generateUrl('/apps/dav/settings/absence'), {
firstDay: formatDateAsYMD(this.firstDay),
lastDay: formatDateAsYMD(this.lastDay),
status: this.status,
message: this.message,
})
} catch (error) {
showError(this.$t('dav', 'Failed to save your absence settings'))
} finally {
this.loading = false
}
},
async clearAbsence() {
this.loading = true
try {
await axios.delete(generateUrl('/apps/dav/settings/absence'))
this.resetForm()
} catch (error) {
// TODO: Handle error
} finally {
this.loading = false
}
},
},
}
</script>

<style lang="scss" scoped>
.absence {
display: flex;
flex-direction: column;
gap: 5px;

&__dates {
display: flex;
gap: 10px;
width: 100%;

&__picker {
flex: 1 auto;

::v-deep .native-datetime-picker--input {
margin-bottom: 0;
}
}
}

&__buttons {
display: flex;
gap: 5px;
}
}
</style>
Loading

0 comments on commit c557c4a

Please sign in to comment.