Skip to content

Commit

Permalink
feat(dav): dispatch out-of-office started and ended events
Browse files Browse the repository at this point in the history
Signed-off-by: Richard Steinmetz <[email protected]>
  • Loading branch information
st3iny committed Nov 13, 2023
1 parent eaaf954 commit f57d8e7
Show file tree
Hide file tree
Showing 10 changed files with 278 additions and 28 deletions.
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 @@ -18,6 +18,7 @@
'OCA\\DAV\\BackgroundJob\\CleanupInvitationTokenJob' => $baseDir . '/../lib/BackgroundJob/CleanupInvitationTokenJob.php',
'OCA\\DAV\\BackgroundJob\\EventReminderJob' => $baseDir . '/../lib/BackgroundJob/EventReminderJob.php',
'OCA\\DAV\\BackgroundJob\\GenerateBirthdayCalendarBackgroundJob' => $baseDir . '/../lib/BackgroundJob/GenerateBirthdayCalendarBackgroundJob.php',
'OCA\\DAV\\BackgroundJob\\OutOfOfficeEventDispatcherJob' => $baseDir . '/../lib/BackgroundJob/OutOfOfficeEventDispatcherJob.php',
'OCA\\DAV\\BackgroundJob\\PruneOutdatedSyncTokensJob' => $baseDir . '/../lib/BackgroundJob/PruneOutdatedSyncTokensJob.php',
'OCA\\DAV\\BackgroundJob\\RefreshWebcalJob' => $baseDir . '/../lib/BackgroundJob/RefreshWebcalJob.php',
'OCA\\DAV\\BackgroundJob\\RegisterRegenerateBirthdayCalendars' => $baseDir . '/../lib/BackgroundJob/RegisterRegenerateBirthdayCalendars.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 @@ -33,6 +33,7 @@ class ComposerStaticInitDAV
'OCA\\DAV\\BackgroundJob\\CleanupInvitationTokenJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/CleanupInvitationTokenJob.php',
'OCA\\DAV\\BackgroundJob\\EventReminderJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/EventReminderJob.php',
'OCA\\DAV\\BackgroundJob\\GenerateBirthdayCalendarBackgroundJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/GenerateBirthdayCalendarBackgroundJob.php',
'OCA\\DAV\\BackgroundJob\\OutOfOfficeEventDispatcherJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/OutOfOfficeEventDispatcherJob.php',
'OCA\\DAV\\BackgroundJob\\PruneOutdatedSyncTokensJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/PruneOutdatedSyncTokensJob.php',
'OCA\\DAV\\BackgroundJob\\RefreshWebcalJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/RefreshWebcalJob.php',
'OCA\\DAV\\BackgroundJob\\RegisterRegenerateBirthdayCalendars' => __DIR__ . '/..' . '/../lib/BackgroundJob/RegisterRegenerateBirthdayCalendars.php',
Expand Down
105 changes: 105 additions & 0 deletions apps/dav/lib/BackgroundJob/OutOfOfficeEventDispatcherJob.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?php

declare(strict_types=1);

/**
* @copyright Copyright (c) 2023 Richard Steinmetz <[email protected]>
*
* @author Richard Steinmetz <[email protected]>
*
* @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\BackgroundJob;

use OCA\DAV\Db\AbsenceMapper;
use OCA\Mail\Vendor\Psr\Log\LoggerInterface;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\QueuedJob;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IUser;
use OCP\IUserManager;
use OCP\User\Events\OutOfOfficeEndedEvent;
use OCP\User\Events\OutOfOfficeStartedEvent;

class OutOfOfficeEventDispatcherJob extends QueuedJob {
public const EVENT_START = 'start';
public const EVENT_END = 'end';

/** @var array<string, IUser> $users */
private array $userCache = [];

public function __construct(
ITimeFactory $time,
private AbsenceMapper $absenceMapper,
private LoggerInterface $logger,
private IEventDispatcher $eventDispatcher,
private IUserManager $userManager,
) {
parent::__construct($time);
}

protected function run($argument) {
$id = $argument['id'];
$event = $argument['event'];

try {
$absence = $this->absenceMapper->findById($id);
} catch (DoesNotExistException | \OCP\DB\Exception $e) {
$this->logger->error('Failed to dispatch out-of-office event: ' . $e->getMessage(), [
'exception' => $e,
'argument' => $argument,
]);
return;
}

$userId = $absence->getUserId();
$user = $this->getUser($userId);
if ($user === null) {
$this->logger->error("Failed to dispatch out-of-office event: User $userId does not exist", [
'argument' => $argument,
]);
return;
}

$data = $absence->toOutOufOfficeData($user);
if ($event === self::EVENT_START) {
$this->eventDispatcher->dispatchTyped(new OutOfOfficeStartedEvent($data));
} elseif ($event === self::EVENT_END) {
$this->eventDispatcher->dispatchTyped(new OutOfOfficeEndedEvent($data));
} else {
$this->logger->error("Invalid out-of-office event: $event", [
'argument' => $argument,
]);
}
}

private function getUser(string $userId): ?IUser {
if (isset($this->userCache[$userId])) {
return $this->userCache[$userId];
}

$user = $this->userManager->get($userId);
if ($user === null) {
return null;
}

$this->userCache[$userId] = $user;
return $user;
}
}
15 changes: 8 additions & 7 deletions apps/dav/lib/Controller/AvailabilitySettingsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,12 @@
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\Response;
use OCP\IRequest;
use OCP\IUserSession;

class AvailabilitySettingsController extends Controller {
public function __construct(
IRequest $request,
private ?string $userId,
private ?IUserSession $userSession,
private AbsenceService $absenceService,
) {
parent::__construct(Application::APP_ID, $request);
Expand All @@ -56,8 +57,8 @@ public function updateAbsence(
string $status,
string $message,
): Response {
$userId = $this->userId;
if ($userId === null) {
$user = $this->userSession->getUser();
if ($user === null) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}

Expand All @@ -68,7 +69,7 @@ public function updateAbsence(
}

$absence = $this->absenceService->createOrUpdateAbsence(
$userId,
$user,
$firstDay,
$lastDay,
$status,
Expand All @@ -82,12 +83,12 @@ public function updateAbsence(
*/
#[NoAdminRequired]
public function clearAbsence(): Response {
$userId = $this->userId;
if ($userId === null) {
$user = $this->userSession->getUser();
if ($user === null) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}

$this->absenceService->clearAbsence($userId);
$this->absenceService->clearAbsence($user);
return new JSONResponse([]);
}

Expand Down
25 changes: 25 additions & 0 deletions apps/dav/lib/Db/AbsenceMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,31 @@ public function __construct(IDBConnection $db) {
parent::__construct($db, 'dav_absence', Absence::class);
}

/**
* @throws DoesNotExistException
* @throws \OCP\DB\Exception
*/
public function findById(int $id): Absence {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where($qb->expr()->eq(
'id',
$qb->createNamedParameter($id, IQueryBuilder::PARAM_INT),
IQueryBuilder::PARAM_INT),
);
try {
return $this->findEntity($qb);
} catch (MultipleObjectsReturnedException $e) {
// Won't happen as there is a unique index on user_id
throw new \RuntimeException(
'The impossible has happened! The query returned multiple absence settings for one user.',
0,
$e,
);
}
}

/**
* @throws DoesNotExistException
* @throws \OCP\DB\Exception
Expand Down
53 changes: 32 additions & 21 deletions apps/dav/lib/Service/AbsenceService.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@
namespace OCA\DAV\Service;

use InvalidArgumentException;
use OCA\DAV\BackgroundJob\OutOfOfficeEventDispatcherJob;
use OCA\DAV\Db\Absence;
use OCA\DAV\Db\AbsenceMapper;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\BackgroundJob\IJobList;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IUserManager;
use OCP\IUser;
use OCP\User\Events\OutOfOfficeChangedEvent;
use OCP\User\Events\OutOfOfficeClearedEvent;
use OCP\User\Events\OutOfOfficeScheduledEvent;
Expand All @@ -40,7 +42,7 @@ class AbsenceService {
public function __construct(
private AbsenceMapper $absenceMapper,
private IEventDispatcher $eventDispatcher,
private IUserManager $userManager,
private IJobList $jobList,
) {
}

Expand All @@ -52,56 +54,65 @@ public function __construct(
* @throws InvalidArgumentException If no user with the given user id exists.
*/
public function createOrUpdateAbsence(
string $userId,
IUser $user,
string $firstDay,
string $lastDay,
string $status,
string $message,
): Absence {
try {
$absence = $this->absenceMapper->findByUserId($userId);
$absence = $this->absenceMapper->findByUserId($user->getUID());
} catch (DoesNotExistException) {
$absence = new Absence();
}

$absence->setUserId($userId);
$absence->setUserId($user->getUID());
$absence->setFirstDay($firstDay);
$absence->setLastDay($lastDay);
$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) {
$absence = $this->absenceMapper->insert($absence);
$this->eventDispatcher->dispatchTyped(new OutOfOfficeScheduledEvent($eventData));
return $this->absenceMapper->insert($absence);
} else {
$absence = $this->absenceMapper->update($absence);
$this->eventDispatcher->dispatchTyped(new OutOfOfficeChangedEvent($eventData));
}

$this->eventDispatcher->dispatchTyped(new OutOfOfficeChangedEvent($eventData));
return $this->absenceMapper->update($absence);
$this->jobList->scheduleAfter(
OutOfOfficeEventDispatcherJob::class,
$eventData->getStartDate(),
[
'id' => $absence->getId(),
'event' => OutOfOfficeEventDispatcherJob::EVENT_START,
],
);
$this->jobList->scheduleAfter(
OutOfOfficeEventDispatcherJob::class,
$eventData->getEndDate(),
[
'id' => $absence->getId(),
'event' => OutOfOfficeEventDispatcherJob::EVENT_END,
],
);

return $absence;
}

/**
* @throws \OCP\DB\Exception
*/
public function clearAbsence(string $userId): void {
public function clearAbsence(IUser $user): void {
try {
$absence = $this->absenceMapper->findByUserId($userId);
$absence = $this->absenceMapper->findByUserId($user->getUID());
} 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");
}
$this->jobList->remove(OutOfOfficeEventDispatcherJob::class);
$eventData = $absence->toOutOufOfficeData($user);
$this->eventDispatcher->dispatchTyped(new OutOfOfficeClearedEvent($eventData));
}
Expand Down
2 changes: 2 additions & 0 deletions lib/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -740,7 +740,9 @@
'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\\OutOfOfficeEndedEvent' => $baseDir . '/lib/public/User/Events/OutOfOfficeEndedEvent.php',
'OCP\\User\\Events\\OutOfOfficeScheduledEvent' => $baseDir . '/lib/public/User/Events/OutOfOfficeScheduledEvent.php',
'OCP\\User\\Events\\OutOfOfficeStartedEvent' => $baseDir . '/lib/public/User/Events/OutOfOfficeStartedEvent.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',
Expand Down
2 changes: 2 additions & 0 deletions lib/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -773,7 +773,9 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'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\\OutOfOfficeEndedEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/OutOfOfficeEndedEvent.php',
'OCP\\User\\Events\\OutOfOfficeScheduledEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/OutOfOfficeScheduledEvent.php',
'OCP\\User\\Events\\OutOfOfficeStartedEvent' => __DIR__ . '/../../..' . '/lib/public/User/Events/OutOfOfficeStartedEvent.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',
Expand Down
51 changes: 51 additions & 0 deletions lib/public/User/Events/OutOfOfficeEndedEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

/**
* @copyright Copyright (c) 2023 Richard Steinmetz <[email protected]>
*
* @author Richard Steinmetz <[email protected]>
*
* @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 OCP\User\Events;

use OCP\EventDispatcher\Event;
use OCP\User\IOutOfOfficeData;

/**
* Emitted when a user's out-of-office period ended
*
* @since 28.0.0
*/
class OutOfOfficeEndedEvent 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;
}
}
Loading

0 comments on commit f57d8e7

Please sign in to comment.