diff --git a/appinfo/routes/routesRoomController.php b/appinfo/routes/routesRoomController.php index 0c2d29febbe..9bff6349ecf 100644 --- a/appinfo/routes/routesRoomController.php +++ b/appinfo/routes/routesRoomController.php @@ -115,5 +115,7 @@ ['name' => 'Room#archiveConversation', 'url' => '/api/{apiVersion}/room/{token}/archive', 'verb' => 'POST', 'requirements' => $requirementsWithToken], /** @see \OCA\Talk\Controller\RoomController::unarchiveConversation() */ ['name' => 'Room#unarchiveConversation', 'url' => '/api/{apiVersion}/room/{token}/archive', 'verb' => 'DELETE', 'requirements' => $requirementsWithToken], + /** @see \OCA\Talk\Controller\RoomController::importEmailsAsParticipants() */ + ['name' => 'Room#importEmailsAsParticipants', 'url' => '/api/{apiVersion}/room/{token}/import-emails', 'verb' => 'POST', 'requirements' => $requirementsWithToken], ], ]; diff --git a/docs/capabilities.md b/docs/capabilities.md index dc7ad23cdc0..5ee8adda207 100644 --- a/docs/capabilities.md +++ b/docs/capabilities.md @@ -161,6 +161,7 @@ * `talk-polls-drafts` - Whether moderators can store and retrieve poll drafts * `download-call-participants` - Whether the endpoints for moderators to download the call participants is available * `chat-summary-api` (local) - Whether the endpoint to get summarized chat messages in a conversation is available +* `email-csv-import` - Whether the endpoint to import a CSV email list as participants exists * `config => chat => summary-threshold` (local) - Number of unread messages that should exist to show a "Generate summary" option * `config => call => start-without-media` (local) - Boolean, whether media should be disabled when starting or joining a conversation * `config => call => max-duration` - Integer, maximum call duration in seconds. Please note that this should only be used with system cron and with a reasonable high value, due to the expended duration until the background job ran. diff --git a/lib/Capabilities.php b/lib/Capabilities.php index 9982db71c2a..91280aeb16c 100644 --- a/lib/Capabilities.php +++ b/lib/Capabilities.php @@ -108,6 +108,7 @@ class Capabilities implements IPublicCapability { 'archived-conversations-v2', 'talk-polls-drafts', 'download-call-participants', + 'email-csv-import', ]; public const CONDITIONAL_FEATURES = [ diff --git a/lib/Controller/RoomController.php b/lib/Controller/RoomController.php index 0bb34dfb904..1d3ca673b1e 100644 --- a/lib/Controller/RoomController.php +++ b/lib/Controller/RoomController.php @@ -14,6 +14,7 @@ use OCA\Talk\Events\BeforeRoomsFetchEvent; use OCA\Talk\Exceptions\CannotReachRemoteException; use OCA\Talk\Exceptions\ForbiddenException; +use OCA\Talk\Exceptions\GuestImportException; use OCA\Talk\Exceptions\InvalidPasswordException; use OCA\Talk\Exceptions\ParticipantNotFoundException; use OCA\Talk\Exceptions\RoomNotFoundException; @@ -73,6 +74,7 @@ use OCP\IConfig; use OCP\IGroup; use OCP\IGroupManager; +use OCP\IL10N; use OCP\IPhoneNumberUtil; use OCP\IRequest; use OCP\IUser; @@ -121,6 +123,7 @@ public function __construct( protected Capabilities $capabilities, protected FederationManager $federationManager, protected BanService $banService, + protected IL10N $l, ) { parent::__construct($appName, $request); } @@ -1204,7 +1207,7 @@ public function addParticipantToRoom(string $newParticipant, string $source = 'u } catch (TypeException) { } - $email = $newParticipant; + $email = strtolower($newParticipant); $actorId = hash('sha256', $email); try { $this->participantService->getParticipantByActor($this->room, Attendee::ACTOR_EMAILS, $actorId); @@ -2424,6 +2427,48 @@ public function setMessageExpiration(int $seconds): DataResponse { return new DataResponse(); } + /** + * Import a list of email attendees + * + * Content format is comma separated values: + * - Header line is required and must match `"email","name"` or `"email"` + * - one entry per line + * + * Required capability: `email-csv-import` + * + * @param bool $testRun When set to true, the file is validated and no email is actually sent nor any participant added to the conversation + * @return DataResponse, type?: int<-1, 6>}, array{}>|DataResponse, type?: int<-1, 6>}, array{}> + * + * 200: All entries imported successfully + * 400: Import was not successful. When message is provided the string is in user language and should be displayed as an error. + */ + #[NoAdminRequired] + #[RequireModeratorParticipant] + public function importEmailsAsParticipants(bool $testRun = false): DataResponse { + $file = $this->request->getUploadedFile('file'); + if ($file === null) { + return new DataResponse([ + 'error' => 'file', + 'message' => $this->l->t('Uploading the file failed'), + ], Http::STATUS_BAD_REQUEST); + } + if ($file['error'] !== 0) { + $this->logger->error('Uploading email CSV file failed with error: ' . $file['error']); + return new DataResponse([ + 'error' => 'file', + 'message' => $this->l->t('Uploading the file failed'), + ], Http::STATUS_BAD_REQUEST); + } + + try { + $data = $this->guestManager->importEmails($this->room, $file, $testRun); + return new DataResponse($data); + } catch (GuestImportException $e) { + return new DataResponse($e->getData(), Http::STATUS_BAD_REQUEST); + } + + } + /** * Get capabilities for a room * diff --git a/lib/Exceptions/GuestImportException.php b/lib/Exceptions/GuestImportException.php new file mode 100644 index 00000000000..e5c780c05a6 --- /dev/null +++ b/lib/Exceptions/GuestImportException.php @@ -0,0 +1,91 @@ +|null $invalidLines + * @param non-negative-int|null $invites + * @param non-negative-int|null $duplicates + */ + public function __construct( + protected readonly string $reason, + protected readonly ?string $errorMessage = null, + protected readonly ?array $invalidLines = null, + protected readonly ?int $invites = null, + protected readonly ?int $duplicates = null, + ) { + parent::__construct(); + } + + /** + * @return self::REASON_* + */ + public function getReason(): string { + return $this->reason; + } + + public function getErrorMessage(): ?string { + return $this->errorMessage; + } + + /** + * @return non-negative-int|null + */ + public function getInvites(): ?int { + return $this->invites; + } + + /** + * @return non-negative-int|null + */ + public function getDuplicates(): ?int { + return $this->duplicates; + } + + /** + * @return non-negative-int|null + */ + public function getInvalid(): ?int { + return $this->invalidLines === null ? null : count($this->invalidLines); + } + + /** + * @return list|null + */ + public function getInvalidLines(): ?array { + return $this->invalidLines; + } + + public function getData(): array { + $data = ['error' => $this->errorMessage]; + if ($this->errorMessage !== null) { + $data['message'] = $this->errorMessage; + } + if ($this->invites !== null) { + $data['invites'] = $this->invites; + } + if ($this->duplicates !== null) { + $data['duplicates'] = $this->duplicates; + } + if ($this->invalidLines !== null) { + $data['invalid'] = count($this->invalidLines); + $data['invalidLines'] = $this->invalidLines; + } + + return $data; + } +} diff --git a/lib/GuestManager.php b/lib/GuestManager.php index 31f8ae6680a..c0d89951c45 100644 --- a/lib/GuestManager.php +++ b/lib/GuestManager.php @@ -13,9 +13,13 @@ use OCA\Talk\Events\BeforeParticipantModifiedEvent; use OCA\Talk\Events\EmailInvitationSentEvent; use OCA\Talk\Events\ParticipantModifiedEvent; +use OCA\Talk\Exceptions\GuestImportException; +use OCA\Talk\Exceptions\RoomProperty\TypeException; use OCA\Talk\Model\Attendee; +use OCA\Talk\Model\BreakoutRoom; use OCA\Talk\Service\ParticipantService; use OCA\Talk\Service\PollService; +use OCA\Talk\Service\RoomService; use OCP\Defaults; use OCP\EventDispatcher\IEventDispatcher; use OCP\IL10N; @@ -24,6 +28,7 @@ use OCP\IUserSession; use OCP\Mail\IMailer; use OCP\Util; +use Psr\Log\LoggerInterface; class GuestManager { public function __construct( @@ -33,9 +38,11 @@ public function __construct( protected IUserSession $userSession, protected ParticipantService $participantService, protected PollService $pollService, + protected RoomService $roomService, protected IURLGenerator $url, protected IL10N $l, protected IEventDispatcher $dispatcher, + protected LoggerInterface $logger, ) { } @@ -69,6 +76,124 @@ public function updateName(Room $room, Participant $participant, string $display } } + public function validateMailAddress(string $email): bool { + return $this->mailer->validateMailAddress($email); + } + + /** + * @return array{invites: non-negative-int, duplicates: non-negative-int, invalid?: non-negative-int, invalidLines?: list, type?: int<-1, 6>} + * @throws GuestImportException + */ + public function importEmails(Room $room, $file, bool $testRun): array { + if ($room->getType() === Room::TYPE_ONE_TO_ONE + || $room->getType() === Room::TYPE_ONE_TO_ONE_FORMER + || $room->getType() === Room::TYPE_NOTE_TO_SELF + || $room->getObjectType() === BreakoutRoom::PARENT_OBJECT_TYPE + || $room->getObjectType() === Room::OBJECT_TYPE_VIDEO_VERIFICATION) { + throw new GuestImportException(GuestImportException::REASON_ROOM); + } + + $content = fopen($file['tmp_name'], 'rb'); + $details = fgetcsv($content, escape: ''); + if (!isset($details[0]) || strtolower($details[0]) !== 'email') { + throw new GuestImportException( + GuestImportException::REASON_HEADER_EMAIL, + $this->l->t('Missing email field in header line'), + ); + } + if (isset($details[1]) && strtolower($details[1]) !== 'name') { + throw new GuestImportException( + GuestImportException::REASON_HEADER_NAME, + $this->l->t('Missing name field in header line'), + ); + } + + $participants = $this->participantService->getParticipantsByActorType($room, Attendee::ACTOR_EMAILS); + $alreadyInvitedEmails = array_flip(array_map(static fn (Participant $participant): string => $participant->getAttendee()->getInvitedCloudId(), $participants)); + + $line = $duplicates = 0; + $emailsToAdd = $invalidLines = []; + while ($details = fgetcsv($content, escape: '')) { + $line++; + if (isset($alreadyInvitedEmails[$details[0]])) { + $this->logger->debug('Skipping import of ' . $details[0] . ' (line: ' . $line . ') as they are already invited'); + $duplicates++; + continue; + } + + if (count($details) > 2) { + $this->logger->debug('Invalid entry with too many fields on line: ' . $line); + $invalidLines[] = $line; + continue; + } + + $email = strtolower(trim($details[0])); + if (count($details) === 2) { + $name = trim($details[1]); + } else { + $name = null; + } + + if (!$this->validateMailAddress($email)) { + $this->logger->debug('Invalid email "' . $email . '" on line: ' . $line); + $invalidLines[] = $line; + continue; + } + + if ($name !== null && strcasecmp($name, $email) === 0) { + $name = null; + } + + $actorId = hash('sha256', $email); + $alreadyInvitedEmails[$email] = $actorId; + $emailsToAdd[] = [ + 'email' => $email, + 'actorId' => $actorId, + 'name' => $name, + ]; + } + + if ($testRun) { + if (empty($invalidLines)) { + return [ + 'invites' => count($emailsToAdd), + 'duplicates' => $duplicates, + ]; + } + + throw new GuestImportException( + GuestImportException::REASON_ROWS, + $this->l->t('Following lines are invalid: %s', implode(', ', $invalidLines)), + $invalidLines, + count($emailsToAdd), + $duplicates, + ); + } + + $data = [ + 'invites' => count($emailsToAdd), + 'duplicates' => $duplicates, + ]; + + try { + $this->roomService->setType($room, Room::TYPE_PUBLIC); + $data['type'] = $room->getType(); + } catch (TypeException) { + } + + foreach ($emailsToAdd as $add) { + $participant = $this->participantService->inviteEmailAddress($room, $add['actorId'], $add['email'], $add['name']); + $this->sendEmailInvitation($room, $participant); + } + + if (!empty($invalidLines)) { + $data['invalidLines'] = $invalidLines; + $data['invalid'] = count($invalidLines); + } + + return $data; + } + public function sendEmailInvitation(Room $room, Participant $participant): void { if ($participant->getAttendee()->getActorType() !== Attendee::ACTOR_EMAILS) { throw new \InvalidArgumentException('Cannot send email for non-email participant actor type'); diff --git a/lib/Service/ParticipantService.php b/lib/Service/ParticipantService.php index 99ed50a1955..356c8c0151b 100644 --- a/lib/Service/ParticipantService.php +++ b/lib/Service/ParticipantService.php @@ -806,7 +806,7 @@ public function addCircle(Room $room, Circle $circle, array $existingParticipant $this->addUsers($room, $newParticipants, bansAlreadyChecked: true); } - public function inviteEmailAddress(Room $room, string $actorId, string $email): Participant { + public function inviteEmailAddress(Room $room, string $actorId, string $email, ?string $name = null): Participant { $lastMessage = 0; if ($room->getLastMessage() instanceof IComment) { $lastMessage = (int)$room->getLastMessage()->getId(); @@ -822,6 +822,10 @@ public function inviteEmailAddress(Room $room, string $actorId, string $email): ISecureRandom::CHAR_HUMAN_READABLE )); + if ($name !== null) { + $attendee->setDisplayName($name); + } + if ($room->getSIPEnabled() !== Webinary::SIP_DISABLED && $this->talkConfig->isSIPConfigured()) { $attendee->setPin($this->generatePin()); diff --git a/openapi-full.json b/openapi-full.json index 478347a0bc3..4509f6afe86 100644 --- a/openapi-full.json +++ b/openapi-full.json @@ -16551,6 +16551,218 @@ } } }, + "/ocs/v2.php/apps/spreed/api/{apiVersion}/room/{token}/import-emails": { + "post": { + "operationId": "room-import-emails-as-participants", + "summary": "Import a list of email attendees", + "description": "Content format is comma separated values: - Header line is required and must match `\"email\",\"name\"` or `\"email\"` - one entry per line\nRequired capability: `email-csv-import`", + "tags": [ + "room" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "testRun": { + "type": "boolean", + "default": false, + "description": "When set to true, the file is validated and no email is actually sent nor any participant added to the conversation" + } + } + } + } + } + }, + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v4" + ], + "default": "v4" + } + }, + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string", + "pattern": "^[a-z0-9]{4,30}$" + } + }, + { + "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": "All entries imported successfully", + "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": [ + "invites", + "duplicates" + ], + "properties": { + "invites": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "duplicates": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "invalid": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "invalidLines": { + "type": "array", + "items": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + }, + "type": { + "type": "integer", + "format": "int64", + "minimum": -1, + "maximum": 6 + } + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Import was not successful. When message is provided the string is in user language and should be displayed as an error.", + "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": [ + "error" + ], + "properties": { + "error": { + "type": "string", + "enum": [ + "room", + "file", + "header-email", + "header-name", + "rows" + ] + }, + "message": { + "type": "string" + }, + "invites": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "duplicates": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "invalid": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "invalidLines": { + "type": "array", + "items": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + }, + "type": { + "type": "integer", + "format": "int64", + "minimum": -1, + "maximum": 6 + } + } + } + } + } + } + } + } + } + } + } + } + }, "/ocs/v2.php/apps/spreed/api/{apiVersion}/settings/user": { "post": { "operationId": "settings-set-user-setting", diff --git a/openapi.json b/openapi.json index 01554cd38a4..efbc617aa59 100644 --- a/openapi.json +++ b/openapi.json @@ -16685,6 +16685,218 @@ } } }, + "/ocs/v2.php/apps/spreed/api/{apiVersion}/room/{token}/import-emails": { + "post": { + "operationId": "room-import-emails-as-participants", + "summary": "Import a list of email attendees", + "description": "Content format is comma separated values: - Header line is required and must match `\"email\",\"name\"` or `\"email\"` - one entry per line\nRequired capability: `email-csv-import`", + "tags": [ + "room" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "testRun": { + "type": "boolean", + "default": false, + "description": "When set to true, the file is validated and no email is actually sent nor any participant added to the conversation" + } + } + } + } + } + }, + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v4" + ], + "default": "v4" + } + }, + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string", + "pattern": "^[a-z0-9]{4,30}$" + } + }, + { + "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": "All entries imported successfully", + "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": [ + "invites", + "duplicates" + ], + "properties": { + "invites": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "duplicates": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "invalid": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "invalidLines": { + "type": "array", + "items": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + }, + "type": { + "type": "integer", + "format": "int64", + "minimum": -1, + "maximum": 6 + } + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Import was not successful. When message is provided the string is in user language and should be displayed as an error.", + "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": [ + "error" + ], + "properties": { + "error": { + "type": "string", + "enum": [ + "room", + "file", + "header-email", + "header-name", + "rows" + ] + }, + "message": { + "type": "string" + }, + "invites": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "duplicates": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "invalid": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "invalidLines": { + "type": "array", + "items": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + }, + "type": { + "type": "integer", + "format": "int64", + "minimum": -1, + "maximum": 6 + } + } + } + } + } + } + } + } + } + } + } + } + }, "/ocs/v2.php/apps/spreed/api/{apiVersion}/settings/user": { "post": { "operationId": "settings-set-user-setting", diff --git a/src/types/openapi/openapi-full.ts b/src/types/openapi/openapi-full.ts index b3a280c3d37..c32f80af0a8 100644 --- a/src/types/openapi/openapi-full.ts +++ b/src/types/openapi/openapi-full.ts @@ -1306,6 +1306,27 @@ export type paths = { patch?: never; trace?: never; }; + "/ocs/v2.php/apps/spreed/api/{apiVersion}/room/{token}/import-emails": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Import a list of email attendees + * @description Content format is comma separated values: - Header line is required and must match `"email","name"` or `"email"` - one entry per line + * Required capability: `email-csv-import` + */ + post: operations["room-import-emails-as-participants"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/ocs/v2.php/apps/spreed/api/{apiVersion}/settings/user": { parameters: { query?: never; @@ -8357,6 +8378,84 @@ export interface operations { }; }; }; + "room-import-emails-as-participants": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v4"; + token: string; + }; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": { + /** + * @description When set to true, the file is validated and no email is actually sent nor any participant added to the conversation + * @default false + */ + testRun?: boolean; + }; + }; + }; + responses: { + /** @description All entries imported successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** Format: int64 */ + invites: number; + /** Format: int64 */ + duplicates: number; + /** Format: int64 */ + invalid?: number; + invalidLines?: number[]; + /** Format: int64 */ + type?: number; + }; + }; + }; + }; + }; + /** @description Import was not successful. When message is provided the string is in user language and should be displayed as an error. */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** @enum {string} */ + error: "room" | "file" | "header-email" | "header-name" | "rows"; + message?: string; + /** Format: int64 */ + invites?: number; + /** Format: int64 */ + duplicates?: number; + /** Format: int64 */ + invalid?: number; + invalidLines?: number[]; + /** Format: int64 */ + type?: number; + }; + }; + }; + }; + }; + }; + }; "settings-set-user-setting": { parameters: { query?: never; diff --git a/src/types/openapi/openapi.ts b/src/types/openapi/openapi.ts index d43bc1046ff..5b8d795075c 100644 --- a/src/types/openapi/openapi.ts +++ b/src/types/openapi/openapi.ts @@ -1308,6 +1308,27 @@ export type paths = { patch?: never; trace?: never; }; + "/ocs/v2.php/apps/spreed/api/{apiVersion}/room/{token}/import-emails": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Import a list of email attendees + * @description Content format is comma separated values: - Header line is required and must match `"email","name"` or `"email"` - one entry per line + * Required capability: `email-csv-import` + */ + post: operations["room-import-emails-as-participants"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/ocs/v2.php/apps/spreed/api/{apiVersion}/settings/user": { parameters: { query?: never; @@ -7938,6 +7959,84 @@ export interface operations { }; }; }; + "room-import-emails-as-participants": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v4"; + token: string; + }; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": { + /** + * @description When set to true, the file is validated and no email is actually sent nor any participant added to the conversation + * @default false + */ + testRun?: boolean; + }; + }; + }; + responses: { + /** @description All entries imported successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** Format: int64 */ + invites: number; + /** Format: int64 */ + duplicates: number; + /** Format: int64 */ + invalid?: number; + invalidLines?: number[]; + /** Format: int64 */ + type?: number; + }; + }; + }; + }; + }; + /** @description Import was not successful. When message is provided the string is in user language and should be displayed as an error. */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** @enum {string} */ + error: "room" | "file" | "header-email" | "header-name" | "rows"; + message?: string; + /** Format: int64 */ + invites?: number; + /** Format: int64 */ + duplicates?: number; + /** Format: int64 */ + invalid?: number; + invalidLines?: number[]; + /** Format: int64 */ + type?: number; + }; + }; + }; + }; + }; + }; + }; "settings-set-user-setting": { parameters: { query?: never;