Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>pull/63002/head
parent
e161c8a99e
commit
16b784601a
@ -0,0 +1,177 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace OCA\Profile\Controller; |
||||
|
||||
use OCP\App\IAppManager; |
||||
use OCP\AppFramework\Http; |
||||
use OCP\AppFramework\Http\Attribute\ApiRoute; |
||||
use OCP\AppFramework\Http\Attribute\NoAdminRequired; |
||||
use OCP\AppFramework\Http\Attribute\NoCSRFRequired; |
||||
use OCP\AppFramework\Http\Attribute\OpenAPI; |
||||
use OCP\AppFramework\Http\DataResponse; |
||||
use OCP\AppFramework\OCS\OCSNotFoundException; |
||||
use OCP\AppFramework\OCSController; |
||||
use OCP\Calendar\ICalendarQuery; |
||||
use OCP\Calendar\IManager; |
||||
use OCP\IDateTimeFormatter; |
||||
use OCP\IRequest; |
||||
use OCP\IURLGenerator; |
||||
use OCP\IUserManager; |
||||
use OCP\IUserSession; |
||||
use OCP\Share\IManager as IShareManager; |
||||
use OCP\Share\IShare; |
||||
|
||||
/** |
||||
* @psalm-import-type ProfileSharedResource from \OCA\Profile\ResponseDefinitions |
||||
*/ |
||||
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)] |
||||
class ProfileApiController extends OCSController { |
||||
public function __construct( |
||||
string $appName, |
||||
IRequest $request, |
||||
private IUserSession $userSession, |
||||
private IUserManager $userManager, |
||||
private IManager $calendarManager, |
||||
private IShareManager $shareManager, |
||||
private IDateTimeFormatter $formatter, |
||||
private IURLGenerator $urlGenerator, |
||||
private IAppManager $appManager, |
||||
) { |
||||
parent::__construct($appName, $request); |
||||
} |
||||
|
||||
/** |
||||
* Get resources shared between the current user and the specified user. |
||||
* |
||||
* @param string $userId - The user ID of the user to get shared resources with. |
||||
* @return DataResponse<Http::STATUS_OK, list<ProfileSharedResource>, array{}> |
||||
* @throws OCSNotFoundException - The specified user does not exist. |
||||
* |
||||
* 200: The shared resources between the current user and the specified user. |
||||
* 404: The specified user does not exist. |
||||
*/ |
||||
#[NoCSRFRequired] |
||||
#[NoAdminRequired] |
||||
#[ApiRoute(verb: 'GET', url: '/api/v1/resources/{userId}')] |
||||
public function getResources(string $userId): DataResponse { |
||||
$user = $this->userManager->get($userId); |
||||
if (!$user) { |
||||
throw new OCSNotFoundException(); |
||||
} |
||||
|
||||
$files = $this->getSharedNodes($userId); |
||||
$events = $this->getSharedCalendarEvents($userId); |
||||
|
||||
$entries = array_values(array_merge($files, $events)); |
||||
return new DataResponse($entries); |
||||
} |
||||
|
||||
/** |
||||
* Get all upcoming events shared between a user and the current user. |
||||
* |
||||
* If the calendar app is disabled for the current user no events will be returned. |
||||
* |
||||
* @param string $userId - The user ID of the user to get shared events with. |
||||
* @return list<ProfileSharedResource> |
||||
*/ |
||||
private function getSharedCalendarEvents(string $userId) { |
||||
if (!$this->appManager->isEnabledForUser('calendar', $this->userSession->getUser())) { |
||||
return []; |
||||
} |
||||
|
||||
$mePrincipal = 'principals/users/' . $this->userSession->getUser()->getUID(); |
||||
|
||||
$query = $this->calendarManager->newQuery($mePrincipal); |
||||
$query->setSearchPattern($userId); |
||||
$query->addType('VEVENT'); |
||||
$query->addSearchProperty(ICalendarQuery::SEARCH_PROPERTY_ATTENDEE); |
||||
$query->addSearchProperty(ICalendarQuery::SEARCH_PROPERTY_ORGANIZER); |
||||
$now = new \DateTimeImmutable('now'); |
||||
$query->setTimerangeStart($now->modify('-1 hour')); |
||||
$query->setLimit(9); |
||||
|
||||
$events = $this->calendarManager->searchForPrincipal($query); |
||||
$result = []; |
||||
foreach ($events as $event) { |
||||
if (isset($event['objects'][0]['STATUS']) && $event['objects'][0]['STATUS'][0] === 'CANCELLED') { |
||||
continue; |
||||
} |
||||
|
||||
$end = $event['objects'][0]['DTEND'][0]; |
||||
if ($now->diff($end)->invert === 1) { |
||||
// already ended, skip |
||||
continue; |
||||
} |
||||
|
||||
$calendarUid = $event['objects'][0]['UID'][0]; |
||||
if (isset($event['RECURRENCE-ID'])) { |
||||
$recurrenceId = $event['RECURRENCE-ID'][0]; |
||||
$href = $this->urlGenerator->linkToRouteAbsolute('calendar.object.indexuid.recurrenceId', ['uid' => $calendarUid, 'recurrenceId' => $recurrenceId]); |
||||
} else { |
||||
$href = $this->urlGenerator->linkToRouteAbsolute('calendar.object.indexuid', ['uid' => $calendarUid]); |
||||
} |
||||
|
||||
$start = \DateTime::createFromImmutable($event['objects'][0]['DTSTART'][0]); |
||||
$result[] = [ |
||||
'label' => $event['objects'][0]['SUMMARY'][0], |
||||
'text' => $this->formatter->formatTimeSpan($start, \DateTime::createFromImmutable($now)), |
||||
'href' => $href, |
||||
'img' => $this->urlGenerator->getAbsoluteURL($this->appManager->getAppIcon('calendar')), |
||||
]; |
||||
} |
||||
return $result; |
||||
} |
||||
|
||||
/** |
||||
* @return ProfileSharedResource[] |
||||
*/ |
||||
private function getSharedNodes(string $userId): array { |
||||
$outgoingShares = []; |
||||
$offset = 0; |
||||
while (count($outgoingShares) < 5) { |
||||
$shares = $this->shareManager->getSharesBy($userId, IShare::TYPE_USER, limit: 50, offset: $offset); |
||||
$outgoingShares = array_merge($outgoingShares, array_filter($shares, fn ($share) => $share->getSharedWith() === $this->userSession->getUser()->getUID())); |
||||
$offset += 50; |
||||
if (count($shares) < 50) { |
||||
break; |
||||
} |
||||
} |
||||
|
||||
$incomingShares = []; |
||||
$offset = 0; |
||||
while (count($incomingShares) < 5) { |
||||
$shares = $this->shareManager->getSharesBy($this->userSession->getUser()->getUID(), IShare::TYPE_USER, limit: 50, offset: $offset); |
||||
$incomingShares = array_merge($incomingShares, array_filter($shares, fn ($share) => $share->getSharedWith() === $userId)); |
||||
$offset += 50; |
||||
if (count($shares) < 50) { |
||||
break; |
||||
} |
||||
} |
||||
|
||||
$shares = array_slice(array_merge($outgoingShares, $incomingShares), 0, 5); |
||||
usort($shares, fn ($a, $b) => $a->getNode()->getMTime() <=> $b->getNode()->getMTime()); |
||||
$files = []; |
||||
foreach ($shares as $share) { |
||||
$files[] = [ |
||||
'label' => $share->getNode()->getName(), |
||||
'text' => $this->formatter->formatTimeSpan($share->getNode()->getMTime()), |
||||
'href' => $this->urlGenerator->linkToRouteAbsolute('files.view.index', [ |
||||
'dir' => $share->getNode()->getParent()->getPath(), |
||||
'fileid' => $share->getNode()->getId(), |
||||
]), |
||||
'img' => $this->urlGenerator->linkToRouteAbsolute('core.Preview.getPreviewByFileId', [ |
||||
'fileId' => $share->getNode()->getId(), |
||||
'mimeFallback' => true, |
||||
]), |
||||
]; |
||||
} |
||||
return $files; |
||||
} |
||||
} |
||||
@ -0,0 +1,21 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace OCA\Profile; |
||||
|
||||
/** |
||||
* @psalm-type ProfileSharedResource array{ |
||||
* label: string, |
||||
* text: string, |
||||
* href: string, |
||||
* img: string |
||||
* } |
||||
*/ |
||||
class ResponseDefinitions { |
||||
} |
||||
@ -0,0 +1,204 @@ |
||||
{ |
||||
"openapi": "3.0.3", |
||||
"info": { |
||||
"title": "profile", |
||||
"version": "0.0.1", |
||||
"description": "This application provides the profile", |
||||
"license": { |
||||
"name": "AGPL-3.0-or-later" |
||||
} |
||||
}, |
||||
"components": { |
||||
"securitySchemes": { |
||||
"basic_auth": { |
||||
"type": "http", |
||||
"scheme": "basic" |
||||
}, |
||||
"bearer_auth": { |
||||
"type": "http", |
||||
"scheme": "bearer" |
||||
} |
||||
}, |
||||
"schemas": { |
||||
"OCSMeta": { |
||||
"type": "object", |
||||
"required": [ |
||||
"status", |
||||
"statuscode" |
||||
], |
||||
"properties": { |
||||
"status": { |
||||
"type": "string" |
||||
}, |
||||
"statuscode": { |
||||
"type": "integer" |
||||
}, |
||||
"message": { |
||||
"type": "string" |
||||
}, |
||||
"totalitems": { |
||||
"type": "string" |
||||
}, |
||||
"itemsperpage": { |
||||
"type": "string" |
||||
} |
||||
} |
||||
}, |
||||
"SharedResource": { |
||||
"type": "object", |
||||
"required": [ |
||||
"label", |
||||
"text", |
||||
"href", |
||||
"img" |
||||
], |
||||
"properties": { |
||||
"label": { |
||||
"type": "string" |
||||
}, |
||||
"text": { |
||||
"type": "string" |
||||
}, |
||||
"href": { |
||||
"type": "string" |
||||
}, |
||||
"img": { |
||||
"type": "string" |
||||
} |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
"paths": { |
||||
"/ocs/v2.php/apps/profile/api/v1/resources/{userId}": { |
||||
"get": { |
||||
"operationId": "profile_api-get-resources", |
||||
"summary": "Get resources shared between the current user and the specified user.", |
||||
"tags": [ |
||||
"profile_api" |
||||
], |
||||
"security": [ |
||||
{ |
||||
"bearer_auth": [] |
||||
}, |
||||
{ |
||||
"basic_auth": [] |
||||
} |
||||
], |
||||
"parameters": [ |
||||
{ |
||||
"name": "userId", |
||||
"in": "path", |
||||
"description": "- The user ID of the user to get shared resources with.", |
||||
"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": "The shared resources between the current user and the specified user.", |
||||
"content": { |
||||
"application/json": { |
||||
"schema": { |
||||
"type": "object", |
||||
"required": [ |
||||
"ocs" |
||||
], |
||||
"properties": { |
||||
"ocs": { |
||||
"type": "object", |
||||
"required": [ |
||||
"meta", |
||||
"data" |
||||
], |
||||
"properties": { |
||||
"meta": { |
||||
"$ref": "#/components/schemas/OCSMeta" |
||||
}, |
||||
"data": { |
||||
"type": "array", |
||||
"items": { |
||||
"$ref": "#/components/schemas/SharedResource" |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
"404": { |
||||
"description": "- The specified user does not exist.", |
||||
"content": { |
||||
"application/json": { |
||||
"schema": { |
||||
"type": "object", |
||||
"required": [ |
||||
"ocs" |
||||
], |
||||
"properties": { |
||||
"ocs": { |
||||
"type": "object", |
||||
"required": [ |
||||
"meta", |
||||
"data" |
||||
], |
||||
"properties": { |
||||
"meta": { |
||||
"$ref": "#/components/schemas/OCSMeta" |
||||
}, |
||||
"data": {} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
"401": { |
||||
"description": "Current user is not logged in", |
||||
"content": { |
||||
"application/json": { |
||||
"schema": { |
||||
"type": "object", |
||||
"required": [ |
||||
"ocs" |
||||
], |
||||
"properties": { |
||||
"ocs": { |
||||
"type": "object", |
||||
"required": [ |
||||
"meta", |
||||
"data" |
||||
], |
||||
"properties": { |
||||
"meta": { |
||||
"$ref": "#/components/schemas/OCSMeta" |
||||
}, |
||||
"data": {} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
"tags": [] |
||||
} |
||||
Loading…
Reference in new issue