feat: Add out-of-office message API

[skipci]

Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
pull/40653/head
Christoph Wurst 8 months ago committed by Richard Steinmetz
parent 1acc7c0468
commit ab1a1d688d
No known key found for this signature in database
GPG Key ID: 27137D9E7D273FB2

@ -35,5 +35,6 @@ return [
],
'ocs' => [
['name' => 'direct#getUrl', 'url' => '/api/v1/direct', 'verb' => 'POST'],
['name' => 'out_of_office#getCurrentOutOfOfficeData', 'url' => '/api/v1/outOfOffice/{userId}', 'verb' => 'GET'],
],
];

@ -192,6 +192,7 @@ return array(
'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',
'OCA\\DAV\\Controller\\OutOfOfficeController' => $baseDir . '/../lib/Controller/OutOfOfficeController.php',
'OCA\\DAV\\DAV\\CustomPropertiesBackend' => $baseDir . '/../lib/DAV/CustomPropertiesBackend.php',
'OCA\\DAV\\DAV\\GroupPrincipalBackend' => $baseDir . '/../lib/DAV/GroupPrincipalBackend.php',
'OCA\\DAV\\DAV\\PublicAuth' => $baseDir . '/../lib/DAV/PublicAuth.php',
@ -305,6 +306,7 @@ return array(
'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',
'OCA\\DAV\\ResponseDefinitions' => $baseDir . '/../lib/ResponseDefinitions.php',
'OCA\\DAV\\RootCollection' => $baseDir . '/../lib/RootCollection.php',
'OCA\\DAV\\Search\\ACalendarSearchProvider' => $baseDir . '/../lib/Search/ACalendarSearchProvider.php',
'OCA\\DAV\\Search\\ContactsSearchProvider' => $baseDir . '/../lib/Search/ContactsSearchProvider.php',

@ -207,6 +207,7 @@ class ComposerStaticInitDAV
'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',
'OCA\\DAV\\Controller\\OutOfOfficeController' => __DIR__ . '/..' . '/../lib/Controller/OutOfOfficeController.php',
'OCA\\DAV\\DAV\\CustomPropertiesBackend' => __DIR__ . '/..' . '/../lib/DAV/CustomPropertiesBackend.php',
'OCA\\DAV\\DAV\\GroupPrincipalBackend' => __DIR__ . '/..' . '/../lib/DAV/GroupPrincipalBackend.php',
'OCA\\DAV\\DAV\\PublicAuth' => __DIR__ . '/..' . '/../lib/DAV/PublicAuth.php',
@ -320,6 +321,7 @@ class ComposerStaticInitDAV
'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',
'OCA\\DAV\\ResponseDefinitions' => __DIR__ . '/..' . '/../lib/ResponseDefinitions.php',
'OCA\\DAV\\RootCollection' => __DIR__ . '/..' . '/../lib/RootCollection.php',
'OCA\\DAV\\Search\\ACalendarSearchProvider' => __DIR__ . '/..' . '/../lib/Search/ACalendarSearchProvider.php',
'OCA\\DAV\\Search\\ContactsSearchProvider' => __DIR__ . '/..' . '/../lib/Search/ContactsSearchProvider.php',

@ -0,0 +1,78 @@
<?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 OCA\DAV\Db\AbsenceMapper;
use OCA\DAV\ResponseDefinitions;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\IRequest;
/**
* @psalm-import-type DAVOutOfOfficeData from ResponseDefinitions
*/
class OutOfOfficeController extends OCSController {
public function __construct(
string $appName,
IRequest $request,
private AbsenceMapper $absenceMapper,
) {
parent::__construct($appName, $request);
}
/**
* Get the currently configured out-of-office data of a user.
*
* @NoAdminRequired
* @NoCSRFRequired
*
* @param string $userId The user id to get out-of-office data for.
* @return DataResponse<Http::STATUS_OK|Http::STATUS_NOT_FOUND, ?DAVOutOfOfficeData, array{}>
*
* 200: Out-of-office data
* 404: No out-of-office data was found
*/
public function getCurrentOutOfOfficeData(string $userId): DataResponse {
try {
$data = $this->absenceMapper->findByUserId($userId);
} catch (DoesNotExistException) {
return new DataResponse(null, Http::STATUS_NOT_FOUND);
}
return new DataResponse([
'id' => $data->getId(),
'userId' => $data->getUserId(),
'firstDay' => $data->getFirstDay(),
'lastDay' => $data->getLastDay(),
'status' => $data->getStatus(),
'message' => $data->getMessage(),
]);
}
}

@ -26,8 +26,13 @@ declare(strict_types=1);
namespace OCA\DAV\Db;
use DateTimeImmutable;
use InvalidArgumentException;
use JsonSerializable;
use OC\User\OutOfOfficeData;
use OCP\AppFramework\Db\Entity;
use OCP\IUser;
use OCP\User\IOutOfOfficeData;
/**
* @method string getUserId()
@ -43,8 +48,13 @@ use OCP\AppFramework\Db\Entity;
*/
class Absence extends Entity implements JsonSerializable {
protected string $userId = '';
/** Inclusive, formatted as YYYY-MM-DD */
protected string $firstDay = '';
/** Inclusive, formatted as YYYY-MM-DD */
protected string $lastDay = '';
protected string $status = '';
protected string $message = '';
@ -56,6 +66,24 @@ class Absence extends Entity implements JsonSerializable {
$this->addType('message', 'string');
}
public function toOutOufOfficeData(IUser $user): IOutOfOfficeData {
if ($user->getUID() !== $this->getUserId()) {
throw new InvalidArgumentException("The user doesn't match the user id of this absence! Expected " . $this->getUserId() . ", got " . $user->getUID());
}
//$user = $userManager->get($this->getUserId());
$startDate = new DateTimeImmutable($this->getFirstDay());
$endDate = new DateTimeImmutable($this->getLastDay());
return new OutOfOfficeData(
(string)$this->getId(),
$user,
$startDate->getTimestamp(),
$endDate->getTimestamp(),
$this->getStatus(),
$this->getMessage(),
);
}
public function jsonSerialize(): array {
return [
'userId' => $this->userId,

@ -0,0 +1,40 @@
<?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;
/**
* @psalm-type DAVOutOfOfficeData = array{
* id: int,
* userId: string,
* firstDay: string,
* lastDay: string,
* status: string,
* message: string,
* }
*/
class ResponseDefinitions {
}

@ -26,18 +26,30 @@ declare(strict_types=1);
namespace OCA\DAV\Service;
use InvalidArgumentException;
use OCA\DAV\Db\Absence;
use OCA\DAV\Db\AbsenceMapper;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IUserManager;
use OCP\User\Events\OutOfOfficeChangedEvent;
use OCP\User\Events\OutOfOfficeClearedEvent;
use OCP\User\Events\OutOfOfficeScheduledEvent;
class AbsenceService {
public function __construct(
private AbsenceMapper $absenceMapper,
private IEventDispatcher $eventDispatcher,
private IUserManager $userManager,
) {
}
/**
* @param string $firstDay The first day (inclusive) of the absence formatted as YYYY-MM-DD.
* @param string $lastDay The last day (inclusive) of the absence formatted as YYYY-MM-DD.
*
* @throws \OCP\DB\Exception
* @throws InvalidArgumentException If no user with the given user id exists.
*/
public function createOrUpdateAbsence(
string $userId,
@ -58,9 +70,19 @@ class AbsenceService {
$absence->setStatus($status);
$absence->setMessage($message);
// TODO: this method should probably just take a IUser instance
$user = $this->userManager->get($userId);
if ($user === null) {
throw new InvalidArgumentException("User $userId does not exist");
}
$eventData = $absence->toOutOufOfficeData($user);
if ($absence->getId() === null) {
$this->eventDispatcher->dispatchTyped(new OutOfOfficeScheduledEvent($eventData));
return $this->absenceMapper->insert($absence);
}
$this->eventDispatcher->dispatchTyped(new OutOfOfficeChangedEvent($eventData));
return $this->absenceMapper->update($absence);
}
@ -68,7 +90,20 @@ class AbsenceService {
* @throws \OCP\DB\Exception
*/
public function clearAbsence(string $userId): void {
$this->absenceMapper->deleteByUserId($userId);
try {
$absence = $this->absenceMapper->findByUserId($userId);
} catch (DoesNotExistException $e) {
// Nothing to clear
return;
}
$this->absenceMapper->delete($absence);
// TODO: this method should probably just take a IUser instance
$user = $this->userManager->get($userId);
if ($user === null) {
throw new InvalidArgumentException("User $userId does not exist");
}
$eventData = $absence->toOutOufOfficeData($user);
$this->eventDispatcher->dispatchTyped(new OutOfOfficeClearedEvent($eventData));
}
}

@ -65,6 +65,38 @@
"type": "string"
}
}
},
"OutOfOfficeData": {
"type": "object",
"required": [
"id",
"userId",
"firstDay",
"lastDay",
"status",
"message"
],
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"userId": {
"type": "string"
},
"firstDay": {
"type": "string"
},
"lastDay": {
"type": "string"
},
"status": {
"type": "string"
},
"message": {
"type": "string"
}
}
}
}
},
@ -186,6 +218,108 @@
}
}
}
},
"/ocs/v2.php/apps/dav/api/v1/outOfOffice/{userId}": {
"get": {
"operationId": "out_of_office-get-current-out-of-office-data",
"summary": "Get the currently configured out-of-office data of a user.",
"tags": [
"out_of_office"
],
"security": [
{
"bearer_auth": []
},
{
"basic_auth": []
}
],
"parameters": [
{
"name": "userId",
"in": "path",
"description": "The user id to get out-of-office data for.",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "OCS-APIRequest",
"in": "header",
"description": "Required to be true for the API request to pass",
"required": true,
"schema": {
"type": "boolean",
"default": true
}
}
],
"responses": {
"200": {
"description": "Out-of-office data",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"ocs"
],
"properties": {
"ocs": {
"type": "object",
"required": [
"meta",
"data"
],
"properties": {
"meta": {
"$ref": "#/components/schemas/OCSMeta"
},
"data": {
"$ref": "#/components/schemas/OutOfOfficeData",
"nullable": true
}
}
}
}
}
}
}
},
"404": {
"description": "No out-of-office data was found",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"ocs"
],
"properties": {
"ocs": {
"type": "object",
"required": [
"meta",
"data"
],
"properties": {
"meta": {
"$ref": "#/components/schemas/OCSMeta"
},
"data": {
"$ref": "#/components/schemas/OutOfOfficeData",
"nullable": true
}
}
}
}
}
}
}
}
}
}
}
},
"tags": []

@ -731,6 +731,9 @@ return array(
'OCP\\User\\Events\\BeforeUserLoggedInEvent' => $baseDir . '/lib/public/User/Events/BeforeUserLoggedInEvent.php',
'OCP\\User\\Events\\BeforeUserLoggedInWithCookieEvent' => $baseDir . '/lib/public/User/Events/BeforeUserLoggedInWithCookieEvent.php',
'OCP\\User\\Events\\BeforeUserLoggedOutEvent' => $baseDir . '/lib/public/User/Events/BeforeUserLoggedOutEvent.php',
'OCP\\User\\Events\\OutOfOfficeChangedEvent' => $baseDir . '/lib/public/User/Events/OutOfOfficeChangedEvent.php',
'OCP\\User\\Events\\OutOfOfficeClearedEvent' => $baseDir . '/lib/public/User/Events/OutOfOfficeClearedEvent.php',
'OCP\\User\\Events\\OutOfOfficeScheduledEvent' => $baseDir . '/lib/public/User/Events/OutOfOfficeScheduledEvent.php',
'OCP\\User\\Events\\PasswordUpdatedEvent' => $baseDir . '/lib/public/User/Events/PasswordUpdatedEvent.php',
'OCP\\User\\Events\\PostLoginEvent' => $baseDir . '/lib/public/User/Events/PostLoginEvent.php',
'OCP\\User\\Events\\UserChangedEvent' => $baseDir . '/lib/public/User/Events/UserChangedEvent.php',
@ -742,6 +745,8 @@ return array(
'OCP\\User\\Events\\UserLoggedInWithCookieEvent' => $baseDir . '/lib/public/User/Events/UserLoggedInWithCookieEvent.php',
'OCP\\User\\Events\\UserLoggedOutEvent' => $baseDir . '/lib/public/User/Events/UserLoggedOutEvent.php',
'OCP\\User\\GetQuotaEvent' => $baseDir . '/lib/public/User/GetQuotaEvent.php',
'OCP\\User\\IAvailabilityCoordinator' => $baseDir . '/lib/public/User/IAvailabilityCoordinator.php',
'OCP\\User\\IOutOfOfficeData' => $baseDir . '/lib/public/User/IOutOfOfficeData.php',
'OCP\\Util' => $baseDir . '/lib/public/Util.php',
'OCP\\WorkflowEngine\\EntityContext\\IContextPortation' => $baseDir . '/lib/public/WorkflowEngine/EntityContext/IContextPortation.php',
'OCP\\WorkflowEngine\\EntityContext\\IDisplayName' => $baseDir . '/lib/public/WorkflowEngine/EntityContext/IDisplayName.php',
@ -1762,6 +1767,7 @@ return array(
'OC\\Updater\\VersionCheck' => $baseDir . '/lib/private/Updater/VersionCheck.php',
'OC\\UserStatus\\ISettableProvider' => $baseDir . '/lib/private/UserStatus/ISettableProvider.php',
'OC\\UserStatus\\Manager' => $baseDir . '/lib/private/UserStatus/Manager.php',
'OC\\User\\AvailabilityCoordinator' => $baseDir . '/lib/private/User/AvailabilityCoordinator.php',
'OC\\User\\Backend' => $baseDir . '/lib/private/User/Backend.php',
'OC\\User\\Database' => $baseDir . '/lib/private/User/Database.php',
'OC\\User\\DisplayNameCache' => $baseDir . '/lib/private/User/DisplayNameCache.php',
@ -1771,6 +1777,7 @@ return array(
'OC\\User\\LoginException' => $baseDir . '/lib/private/User/LoginException.php',
'OC\\User\\Manager' => $baseDir . '/lib/private/User/Manager.php',
'OC\\User\\NoUserException' => $baseDir . '/lib/private/User/NoUserException.php',
'OC\\User\\OutOfOfficeData' => $baseDir . '/lib/private/User/OutOfOfficeData.php',
'OC\\User\\Session' => $baseDir . '/lib/private/User/Session.php',
'OC\\User\\User' => $baseDir . '/lib/private/User/User.php',
'OC_API' => $baseDir . '/lib/private/legacy/OC_API.php',

@ -764,6 +764,9 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\User\\Events\\BeforeUserLoggedInEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/BeforeUserLoggedInEvent.php',
'OCP\\User\\Events\\BeforeUserLoggedInWithCookieEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/BeforeUserLoggedInWithCookieEvent.php',
'OCP\\User\\Events\\BeforeUserLoggedOutEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/BeforeUserLoggedOutEvent.php',
'OCP\\User\\Events\\OutOfOfficeChangedEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/OutOfOfficeChangedEvent.php',
'OCP\\User\\Events\\OutOfOfficeClearedEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/OutOfOfficeClearedEvent.php',
'OCP\\User\\Events\\OutOfOfficeScheduledEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/OutOfOfficeScheduledEvent.php',
'OCP\\User\\Events\\PasswordUpdatedEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/PasswordUpdatedEvent.php',
'OCP\\User\\Events\\PostLoginEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/PostLoginEvent.php',
'OCP\\User\\Events\\UserChangedEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/UserChangedEvent.php',
@ -775,6 +778,8 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\User\\Events\\UserLoggedInWithCookieEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/UserLoggedInWithCookieEvent.php',
'OCP\\User\\Events\\UserLoggedOutEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/UserLoggedOutEvent.php',
'OCP\\User\\GetQuotaEvent' => __DIR__ . '/../../..' . '/lib/public/User/GetQuotaEvent.php',
'OCP\\User\\IAvailabilityCoordinator' => __DIR__ . '/../../..' . '/lib/public/User/IAvailabilityCoordinator.php',
'OCP\\User\\IOutOfOfficeData' => __DIR__ . '/../../..' . '/lib/public/User/IOutOfOfficeData.php',
'OCP\\Util' => __DIR__ . '/../../..' . '/lib/public/Util.php',
'OCP\\WorkflowEngine\\EntityContext\\IContextPortation' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/EntityContext/IContextPortation.php',
'OCP\\WorkflowEngine\\EntityContext\\IDisplayName' => __DIR__ . '/../../..' . '/lib/public/WorkflowEngine/EntityContext/IDisplayName.php',
@ -1795,6 +1800,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Updater\\VersionCheck' => __DIR__ . '/../../..' . '/lib/private/Updater/VersionCheck.php',
'OC\\UserStatus\\ISettableProvider' => __DIR__ . '/../../..' . '/lib/private/UserStatus/ISettableProvider.php',
'OC\\UserStatus\\Manager' => __DIR__ . '/../../..' . '/lib/private/UserStatus/Manager.php',
'OC\\User\\AvailabilityCoordinator' => __DIR__ . '/../../..' . '/lib/private/User/AvailabilityCoordinator.php',
'OC\\User\\Backend' => __DIR__ . '/../../..' . '/lib/private/User/Backend.php',
'OC\\User\\Database' => __DIR__ . '/../../..' . '/lib/private/User/Database.php',
'OC\\User\\DisplayNameCache' => __DIR__ . '/../../..' . '/lib/private/User/DisplayNameCache.php',
@ -1804,6 +1810,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\User\\LoginException' => __DIR__ . '/../../..' . '/lib/private/User/LoginException.php',
'OC\\User\\Manager' => __DIR__ . '/../../..' . '/lib/private/User/Manager.php',
'OC\\User\\NoUserException' => __DIR__ . '/../../..' . '/lib/private/User/NoUserException.php',
'OC\\User\\OutOfOfficeData' => __DIR__ . '/../../..' . '/lib/private/User/OutOfOfficeData.php',
'OC\\User\\Session' => __DIR__ . '/../../..' . '/lib/private/User/Session.php',
'OC\\User\\User' => __DIR__ . '/../../..' . '/lib/private/User/User.php',
'OC_API' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_API.php',

@ -0,0 +1,111 @@
<?php
declare(strict_types=1);
/**
* @copyright 2023 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2023 Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Richard Steinmetz <richard@steinmetz.cloud>
*
* @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 OC\User;
use JsonException;
use OCA\DAV\Db\AbsenceMapper;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\ICache;
use OCP\ICacheFactory;
use OCP\IUser;
use OCP\User\IAvailabilityCoordinator;
use OCP\User\IOutOfOfficeData;
use Psr\Log\LoggerInterface;
class AvailabilityCoordinator implements IAvailabilityCoordinator {
private ICache $cache;
public function __construct(
ICacheFactory $cacheFactory,
private AbsenceMapper $absenceMapper,
private LoggerInterface $logger,
) {
$this->cache = $cacheFactory->createLocal('OutOfOfficeData');
}
private function getCachedOutOfOfficeData(IUser $user): ?OutOfOfficeData {
$cachedString = $this->cache->get($user->getUID());
if ($cachedString === null) {
return null;
}
try {
$cachedData = json_decode($cachedString, true, 10, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
$this->logger->error('Failed to deserialize cached out-of-office data: ' . $e->getMessage(), [
'exception' => $e,
'json' => $cachedString,
]);
return null;
}
return new OutOfOfficeData(
$cachedData['id'],
$user,
$cachedData['startDate'],
$cachedData['endDate'],
$cachedData['shortMessage'],
$cachedData['message'],
);
}
private function setCachedOutOfOfficeData(IOutOfOfficeData $data): void {
try {
$cachedString = json_encode([
'id' => $data->getId(),
'startDate' => $data->getStartDate(),
'endDate' => $data->getEndDate(),
'shortMessage' => $data->getShortMessage(),
'message' => $data->getMessage(),
], JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
$this->logger->error('Failed to serialize out-of-office data: ' . $e->getMessage(), [
'exception' => $e,
]);
return;
}
$this->cache->set($data->getUser()->getUID(), $cachedString, 300);
}
public function getCurrentOutOfOfficeData(IUser $user): ?IOutOfOfficeData {
$cachedData = $this->getCachedOutOfOfficeData($user);
if ($cachedData !== null) {
return $cachedData;
}
try {
$absenceData = $this->absenceMapper->findByUserId($user->getUID());
} catch (DoesNotExistException $e) {
return null;
}
$data = $absenceData->toOutOufOfficeData($user);
$this->setCachedOutOfOfficeData($data);
return $data;
}
}

@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
/**
* @copyright 2023 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2023 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/>.
*/
namespace OC\User;
use OCP\IUser;
use OCP\User\IOutOfOfficeData;
class OutOfOfficeData implements IOutOfOfficeData {
public function __construct(private string $id,
private IUser $user,
private int $startDate,
private int $endDate,
private string $shortMessage,
private string $message) {
}
public function getId(): string {
return $this->id;
}
public function getUser(): IUser {
return $this->user;
}
public function getStartDate(): int {
return $this->startDate;
}
public function getEndDate(): int {
return $this->endDate;
}
public function getShortMessage(): string {
return $this->shortMessage;
}
public function getMessage(): string {
return $this->message;
}
}

@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
/*
* @copyright 2023 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2023 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/>.
*/
namespace OCP\User\Events;
use OCP\EventDispatcher\Event;
use OCP\User\IOutOfOfficeData;
/**
* Emitted when a user's out-of-office period has changed
*
* @since 28.0.0
*/
class OutOfOfficeChangedEvent extends Event {
/**
* @since 28.0.0
*/
public function __construct(private IOutOfOfficeData $data) {
parent::__construct();
}
/**
* @since 28.0.0
*/
public function getData(): IOutOfOfficeData {
return $this->data;
}
}

@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
/*
* @copyright 2023 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2023 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/>.
*/
namespace OCP\User\Events;
use OCP\EventDispatcher\Event;
use OCP\User\IOutOfOfficeData;
/**
* Emitted when a user's out-of-office period is cleared
*
* @since 28.0.0
*/
class OutOfOfficeClearedEvent extends Event {
/**
* @since 28.0.0
*/
public function __construct(private IOutOfOfficeData $data) {
parent::__construct();
}
/**
* @since 28.0.0
*/
public function getData(): IOutOfOfficeData {
return $this->data;
}
}

@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
/*
* @copyright 2023 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2023 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/>.
*/
namespace OCP\User\Events;
use OCP\EventDispatcher\Event;
use OCP\User\IOutOfOfficeData;
/**
* Emitted when a user's out-of-office period is scheduled
*
* @since 28.0.0
*/
class OutOfOfficeScheduledEvent extends Event {
/**
* @since 28.0.0
*/
public function __construct(private IOutOfOfficeData $data) {
parent::__construct();
}
/**
* @since 28.0.0
*/
public function getData(): IOutOfOfficeData {
return $this->data;
}
}

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/**
* @copyright 2023 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2023 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/>.
*/
namespace OCP\User;
use OCP\IUser;
/**
* Coordinator for availability and out-of-office messages
*
* @since 28.0.0
*/
interface IAvailabilityCoordinator {
/**
* Get the user's out-of-office message, if any
*
* @since 28.0.0
*/
public function getCurrentOutOfOfficeData(IUser $user): ?IOutOfOfficeData;
}

@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
/*
* @copyright 2023 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2023 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/>.
*/
namespace OCP\User;
use OCP\IUser;
/**
* DTO to hold out-of-office information of a user
*
* @since 28.0.0
*/
interface IOutOfOfficeData {
/**
* Get the unique token assigned to the current out-of-office event
*
* @since 28.0.0
*/
public function getId(): string;
/**
* @since 28.0.0
*/
public function getUser(): IUser;
/**
* Get the accurate out-of-office start date
*
* This event is not guaranteed to be emitted exactly at start date
*
* @since 28.0.0
*/
public function getStartDate(): int;
/**
* Get the (preliminary) out-of-office end date
*
* @since 28.0.0
*/
public function getEndDate(): int;
/**
* Get the short summary text displayed in the user status and similar
*
* @since 28.0.0
*/
public function getShortMessage(): string;
/**
* Get the long out-of-office message for auto responders and similar
*
* @since 28.0.0
*/
public function getMessage(): string;
}

@ -0,0 +1,164 @@
<?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 Test\User;
use OC\User\AvailabilityCoordinator;
use OC\User\OutOfOfficeData;
use OCA\DAV\Db\Absence;
use OCA\DAV\Db\AbsenceMapper;
use OCP\ICache;
use OCP\ICacheFactory;
use OCP\IUser;
use Psr\Log\LoggerInterface;
use Test\TestCase;
class AvailabilityCoordinatorTest extends TestCase {
private AvailabilityCoordinator $availabilityCoordinator;
private ICacheFactory $cacheFactory;
private ICache $cache;
private AbsenceMapper $absenceMapper;
private LoggerInterface $logger;
protected function setUp(): void {
parent::setUp();
$this->cacheFactory = $this->createMock(ICacheFactory::class);
$this->cache = $this->createMock(ICache::class);
$this->absenceMapper = $this->createMock(AbsenceMapper::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->cacheFactory->expects(self::once())
->method('createLocal')
->willReturn($this->cache);
$this->availabilityCoordinator = new AvailabilityCoordinator(
$this->cacheFactory,
$this->absenceMapper,
$this->logger,
);
}
public function testGetOutOfOfficeData(): void {
$absence = new Absence();
$absence->setId(420);
$absence->setUserId('user');
$absence->setFirstDay('2023-10-01');
$absence->setLastDay('2023-10-08');
$absence->setStatus('Vacation');
$absence->setMessage('On vacation');
$user = $this->createMock(IUser::class);
$user->method('getUID')
->willReturn('user');
$this->cache->expects(self::once())
->method('get')
->with('user')
->willReturn(null);
$this->absenceMapper->expects(self::once())
->method('findByUserId')
->with('user')
->willReturn($absence);
$this->cache->expects(self::once())
->method('set')
->with('user', '{"id":"420","startDate":1696118400,"endDate":1696723200,"shortMessage":"Vacation","message":"On vacation"}', 300);
$expected = new OutOfOfficeData(
'420',
$user,
1696118400,
1696723200,
'Vacation',
'On vacation',
);
$actual = $this->availabilityCoordinator->getCurrentOutOfOfficeData($user);
self::assertEquals($expected, $actual);
}
public function testGetOutOfOfficeDataWithCachedData(): void {
$user = $this->createMock(IUser::class);
$user->method('getUID')
->willReturn('user');
$this->cache->expects(self::once())
->method('get')
->with('user')
->willReturn('{"id":"420","startDate":1696118400,"endDate":1696723200,"shortMessage":"Vacation","message":"On vacation"}');
$this->absenceMapper->expects(self::never())
->method('findByUserId');
$this->cache->expects(self::never())
->method('set');
$expected = new OutOfOfficeData(
'420',
$user,
1696118400,
1696723200,
'Vacation',
'On vacation',
);
$actual = $this->availabilityCoordinator->getCurrentOutOfOfficeData($user);
self::assertEquals($expected, $actual);
}
public function testGetOutOfOfficeDataWithInvalidCachedData(): void {
$absence = new Absence();
$absence->setId(420);
$absence->setUserId('user');
$absence->setFirstDay('2023-10-01');
$absence->setLastDay('2023-10-08');
$absence->setStatus('Vacation');
$absence->setMessage('On vacation');
$user = $this->createMock(IUser::class);
$user->method('getUID')
->willReturn('user');
$this->cache->expects(self::once())
->method('get')
->with('user')
->willReturn('{"id":"420",}');
$this->absenceMapper->expects(self::once())
->method('findByUserId')
->with('user')
->willReturn($absence);
$this->cache->expects(self::once())
->method('set')
->with('user', '{"id":"420","startDate":1696118400,"endDate":1696723200,"shortMessage":"Vacation","message":"On vacation"}', 300);
$expected = new OutOfOfficeData(
'420',
$user,
1696118400,
1696723200,
'Vacation',
'On vacation',
);
$actual = $this->availabilityCoordinator->getCurrentOutOfOfficeData($user);
self::assertEquals($expected, $actual);
}
}
Loading…
Cancel
Save