From 9941e9bd3201bff0f18ba27e7cab98130371d511 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Mon, 9 Dec 2024 11:46:18 +0100 Subject: [PATCH 1/2] feat(meetings): Add endpoint to get writable calendars and the default calendar Signed-off-by: Joas Schilling --- appinfo/routes/routesSettingsController.php | 2 + lib/Controller/SettingsController.php | 86 +++++++++++++++ lib/ResponseDefinitions.php | 6 ++ openapi-full.json | 110 ++++++++++++++++++++ openapi.json | 110 ++++++++++++++++++++ src/types/openapi/openapi-full.ts | 58 +++++++++++ src/types/openapi/openapi.ts | 58 +++++++++++ 7 files changed, 430 insertions(+) diff --git a/appinfo/routes/routesSettingsController.php b/appinfo/routes/routesSettingsController.php index b707fcf5c3e..bf68431792f 100644 --- a/appinfo/routes/routesSettingsController.php +++ b/appinfo/routes/routesSettingsController.php @@ -16,5 +16,7 @@ ['name' => 'Settings#setSIPSettings', 'url' => '/api/{apiVersion}/settings/sip', 'verb' => 'POST', 'requirements' => $requirements], /** @see \OCA\Talk\Controller\SettingsController::setUserSetting() */ ['name' => 'Settings#setUserSetting', 'url' => '/api/{apiVersion}/settings/user', 'verb' => 'POST', 'requirements' => $requirements], + /** @see \OCA\Talk\Controller\SettingsController::getPersonalCalendars() */ + ['name' => 'Settings#getPersonalCalendars', 'url' => '/api/{apiVersion}/personal-calendars', 'verb' => 'GET', 'requirements' => $requirements], ], ]; diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php index 55c90506f22..7b177312af2 100644 --- a/lib/Controller/SettingsController.php +++ b/lib/Controller/SettingsController.php @@ -8,29 +8,38 @@ namespace OCA\Talk\Controller; +use OCA\Talk\ResponseDefinitions; use OCA\Talk\Settings\BeforePreferenceSetEventListener; use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\Attribute\OpenAPI; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\OCSController; +use OCP\Calendar\IManager; +use OCP\Constants; use OCP\Files\IRootFolder; use OCP\IConfig; +use OCP\IDBConnection; use OCP\IGroup; use OCP\IGroupManager; use OCP\IRequest; use Psr\Log\LoggerInterface; +/** + * @psalm-import-type TalkCalendar from ResponseDefinitions + */ class SettingsController extends OCSController { public function __construct( string $appName, IRequest $request, protected IRootFolder $rootFolder, + protected IDBConnection $db, protected IConfig $config, protected IGroupManager $groupManager, protected LoggerInterface $logger, protected BeforePreferenceSetEventListener $preferenceListener, + protected IManager $calendarManager, protected ?string $userId, ) { parent::__construct($appName, $request); @@ -86,4 +95,81 @@ public function setSIPSettings( return new DataResponse(null); } + + /** + * Get writable calendars and the default calendar + * + * Required capability: `schedule-meeting` + * + * @return DataResponse}, array{}> + * + * 200: Get a list of calendars + */ + #[NoAdminRequired] + public function getPersonalCalendars(): DataResponse { + $calendars = $this->calendarManager->getCalendarsForPrincipal('principals/users/' . $this->userId); + + $defaultCalendarUri = $selectedCalendarUri = null; + $selectedCalendar = $this->getSchedulingCalendarFromPropertiesTable($this->userId); + if ($selectedCalendar !== false) { + $parts = explode('/', rtrim($selectedCalendar, '/')); + if (count($parts) === 3 && $parts[0] === 'calendars' && $parts[1] === $this->userId) { + $selectedCalendarUri = $parts[2]; + } + } + + $hasPersonal = false; + $list = []; + foreach ($calendars as $calendar) { + if ($calendar->isDeleted()) { + continue; + } + + if (!($calendar->getPermissions('principals/users/' . $this->userId) & Constants::PERMISSION_CREATE)) { + continue; + } + + if ($calendar->getUri() === 'personal') { + $hasPersonal = true; + } + + if ($selectedCalendarUri === $calendar->getUri()) { + $defaultCalendarUri = $selectedCalendarUri; + } + + $list[] = [ + 'uri' => $calendar->getUri(), + 'name' => $calendar->getDisplayName() ?? $calendar->getUri(), + 'color' => $calendar->getDisplayColor(), + ]; + } + + return new DataResponse([ + 'defaultCalendarUri' => $defaultCalendarUri ?? ($hasPersonal ? 'personal' : null), + 'calendars' => $list, + ]); + } + + /** + * @param string $userId + * @return false|string + */ + protected function getSchedulingCalendarFromPropertiesTable(string $userId) { + $propertyPath = 'principals/users/' . $userId; + $propertyName = '{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL'; + + $query = $this->db->getQueryBuilder(); + $query->select('propertyvalue') + ->from('properties') + ->where($query->expr()->eq('userid', $query->createNamedParameter($userId))) + ->andWhere($query->expr()->eq('propertypath', $query->createNamedParameter($propertyPath))) + ->andWhere($query->expr()->eq('propertyname', $query->createNamedParameter($propertyName))) + ->setMaxResults(1); + + $result = $query->executeQuery(); + $property = $result->fetchOne(); + $result->closeCursor(); + + return $property; + } } diff --git a/lib/ResponseDefinitions.php b/lib/ResponseDefinitions.php index 60da06cf6cb..8651e2b1629 100644 --- a/lib/ResponseDefinitions.php +++ b/lib/ResponseDefinitions.php @@ -44,6 +44,12 @@ * secret: string, * } * + * @psalm-type TalkCalendar = array{ + * uri: string, + * name: string, + * color: ?string, + * } + * * @psalm-type TalkCallPeer = array{ * actorId: string, * actorType: string, diff --git a/openapi-full.json b/openapi-full.json index eccc60c132b..d29a3090815 100644 --- a/openapi-full.json +++ b/openapi-full.json @@ -207,6 +207,26 @@ } ] }, + "Calendar": { + "type": "object", + "required": [ + "uri", + "name", + "color" + ], + "properties": { + "uri": { + "type": "string" + }, + "name": { + "type": "string" + }, + "color": { + "type": "string", + "nullable": true + } + } + }, "CallPeer": { "type": "object", "required": [ @@ -17680,6 +17700,96 @@ } } }, + "/ocs/v2.php/apps/spreed/api/{apiVersion}/personal-calendars": { + "get": { + "operationId": "settings-get-personal-calendars", + "summary": "Get writable calendars and the default calendar", + "description": "Required capability: `schedule-meeting`", + "tags": [ + "settings" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + }, + { + "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": "Get a list of calendars", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "defaultCalendarUri", + "calendars" + ], + "properties": { + "defaultCalendarUri": { + "type": "string", + "nullable": true + }, + "calendars": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Calendar" + } + } + } + } + } + } + } + } + } + } + } + } + } + }, "/ocs/v2.php/apps/spreed/api/{apiVersion}/signaling/settings": { "get": { "operationId": "signaling-get-settings", diff --git a/openapi.json b/openapi.json index e00793473bf..87108cfd7af 100644 --- a/openapi.json +++ b/openapi.json @@ -148,6 +148,26 @@ } } }, + "Calendar": { + "type": "object", + "required": [ + "uri", + "name", + "color" + ], + "properties": { + "uri": { + "type": "string" + }, + "name": { + "type": "string" + }, + "color": { + "type": "string", + "nullable": true + } + } + }, "CallPeer": { "type": "object", "required": [ @@ -17820,6 +17840,96 @@ } } }, + "/ocs/v2.php/apps/spreed/api/{apiVersion}/personal-calendars": { + "get": { + "operationId": "settings-get-personal-calendars", + "summary": "Get writable calendars and the default calendar", + "description": "Required capability: `schedule-meeting`", + "tags": [ + "settings" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + }, + { + "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": "Get a list of calendars", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "defaultCalendarUri", + "calendars" + ], + "properties": { + "defaultCalendarUri": { + "type": "string", + "nullable": true + }, + "calendars": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Calendar" + } + } + } + } + } + } + } + } + } + } + } + } + } + }, "/ocs/v2.php/apps/spreed/api/{apiVersion}/signaling/settings": { "get": { "operationId": "signaling-get-settings", diff --git a/src/types/openapi/openapi-full.ts b/src/types/openapi/openapi-full.ts index eafe8a13943..e2bca6e0a55 100644 --- a/src/types/openapi/openapi-full.ts +++ b/src/types/openapi/openapi-full.ts @@ -1367,6 +1367,26 @@ export type paths = { patch?: never; trace?: never; }; + "/ocs/v2.php/apps/spreed/api/{apiVersion}/personal-calendars": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get writable calendars and the default calendar + * @description Required capability: `schedule-meeting` + */ + get: operations["settings-get-personal-calendars"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/ocs/v2.php/apps/spreed/api/{apiVersion}/signaling/settings": { parameters: { query?: never; @@ -1965,6 +1985,11 @@ export type components = { BotWithDetailsAndSecret: components["schemas"]["BotWithDetails"] & { secret: string; }; + Calendar: { + uri: string; + name: string; + color: string | null; + }; CallPeer: { actorId: string; actorType: string; @@ -8719,6 +8744,39 @@ export interface operations { }; }; }; + "settings-get-personal-calendars": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v1"; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Get a list of calendars */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + defaultCalendarUri: string | null; + calendars: components["schemas"]["Calendar"][]; + }; + }; + }; + }; + }; + }; + }; "signaling-get-settings": { parameters: { query?: { diff --git a/src/types/openapi/openapi.ts b/src/types/openapi/openapi.ts index 6a208fd0f90..5ce5d8a462a 100644 --- a/src/types/openapi/openapi.ts +++ b/src/types/openapi/openapi.ts @@ -1369,6 +1369,26 @@ export type paths = { patch?: never; trace?: never; }; + "/ocs/v2.php/apps/spreed/api/{apiVersion}/personal-calendars": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get writable calendars and the default calendar + * @description Required capability: `schedule-meeting` + */ + get: operations["settings-get-personal-calendars"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/ocs/v2.php/apps/spreed/api/{apiVersion}/signaling/settings": { parameters: { query?: never; @@ -1462,6 +1482,11 @@ export type components = { /** Format: int64 */ state: number; }; + Calendar: { + uri: string; + name: string; + color: string | null; + }; CallPeer: { actorId: string; actorType: string; @@ -8300,6 +8325,39 @@ export interface operations { }; }; }; + "settings-get-personal-calendars": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v1"; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Get a list of calendars */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + defaultCalendarUri: string | null; + calendars: components["schemas"]["Calendar"][]; + }; + }; + }; + }; + }; + }; + }; "signaling-get-settings": { parameters: { query?: { From 14dc6a7eca50f03a5fec0e587b113c5376c5efca Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Wed, 11 Dec 2024 11:55:11 +0100 Subject: [PATCH 2/2] fixup! feat(meetings): Add endpoint to get writable calendars and the default calendar --- lib/Controller/SettingsController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php index 7b177312af2..7e2e53e6e9a 100644 --- a/lib/Controller/SettingsController.php +++ b/lib/Controller/SettingsController.php @@ -125,7 +125,7 @@ public function getPersonalCalendars(): DataResponse { continue; } - if (!($calendar->getPermissions('principals/users/' . $this->userId) & Constants::PERMISSION_CREATE)) { + if (!($calendar->getPermissions() & Constants::PERMISSION_CREATE)) { continue; }