Browse Source

feat(invitations): Allow importing CSV email lists

Signed-off-by: Joas Schilling <coding@schilljs.com>
pull/13871/head
Joas Schilling 11 months ago
parent
commit
6017b86d4f
No known key found for this signature in database GPG Key ID: F72FA5B49FFA96B0
  1. 2
      appinfo/routes/routesRoomController.php
  2. 1
      docs/capabilities.md
  3. 1
      lib/Capabilities.php
  4. 47
      lib/Controller/RoomController.php
  5. 91
      lib/Exceptions/GuestImportException.php
  6. 125
      lib/GuestManager.php
  7. 6
      lib/Service/ParticipantService.php
  8. 212
      openapi-full.json
  9. 212
      openapi.json
  10. 99
      src/types/openapi/openapi-full.ts
  11. 99
      src/types/openapi/openapi.ts

2
appinfo/routes/routesRoomController.php

@ -115,5 +115,7 @@ return [
['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],
],
];

1
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.

1
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 = [

47
lib/Controller/RoomController.php

@ -14,6 +14,7 @@ use OCA\Talk\Events\AAttendeeRemovedEvent;
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\Federation\ICloudIdManager;
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 @@ class RoomController extends AEnvironmentAwareController {
protected Capabilities $capabilities,
protected FederationManager $federationManager,
protected BanService $banService,
protected IL10N $l,
) {
parent::__construct($appName, $request);
}
@ -1204,7 +1207,7 @@ class RoomController extends AEnvironmentAwareController {
} 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 @@ class RoomController extends AEnvironmentAwareController {
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<Http::STATUS_OK, array{invites: non-negative-int, duplicates: non-negative-int, invalid?: non-negative-int, invalidLines?: list<non-negative-int>, type?: int<-1, 6>}, array{}>|DataResponse<Http::STATUS_BAD_REQUEST, array{error: 'room'|'file'|'header-email'|'header-name'|'rows', message?: string, invites?: non-negative-int, duplicates?: non-negative-int, invalid?: non-negative-int, invalidLines?: list<non-negative-int>, 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
*

91
lib/Exceptions/GuestImportException.php

@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Exceptions;
class GuestImportException extends \Exception {
public const REASON_ROOM = 'room';
public const REASON_ROWS = 'rows';
public const REASON_HEADER_EMAIL = 'header-email';
public const REASON_HEADER_NAME = 'header-name';
/**
* @param self::REASON_* $reason
* @param list<non-negative-int>|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<non-negative-int>|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;
}
}

125
lib/GuestManager.php

@ -13,9 +13,13 @@ use OCA\Talk\Events\BeforeEmailInvitationSentEvent;
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\IUser;
use OCP\IUserSession;
use OCP\Mail\IMailer;
use OCP\Util;
use Psr\Log\LoggerInterface;
class GuestManager {
public function __construct(
@ -33,9 +38,11 @@ class GuestManager {
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 @@ class GuestManager {
}
}
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<non-negative-int>, 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');

6
lib/Service/ParticipantService.php

@ -806,7 +806,7 @@ class ParticipantService {
$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 @@ class ParticipantService {
ISecureRandom::CHAR_HUMAN_READABLE
));
if ($name !== null) {
$attendee->setDisplayName($name);
}
if ($room->getSIPEnabled() !== Webinary::SIP_DISABLED
&& $this->talkConfig->isSIPConfigured()) {
$attendee->setPin($this->generatePin());

212
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",

212
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",

99
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;

99
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;

Loading…
Cancel
Save