Skip to content

Commit

Permalink
feat(ocp): implement public API to get out of office data
Browse files Browse the repository at this point in the history
Signed-off-by: Richard Steinmetz <[email protected]>
  • Loading branch information
st3iny authored and ChristophWurst committed Nov 8, 2023
1 parent c013a6a commit 033db53
Show file tree
Hide file tree
Showing 14 changed files with 356 additions and 19 deletions.
4 changes: 2 additions & 2 deletions apps/dav/lib/Controller/OutOfOfficeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
use OCP\IRequest;

/**
* @psalm-import-type DavOutOfOfficeData from ResponseDefinitions
* @psalm-import-type DAVOutOfOfficeData from ResponseDefinitions
*/
class OutOfOfficeController extends OCSController {

Expand All @@ -54,7 +54,7 @@ public function __construct(
* @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{}>
* @return DataResponse<Http::STATUS_OK|Http::STATUS_NOT_FOUND, ?DAVOutOfOfficeData, array{}>
*
* 200: Out-of-office data
* 404: No out-of-office data was found
Expand Down
28 changes: 28 additions & 0 deletions apps/dav/lib/Db/Absence.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,13 @@

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()
Expand All @@ -43,8 +48,13 @@
*/
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 = '';

Expand All @@ -56,6 +66,24 @@ public function __construct() {
$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,
Expand Down
2 changes: 1 addition & 1 deletion apps/dav/lib/ResponseDefinitions.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
namespace OCA\DAV;

/**
* @psalm-type DavOutOfOfficeData = array{
* @psalm-type DAVOutOfOfficeData = array{
* id: int,
* userId: string,
* firstDay: string,
Expand Down
37 changes: 36 additions & 1 deletion apps/dav/lib/Service/AbsenceService.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,30 @@

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,
Expand All @@ -58,17 +70,40 @@ public function createOrUpdateAbsence(
$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);
}

/**
* @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));
}
}

77 changes: 75 additions & 2 deletions lib/private/User/AvailabilityCoordinator.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* @copyright 2023 Christoph Wurst <[email protected]>
*
* @author 2023 Christoph Wurst <[email protected]>
* @author Richard Steinmetz <[email protected]>
*
* @license GNU AGPL version 3 or any later version
*
Expand All @@ -25,14 +26,86 @@

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 getCurrentOutOfOfficeData(IUser $user): ?IOutOfOfficeData {
return null;
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;
}
}
1 change: 0 additions & 1 deletion lib/private/User/OutOfOfficeData.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
use OCP\User\IOutOfOfficeData;

class OutOfOfficeData implements IOutOfOfficeData {

public function __construct(private string $id,
private IUser $user,
private int $startDate,
Expand Down
2 changes: 0 additions & 2 deletions lib/public/User/Events/OutOfOfficeChangedEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
* @since 28.0.0
*/
class OutOfOfficeChangedEvent extends Event {

/**
* @since 28.0.0
*/
Expand All @@ -48,5 +47,4 @@ public function __construct(private IOutOfOfficeData $data) {
public function getData(): IOutOfOfficeData {
return $this->data;
}

}
50 changes: 50 additions & 0 deletions lib/public/User/Events/OutOfOfficeClearedEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

declare(strict_types=1);

/*
* @copyright 2023 Christoph Wurst <[email protected]>
*
* @author 2023 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 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;
}
}
2 changes: 0 additions & 2 deletions lib/public/User/Events/OutOfOfficeEndedEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
* @since 28.0.0
*/
class OutOfOfficeEndedEvent extends Event {

/**
* @since 28.0.0
*/
Expand All @@ -48,5 +47,4 @@ public function __construct(private IOutOfOfficeData $data) {
public function getData(): IOutOfOfficeData {
return $this->data;
}

}
2 changes: 0 additions & 2 deletions lib/public/User/Events/OutOfOfficeScheduledEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
* @since 28.0.0
*/
class OutOfOfficeScheduledEvent extends Event {

/**
* @since 28.0.0
*/
Expand All @@ -48,5 +47,4 @@ public function __construct(private IOutOfOfficeData $data) {
public function getData(): IOutOfOfficeData {
return $this->data;
}

}
2 changes: 0 additions & 2 deletions lib/public/User/Events/OutOfOfficeStartedEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
* @since 28.0.0
*/
class OutOfOfficeStartedEvent extends Event {

/**
* @since 28.0.0
*/
Expand All @@ -48,5 +47,4 @@ public function __construct(private IOutOfOfficeData $data) {
public function getData(): IOutOfOfficeData {
return $this->data;
}

}
2 changes: 0 additions & 2 deletions lib/public/User/IAvailabilityCoordinator.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,10 @@
* @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;

}
2 changes: 0 additions & 2 deletions lib/public/User/IOutOfOfficeData.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
* @since 28.0.0
*/
interface IOutOfOfficeData {

/**
* Get the unique token assigned to the current out-of-office event
*
Expand Down Expand Up @@ -75,5 +74,4 @@ public function getShortMessage(): string;
* @since 28.0.0
*/
public function getMessage(): string;

}
Loading

0 comments on commit 033db53

Please sign in to comment.