diff options
Diffstat (limited to 'apps/dav/lib/Controller')
-rw-r--r-- | apps/dav/lib/Controller/BirthdayCalendarController.php | 77 | ||||
-rw-r--r-- | apps/dav/lib/Controller/DirectController.php | 99 | ||||
-rw-r--r-- | apps/dav/lib/Controller/ExampleContentController.php | 98 | ||||
-rw-r--r-- | apps/dav/lib/Controller/InvitationResponseController.php | 204 | ||||
-rw-r--r-- | apps/dav/lib/Controller/OutOfOfficeController.php | 189 | ||||
-rw-r--r-- | apps/dav/lib/Controller/UpcomingEventsController.php | 57 |
6 files changed, 724 insertions, 0 deletions
diff --git a/apps/dav/lib/Controller/BirthdayCalendarController.php b/apps/dav/lib/Controller/BirthdayCalendarController.php new file mode 100644 index 00000000000..f6bfb229a9c --- /dev/null +++ b/apps/dav/lib/Controller/BirthdayCalendarController.php @@ -0,0 +1,77 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\DAV\Controller; + +use OCA\DAV\BackgroundJob\GenerateBirthdayCalendarBackgroundJob; +use OCA\DAV\CalDAV\CalDavBackend; +use OCA\DAV\Settings\CalDAVSettings; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\Attribute\AuthorizedAdminSetting; +use OCP\AppFramework\Http\JSONResponse; +use OCP\AppFramework\Http\Response; +use OCP\BackgroundJob\IJobList; +use OCP\IConfig; +use OCP\IDBConnection; +use OCP\IRequest; +use OCP\IUser; +use OCP\IUserManager; + +class BirthdayCalendarController extends Controller { + + /** + * BirthdayCalendar constructor. + * + * @param string $appName + * @param IRequest $request + * @param IDBConnection $db + * @param IConfig $config + * @param IJobList $jobList + * @param IUserManager $userManager + * @param CalDavBackend $caldavBackend + */ + public function __construct( + $appName, + IRequest $request, + protected IDBConnection $db, + protected IConfig $config, + protected IJobList $jobList, + protected IUserManager $userManager, + protected CalDavBackend $caldavBackend, + ) { + parent::__construct($appName, $request); + } + + /** + * @return Response + */ + #[AuthorizedAdminSetting(settings: CalDAVSettings::class)] + public function enable() { + $this->config->setAppValue($this->appName, 'generateBirthdayCalendar', 'yes'); + + // add background job for each user + $this->userManager->callForSeenUsers(function (IUser $user): void { + $this->jobList->add(GenerateBirthdayCalendarBackgroundJob::class, [ + 'userId' => $user->getUID(), + ]); + }); + + return new JSONResponse([]); + } + + /** + * @return Response + */ + #[AuthorizedAdminSetting(settings: CalDAVSettings::class)] + public function disable() { + $this->config->setAppValue($this->appName, 'generateBirthdayCalendar', 'no'); + + $this->jobList->remove(GenerateBirthdayCalendarBackgroundJob::class); + $this->caldavBackend->deleteAllBirthdayCalendars(); + + return new JSONResponse([]); + } +} diff --git a/apps/dav/lib/Controller/DirectController.php b/apps/dav/lib/Controller/DirectController.php new file mode 100644 index 00000000000..ea209168123 --- /dev/null +++ b/apps/dav/lib/Controller/DirectController.php @@ -0,0 +1,99 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\DAV\Controller; + +use OCA\DAV\Db\Direct; +use OCA\DAV\Db\DirectMapper; +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\Attribute\NoAdminRequired; +use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\OCS\OCSBadRequestException; +use OCP\AppFramework\OCS\OCSForbiddenException; +use OCP\AppFramework\OCS\OCSNotFoundException; +use OCP\AppFramework\OCSController; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\Files\Events\BeforeDirectFileDownloadEvent; +use OCP\Files\File; +use OCP\Files\IRootFolder; +use OCP\IRequest; +use OCP\IURLGenerator; +use OCP\Security\ISecureRandom; + +class DirectController extends OCSController { + + public function __construct( + string $appName, + IRequest $request, + private IRootFolder $rootFolder, + private string $userId, + private DirectMapper $mapper, + private ISecureRandom $random, + private ITimeFactory $timeFactory, + private IURLGenerator $urlGenerator, + private IEventDispatcher $eventDispatcher, + ) { + parent::__construct($appName, $request); + } + + /** + * Get a direct link to a file + * + * @param int $fileId ID of the file + * @param int $expirationTime Duration until the link expires + * @return DataResponse<Http::STATUS_OK, array{url: string}, array{}> + * @throws OCSNotFoundException File not found + * @throws OCSBadRequestException Getting direct link is not possible + * @throws OCSForbiddenException Missing permissions to get direct link + * + * 200: Direct link returned + */ + #[NoAdminRequired] + public function getUrl(int $fileId, int $expirationTime = 60 * 60 * 8): DataResponse { + $userFolder = $this->rootFolder->getUserFolder($this->userId); + + $file = $userFolder->getFirstNodeById($fileId); + + if (!$file) { + throw new OCSNotFoundException(); + } + + if ($expirationTime <= 0 || $expirationTime > (60 * 60 * 24)) { + throw new OCSBadRequestException('Expiration time should be greater than 0 and less than or equal to ' . (60 * 60 * 24)); + } + + if (!($file instanceof File)) { + throw new OCSBadRequestException('Direct download only works for files'); + } + + $event = new BeforeDirectFileDownloadEvent($userFolder->getRelativePath($file->getPath())); + $this->eventDispatcher->dispatchTyped($event); + + if ($event->isSuccessful() === false) { + throw new OCSForbiddenException('Permission denied to download file'); + } + + //TODO: at some point we should use the directdownlaod function of storages + $direct = new Direct(); + $direct->setUserId($this->userId); + $direct->setFileId($fileId); + + $token = $this->random->generate(60, ISecureRandom::CHAR_ALPHANUMERIC); + $direct->setToken($token); + $direct->setExpiration($this->timeFactory->getTime() + $expirationTime); + + $this->mapper->insert($direct); + + $url = $this->urlGenerator->getAbsoluteURL('remote.php/direct/' . $token); + + return new DataResponse([ + 'url' => $url, + ]); + } +} diff --git a/apps/dav/lib/Controller/ExampleContentController.php b/apps/dav/lib/Controller/ExampleContentController.php new file mode 100644 index 00000000000..e20ee4b7f49 --- /dev/null +++ b/apps/dav/lib/Controller/ExampleContentController.php @@ -0,0 +1,98 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\DAV\Controller; + +use OCA\DAV\AppInfo\Application; +use OCA\DAV\Service\ExampleContactService; +use OCA\DAV\Service\ExampleEventService; +use OCP\AppFramework\ApiController; +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\Attribute\FrontpageRoute; +use OCP\AppFramework\Http\Attribute\NoCSRFRequired; +use OCP\AppFramework\Http\DataDownloadResponse; +use OCP\AppFramework\Http\JSONResponse; +use OCP\IRequest; +use Psr\Log\LoggerInterface; + +class ExampleContentController extends ApiController { + public function __construct( + IRequest $request, + private readonly LoggerInterface $logger, + private readonly ExampleEventService $exampleEventService, + private readonly ExampleContactService $exampleContactService, + ) { + parent::__construct(Application::APP_ID, $request); + } + + #[FrontpageRoute(verb: 'PUT', url: '/api/defaultcontact/config')] + public function setEnableDefaultContact(bool $allow): JSONResponse { + if ($allow && !$this->exampleContactService->defaultContactExists()) { + try { + $this->exampleContactService->setCard(); + } catch (\Exception $e) { + $this->logger->error('Could not create default contact', ['exception' => $e]); + return new JSONResponse([], Http::STATUS_INTERNAL_SERVER_ERROR); + } + } + $this->exampleContactService->setDefaultContactEnabled($allow); + return new JSONResponse([], Http::STATUS_OK); + } + + #[NoCSRFRequired] + #[FrontpageRoute(verb: 'GET', url: '/api/defaultcontact/contact')] + public function getDefaultContact(): DataDownloadResponse { + $cardData = $this->exampleContactService->getCard() + ?? file_get_contents(__DIR__ . '/../ExampleContentFiles/exampleContact.vcf'); + return new DataDownloadResponse($cardData, 'example_contact.vcf', 'text/vcard'); + } + + #[FrontpageRoute(verb: 'PUT', url: '/api/defaultcontact/contact')] + public function setDefaultContact(?string $contactData = null) { + if (!$this->exampleContactService->isDefaultContactEnabled()) { + return new JSONResponse([], Http::STATUS_FORBIDDEN); + } + $this->exampleContactService->setCard($contactData); + return new JSONResponse([], Http::STATUS_OK); + } + + #[FrontpageRoute(verb: 'POST', url: '/api/exampleEvent/enable')] + public function setCreateExampleEvent(bool $enable): JSONResponse { + $this->exampleEventService->setCreateExampleEvent($enable); + return new JsonResponse([]); + } + + #[FrontpageRoute(verb: 'GET', url: '/api/exampleEvent/event')] + #[NoCSRFRequired] + public function downloadExampleEvent(): DataDownloadResponse { + $exampleEvent = $this->exampleEventService->getExampleEvent(); + return new DataDownloadResponse( + $exampleEvent->getIcs(), + 'example_event.ics', + 'text/calendar', + ); + } + + #[FrontpageRoute(verb: 'POST', url: '/api/exampleEvent/event')] + public function uploadExampleEvent(string $ics): JSONResponse { + if (!$this->exampleEventService->shouldCreateExampleEvent()) { + return new JSONResponse([], Http::STATUS_FORBIDDEN); + } + + $this->exampleEventService->saveCustomExampleEvent($ics); + return new JsonResponse([]); + } + + #[FrontpageRoute(verb: 'DELETE', url: '/api/exampleEvent/event')] + public function deleteExampleEvent(): JSONResponse { + $this->exampleEventService->deleteCustomExampleEvent(); + return new JsonResponse([]); + } + +} diff --git a/apps/dav/lib/Controller/InvitationResponseController.php b/apps/dav/lib/Controller/InvitationResponseController.php new file mode 100644 index 00000000000..19eb4097b45 --- /dev/null +++ b/apps/dav/lib/Controller/InvitationResponseController.php @@ -0,0 +1,204 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\DAV\Controller; + +use OCA\DAV\CalDAV\InvitationResponse\InvitationResponseServer; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\Attribute\NoCSRFRequired; +use OCP\AppFramework\Http\Attribute\OpenAPI; +use OCP\AppFramework\Http\Attribute\PublicPage; +use OCP\AppFramework\Http\TemplateResponse; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\IDBConnection; +use OCP\IRequest; +use Sabre\VObject\ITip\Message; +use Sabre\VObject\Reader; + +#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)] +class InvitationResponseController extends Controller { + + /** + * InvitationResponseController constructor. + * + * @param string $appName + * @param IRequest $request + * @param IDBConnection $db + * @param ITimeFactory $timeFactory + * @param InvitationResponseServer $responseServer + */ + public function __construct( + string $appName, + IRequest $request, + private IDBConnection $db, + private ITimeFactory $timeFactory, + private InvitationResponseServer $responseServer, + ) { + parent::__construct($appName, $request); + // Don't run `$server->exec()`, because we just need access to the + // fully initialized schedule plugin, but we don't want Sabre/DAV + // to actually handle and reply to the request + } + + /** + * @param string $token + * @return TemplateResponse + */ + #[PublicPage] + #[NoCSRFRequired] + public function accept(string $token):TemplateResponse { + $row = $this->getTokenInformation($token); + if (!$row) { + return new TemplateResponse($this->appName, 'schedule-response-error', [], 'guest'); + } + + $iTipMessage = $this->buildITipResponse($row, 'ACCEPTED'); + $this->responseServer->handleITipMessage($iTipMessage); + if ($iTipMessage->getScheduleStatus() === '1.2') { + return new TemplateResponse($this->appName, 'schedule-response-success', [], 'guest'); + } + + return new TemplateResponse($this->appName, 'schedule-response-error', [ + 'organizer' => $row['organizer'], + ], 'guest'); + } + + /** + * @param string $token + * @return TemplateResponse + */ + #[PublicPage] + #[NoCSRFRequired] + public function decline(string $token):TemplateResponse { + $row = $this->getTokenInformation($token); + if (!$row) { + return new TemplateResponse($this->appName, 'schedule-response-error', [], 'guest'); + } + + $iTipMessage = $this->buildITipResponse($row, 'DECLINED'); + $this->responseServer->handleITipMessage($iTipMessage); + + if ($iTipMessage->getScheduleStatus() === '1.2') { + return new TemplateResponse($this->appName, 'schedule-response-success', [], 'guest'); + } + + return new TemplateResponse($this->appName, 'schedule-response-error', [ + 'organizer' => $row['organizer'], + ], 'guest'); + } + + /** + * @param string $token + * @return TemplateResponse + */ + #[PublicPage] + #[NoCSRFRequired] + public function options(string $token):TemplateResponse { + return new TemplateResponse($this->appName, 'schedule-response-options', [ + 'token' => $token + ], 'guest'); + } + + /** + * @param string $token + * + * @return TemplateResponse + */ + #[PublicPage] + #[NoCSRFRequired] + public function processMoreOptionsResult(string $token):TemplateResponse { + $partstat = $this->request->getParam('partStat'); + + $row = $this->getTokenInformation($token); + if (!$row || !\in_array($partstat, ['ACCEPTED', 'DECLINED', 'TENTATIVE'])) { + return new TemplateResponse($this->appName, 'schedule-response-error', [], 'guest'); + } + + $iTipMessage = $this->buildITipResponse($row, $partstat); + $this->responseServer->handleITipMessage($iTipMessage); + if ($iTipMessage->getScheduleStatus() === '1.2') { + return new TemplateResponse($this->appName, 'schedule-response-success', [], 'guest'); + } + + return new TemplateResponse($this->appName, 'schedule-response-error', [ + 'organizer' => $row['organizer'], + ], 'guest'); + } + + /** + * @param string $token + * @return array|null + */ + private function getTokenInformation(string $token) { + $query = $this->db->getQueryBuilder(); + $query->select('*') + ->from('calendar_invitations') + ->where($query->expr()->eq('token', $query->createNamedParameter($token))); + $stmt = $query->executeQuery(); + $row = $stmt->fetch(\PDO::FETCH_ASSOC); + $stmt->closeCursor(); + + if (!$row) { + return null; + } + + $currentTime = $this->timeFactory->getTime(); + if (((int)$row['expiration']) < $currentTime) { + return null; + } + + return $row; + } + + /** + * @param array $row + * @param string $partStat participation status of attendee - SEE RFC 5545 + * @param int|null $guests + * @param string|null $comment + * @return Message + */ + private function buildITipResponse(array $row, string $partStat):Message { + $iTipMessage = new Message(); + $iTipMessage->uid = $row['uid']; + $iTipMessage->component = 'VEVENT'; + $iTipMessage->method = 'REPLY'; + $iTipMessage->sequence = $row['sequence']; + $iTipMessage->sender = $row['attendee']; + + if ($this->responseServer->isExternalAttendee($row['attendee'])) { + $iTipMessage->recipient = $row['organizer']; + } else { + $iTipMessage->recipient = $row['attendee']; + } + + $message = <<<EOF +BEGIN:VCALENDAR +PRODID:-//Nextcloud/Nextcloud CalDAV Server//EN +METHOD:REPLY +VERSION:2.0 +BEGIN:VEVENT +ATTENDEE;PARTSTAT=%s:%s +ORGANIZER:%s +UID:%s +SEQUENCE:%s +REQUEST-STATUS:2.0;Success +%sEND:VEVENT +END:VCALENDAR +EOF; + + $vObject = Reader::read(vsprintf($message, [ + $partStat, $row['attendee'], $row['organizer'], + $row['uid'], $row['sequence'] ?? 0, $row['recurrenceid'] ?? '' + ])); + $vEvent = $vObject->{'VEVENT'}; + $vEvent->DTSTAMP = date('Ymd\\THis\\Z', $this->timeFactory->getTime()); + $iTipMessage->message = $vObject; + + return $iTipMessage; + } +} diff --git a/apps/dav/lib/Controller/OutOfOfficeController.php b/apps/dav/lib/Controller/OutOfOfficeController.php new file mode 100644 index 00000000000..d3516d092e8 --- /dev/null +++ b/apps/dav/lib/Controller/OutOfOfficeController.php @@ -0,0 +1,189 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\DAV\Controller; + +use DateTimeImmutable; +use OCA\DAV\ResponseDefinitions; +use OCA\DAV\Service\AbsenceService; +use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\Attribute\NoAdminRequired; +use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\OCSController; +use OCP\IRequest; +use OCP\IUserManager; +use OCP\IUserSession; +use OCP\User\IAvailabilityCoordinator; +use function mb_strlen; + +/** + * @psalm-import-type DAVOutOfOfficeData from ResponseDefinitions + * @psalm-import-type DAVCurrentOutOfOfficeData from ResponseDefinitions + */ +class OutOfOfficeController extends OCSController { + + public function __construct( + string $appName, + IRequest $request, + private IUserManager $userManager, + private ?IUserSession $userSession, + private AbsenceService $absenceService, + private IAvailabilityCoordinator $coordinator, + ) { + parent::__construct($appName, $request); + } + + /** + * Get the currently configured out-of-office data of a user + * + * @param string $userId The user id to get out-of-office data for. + * @return DataResponse<Http::STATUS_OK, DAVCurrentOutOfOfficeData, array{}>|DataResponse<Http::STATUS_NOT_FOUND, null, array{}> + * + * 200: Out-of-office data + * 404: No out-of-office data was found + */ + #[NoAdminRequired] + public function getCurrentOutOfOfficeData(string $userId): DataResponse { + $user = $this->userManager->get($userId); + if ($user === null) { + return new DataResponse(null, Http::STATUS_NOT_FOUND); + } + try { + $data = $this->absenceService->getCurrentAbsence($user); + if ($data === null) { + return new DataResponse(null, Http::STATUS_NOT_FOUND); + } + } catch (DoesNotExistException) { + return new DataResponse(null, Http::STATUS_NOT_FOUND); + } + + return new DataResponse($data->jsonSerialize()); + } + + /** + * Get the configured out-of-office data of a user. + * + * @param string $userId The user id to get out-of-office data for. + * @return DataResponse<Http::STATUS_OK, DAVOutOfOfficeData, array{}>|DataResponse<Http::STATUS_NOT_FOUND, null, array{}> + * + * 200: Out-of-office data + * 404: No out-of-office data was found + */ + #[NoAdminRequired] + public function getOutOfOffice(string $userId): DataResponse { + try { + $data = $this->absenceService->getAbsence($userId); + if ($data === null) { + return new DataResponse(null, Http::STATUS_NOT_FOUND); + } + } 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(), + 'replacementUserId' => $data->getReplacementUserId(), + 'replacementUserDisplayName' => $data->getReplacementUserDisplayName(), + ]); + } + + /** + * Set out-of-office absence + * + * @param string $firstDay First day of the absence in format `YYYY-MM-DD` + * @param string $lastDay Last day of the absence in format `YYYY-MM-DD` + * @param string $status Short text that is set as user status during the absence + * @param string $message Longer multiline message that is shown to others during the absence + * @param ?string $replacementUserId User id of the replacement user + * @return DataResponse<Http::STATUS_OK, DAVOutOfOfficeData, array{}>|DataResponse<Http::STATUS_BAD_REQUEST, array{error: 'firstDay'|'statusLength'}, array{}>|DataResponse<Http::STATUS_UNAUTHORIZED, null, array{}>|DataResponse<Http::STATUS_NOT_FOUND, null, array{}> + * + * 200: Absence data + * 400: When validation fails, e.g. data range error or the first day is not before the last day + * 401: When the user is not logged in + * 404: When the replacementUserId was provided but replacement user was not found + */ + #[NoAdminRequired] + public function setOutOfOffice( + string $firstDay, + string $lastDay, + string $status, + string $message, + ?string $replacementUserId, + ): DataResponse { + $user = $this->userSession?->getUser(); + if ($user === null) { + return new DataResponse(null, Http::STATUS_UNAUTHORIZED); + } + if (mb_strlen($status) > 100) { + return new DataResponse(['error' => 'statusLength'], Http::STATUS_BAD_REQUEST); + } + + $replacementUser = null; + if ($replacementUserId !== null) { + $replacementUser = $this->userManager->get($replacementUserId); + if ($replacementUser === null) { + return new DataResponse(null, Http::STATUS_NOT_FOUND); + } + } + + $parsedFirstDay = new DateTimeImmutable($firstDay); + $parsedLastDay = new DateTimeImmutable($lastDay); + if ($parsedFirstDay->getTimestamp() > $parsedLastDay->getTimestamp()) { + return new DataResponse(['error' => 'firstDay'], Http::STATUS_BAD_REQUEST); + } + + $data = $this->absenceService->createOrUpdateAbsence( + $user, + $firstDay, + $lastDay, + $status, + $message, + $replacementUserId, + $replacementUser?->getDisplayName() + ); + $this->coordinator->clearCache($user->getUID()); + + return new DataResponse([ + 'id' => $data->getId(), + 'userId' => $data->getUserId(), + 'firstDay' => $data->getFirstDay(), + 'lastDay' => $data->getLastDay(), + 'status' => $data->getStatus(), + 'message' => $data->getMessage(), + 'replacementUserId' => $data->getReplacementUserId(), + 'replacementUserDisplayName' => $data->getReplacementUserDisplayName(), + ]); + } + + /** + * Clear the out-of-office + * + * @return DataResponse<Http::STATUS_OK|Http::STATUS_UNAUTHORIZED, null, array{}> + * + * 200: When the absence was cleared successfully + * 401: When the user is not logged in + */ + #[NoAdminRequired] + public function clearOutOfOffice(): DataResponse { + $user = $this->userSession?->getUser(); + if ($user === null) { + return new DataResponse(null, Http::STATUS_UNAUTHORIZED); + } + + $this->absenceService->clearAbsence($user); + $this->coordinator->clearCache($user->getUID()); + return new DataResponse(null); + } +} diff --git a/apps/dav/lib/Controller/UpcomingEventsController.php b/apps/dav/lib/Controller/UpcomingEventsController.php new file mode 100644 index 00000000000..a5d54f44754 --- /dev/null +++ b/apps/dav/lib/Controller/UpcomingEventsController.php @@ -0,0 +1,57 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\DAV\Controller; + +use OCA\DAV\AppInfo\Application; +use OCA\DAV\CalDAV\UpcomingEvent; +use OCA\DAV\CalDAV\UpcomingEventsService; +use OCA\DAV\ResponseDefinitions; +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\Attribute\NoAdminRequired; +use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\OCSController; +use OCP\IRequest; + +/** + * @psalm-import-type DAVUpcomingEvent from ResponseDefinitions + */ +class UpcomingEventsController extends OCSController { + public function __construct( + IRequest $request, + private ?string $userId, + private UpcomingEventsService $service, + ) { + parent::__construct(Application::APP_ID, $request); + } + + /** + * Get information about upcoming events + * + * @param string|null $location location/URL to filter by + * @return DataResponse<Http::STATUS_OK, array{events: list<DAVUpcomingEvent>}, array{}>|DataResponse<Http::STATUS_UNAUTHORIZED, null, array{}> + * + * 200: Upcoming events + * 401: When not authenticated + */ + #[NoAdminRequired] + public function getEvents(?string $location = null): DataResponse { + if ($this->userId === null) { + return new DataResponse(null, Http::STATUS_UNAUTHORIZED); + } + + return new DataResponse([ + 'events' => array_values(array_map(fn (UpcomingEvent $e) => $e->jsonSerialize(), $this->service->getEvents( + $this->userId, + $location, + ))), + ]); + } + +} |