From d34456a69b71781bb40426e1cfbf83bc4cd01a42 Mon Sep 17 00:00:00 2001 From: Vojtech Kloda Date: Wed, 18 Jun 2025 10:14:02 +0200 Subject: [PATCH 01/19] added response format --- app/V1Module/presenters/GroupsPresenter.php | 3 + .../MetaFormats/Attributes/ResponseFormat.php | 19 +++ .../FormatDefinitions/GroupFormat.php | 111 ++++++++++++++++++ app/helpers/MetaFormats/MetaFormatHelper.php | 19 +++ app/helpers/Swagger/AnnotationData.php | 2 + app/helpers/Swagger/AnnotationHelper.php | 19 ++- app/model/view/GroupViewFactory.php | 23 ++++ 7 files changed, 193 insertions(+), 3 deletions(-) create mode 100644 app/helpers/MetaFormats/Attributes/ResponseFormat.php create mode 100644 app/helpers/MetaFormats/FormatDefinitions/GroupFormat.php diff --git a/app/V1Module/presenters/GroupsPresenter.php b/app/V1Module/presenters/GroupsPresenter.php index a00a16f85..f3afe634f 100644 --- a/app/V1Module/presenters/GroupsPresenter.php +++ b/app/V1Module/presenters/GroupsPresenter.php @@ -5,6 +5,8 @@ use App\Helpers\MetaFormats\Attributes\Post; use App\Helpers\MetaFormats\Attributes\Query; use App\Helpers\MetaFormats\Attributes\Path; +use App\Helpers\MetaFormats\Attributes\ResponseFormat; +use App\Helpers\MetaFormats\FormatDefinitions\GroupFormat; use App\Helpers\MetaFormats\Validators\VArray; use App\Helpers\MetaFormats\Validators\VBool; use App\Helpers\MetaFormats\Validators\VInt; @@ -404,6 +406,7 @@ public function checkUpdateGroup(string $id) #[Post("pointsLimit", new VInt(), "A minimum of (absolute) points needed to pass the course", required: false)] #[Post("localizedTexts", new VArray(), "Localized names and descriptions")] #[Path("id", new VUuid(), "An identifier of the updated group", required: true)] + #[ResponseFormat(GroupFormat::class)] public function actionUpdateGroup(string $id) { $req = $this->getRequest(); diff --git a/app/helpers/MetaFormats/Attributes/ResponseFormat.php b/app/helpers/MetaFormats/Attributes/ResponseFormat.php new file mode 100644 index 000000000..47b73efe6 --- /dev/null +++ b/app/helpers/MetaFormats/Attributes/ResponseFormat.php @@ -0,0 +1,19 @@ +class = $class; + } +} diff --git a/app/helpers/MetaFormats/FormatDefinitions/GroupFormat.php b/app/helpers/MetaFormats/FormatDefinitions/GroupFormat.php new file mode 100644 index 000000000..ff2fe9293 --- /dev/null +++ b/app/helpers/MetaFormats/FormatDefinitions/GroupFormat.php @@ -0,0 +1,111 @@ +$offset); + // } + + // /** + // * Offset to retrieve + // * @param mixed $offset The offset to retrieve. + // * @return mixed Can return all value types. + // */ + // public function offsetGet(mixed $offset): mixed + // { + // return $this->$offset ?? null; + // } + + // /** + // * Offset to set + // * @param mixed $offset The offset to assign the value to. + // * @param mixed $value The value to set. + // */ + // public function offsetSet(mixed $offset, mixed $value): void + // { + // $this->$offset = $value; + // } + + // /** + // * Offset to unset + // * @param mixed $offset The offset to unset. + // */ + // public function offsetUnset(mixed $offset): void + // { + // $this->$offset = null; + // } +} diff --git a/app/helpers/MetaFormats/MetaFormatHelper.php b/app/helpers/MetaFormats/MetaFormatHelper.php index e25363eb0..d1d1d3794 100644 --- a/app/helpers/MetaFormats/MetaFormatHelper.php +++ b/app/helpers/MetaFormats/MetaFormatHelper.php @@ -14,6 +14,7 @@ use App\Helpers\MetaFormats\Attributes\Path; use App\Helpers\MetaFormats\Attributes\Post; use App\Helpers\MetaFormats\Attributes\Query; +use App\Helpers\MetaFormats\Attributes\ResponseFormat; use ReflectionClass; use App\Helpers\Swagger\AnnotationHelper; use ReflectionMethod; @@ -42,6 +43,24 @@ public static function extractFormatFromAttribute( return $formatAttribute->class; } + /** + * Checks whether an entity contains a ResponseFormat attribute and extracts the format if so. + * @param \ReflectionClass|\ReflectionProperty|\ReflectionMethod $reflectionObject A reflection + * object of the entity. + * @return ?string Returns the format or null if no ResponseFormat attribute was present. + */ + public static function extractResponseFormatFromAttribute( + ReflectionClass | ReflectionProperty | ReflectionMethod $reflectionObject + ): ?string { + $formatAttributes = $reflectionObject->getAttributes(ResponseFormat::class); + if (count($formatAttributes) === 0) { + return null; + } + + $formatAttribute = $formatAttributes[0]->newInstance(); + return $formatAttribute->class; + } + /** * Extracts all endpoint parameter attributes. * @param \ReflectionMethod $reflectionMethod The endpoint reflection method. diff --git a/app/helpers/Swagger/AnnotationData.php b/app/helpers/Swagger/AnnotationData.php index b0d0a2ce5..d376f4741 100644 --- a/app/helpers/Swagger/AnnotationData.php +++ b/app/helpers/Swagger/AnnotationData.php @@ -42,6 +42,7 @@ public function __construct( array $bodyParams, array $fileParams, ?string $endpointDescription = null, + ?array $responseParams = null, ) { $this->className = $className; $this->methodName = $methodName; @@ -51,6 +52,7 @@ public function __construct( $this->bodyParams = $bodyParams; $this->fileParams = $fileParams; $this->endpointDescription = $endpointDescription; + $this->responseParams = $responseParams; } public function getAllParams(): array diff --git a/app/helpers/Swagger/AnnotationHelper.php b/app/helpers/Swagger/AnnotationHelper.php index 6eddf5f7f..d2570670c 100644 --- a/app/helpers/Swagger/AnnotationHelper.php +++ b/app/helpers/Swagger/AnnotationHelper.php @@ -289,7 +289,8 @@ private static function annotationParameterDataToAnnotationData( string $methodName, HttpMethods $httpMethod, array $params, - ?string $description + ?string $description, + ?array $responseParams = null, ): AnnotationData { $pathParams = []; $queryParams = []; @@ -318,7 +319,8 @@ private static function annotationParameterDataToAnnotationData( $queryParams, $bodyParams, $fileParams, - $description + $description, + $responseParams, ); } @@ -377,6 +379,16 @@ public static function extractAttributeData(string $className, string $methodNam $attributeData = array_merge($attributeData, FormatCache::getFieldDefinitions($format)); } + // if the endpoint uses a response format, extract its parameters + $responseFormat = MetaFormatHelper::extractResponseFormatFromAttribute($reflectionMethod); + $responseParams = null; + if ($responseFormat !== null) { + $responseFieldDefinitions = FormatCache::getFieldDefinitions($responseFormat); + $responseParams = array_map(function ($data) { + return $data->toAnnotationParameterData(); + }, $responseFieldDefinitions); + } + $params = array_map(function ($data) { return $data->toAnnotationParameterData(); }, $attributeData); @@ -387,7 +399,8 @@ public static function extractAttributeData(string $className, string $methodNam $methodName, $httpMethod, $params, - $description + $description, + $responseParams, ); } diff --git a/app/model/view/GroupViewFactory.php b/app/model/view/GroupViewFactory.php index 5f01a99df..613d28034 100644 --- a/app/model/view/GroupViewFactory.php +++ b/app/model/view/GroupViewFactory.php @@ -4,6 +4,7 @@ use App\Helpers\EvaluationStatus\EvaluationStatus; use App\Helpers\GroupBindings\GroupBindingAccessor; +use App\Helpers\MetaFormats\FormatDefinitions\GroupFormat; use App\Helpers\PermissionHints; use App\Model\Entity\Assignment; use App\Model\Entity\AssignmentSolution; @@ -271,6 +272,28 @@ function (Group $group) use ($ignoreArchived) { } ); + // $groupFormat = new GroupFormat(); + // $groupFormat->id = $group->getId(); + // $groupFormat->externalId = $group->getExternalId(); + // $groupFormat->organizational = $group->isOrganizational(); + // $groupFormat->exam = $group->isExam(); + // $groupFormat->archived = $group->isArchived(); + // $groupFormat->public = $group->isPublic(); + // $groupFormat->directlyArchived = $group->isDirectlyArchived(); + // $groupFormat->localizedTexts = $group->getLocalizedTexts()->getValues(); + // $groupFormat->primaryAdminsIds = $group->getPrimaryAdminsIds(); + // $groupFormat->parentGroupId = $group->getParentGroup() ? $group->getParentGroup()->getId() : null; + // $groupFormat->parentGroupsIds = $group->getParentGroupsIds(); + // $groupFormat->childGroups = $childGroups->map( + // function (Group $group) { + // return $group->getId(); + // } + // )->getValues(); + // $groupFormat->privateData = $privateData; + // $groupFormat->permissionHints = PermissionHints::get($this->groupAcl, $group); + + // return $groupFormat; + return [ "id" => $group->getId(), "externalId" => $group->getExternalId(), From c351c5233f1a96f612b2d6effb75b0c0402f349f Mon Sep 17 00:00:00 2001 From: Vojtech Kloda Date: Wed, 18 Jun 2025 15:00:28 +0200 Subject: [PATCH 02/19] response formats now propagate to the swagger --- .../MetaFormats/Attributes/ResponseFormat.php | 10 ++++--- app/helpers/MetaFormats/MetaFormatHelper.php | 14 +++++----- app/helpers/Swagger/AnnotationData.php | 26 ++++++++++++++---- app/helpers/Swagger/AnnotationHelper.php | 27 +++++++++++++------ 4 files changed, 53 insertions(+), 24 deletions(-) diff --git a/app/helpers/MetaFormats/Attributes/ResponseFormat.php b/app/helpers/MetaFormats/Attributes/ResponseFormat.php index 47b73efe6..6d7c4484c 100644 --- a/app/helpers/MetaFormats/Attributes/ResponseFormat.php +++ b/app/helpers/MetaFormats/Attributes/ResponseFormat.php @@ -7,13 +7,15 @@ /** * Attribute defining response format on endpoints. */ -#[Attribute] +#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_METHOD)] class ResponseFormat { - public string $class; + public readonly string $format; + public readonly int $statusCode; - public function __construct(string $class) + public function __construct(string $format, int $statusCode = 200) { - $this->class = $class; + $this->format = $format; + $this->statusCode = $statusCode; } } diff --git a/app/helpers/MetaFormats/MetaFormatHelper.php b/app/helpers/MetaFormats/MetaFormatHelper.php index d1d1d3794..1b95462a1 100644 --- a/app/helpers/MetaFormats/MetaFormatHelper.php +++ b/app/helpers/MetaFormats/MetaFormatHelper.php @@ -44,21 +44,21 @@ public static function extractFormatFromAttribute( } /** - * Checks whether an entity contains a ResponseFormat attribute and extracts the format if so. + * Checks whether an entity contains ResponseFormat attributes and returns them. * @param \ReflectionClass|\ReflectionProperty|\ReflectionMethod $reflectionObject A reflection * object of the entity. - * @return ?string Returns the format or null if no ResponseFormat attribute was present. + * @return array Returns an array of attribute instances. */ public static function extractResponseFormatFromAttribute( ReflectionClass | ReflectionProperty | ReflectionMethod $reflectionObject - ): ?string { + ): array { $formatAttributes = $reflectionObject->getAttributes(ResponseFormat::class); - if (count($formatAttributes) === 0) { - return null; + $instances = []; + foreach ($formatAttributes as $formatAttribute) { + $instances[] = $formatAttribute->newInstance(); } - $formatAttribute = $formatAttributes[0]->newInstance(); - return $formatAttribute->class; + return $instances; } /** diff --git a/app/helpers/Swagger/AnnotationData.php b/app/helpers/Swagger/AnnotationData.php index d376f4741..e6c3b3f12 100644 --- a/app/helpers/Swagger/AnnotationData.php +++ b/app/helpers/Swagger/AnnotationData.php @@ -31,6 +31,10 @@ class AnnotationData * @var AnnotationParameterData[] */ public array $fileParams; + /** + * @var array> + */ + public array $statusToParamsMap; public ?string $endpointDescription; public function __construct( @@ -41,8 +45,8 @@ public function __construct( array $queryParams, array $bodyParams, array $fileParams, + array $statusToParamsMap, ?string $endpointDescription = null, - ?array $responseParams = null, ) { $this->className = $className; $this->methodName = $methodName; @@ -51,8 +55,8 @@ public function __construct( $this->queryParams = $queryParams; $this->bodyParams = $bodyParams; $this->fileParams = $fileParams; + $this->statusToParamsMap = $statusToParamsMap; $this->endpointDescription = $endpointDescription; - $this->responseParams = $responseParams; } public function getAllParams(): array @@ -231,9 +235,21 @@ public function toSwaggerAnnotations(string $route) $body->addValue($bodyProperties); } - ///TODO: A placeholder for the response type. This has to be replaced with the autogenerated meta-view - /// response data structure in the future. - $body->addValue('@OA\Response(response="200",description="The data")'); + // generate responses + foreach ($this->statusToParamsMap as $statusCode => $responseParams) { + $responseSchema = $this->serializeBodyParams( + "application/json", + $responseParams + ); + + $body->addValue('@OA\Response(response="' . $statusCode . '", ' . $responseSchema . ' )'); + } + + // add a placeholder response if none present + if (count($this->statusToParamsMap) === 0) { + $body->addValue('@OA\Response(response="200",description="Placeholder response")'); + } + return $httpMethodAnnotation . $body->toString(); } } diff --git a/app/helpers/Swagger/AnnotationHelper.php b/app/helpers/Swagger/AnnotationHelper.php index d2570670c..7710621f7 100644 --- a/app/helpers/Swagger/AnnotationHelper.php +++ b/app/helpers/Swagger/AnnotationHelper.php @@ -2,6 +2,7 @@ namespace App\Helpers\Swagger; +use App\Exceptions\InternalServerException; use App\Helpers\MetaFormats\FormatCache; use App\Helpers\MetaFormats\MetaFormatHelper; use App\V1Module\Router\MethodRoute; @@ -289,8 +290,8 @@ private static function annotationParameterDataToAnnotationData( string $methodName, HttpMethods $httpMethod, array $params, + array $statusToParamsMap, ?string $description, - ?array $responseParams = null, ): AnnotationData { $pathParams = []; $queryParams = []; @@ -319,8 +320,8 @@ private static function annotationParameterDataToAnnotationData( $queryParams, $bodyParams, $fileParams, + $statusToParamsMap, $description, - $responseParams, ); } @@ -350,6 +351,7 @@ public static function extractStandardAnnotationData( $methodName, $httpMethod, $params, + [], // there are no reponse params defined in the old annotations $description ); } @@ -379,14 +381,23 @@ public static function extractAttributeData(string $className, string $methodNam $attributeData = array_merge($attributeData, FormatCache::getFieldDefinitions($format)); } - // if the endpoint uses a response format, extract its parameters - $responseFormat = MetaFormatHelper::extractResponseFormatFromAttribute($reflectionMethod); - $responseParams = null; - if ($responseFormat !== null) { - $responseFieldDefinitions = FormatCache::getFieldDefinitions($responseFormat); + // if the endpoint uses response formats, extract their parameters + $responseAttributes = MetaFormatHelper::extractResponseFormatFromAttribute($reflectionMethod); + $statusToParamsMap = []; + foreach ($responseAttributes as $responseAttribute) { + $responseFieldDefinitions = FormatCache::getFieldDefinitions($responseAttribute->format); $responseParams = array_map(function ($data) { return $data->toAnnotationParameterData(); }, $responseFieldDefinitions); + + // check if all response status codes are unique + if (array_key_exists($responseAttribute->statusCode, $statusToParamsMap)) { + throw new InternalServerException( + "The method " . $reflectionMethod->name . " contains duplicate response codes." + ); + } + + $statusToParamsMap[$responseAttribute->statusCode] = $responseParams; } $params = array_map(function ($data) { @@ -399,8 +410,8 @@ public static function extractAttributeData(string $className, string $methodNam $methodName, $httpMethod, $params, + $statusToParamsMap, $description, - $responseParams, ); } From daac8c0d75fd80fa486b44b6c794b23f5cec53fe Mon Sep 17 00:00:00 2001 From: Vojtech Kloda Date: Thu, 19 Jun 2025 12:54:47 +0200 Subject: [PATCH 03/19] reponse data can now be wrapped in the success response wrapper --- .../MetaFormats/Attributes/ResponseFormat.php | 19 +- .../SuccessResponseFormat.php | 23 + app/helpers/Swagger/AnnotationData.php | 21 +- app/helpers/Swagger/AnnotationHelper.php | 21 +- app/helpers/Swagger/ResponseData.php | 60 ++ docs/swagger.yaml | 524 +++++++++--------- 6 files changed, 396 insertions(+), 272 deletions(-) create mode 100644 app/helpers/MetaFormats/FormatDefinitions/SuccessResponseFormat.php create mode 100644 app/helpers/Swagger/ResponseData.php diff --git a/app/helpers/MetaFormats/Attributes/ResponseFormat.php b/app/helpers/MetaFormats/Attributes/ResponseFormat.php index 6d7c4484c..0b7676cfd 100644 --- a/app/helpers/MetaFormats/Attributes/ResponseFormat.php +++ b/app/helpers/MetaFormats/Attributes/ResponseFormat.php @@ -11,11 +11,26 @@ class ResponseFormat { public readonly string $format; + public readonly string $description; public readonly int $statusCode; + public readonly bool $useSuccessWrapper; - public function __construct(string $format, int $statusCode = 200) - { + /** + * @param string $format The Format class that will be used for the response schema. + * @param string $description The description of the response. + * @param int $statusCode The status code of the response. + * @param bool $useSuccessWrapper Whether the reponse will be wrapped with + * the "BasePresenter::sendSuccessResponse" method. + */ + public function __construct( + string $format, + string $description = "Response data", + int $statusCode = 200, + bool $useSuccessWrapper = true + ) { $this->format = $format; + $this->description = $description; $this->statusCode = $statusCode; + $this->useSuccessWrapper = $useSuccessWrapper; } } diff --git a/app/helpers/MetaFormats/FormatDefinitions/SuccessResponseFormat.php b/app/helpers/MetaFormats/FormatDefinitions/SuccessResponseFormat.php new file mode 100644 index 000000000..0ef39a907 --- /dev/null +++ b/app/helpers/MetaFormats/FormatDefinitions/SuccessResponseFormat.php @@ -0,0 +1,23 @@ +> + * @var ResponseData[] */ - public array $statusToParamsMap; + public array $responseDataList; public ?string $endpointDescription; public function __construct( @@ -45,7 +45,7 @@ public function __construct( array $queryParams, array $bodyParams, array $fileParams, - array $statusToParamsMap, + array $responseDataList, ?string $endpointDescription = null, ) { $this->className = $className; @@ -55,7 +55,7 @@ public function __construct( $this->queryParams = $queryParams; $this->bodyParams = $bodyParams; $this->fileParams = $fileParams; - $this->statusToParamsMap = $statusToParamsMap; + $this->responseDataList = $responseDataList; $this->endpointDescription = $endpointDescription; } @@ -236,17 +236,22 @@ public function toSwaggerAnnotations(string $route) } // generate responses - foreach ($this->statusToParamsMap as $statusCode => $responseParams) { + foreach ($this->responseDataList as $responseData) { $responseSchema = $this->serializeBodyParams( "application/json", - $responseParams + $responseData->responseParams, ); - $body->addValue('@OA\Response(response="' . $statusCode . '", ' . $responseSchema . ' )'); + $responseBody = new ParenthesesBuilder(); + $responseBody->addKeyValue("response", $responseData->statusCode); + $responseBody->addKeyValue("description", $responseData->description); + $responseBody->addValue($responseSchema); + + $body->addValue("@OA\Response" . $responseBody->toString()); } // add a placeholder response if none present - if (count($this->statusToParamsMap) === 0) { + if (count($this->responseDataList) === 0) { $body->addValue('@OA\Response(response="200",description="Placeholder response")'); } diff --git a/app/helpers/Swagger/AnnotationHelper.php b/app/helpers/Swagger/AnnotationHelper.php index 7710621f7..7f9d6cc46 100644 --- a/app/helpers/Swagger/AnnotationHelper.php +++ b/app/helpers/Swagger/AnnotationHelper.php @@ -290,7 +290,7 @@ private static function annotationParameterDataToAnnotationData( string $methodName, HttpMethods $httpMethod, array $params, - array $statusToParamsMap, + array $responseDataList, ?string $description, ): AnnotationData { $pathParams = []; @@ -320,7 +320,7 @@ private static function annotationParameterDataToAnnotationData( $queryParams, $bodyParams, $fileParams, - $statusToParamsMap, + $responseDataList, $description, ); } @@ -383,7 +383,8 @@ public static function extractAttributeData(string $className, string $methodNam // if the endpoint uses response formats, extract their parameters $responseAttributes = MetaFormatHelper::extractResponseFormatFromAttribute($reflectionMethod); - $statusToParamsMap = []; + $responseDataList = []; + $statusCodes = []; foreach ($responseAttributes as $responseAttribute) { $responseFieldDefinitions = FormatCache::getFieldDefinitions($responseAttribute->format); $responseParams = array_map(function ($data) { @@ -391,13 +392,21 @@ public static function extractAttributeData(string $className, string $methodNam }, $responseFieldDefinitions); // check if all response status codes are unique - if (array_key_exists($responseAttribute->statusCode, $statusToParamsMap)) { + if (array_key_exists($responseAttribute->statusCode, $statusCodes)) { throw new InternalServerException( "The method " . $reflectionMethod->name . " contains duplicate response codes." ); } + $statusCodes[] = $responseAttribute->statusCode; - $statusToParamsMap[$responseAttribute->statusCode] = $responseParams; + $responseData = new ResponseData( + $responseParams, + $responseAttribute->description, + $responseAttribute->statusCode, + $responseAttribute->useSuccessWrapper + ); + + $responseDataList[] = $responseData; } $params = array_map(function ($data) { @@ -410,7 +419,7 @@ public static function extractAttributeData(string $className, string $methodNam $methodName, $httpMethod, $params, - $statusToParamsMap, + $responseDataList, $description, ); } diff --git a/app/helpers/Swagger/ResponseData.php b/app/helpers/Swagger/ResponseData.php new file mode 100644 index 000000000..6f6b0e94b --- /dev/null +++ b/app/helpers/Swagger/ResponseData.php @@ -0,0 +1,60 @@ +responseParams = $responseParams; + $this->description = $description; + $this->statusCode = $statusCode; + $this->useSuccessWrapper = $useSuccessWrapper; + + // wrap the response parameters in the wrapper defined in "BasePresenter::sendSuccessResponse" + if ($this->useSuccessWrapper) { + $this->wrapResponse(); + } + } + + private function wrapResponse() + { + // get wrapper params + $wrapperFieldDefinitions = FormatCache::getFieldDefinitions(SuccessResponseFormat::class); + /** @var AnnotationParameterData[] */ + $wrapperParams = array_map(function ($data) { + return $data->toAnnotationParameterData(); + }, $wrapperFieldDefinitions); + + // find payload param + $payloadParam = null; + foreach ($wrapperParams as $param) { + if ($param->name === "payload") { + $payloadParam = $param; + break; + } + } + if ($payloadParam === null) { + throw new InternalServerException("The SuccessResponseFormat is corrupted (no 'payload' field)."); + } + + // wrap responseParams + $payloadParam->nestedObjectParameterData = $this->responseParams; + $payloadParam->swaggerType = VObject::SWAGGER_TYPE; + $this->responseParams = $wrapperParams; + } +} diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 1f3fe7a2b..ee14dc080 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -25,7 +25,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/login: post: summary: 'Log in using user credentials' @@ -54,7 +54,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/login/refresh: post: summary: 'Refresh the access token of current user' @@ -62,7 +62,7 @@ paths: operationId: loginPresenterActionRefresh responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/login/issue-restricted-token: post: summary: 'Issue a new access token with a restricted set of scopes' @@ -93,7 +93,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/login/takeover/{userId}': post: summary: 'Takeover user account with specified user identification.' @@ -110,7 +110,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/login/{authenticatorName}': post: summary: 'Log in using an external authentication service' @@ -141,7 +141,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/broker/stats: get: summary: 'Get current statistics from broker.' @@ -149,7 +149,7 @@ paths: operationId: brokerPresenterActionStats responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/broker/freeze: post: summary: 'Freeze broker and its execution.' @@ -157,7 +157,7 @@ paths: operationId: brokerPresenterActionFreeze responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/broker/unfreeze: post: summary: 'Unfreeze broker and its execution.' @@ -165,7 +165,7 @@ paths: operationId: brokerPresenterActionUnfreeze responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/broker-reports/error: post: summary: 'Announce a backend error that is not related to any job (meant to be called by the backend)' @@ -185,7 +185,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/broker-reports/job-status/{jobId}': post: summary: 'Update the status of a job (meant to be called by the backend)' @@ -218,7 +218,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/comments/{id}': get: summary: 'Get a comment thread' @@ -236,7 +236,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Add a comment to a thread' description: 'Add a comment to a thread' @@ -273,7 +273,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/comments/{threadId}/comment/{commentId}/toggle': post: summary: 'Make a private comment public or vice versa' @@ -298,7 +298,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/comments/{threadId}/comment/{commentId}/private': post: summary: 'Set the private flag of a comment' @@ -336,7 +336,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/comments/{threadId}/comment/{commentId}': delete: summary: 'Delete a comment' @@ -361,7 +361,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/exercises: get: summary: 'Get a list of all exercises matching given filters in given pagination rage. The result conforms to pagination protocol.' @@ -411,7 +411,7 @@ paths: nullable: true responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Create exercise with all default values. Exercise detail can be then changed in appropriate endpoint.' description: 'Create exercise with all default values. Exercise detail can be then changed in appropriate endpoint.' @@ -430,7 +430,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/exercises/list: post: summary: 'Get a list of exercises based on given ids.' @@ -451,7 +451,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/exercises/authors: get: summary: 'List authors of all exercises, possibly filtered by a group in which the exercises appear.' @@ -476,7 +476,7 @@ paths: nullable: true responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/exercises/tags: get: summary: 'Get list of global exercise tag names which are currently registered.' @@ -484,7 +484,7 @@ paths: operationId: exercisesPresenterActionAllTags responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/exercises/tags-stats: get: summary: 'Get list of global exercise tag names along with how many times they have been used.' @@ -492,7 +492,7 @@ paths: operationId: exercisesPresenterActionTagsStats responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercises/tags/{tag}': post: summary: "Update the tag globally. At the moment, the only 'update' function is 'rename'. Other types of updates may be implemented in the future." @@ -527,7 +527,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercises/{id}': get: summary: 'Get details of an exercise' @@ -545,7 +545,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Update detail of an exercise' description: 'Update detail of an exercise' @@ -619,7 +619,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' delete: summary: 'Delete an exercise' description: 'Delete an exercise' @@ -636,7 +636,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercises/{id}/validate': post: summary: 'Check if the version of the exercise is up-to-date.' @@ -667,7 +667,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercises/{id}/fork': post: summary: 'Fork exercise from given one into the completely new one.' @@ -697,7 +697,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercises/{id}/assignments': get: summary: 'Get all non-archived assignments created from this exercise.' @@ -723,7 +723,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercises/{id}/hardware-groups': post: summary: 'Set hardware groups which are associated with exercise.' @@ -754,7 +754,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercises/{id}/groups/{groupId}': post: summary: 'Attach exercise to group with given identification.' @@ -780,7 +780,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' delete: summary: 'Detach exercise from given group.' description: 'Detach exercise from given group.' @@ -805,7 +805,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercises/{id}/tags/{name}': post: summary: 'Add tag with given name to the exercise.' @@ -833,7 +833,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' delete: summary: 'Remove tag with given name from exercise.' description: 'Remove tag with given name from exercise.' @@ -858,7 +858,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercises/{id}/archived': post: summary: '(Un)mark the exercise as archived. Nothing happens if the exercise is already in the requested state.' @@ -889,7 +889,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercises/{id}/author': post: summary: 'Change the author of the exercise. This is a very special operation reserved for powerful users.' @@ -921,7 +921,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercises/{id}/admins': post: summary: 'Change who the admins of an exercise are (all users on the list are added, prior admins not on the list are removed).' @@ -952,7 +952,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercises/{id}/notification': post: summary: 'Sends an email to all group admins and supervisors, where the exercise is assigned. The purpose of this is to quickly notify all relevant teachers when a bug is found or the exercise is modified significantly. The response is number of emails sent (number of notified users).' @@ -983,7 +983,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercises/{id}/supplementary-files': get: summary: 'Get list of all supplementary files for an exercise' @@ -1001,7 +1001,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Associate supplementary files with an exercise and upload them to remote file server' description: 'Associate supplementary files with an exercise and upload them to remote file server' @@ -1030,7 +1030,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercises/{id}/supplementary-files/{fileId}': delete: summary: 'Delete supplementary exercise file with given id' @@ -1056,7 +1056,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercises/{id}/supplementary-files/download-archive': get: summary: 'Download archive containing all supplementary files for exercise.' @@ -1074,7 +1074,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercises/{id}/attachment-files': get: summary: 'Get a list of all attachment files for an exercise' @@ -1092,7 +1092,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Associate attachment exercise files with an exercise' description: 'Associate attachment exercise files with an exercise' @@ -1121,7 +1121,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercises/{id}/attachment-files/{fileId}': delete: summary: 'Delete attachment exercise file with given id' @@ -1147,7 +1147,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercises/{id}/attachment-files/download-archive': get: summary: 'Download archive containing all attachment files for exercise.' @@ -1165,7 +1165,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercises/{id}/tests': get: summary: 'Get tests for exercise based on given identification.' @@ -1183,7 +1183,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Set tests for exercise based on given identification.' description: 'Set tests for exercise based on given identification.' @@ -1213,7 +1213,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercises/{id}/environment-configs': get: summary: 'Get runtime configurations for exercise.' @@ -1231,7 +1231,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Change runtime configuration of exercise. Configurations can be added or deleted here, based on what is provided in arguments.' description: 'Change runtime configuration of exercise. Configurations can be added or deleted here, based on what is provided in arguments.' @@ -1261,7 +1261,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercises/{id}/config': get: summary: 'Get a basic exercise high level configuration.' @@ -1279,7 +1279,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Set basic exercise configuration' description: 'Set basic exercise configuration' @@ -1309,7 +1309,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercises/{id}/config/variables': post: summary: 'Get variables for exercise configuration which are derived from given pipelines and runtime environment.' @@ -1346,7 +1346,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercises/{id}/environment/{runtimeEnvironmentId}/hwgroup/{hwGroupId}/limits': get: summary: 'Get a description of resource limits for an exercise for given hwgroup.' @@ -1380,7 +1380,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Set resource limits for an exercise for given hwgroup.' description: 'Set resource limits for an exercise for given hwgroup.' @@ -1426,7 +1426,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' delete: summary: 'Remove resource limits of given hwgroup from an exercise.' description: 'Remove resource limits of given hwgroup from an exercise.' @@ -1459,7 +1459,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercises/{id}/limits': get: summary: 'Get a description of resource limits for given exercise (all hwgroups all environments).' @@ -1477,7 +1477,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Update resource limits for given exercise. If limits for particular hwGroup or environment are not posted, no change occurs. If limits for particular hwGroup or environment are posted as null, they are removed.' description: 'Update resource limits for given exercise. If limits for particular hwGroup or environment are not posted, no change occurs. If limits for particular hwGroup or environment are posted as null, they are removed.' @@ -1507,7 +1507,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercises/{id}/score-config': get: summary: 'Get score configuration for exercise based on given identification.' @@ -1525,7 +1525,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Set score configuration for exercise.' description: 'Set score configuration for exercise.' @@ -1560,7 +1560,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/exercise-assignments: post: summary: 'Assign an exercise to a group' @@ -1585,7 +1585,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercise-assignments/{id}': get: summary: 'Get details of an assignment' @@ -1603,7 +1603,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Update details of an assignment' description: 'Update details of an assignment' @@ -1752,7 +1752,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' delete: summary: 'Delete an assignment' description: 'Delete an assignment' @@ -1769,7 +1769,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercise-assignments/{id}/solutions': get: summary: 'Get a list of solutions of all users for the assignment' @@ -1787,7 +1787,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercise-assignments/{id}/best-solutions': get: summary: 'Get the best solutions to an assignment for all students in group.' @@ -1805,7 +1805,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercise-assignments/{id}/download-best-solutions': get: summary: 'Download the best solutions of an assignment for all students in group.' @@ -1823,7 +1823,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercise-assignments/{id}/users/{userId}/solutions': get: summary: 'Get a list of solutions created by a user of an assignment' @@ -1849,7 +1849,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercise-assignments/{id}/users/{userId}/best-solution': get: summary: 'Get the best solution by a user to an assignment' @@ -1875,7 +1875,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercise-assignments/{id}/validate': post: summary: 'Check if the version of the assignment is up-to-date.' @@ -1906,7 +1906,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercise-assignments/{id}/sync-exercise': post: summary: 'Update the assignment so that it matches with the current version of the exercise (limits, texts, etc.)' @@ -1924,7 +1924,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercise-assignments/{id}/can-submit': get: summary: 'Check if the given user can submit solutions to the assignment' @@ -1950,7 +1950,7 @@ paths: nullable: true responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercise-assignments/{id}/submit': post: summary: 'Submit a solution of an assignment' @@ -2000,7 +2000,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercise-assignments/{id}/resubmit-all': get: summary: 'Return a list of all pending resubmit async jobs associated with given assignment. Under normal circumstances, the list should be either empty, or contain only one job.' @@ -2018,7 +2018,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Start async job that resubmits all submissions of an assignment. No job is started when there are pending resubmit jobs for the selected assignment. Returns list of pending async jobs (same as GET call)' description: 'Start async job that resubmits all submissions of an assignment. No job is started when there are pending resubmit jobs for the selected assignment. Returns list of pending async jobs (same as GET call)' @@ -2035,7 +2035,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercise-assignments/{id}/pre-submit': post: summary: 'Pre submit action which will, based on given files, detect possible runtime environments for the assignment. Also it can be further used for entry points and other important things that should be provided by user during submit.' @@ -2074,7 +2074,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/exercise-assignments/{id}/async-jobs': get: summary: 'Get all pending async jobs related to a particular assignment.' @@ -2092,7 +2092,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/groups: get: summary: 'Get a list of all non-archived groups a user can see. The return set is filtered by parameters.' @@ -2141,7 +2141,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Create a new group' description: 'Create a new group' @@ -2217,7 +2217,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/groups/validate-add-group-data: post: summary: 'Validate group creation data' @@ -2251,7 +2251,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/groups/{id}': get: summary: 'Get details of a group' @@ -2269,7 +2269,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Update group info' description: 'Update group info' @@ -2330,7 +2330,19 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Response data' + content: + application/json: + schema: + required: + - success + - code + - payload + properties: + success: { description: '', type: boolean, example: 'true', nullable: false } + code: { description: '', type: integer, example: '0', nullable: false } + payload: { description: '', properties: { id: { description: 'An identifier of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, externalId: { description: 'An informative, human readable identifier of the group', type: string, example: text, nullable: true }, organizational: { description: 'Whether the group is organizational (no assignments nor students).', type: boolean, example: 'true', nullable: false }, exam: { description: 'Whether the group is an exam group.', type: boolean, example: 'true', nullable: false }, archived: { description: 'Whether the group is archived', type: boolean, example: 'true', nullable: false }, public: { description: 'Should the group be visible to all student?', type: boolean, example: 'true', nullable: false }, directlyArchived: { description: 'Whether the group was explicitly marked as archived', type: boolean, example: 'true', nullable: false }, localizedTexts: { description: 'Localized names and descriptions', type: array, items: { }, nullable: false }, primaryAdminsIds: { description: 'IDs of users which are explicitly listed as direct admins of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, parentGroupId: { description: 'Identifier of the parent group (absent for a top-level group)', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, parentGroupsIds: { description: 'Identifications of groups in descending order.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, childGroups: { description: 'Identifications of child groups.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, privateData: { description: '', type: string, nullable: false }, permissionHints: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false } + type: object delete: summary: 'Delete a group' description: 'Delete a group' @@ -2347,7 +2359,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/groups/{id}/subgroups': get: summary: 'Get a list of subgroups of a group' @@ -2365,7 +2377,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/groups/{id}/organizational': post: summary: "Set the 'isOrganizational' flag for a group" @@ -2396,7 +2408,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/groups/{id}/archived': post: summary: "Set the 'isArchived' flag for a group" @@ -2427,7 +2439,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/groups/{id}/examPeriod': post: summary: 'Set an examination period (in the future) when the group will be secured for submitting. Only locked students may submit solutions in the group during this period. This endpoint is also used to update already planned exam period, but only dates in the future can be edited (e.g., once an exam begins, the beginning may no longer be updated).' @@ -2468,7 +2480,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' delete: summary: 'Change the group back to regular group (remove information about an exam).' description: 'Change the group back to regular group (remove information about an exam).' @@ -2485,7 +2497,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/groups/{id}/exam/{examId}': get: summary: 'Retrieve a list of locks for given exam' @@ -2511,7 +2523,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/groups/{id}/relocate/{newParentId}': post: summary: 'Relocate the group under a different parent.' @@ -2537,7 +2549,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/groups/{id}/students/stats': get: summary: 'Get statistics of a group. If the user does not have the rights to view all of these, try to at least return their statistics.' @@ -2555,7 +2567,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/groups/{id}/students/{userId}': get: summary: 'Get statistics of a single student in a group' @@ -2581,7 +2593,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Add a student to a group' description: 'Add a student to a group' @@ -2606,7 +2618,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' delete: summary: 'Remove a student from a group' description: 'Remove a student from a group' @@ -2631,7 +2643,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/groups/{id}/students/{userId}/solutions': get: summary: 'Get all solutions of a single student from all assignments in a group' @@ -2657,7 +2669,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/groups/{id}/lock/{userId}': post: summary: 'Lock student in a group and with an IP from which the request was made.' @@ -2683,7 +2695,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' delete: summary: 'Unlock a student currently locked in a group.' description: 'Unlock a student currently locked in a group.' @@ -2708,7 +2720,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/groups/{id}/members': get: summary: 'Get a list of members of a group' @@ -2726,7 +2738,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/groups/{id}/members/{userId}': post: summary: 'Add/update a membership (other than student) for given user' @@ -2766,7 +2778,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' delete: summary: 'Remove a member (other than student) from a group' description: 'Remove a member (other than student) from a group' @@ -2791,7 +2803,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/groups/{id}/assignments': get: summary: 'Get all exercise assignments for a group' @@ -2809,7 +2821,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/groups/{id}/shadow-assignments': get: summary: 'Get all shadow assignments for a group' @@ -2827,7 +2839,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/groups/{groupId}/invitations': get: summary: 'List all invitations of a group.' @@ -2844,7 +2856,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Create a new invitation for given group.' description: 'Create a new invitation for given group.' @@ -2878,7 +2890,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/group-invitations/{id}': get: summary: 'Return invitation details including all relevant group entities (so a name can be constructed).' @@ -2896,7 +2908,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Edit the invitation.' description: 'Edit the invitation.' @@ -2931,7 +2943,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' delete: operationId: groupInvitationsPresenterActionRemove parameters: @@ -2946,7 +2958,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/group-invitations/{id}/accept': post: summary: 'Allow the current user to join the corresponding group using the invitation.' @@ -2964,7 +2976,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/group-attributes: get: summary: 'Return all attributes that correspond to given filtering parameters.' @@ -2981,7 +2993,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/group-attributes/{groupId}': post: summary: 'Create an external attribute for given group.' @@ -3028,7 +3040,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/group-attributes/{id}': delete: summary: 'Remove selected attribute' @@ -3046,7 +3058,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/instances: get: summary: 'Get a list of all instances' @@ -3054,7 +3066,7 @@ paths: operationId: instancesPresenterActionDefault responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Create a new instance' description: 'Create a new instance' @@ -3085,7 +3097,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/instances/{id}': get: summary: 'Get details of an instance' @@ -3103,7 +3115,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Update an instance' description: 'Update an instance' @@ -3131,7 +3143,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' delete: summary: 'Delete an instance' description: 'Delete an instance' @@ -3148,7 +3160,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/instances/{id}/licences': get: summary: 'Get a list of licenses associated with an instance' @@ -3166,7 +3178,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Create a new license for an instance' description: 'Create a new license for an instance' @@ -3203,7 +3215,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/instances/licences/{licenceId}': post: summary: 'Update an existing license for an instance' @@ -3243,7 +3255,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' delete: summary: 'Remove existing license for an instance' description: 'Remove existing license for an instance' @@ -3259,7 +3271,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/reference-solutions/exercise/{exerciseId}': get: summary: 'Get reference solutions for an exercise' @@ -3276,7 +3288,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/reference-solutions/exercise/{exerciseId}/pre-submit': post: summary: 'Pre submit action which will, based on given files, detect possible runtime environments for the exercise. Also it can be further used for entry points and other important things that should be provided by user during submit.' @@ -3306,7 +3318,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/reference-solutions/exercise/{exerciseId}/submit': post: summary: 'Add new reference solution to an exercise' @@ -3350,7 +3362,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/reference-solutions/exercise/{exerciseId}/resubmit-all': post: summary: 'Evaluate all reference solutions for an exercise (and for all configured hardware groups).' @@ -3378,7 +3390,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/reference-solutions/{solutionId}': get: summary: 'Get details of a reference solution' @@ -3395,7 +3407,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Update details about the ref. solution (note, etc...)' description: 'Update details about the ref. solution (note, etc...)' @@ -3425,7 +3437,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' delete: summary: 'Delete reference solution with given identification.' description: 'Delete reference solution with given identification.' @@ -3441,7 +3453,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/reference-solutions/{id}/resubmit': post: summary: 'Evaluate a single reference exercise solution for all configured hardware groups' @@ -3470,7 +3482,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/reference-solutions/{solutionId}/submissions': get: summary: 'Get a list of submissions for given reference solution.' @@ -3487,7 +3499,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/reference-solutions/{id}/files': get: summary: 'Get the list of submitted files of the solution.' @@ -3505,7 +3517,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/reference-solutions/{solutionId}/download-solution': get: summary: 'Download archive containing all solution files for particular reference solution.' @@ -3522,7 +3534,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/reference-solutions/{solutionId}/visibility': post: summary: 'Set visibility of given reference solution.' @@ -3552,7 +3564,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/reference-solutions/submission/{submissionId}': get: summary: 'Get reference solution evaluation (i.e., submission) for an exercise solution.' @@ -3569,7 +3581,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' delete: summary: 'Remove reference solution evaluation (submission) permanently.' description: 'Remove reference solution evaluation (submission) permanently.' @@ -3585,7 +3597,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/reference-solutions/submission/{submissionId}/download-result': get: summary: 'Download result archive from backend for a reference solution evaluation' @@ -3602,7 +3614,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/reference-solutions/submission/{submissionId}/score-config': get: summary: 'Get score configuration associated with given submission evaluation' @@ -3619,7 +3631,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/assignment-solutions/{id}': get: summary: 'Get information about solutions.' @@ -3637,7 +3649,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Update details about the solution (note, etc...)' description: 'Update details about the solution (note, etc...)' @@ -3668,7 +3680,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' delete: summary: 'Delete assignment solution with given identification.' description: 'Delete assignment solution with given identification.' @@ -3685,7 +3697,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/assignment-solutions/{id}/bonus-points': post: summary: 'Set new amount of bonus points for a solution (and optionally points override) Returns array of solution entities that has been changed by this.' @@ -3720,7 +3732,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/assignment-solutions/{id}/submissions': get: summary: 'Get list of all submissions of a solution' @@ -3738,7 +3750,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/assignment-solutions/{id}/set-flag/{flag}': post: summary: 'Set flag of the assignment solution.' @@ -3777,7 +3789,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/assignment-solutions/{id}/resubmit': post: summary: 'Resubmit a solution (i.e., create a new submission)' @@ -3806,7 +3818,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/assignment-solutions/{id}/files': get: summary: 'Get the list of submitted files of the solution.' @@ -3824,7 +3836,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/assignment-solutions/{id}/download-solution': get: summary: 'Download archive containing all solution files for particular solution.' @@ -3842,7 +3854,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/assignment-solutions/submission/{submissionId}': get: summary: 'Get information about the evaluation of a submission' @@ -3859,7 +3871,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' delete: summary: 'Remove the submission permanently' description: 'Remove the submission permanently' @@ -3875,7 +3887,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/assignment-solutions/submission/{submissionId}/download-result': get: summary: 'Download result archive from backend for particular submission.' @@ -3892,7 +3904,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/assignment-solutions/submission/{submissionId}/score-config': get: summary: 'Get score configuration associated with given submission evaluation' @@ -3909,7 +3921,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/assignment-solutions/{id}/review': get: summary: 'Get detail of the solution and a list of review comments.' @@ -3927,7 +3939,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Update the state of the review process of the solution.' description: 'Update the state of the review process of the solution.' @@ -3957,7 +3969,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' delete: summary: 'Update the state of the review process of the solution.' description: 'Update the state of the review process of the solution.' @@ -3974,7 +3986,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/assignment-solutions/{id}/review-comment': post: summary: 'Create a new comment within a review.' @@ -4030,7 +4042,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/assignment-solutions/{id}/review-comment/{commentId}': post: summary: 'Update existing comment within a review.' @@ -4081,7 +4093,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' delete: summary: 'Remove one comment from a review.' description: 'Remove one comment from a review.' @@ -4106,7 +4118,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/assignment-solvers: get: summary: 'Get a list of assignment solvers based on given parameters (assignment/group and solver user). Either assignment or group ID must be set (group is ignored if assignment is set), user ID is optional.' @@ -4142,7 +4154,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/submission-failures: get: summary: 'List all submission failures, ever' @@ -4150,7 +4162,7 @@ paths: operationId: submissionFailuresPresenterActionDefault responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/submission-failures/unresolved: get: summary: 'List all unresolved submission failures' @@ -4158,7 +4170,7 @@ paths: operationId: submissionFailuresPresenterActionUnresolved responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/submission-failures/{id}': get: summary: 'Get details of a failure' @@ -4176,7 +4188,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/submission-failures/{id}/resolve': post: summary: 'Mark a submission failure as resolved' @@ -4213,7 +4225,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/uploaded-files/partial: post: summary: 'Start new upload per-partes. This process expects the file is uploaded as a sequence of PUT requests, each one carrying a chunk of data. Once all the chunks are in place, the complete request assembles them together in one file and transforms UploadPartialFile into UploadFile entity.' @@ -4242,7 +4254,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/uploaded-files/partial/{id}': put: summary: 'Add another chunk to partial upload.' @@ -4274,7 +4286,7 @@ paths: format: binary responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Finalize partial upload and convert the partial file into UploadFile. All data chunks are extracted from the store, assembled into one file, and is moved back into the store.' description: 'Finalize partial upload and convert the partial file into UploadFile. All data chunks are extracted from the store, assembled into one file, and is moved back into the store.' @@ -4291,7 +4303,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' delete: summary: 'Cancel partial upload and remove all uploaded chunks.' description: 'Cancel partial upload and remove all uploaded chunks.' @@ -4308,7 +4320,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/uploaded-files: post: summary: 'Upload a file' @@ -4329,7 +4341,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/uploaded-files/supplementary-file/{id}/download': get: summary: 'Download supplementary file' @@ -4347,7 +4359,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/uploaded-files/{id}': get: summary: 'Get details of a file' @@ -4365,7 +4377,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/uploaded-files/{id}/download': get: summary: 'Download a file' @@ -4401,7 +4413,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/uploaded-files/{id}/content': get: summary: 'Get the contents of a file' @@ -4437,7 +4449,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/uploaded-files/{id}/digest': get: summary: 'Compute a digest using a hashing algorithm. This feature is intended for upload checksums only. In the future, we might want to add algorithm selection via query parameter (default is SHA1).' @@ -4455,7 +4467,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/users: get: summary: 'Get a list of all users matching given filters in given pagination rage. The result conforms to pagination protocol.' @@ -4505,7 +4517,7 @@ paths: nullable: true responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Create a user account' description: 'Create a user account' @@ -4578,7 +4590,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/users/validate-registration-data: post: summary: "Check if the registered E-mail isn't already used and if the password is strong enough" @@ -4602,7 +4614,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/users/list: post: summary: 'Get a list of users based on given ids.' @@ -4623,7 +4635,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/users/ical/{id}': get: summary: 'Get calendar values in iCal format that correspond to given token.' @@ -4640,7 +4652,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' delete: summary: 'Set given iCal token to expired state. Expired tokens cannot be used to retrieve calendars.' description: 'Set given iCal token to expired state. Expired tokens cannot be used to retrieve calendars.' @@ -4656,7 +4668,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/users/invite: post: summary: 'Create an invitation for a user and send it over via email' @@ -4728,7 +4740,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/users/accept-invitation: post: summary: 'Accept invitation and create corresponding user account.' @@ -4764,7 +4776,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/users/{id}': get: summary: 'Get details of a user account' @@ -4782,7 +4794,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Update the profile associated with a user account' description: 'Update the profile associated with a user account' @@ -4854,7 +4866,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' delete: summary: 'Delete a user account' description: 'Delete a user account' @@ -4871,7 +4883,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/users/{id}/invalidate-tokens': post: summary: 'Invalidate all existing tokens issued for given user' @@ -4889,7 +4901,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/users/{id}/groups': get: summary: 'Get a list of non-archived groups for a user' @@ -4907,7 +4919,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/users/{id}/groups/all': get: summary: 'Get a list of all groups for a user' @@ -4925,7 +4937,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/users/{id}/instances': get: summary: 'Get a list of instances where a user is registered' @@ -4943,7 +4955,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/users/{id}/settings': post: summary: 'Update the profile settings' @@ -5027,7 +5039,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/users/{id}/ui-data': post: summary: 'Update the user-specific structured UI data' @@ -5063,7 +5075,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/users/{id}/create-local': post: summary: 'If user is registered externally, add local account as another login method. Created password is empty and has to be changed in order to use it.' @@ -5081,7 +5093,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/users/{id}/role': post: summary: 'Set a given role to the given user.' @@ -5113,7 +5125,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/users/{id}/external-login/{service}': post: summary: 'Add or update existing external ID of given authentication service.' @@ -5154,7 +5166,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' delete: summary: 'Remove external ID of given authentication service.' description: 'Remove external ID of given authentication service.' @@ -5179,7 +5191,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/users/{id}/calendar-tokens': get: summary: 'Get all iCal tokens of one user (including expired ones).' @@ -5197,7 +5209,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Create new iCal token for a particular user.' description: 'Create new iCal token for a particular user.' @@ -5214,7 +5226,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/users/{id}/pending-reviews': get: summary: 'Return all solutions with pending reviews that given user teaches (is admin/supervisor in corresponding groups). Along with that it returns all assignment entities of the corresponding solutions.' @@ -5232,7 +5244,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/users/{id}/review-requests': get: summary: 'Return all solutions with reviewRequest flag that given user might need to review (is admin/supervisor in corresponding groups). Along with that it returns all assignment entities of the corresponding solutions.' @@ -5250,7 +5262,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/email-verification/verify: post: summary: 'Verify users email.' @@ -5258,7 +5270,7 @@ paths: operationId: emailVerificationPresenterActionEmailVerification responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/email-verification/resend: post: summary: 'Resend the email for the current user to verify his/her email address.' @@ -5266,7 +5278,7 @@ paths: operationId: emailVerificationPresenterActionResendVerificationEmail responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/forgotten-password: post: summary: 'Request a password reset (user will receive an e-mail that prompts them to reset their password)' @@ -5288,7 +5300,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/forgotten-password/change: post: summary: "Change the user's password" @@ -5310,7 +5322,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/forgotten-password/validate-password-strength: post: summary: 'Check if a password is strong enough' @@ -5330,7 +5342,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/runtime-environments: get: summary: 'Get a list of all runtime environments' @@ -5338,7 +5350,7 @@ paths: operationId: runtimeEnvironmentsPresenterActionDefault responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/hardware-groups: get: summary: 'Get a list of all hardware groups in system' @@ -5346,7 +5358,7 @@ paths: operationId: hardwareGroupsPresenterActionDefault responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/pipelines: get: summary: 'Get a list of pipelines with an optional filter, ordering, and pagination pruning. The result conforms to pagination protocol.' @@ -5396,7 +5408,7 @@ paths: nullable: true responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Create a brand new pipeline.' description: 'Create a brand new pipeline.' @@ -5414,7 +5426,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/pipelines/boxes: get: summary: 'Get a list of default boxes which might be used in pipeline.' @@ -5422,7 +5434,7 @@ paths: operationId: pipelinesPresenterActionGetDefaultBoxes responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/pipelines/{id}/fork': post: summary: 'Create a complete copy of given pipeline.' @@ -5451,7 +5463,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/pipelines/{id}': get: summary: 'Get pipeline based on given identification.' @@ -5469,7 +5481,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Update pipeline with given data.' description: 'Update pipeline with given data.' @@ -5525,7 +5537,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' delete: summary: 'Delete an pipeline' description: 'Delete an pipeline' @@ -5542,7 +5554,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/pipelines/{id}/runtime-environments': post: summary: 'Set runtime environments associated with given pipeline.' @@ -5560,7 +5572,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/pipelines/{id}/validate': post: summary: 'Check if the version of the pipeline is up-to-date.' @@ -5591,7 +5603,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/pipelines/{id}/supplementary-files': get: summary: 'Get list of all supplementary files for a pipeline' @@ -5609,7 +5621,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Associate supplementary files with a pipeline and upload them to remote file server' description: 'Associate supplementary files with a pipeline and upload them to remote file server' @@ -5638,7 +5650,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/pipelines/{id}/supplementary-files/{fileId}': delete: summary: 'Delete supplementary pipeline file with given id' @@ -5664,7 +5676,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/pipelines/{id}/exercises': get: summary: 'Get all exercises that use given pipeline. Only bare minimum is retrieved for each exercise (localized name and author).' @@ -5682,13 +5694,13 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/extensions/sis/status/: get: operationId: sisPresenterActionStatus responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/extensions/sis/terms/: get: summary: 'Get a list of all registered SIS terms' @@ -5696,7 +5708,7 @@ paths: operationId: sisPresenterActionGetTerms responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Register a new term' description: 'Register a new term' @@ -5720,7 +5732,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/extensions/sis/terms/{id}': post: summary: 'Set details of a term' @@ -5762,7 +5774,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' delete: summary: 'Delete a term' description: 'Delete a term' @@ -5778,7 +5790,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/extensions/sis/users/{userId}/subscribed-groups/{year}/{term}/as-student': get: summary: 'Get all courses subscirbed by a student and corresponding ReCodEx groups. Organizational and archived groups are filtered out from the result. Each course holds bound group IDs and group objects are returned in a separate array. Whole ancestral closure of groups is returned, so the webapp may properly assemble hiarichial group names.' @@ -5811,7 +5823,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/extensions/sis/users/{userId}/supervised-courses/{year}/{term}': get: summary: 'Get supervised SIS courses and corresponding ReCodEx groups. Each course holds bound group IDs and group objects are returned in a separate array. Whole ancestral closure of groups is returned, so the webapp may properly assemble hiarichial group names.' @@ -5844,7 +5856,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/extensions/sis/remote-courses/{courseId}/possible-parents': get: summary: 'Find groups that can be chosen as parents of a group created from given SIS group by current user' @@ -5861,7 +5873,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/extensions/sis/remote-courses/{courseId}/create': post: summary: 'Create a new group based on a SIS group' @@ -5890,7 +5902,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/extensions/sis/remote-courses/{courseId}/bind': post: summary: 'Bind an existing local group to a SIS group' @@ -5919,7 +5931,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/extensions/sis/remote-courses/{courseId}/bindings/{groupId}': delete: summary: 'Delete a binding between a local group and a SIS group' @@ -5944,7 +5956,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/emails: post: summary: 'Sends an email with provided subject and message to all ReCodEx users.' @@ -5973,7 +5985,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/emails/supervisors: post: summary: 'Sends an email with provided subject and message to all supervisors and superadmins.' @@ -6002,7 +6014,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/emails/regular-users: post: summary: 'Sends an email with provided subject and message to all regular users.' @@ -6031,7 +6043,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/emails/groups/{groupId}': post: summary: 'Sends an email with provided subject and message to regular members of given group and optionally to supervisors and admins.' @@ -6090,7 +6102,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/shadow-assignments/{id}': get: summary: 'Get details of a shadow assignment' @@ -6108,7 +6120,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Update details of an shadow assignment' description: 'Update details of an shadow assignment' @@ -6172,7 +6184,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' delete: summary: 'Delete shadow assignment' description: 'Delete shadow assignment' @@ -6189,7 +6201,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/shadow-assignments: post: summary: 'Create new shadow assignment in given group.' @@ -6209,7 +6221,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/shadow-assignments/{id}/validate': post: summary: 'Check if the version of the shadow assignment is up-to-date.' @@ -6240,7 +6252,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/shadow-assignments/{id}/create-points': post: summary: 'Create new points for shadow assignment and user.' @@ -6288,7 +6300,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/shadow-assignments/points/{pointsId}': post: summary: 'Update detail of shadow assignment points.' @@ -6330,7 +6342,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' delete: summary: 'Remove points of shadow assignment.' description: 'Remove points of shadow assignment.' @@ -6346,7 +6358,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/notifications: get: summary: 'Get all notifications which are currently active. If groupsIds is given returns only the ones from given groups (and their ancestors) and global ones (without group).' @@ -6364,7 +6376,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Create notification with given attributes' description: 'Create notification with given attributes' @@ -6415,7 +6427,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/notifications/all: get: summary: 'Get all notifications in the system.' @@ -6423,7 +6435,7 @@ paths: operationId: notificationsPresenterActionAll responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/notifications/{id}': post: summary: 'Update notification' @@ -6485,7 +6497,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' delete: summary: 'Delete a notification' description: 'Delete a notification' @@ -6502,7 +6514,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/worker-files/supplementary-file/{hash}': get: summary: 'Sends over an exercise supplementary file (a data file required by the tests).' @@ -6519,7 +6531,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/async-jobs/{id}': get: summary: 'Retrieves details about particular async job.' @@ -6537,7 +6549,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/async-jobs: get: summary: 'Retrieves details about async jobs that are either pending or were recently completed.' @@ -6562,7 +6574,7 @@ paths: nullable: true responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/async-jobs/{id}/abort': post: summary: 'Retrieves details about particular async job.' @@ -6580,7 +6592,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/async-jobs/ping: post: summary: 'Initiates ping job. An empty job designed to verify the async handler is running.' @@ -6588,7 +6600,7 @@ paths: operationId: asyncJobsPresenterActionPing responses: '200': - description: 'The data' + description: 'Placeholder response' /v1/plagiarism: get: summary: 'Get a list of all batches, optionally filtered by query params.' @@ -6616,7 +6628,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Create new detection batch record' description: 'Create new detection batch record' @@ -6644,7 +6656,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/plagiarism/{id}': get: summary: 'Fetch a detail of a particular batch record.' @@ -6662,7 +6674,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Update detection bath record. At the moment, only the uploadCompletedAt can be changed.' description: 'Update detection bath record. At the moment, only the uploadCompletedAt can be changed.' @@ -6695,7 +6707,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/plagiarism/{id}/{solutionId}': get: summary: 'Retrieve detected plagiarism records from a specific batch related to one solution. Returns a list of detected similarities entities (similar file records are nested within).' @@ -6721,7 +6733,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' post: summary: 'Appends one detected similarity record (similarities associated with one file and one other author) into a detected batch. This division was selected to make the appends relatively small and manageable.' description: 'Appends one detected similarity record (similarities associated with one file and one other author) into a detected batch. This division was selected to make the appends relatively small and manageable.' @@ -6785,7 +6797,7 @@ paths: type: object responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/extensions/{extId}/{instanceId}': get: summary: 'Return URL referring to the extension with properly injected temporary JWT token.' @@ -6828,7 +6840,7 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' '/v1/extensions/{extId}': post: summary: 'This endpoint is used by a backend of an extension to get a proper access token (from a temp token passed via URL). It also returns details about authenticated user.' @@ -6845,5 +6857,5 @@ paths: nullable: false responses: '200': - description: 'The data' + description: 'Placeholder response' From 53675614a29925f3fd1f40d4a2fec33e09d5decd Mon Sep 17 00:00:00 2001 From: Vojtech Kloda Date: Thu, 19 Jun 2025 16:39:46 +0200 Subject: [PATCH 04/19] added more comments --- app/helpers/Swagger/ResponseData.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/helpers/Swagger/ResponseData.php b/app/helpers/Swagger/ResponseData.php index 6f6b0e94b..25db110f5 100644 --- a/app/helpers/Swagger/ResponseData.php +++ b/app/helpers/Swagger/ResponseData.php @@ -7,13 +7,26 @@ use App\Helpers\MetaFormats\FormatDefinitions\SuccessResponseFormat; use App\Helpers\MetaFormats\Validators\VObject; +/** + * Class containing relevant information used to make swagger responses. + */ class ResponseData { + /** + * @var AnnotationParameterData[] + */ public array $responseParams; public string $description; public int $statusCode; public bool $useSuccessWrapper; + /** + * @param array $responseParams AnnotationParameterData describing the response object. + * @param string $description The description of the response. + * @param int $statusCode The response status code. + * @param bool $useSuccessWrapper Whether the response should be contained in + * the "BasePresenter::sendSuccessResponse" wrapper. + */ public function __construct( array $responseParams, string $description, From 056ab1ef052860465558ced9b3b4d63f51247f6e Mon Sep 17 00:00:00 2001 From: Vojtech Kloda Date: Fri, 27 Jun 2025 14:23:43 +0200 Subject: [PATCH 05/19] added private data format --- .../FormatDefinitions/GroupFormat.php | 10 ++- .../GroupPrivateDataFormat.php | 72 +++++++++++++++++++ docs/swagger.yaml | 2 +- 3 files changed, 77 insertions(+), 7 deletions(-) create mode 100644 app/helpers/MetaFormats/FormatDefinitions/GroupPrivateDataFormat.php diff --git a/app/helpers/MetaFormats/FormatDefinitions/GroupFormat.php b/app/helpers/MetaFormats/FormatDefinitions/GroupFormat.php index ff2fe9293..b6bcf94fe 100644 --- a/app/helpers/MetaFormats/FormatDefinitions/GroupFormat.php +++ b/app/helpers/MetaFormats/FormatDefinitions/GroupFormat.php @@ -9,13 +9,11 @@ use App\Helpers\MetaFormats\Validators\VBool; use App\Helpers\MetaFormats\Validators\VEmail; use App\Helpers\MetaFormats\Validators\VMixed; +use App\Helpers\MetaFormats\Validators\VObject; use App\Helpers\MetaFormats\Validators\VString; use App\Helpers\MetaFormats\Validators\VUuid; use ArrayAccess; -/** - * Format definition used by the RegistrationPresenter::actionCreateInvitation endpoint. - */ #[Format(GroupFormat::class)] class GroupFormat extends MetaFormat// implements ArrayAccess { @@ -68,10 +66,10 @@ class GroupFormat extends MetaFormat// implements ArrayAccess #[FPost(new VArray(new VUuid()), "Identifications of child groups.")] public ?array $childGroups; - #[FPost(new VMixed(), "")] - public mixed $privateData; + #[FPost(new VObject(GroupPrivateDataFormat::class), required: false)] + public ?GroupPrivateDataFormat $privateData; - #[FPost(new VArray(), "")] + #[FPost(new VArray())] public ?array $permissionHints; diff --git a/app/helpers/MetaFormats/FormatDefinitions/GroupPrivateDataFormat.php b/app/helpers/MetaFormats/FormatDefinitions/GroupPrivateDataFormat.php new file mode 100644 index 000000000..da8421253 --- /dev/null +++ b/app/helpers/MetaFormats/FormatDefinitions/GroupPrivateDataFormat.php @@ -0,0 +1,72 @@ + Date: Mon, 7 Jul 2025 19:55:13 +0200 Subject: [PATCH 06/19] added Format attribute caching --- .../presenters/base/BasePresenter.php | 10 +++++-- app/helpers/MetaFormats/FormatCache.php | 30 +++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/app/V1Module/presenters/base/BasePresenter.php b/app/V1Module/presenters/base/BasePresenter.php index 04dfe8e87..a0f62c712 100644 --- a/app/V1Module/presenters/base/BasePresenter.php +++ b/app/V1Module/presenters/base/BasePresenter.php @@ -207,8 +207,15 @@ public function getFormatInstance(): MetaFormat private function processParams(ReflectionMethod $reflection) { + $actionPath = get_class($this) . $reflection->name; + + // cache whether the action has a Format attribute + if (!FormatCache::formatAttributeStringCached($actionPath)) { + $extractedFormat = MetaFormatHelper::extractFormatFromAttribute($reflection); + FormatCache::cacheFormatAttributeString($actionPath, $extractedFormat); + } // use a method specialized for formats if there is a format available - $format = MetaFormatHelper::extractFormatFromAttribute($reflection); + $format = FormatCache::getFormatAttributeString($actionPath); if ($format !== null) { $this->requestFormatInstance = $this->processParamsFormat($format, null); } @@ -216,7 +223,6 @@ private function processParams(ReflectionMethod $reflection) // handle loose parameters // cache the data from the loose attributes to improve performance - $actionPath = get_class($this) . $reflection->name; if (!FormatCache::looseParametersCached($actionPath)) { $newParamData = MetaFormatHelper::extractRequestParamData($reflection); FormatCache::cacheLooseParameters($actionPath, $newParamData); diff --git a/app/helpers/MetaFormats/FormatCache.php b/app/helpers/MetaFormats/FormatCache.php index a341cc014..0dc22a601 100644 --- a/app/helpers/MetaFormats/FormatCache.php +++ b/app/helpers/MetaFormats/FormatCache.php @@ -19,6 +19,9 @@ class FormatCache // this array caches loose attribute data which are added over time by the presenters private static array $actionToRequestParamDataMap = []; + // array that caches Format attribute format strings for actions + private static array $actionToFormatMap = []; + /** * @param string $actionPath The presenter class name joined with the name of the action method. * @return bool Returns whether the loose parameters of the action are cached. @@ -28,6 +31,33 @@ public static function looseParametersCached(string $actionPath): bool return array_key_exists($actionPath, self::$actionToRequestParamDataMap); } + /** + * @param string $actionPath The presenter class name joined with the name of the action method. + * @return bool Returns whether the action Format attribute string was cached. + */ + public static function formatAttributeStringCached(string $actionPath): bool + { + return array_key_exists($actionPath, self::$actionToFormatMap); + } + + /** + * @param string $actionPath The presenter class name joined with the name of the action method. + * @param string $format The attribute format string. + */ + public static function cacheFormatAttributeString(string $actionPath, string $format) + { + self::$actionToFormatMap[$actionPath] = $format; + } + + /** + * @param string $actionPath The presenter class name joined with the name of the action method. + * @return bool Returns action Format attribute string. + */ + public static function getFormatAttributeString(string $actionPath): string + { + return self::$actionToFormatMap[$actionPath]; + } + /** * @param string $actionPath The presenter class name joined with the name of the action method. * @return array Returns the cached RequestParamData array of the loose attributes. From f57b9a8043be2abeb537cf8e9ef64c710b88a466 Mon Sep 17 00:00:00 2001 From: Vojtech Kloda Date: Mon, 7 Jul 2025 19:59:43 +0200 Subject: [PATCH 07/19] fixed bad parameter type --- app/helpers/MetaFormats/FormatCache.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/helpers/MetaFormats/FormatCache.php b/app/helpers/MetaFormats/FormatCache.php index 0dc22a601..2659626eb 100644 --- a/app/helpers/MetaFormats/FormatCache.php +++ b/app/helpers/MetaFormats/FormatCache.php @@ -42,9 +42,9 @@ public static function formatAttributeStringCached(string $actionPath): bool /** * @param string $actionPath The presenter class name joined with the name of the action method. - * @param string $format The attribute format string. + * @param string|null $format The attribute format string or null if there is none. */ - public static function cacheFormatAttributeString(string $actionPath, string $format) + public static function cacheFormatAttributeString(string $actionPath, string | null $format) { self::$actionToFormatMap[$actionPath] = $format; } From abda63090b88a40baeaf18b102f226a02503a958 Mon Sep 17 00:00:00 2001 From: Vojtech Kloda Date: Mon, 7 Jul 2025 20:01:10 +0200 Subject: [PATCH 08/19] fixed bad return type --- app/helpers/MetaFormats/FormatCache.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/helpers/MetaFormats/FormatCache.php b/app/helpers/MetaFormats/FormatCache.php index 2659626eb..a99a61450 100644 --- a/app/helpers/MetaFormats/FormatCache.php +++ b/app/helpers/MetaFormats/FormatCache.php @@ -51,9 +51,9 @@ public static function cacheFormatAttributeString(string $actionPath, string | n /** * @param string $actionPath The presenter class name joined with the name of the action method. - * @return bool Returns action Format attribute string. + * @return string|null Returns action Format attribute string or null if there is no Format attribute. */ - public static function getFormatAttributeString(string $actionPath): string + public static function getFormatAttributeString(string $actionPath): string | null { return self::$actionToFormatMap[$actionPath]; } From 1573674ba3eec91ded94c209c98ffc8e87358a42 Mon Sep 17 00:00:00 2001 From: Vojtech Kloda Date: Sat, 12 Jul 2025 16:06:28 +0200 Subject: [PATCH 09/19] added more references to the group response format --- app/V1Module/presenters/GroupsPresenter.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/V1Module/presenters/GroupsPresenter.php b/app/V1Module/presenters/GroupsPresenter.php index f3afe634f..28881ee9b 100644 --- a/app/V1Module/presenters/GroupsPresenter.php +++ b/app/V1Module/presenters/GroupsPresenter.php @@ -292,6 +292,7 @@ private function setGroupPoints(Request $req, Group $group): void "If true, no admin is assigned to group (current user is assigned as admin by default.", required: false, )] + #[ResponseFormat(GroupFormat::class)] public function actionAddGroup() { $req = $this->getRequest(); @@ -448,6 +449,7 @@ public function checkSetOrganizational(string $id) */ #[Post("value", new VBool(), "The value of the flag", required: true)] #[Path("id", new VUuid(), "An identifier of the updated group", required: true)] + #[ResponseFormat(GroupFormat::class)] public function actionSetOrganizational(string $id) { $group = $this->groups->findOrThrow($id); @@ -484,6 +486,7 @@ public function checkSetArchived(string $id) */ #[Post("value", new VBool(), "The value of the flag", required: true)] #[Path("id", new VUuid(), "An identifier of the updated group", required: true)] + #[ResponseFormat(GroupFormat::class)] public function actionSetArchived(string $id) { $group = $this->groups->findOrThrow($id); @@ -566,6 +569,7 @@ public function checkSetExam(string $id) */ #[Post("value", new VBool(), "The value of the flag", required: true)] #[Path("id", new VUuid(), "An identifier of the updated group", required: true)] + #[ResponseFormat(GroupFormat::class)] public function actionSetExam(string $id) { $group = $this->groups->findOrThrow($id); @@ -606,6 +610,7 @@ public function checkSetExamPeriod(string $id) )] #[Post("strict", new VBool(), "Whether locked users are prevented from accessing other groups.", required: false)] #[Path("id", new VUuid(), "An identifier of the updated group", required: true)] + #[ResponseFormat(GroupFormat::class)] public function actionSetExamPeriod(string $id) { $group = $this->groups->findOrThrow($id); @@ -719,6 +724,7 @@ public function checkRemoveExamPeriod(string $id) * @throws NotFoundException */ #[Path("id", new VUuid(), "An identifier of the updated group", required: true)] + #[ResponseFormat(GroupFormat::class)] public function actionRemoveExamPeriod(string $id) { $group = $this->groups->findOrThrow($id); @@ -865,6 +871,7 @@ public function checkDetail(string $id) * @GET */ #[Path("id", new VUuid(), "Identifier of the group", required: true)] + #[ResponseFormat(GroupFormat::class)] public function actionDetail(string $id) { $group = $this->groups->findOrThrow($id); @@ -953,6 +960,7 @@ public function checkAddMember(string $id, string $userId) #[Post("type", new VString(1), "Identifier of membership type (admin, supervisor, ...)", required: true)] #[Path("id", new VUuid(), "Identifier of the group", required: true)] #[Path("userId", new VString(), "Identifier of the supervisor", required: true)] + #[ResponseFormat(GroupFormat::class)] public function actionAddMember(string $id, string $userId) { $user = $this->users->findOrThrow($userId); @@ -999,6 +1007,7 @@ public function checkRemoveMember(string $id, string $userId) */ #[Path("id", new VUuid(), "Identifier of the group", required: true)] #[Path("userId", new VString(), "Identifier of the supervisor", required: true)] + #[ResponseFormat(GroupFormat::class)] public function actionRemoveMember(string $id, string $userId) { $user = $this->users->findOrThrow($userId); @@ -1217,6 +1226,7 @@ public function checkAddStudent(string $id, string $userId) */ #[Path("id", new VUuid(), "Identifier of the group", required: true)] #[Path("userId", new VString(), "Identifier of the student", required: true)] + #[ResponseFormat(GroupFormat::class)] public function actionAddStudent(string $id, string $userId) { $user = $this->users->findOrThrow($userId); @@ -1248,6 +1258,7 @@ public function checkRemoveStudent(string $id, string $userId) */ #[Path("id", new VUuid(), "Identifier of the group", required: true)] #[Path("userId", new VString(), "Identifier of the student", required: true)] + #[ResponseFormat(GroupFormat::class)] public function actionRemoveStudent(string $id, string $userId) { $user = $this->users->findOrThrow($userId); From b2b3d0ef63ddc4d8b5965ce3972eaac84d5e4744 Mon Sep 17 00:00:00 2001 From: Vojtech Kloda Date: Wed, 16 Jul 2025 10:04:24 +0200 Subject: [PATCH 10/19] code cleanup --- .../FormatDefinitions/GroupFormat.php | 43 ++----------------- .../GroupPrivateDataFormat.php | 7 ++- .../SuccessResponseFormat.php | 4 ++ app/model/view/GroupViewFactory.php | 23 ---------- 4 files changed, 11 insertions(+), 66 deletions(-) diff --git a/app/helpers/MetaFormats/FormatDefinitions/GroupFormat.php b/app/helpers/MetaFormats/FormatDefinitions/GroupFormat.php index b6bcf94fe..89794ffca 100644 --- a/app/helpers/MetaFormats/FormatDefinitions/GroupFormat.php +++ b/app/helpers/MetaFormats/FormatDefinitions/GroupFormat.php @@ -7,15 +7,15 @@ use App\Helpers\MetaFormats\Attributes\FPost; use App\Helpers\MetaFormats\Validators\VArray; use App\Helpers\MetaFormats\Validators\VBool; -use App\Helpers\MetaFormats\Validators\VEmail; -use App\Helpers\MetaFormats\Validators\VMixed; use App\Helpers\MetaFormats\Validators\VObject; use App\Helpers\MetaFormats\Validators\VString; use App\Helpers\MetaFormats\Validators\VUuid; -use ArrayAccess; +/** + * Format definition used by the GroupsPresenter. + */ #[Format(GroupFormat::class)] -class GroupFormat extends MetaFormat// implements ArrayAccess +class GroupFormat extends MetaFormat { #[FPost(new VUuid(), "An identifier of the group")] public string $id; @@ -71,39 +71,4 @@ class GroupFormat extends MetaFormat// implements ArrayAccess #[FPost(new VArray())] public ?array $permissionHints; - - - // public function offsetExists(mixed $offset): bool - // { - // return isset($this->$offset); - // } - - // /** - // * Offset to retrieve - // * @param mixed $offset The offset to retrieve. - // * @return mixed Can return all value types. - // */ - // public function offsetGet(mixed $offset): mixed - // { - // return $this->$offset ?? null; - // } - - // /** - // * Offset to set - // * @param mixed $offset The offset to assign the value to. - // * @param mixed $value The value to set. - // */ - // public function offsetSet(mixed $offset, mixed $value): void - // { - // $this->$offset = $value; - // } - - // /** - // * Offset to unset - // * @param mixed $offset The offset to unset. - // */ - // public function offsetUnset(mixed $offset): void - // { - // $this->$offset = null; - // } } diff --git a/app/helpers/MetaFormats/FormatDefinitions/GroupPrivateDataFormat.php b/app/helpers/MetaFormats/FormatDefinitions/GroupPrivateDataFormat.php index da8421253..9414ff85b 100644 --- a/app/helpers/MetaFormats/FormatDefinitions/GroupPrivateDataFormat.php +++ b/app/helpers/MetaFormats/FormatDefinitions/GroupPrivateDataFormat.php @@ -8,14 +8,13 @@ use App\Helpers\MetaFormats\Validators\VArray; use App\Helpers\MetaFormats\Validators\VBool; use App\Helpers\MetaFormats\Validators\VDouble; -use App\Helpers\MetaFormats\Validators\VEmail; use App\Helpers\MetaFormats\Validators\VInt; -use App\Helpers\MetaFormats\Validators\VMixed; -use App\Helpers\MetaFormats\Validators\VString; use App\Helpers\MetaFormats\Validators\VTimestamp; use App\Helpers\MetaFormats\Validators\VUuid; -use ArrayAccess; +/** + * Nested Format definition used by the GroupFormat. + */ #[Format(GroupPrivateDataFormat::class)] class GroupPrivateDataFormat extends MetaFormat { diff --git a/app/helpers/MetaFormats/FormatDefinitions/SuccessResponseFormat.php b/app/helpers/MetaFormats/FormatDefinitions/SuccessResponseFormat.php index 0ef39a907..c3966d03f 100644 --- a/app/helpers/MetaFormats/FormatDefinitions/SuccessResponseFormat.php +++ b/app/helpers/MetaFormats/FormatDefinitions/SuccessResponseFormat.php @@ -9,6 +9,10 @@ use App\Helpers\MetaFormats\Validators\VInt; use App\Helpers\MetaFormats\Validators\VMixed; +/** + * Wrapper Format definition of the common output schema. + * The ResponseFormat attribute has a flag that can automatically wrap any Format with this one. + */ #[Format(SuccessResponseFormat::class)] class SuccessResponseFormat extends MetaFormat { diff --git a/app/model/view/GroupViewFactory.php b/app/model/view/GroupViewFactory.php index 613d28034..5f01a99df 100644 --- a/app/model/view/GroupViewFactory.php +++ b/app/model/view/GroupViewFactory.php @@ -4,7 +4,6 @@ use App\Helpers\EvaluationStatus\EvaluationStatus; use App\Helpers\GroupBindings\GroupBindingAccessor; -use App\Helpers\MetaFormats\FormatDefinitions\GroupFormat; use App\Helpers\PermissionHints; use App\Model\Entity\Assignment; use App\Model\Entity\AssignmentSolution; @@ -272,28 +271,6 @@ function (Group $group) use ($ignoreArchived) { } ); - // $groupFormat = new GroupFormat(); - // $groupFormat->id = $group->getId(); - // $groupFormat->externalId = $group->getExternalId(); - // $groupFormat->organizational = $group->isOrganizational(); - // $groupFormat->exam = $group->isExam(); - // $groupFormat->archived = $group->isArchived(); - // $groupFormat->public = $group->isPublic(); - // $groupFormat->directlyArchived = $group->isDirectlyArchived(); - // $groupFormat->localizedTexts = $group->getLocalizedTexts()->getValues(); - // $groupFormat->primaryAdminsIds = $group->getPrimaryAdminsIds(); - // $groupFormat->parentGroupId = $group->getParentGroup() ? $group->getParentGroup()->getId() : null; - // $groupFormat->parentGroupsIds = $group->getParentGroupsIds(); - // $groupFormat->childGroups = $childGroups->map( - // function (Group $group) { - // return $group->getId(); - // } - // )->getValues(); - // $groupFormat->privateData = $privateData; - // $groupFormat->permissionHints = PermissionHints::get($this->groupAcl, $group); - - // return $groupFormat; - return [ "id" => $group->getId(), "externalId" => $group->getExternalId(), From f1dd28dd13e0b310676b113025ac05bdc00277ce Mon Sep 17 00:00:00 2001 From: Vojtech Kloda Date: Wed, 16 Jul 2025 10:07:13 +0200 Subject: [PATCH 11/19] updated swagger --- docs/swagger.yaml | 140 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 130 insertions(+), 10 deletions(-) diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 673f01a44..25effac28 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -2217,7 +2217,19 @@ paths: type: object responses: '200': - description: 'Placeholder response' + description: 'Response data' + content: + application/json: + schema: + required: + - success + - code + - payload + properties: + success: { description: '', type: boolean, example: 'true', nullable: false } + code: { description: '', type: integer, example: '0', nullable: false } + payload: { description: '', properties: { id: { description: 'An identifier of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, externalId: { description: 'An informative, human readable identifier of the group', type: string, example: text, nullable: true }, organizational: { description: 'Whether the group is organizational (no assignments nor students).', type: boolean, example: 'true', nullable: false }, exam: { description: 'Whether the group is an exam group.', type: boolean, example: 'true', nullable: false }, archived: { description: 'Whether the group is archived', type: boolean, example: 'true', nullable: false }, public: { description: 'Should the group be visible to all student?', type: boolean, example: 'true', nullable: false }, directlyArchived: { description: 'Whether the group was explicitly marked as archived', type: boolean, example: 'true', nullable: false }, localizedTexts: { description: 'Localized names and descriptions', type: array, items: { }, nullable: false }, primaryAdminsIds: { description: 'IDs of users which are explicitly listed as direct admins of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, parentGroupId: { description: 'Identifier of the parent group (absent for a top-level group)', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, parentGroupsIds: { description: 'Identifications of groups in descending order.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, childGroups: { description: 'Identifications of child groups.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, privateData: { description: '', properties: { admins: { description: 'IDs of all users that have admin privileges to this group (including inherited)', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, supervisors: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, observers: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, students: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, instanceId: { description: '', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, hasValidLicence: { description: '', type: boolean, example: 'true', nullable: false }, assignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, shadowAssignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, publicStats: { description: '', type: boolean, example: 'true', nullable: false }, detaining: { description: '', type: boolean, example: 'true', nullable: false }, threshold: { description: '', type: number, example: '0.1', nullable: false }, pointsLimit: { description: '', type: integer, example: '0', nullable: false }, bindings: { description: '', type: array, items: { }, nullable: false }, examBegin: { description: '', type: integer, example: '1740135333', nullable: false }, examEnd: { description: '', type: integer, example: '1740135333', nullable: false }, examLockStrict: { description: '', type: boolean, example: 'true', nullable: false }, exams: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false }, permissionHints: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false } + type: object /v1/groups/validate-add-group-data: post: summary: 'Validate group creation data' @@ -2269,7 +2281,19 @@ paths: nullable: false responses: '200': - description: 'Placeholder response' + description: 'Response data' + content: + application/json: + schema: + required: + - success + - code + - payload + properties: + success: { description: '', type: boolean, example: 'true', nullable: false } + code: { description: '', type: integer, example: '0', nullable: false } + payload: { description: '', properties: { id: { description: 'An identifier of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, externalId: { description: 'An informative, human readable identifier of the group', type: string, example: text, nullable: true }, organizational: { description: 'Whether the group is organizational (no assignments nor students).', type: boolean, example: 'true', nullable: false }, exam: { description: 'Whether the group is an exam group.', type: boolean, example: 'true', nullable: false }, archived: { description: 'Whether the group is archived', type: boolean, example: 'true', nullable: false }, public: { description: 'Should the group be visible to all student?', type: boolean, example: 'true', nullable: false }, directlyArchived: { description: 'Whether the group was explicitly marked as archived', type: boolean, example: 'true', nullable: false }, localizedTexts: { description: 'Localized names and descriptions', type: array, items: { }, nullable: false }, primaryAdminsIds: { description: 'IDs of users which are explicitly listed as direct admins of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, parentGroupId: { description: 'Identifier of the parent group (absent for a top-level group)', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, parentGroupsIds: { description: 'Identifications of groups in descending order.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, childGroups: { description: 'Identifications of child groups.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, privateData: { description: '', properties: { admins: { description: 'IDs of all users that have admin privileges to this group (including inherited)', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, supervisors: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, observers: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, students: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, instanceId: { description: '', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, hasValidLicence: { description: '', type: boolean, example: 'true', nullable: false }, assignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, shadowAssignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, publicStats: { description: '', type: boolean, example: 'true', nullable: false }, detaining: { description: '', type: boolean, example: 'true', nullable: false }, threshold: { description: '', type: number, example: '0.1', nullable: false }, pointsLimit: { description: '', type: integer, example: '0', nullable: false }, bindings: { description: '', type: array, items: { }, nullable: false }, examBegin: { description: '', type: integer, example: '1740135333', nullable: false }, examEnd: { description: '', type: integer, example: '1740135333', nullable: false }, examLockStrict: { description: '', type: boolean, example: 'true', nullable: false }, exams: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false }, permissionHints: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false } + type: object post: summary: 'Update group info' description: 'Update group info' @@ -2408,7 +2432,19 @@ paths: type: object responses: '200': - description: 'Placeholder response' + description: 'Response data' + content: + application/json: + schema: + required: + - success + - code + - payload + properties: + success: { description: '', type: boolean, example: 'true', nullable: false } + code: { description: '', type: integer, example: '0', nullable: false } + payload: { description: '', properties: { id: { description: 'An identifier of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, externalId: { description: 'An informative, human readable identifier of the group', type: string, example: text, nullable: true }, organizational: { description: 'Whether the group is organizational (no assignments nor students).', type: boolean, example: 'true', nullable: false }, exam: { description: 'Whether the group is an exam group.', type: boolean, example: 'true', nullable: false }, archived: { description: 'Whether the group is archived', type: boolean, example: 'true', nullable: false }, public: { description: 'Should the group be visible to all student?', type: boolean, example: 'true', nullable: false }, directlyArchived: { description: 'Whether the group was explicitly marked as archived', type: boolean, example: 'true', nullable: false }, localizedTexts: { description: 'Localized names and descriptions', type: array, items: { }, nullable: false }, primaryAdminsIds: { description: 'IDs of users which are explicitly listed as direct admins of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, parentGroupId: { description: 'Identifier of the parent group (absent for a top-level group)', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, parentGroupsIds: { description: 'Identifications of groups in descending order.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, childGroups: { description: 'Identifications of child groups.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, privateData: { description: '', properties: { admins: { description: 'IDs of all users that have admin privileges to this group (including inherited)', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, supervisors: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, observers: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, students: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, instanceId: { description: '', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, hasValidLicence: { description: '', type: boolean, example: 'true', nullable: false }, assignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, shadowAssignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, publicStats: { description: '', type: boolean, example: 'true', nullable: false }, detaining: { description: '', type: boolean, example: 'true', nullable: false }, threshold: { description: '', type: number, example: '0.1', nullable: false }, pointsLimit: { description: '', type: integer, example: '0', nullable: false }, bindings: { description: '', type: array, items: { }, nullable: false }, examBegin: { description: '', type: integer, example: '1740135333', nullable: false }, examEnd: { description: '', type: integer, example: '1740135333', nullable: false }, examLockStrict: { description: '', type: boolean, example: 'true', nullable: false }, exams: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false }, permissionHints: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false } + type: object '/v1/groups/{id}/archived': post: summary: "Set the 'isArchived' flag for a group" @@ -2439,7 +2475,19 @@ paths: type: object responses: '200': - description: 'Placeholder response' + description: 'Response data' + content: + application/json: + schema: + required: + - success + - code + - payload + properties: + success: { description: '', type: boolean, example: 'true', nullable: false } + code: { description: '', type: integer, example: '0', nullable: false } + payload: { description: '', properties: { id: { description: 'An identifier of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, externalId: { description: 'An informative, human readable identifier of the group', type: string, example: text, nullable: true }, organizational: { description: 'Whether the group is organizational (no assignments nor students).', type: boolean, example: 'true', nullable: false }, exam: { description: 'Whether the group is an exam group.', type: boolean, example: 'true', nullable: false }, archived: { description: 'Whether the group is archived', type: boolean, example: 'true', nullable: false }, public: { description: 'Should the group be visible to all student?', type: boolean, example: 'true', nullable: false }, directlyArchived: { description: 'Whether the group was explicitly marked as archived', type: boolean, example: 'true', nullable: false }, localizedTexts: { description: 'Localized names and descriptions', type: array, items: { }, nullable: false }, primaryAdminsIds: { description: 'IDs of users which are explicitly listed as direct admins of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, parentGroupId: { description: 'Identifier of the parent group (absent for a top-level group)', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, parentGroupsIds: { description: 'Identifications of groups in descending order.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, childGroups: { description: 'Identifications of child groups.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, privateData: { description: '', properties: { admins: { description: 'IDs of all users that have admin privileges to this group (including inherited)', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, supervisors: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, observers: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, students: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, instanceId: { description: '', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, hasValidLicence: { description: '', type: boolean, example: 'true', nullable: false }, assignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, shadowAssignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, publicStats: { description: '', type: boolean, example: 'true', nullable: false }, detaining: { description: '', type: boolean, example: 'true', nullable: false }, threshold: { description: '', type: number, example: '0.1', nullable: false }, pointsLimit: { description: '', type: integer, example: '0', nullable: false }, bindings: { description: '', type: array, items: { }, nullable: false }, examBegin: { description: '', type: integer, example: '1740135333', nullable: false }, examEnd: { description: '', type: integer, example: '1740135333', nullable: false }, examLockStrict: { description: '', type: boolean, example: 'true', nullable: false }, exams: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false }, permissionHints: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false } + type: object '/v1/groups/{id}/examPeriod': post: summary: 'Set an examination period (in the future) when the group will be secured for submitting. Only locked students may submit solutions in the group during this period. This endpoint is also used to update already planned exam period, but only dates in the future can be edited (e.g., once an exam begins, the beginning may no longer be updated).' @@ -2480,7 +2528,19 @@ paths: type: object responses: '200': - description: 'Placeholder response' + description: 'Response data' + content: + application/json: + schema: + required: + - success + - code + - payload + properties: + success: { description: '', type: boolean, example: 'true', nullable: false } + code: { description: '', type: integer, example: '0', nullable: false } + payload: { description: '', properties: { id: { description: 'An identifier of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, externalId: { description: 'An informative, human readable identifier of the group', type: string, example: text, nullable: true }, organizational: { description: 'Whether the group is organizational (no assignments nor students).', type: boolean, example: 'true', nullable: false }, exam: { description: 'Whether the group is an exam group.', type: boolean, example: 'true', nullable: false }, archived: { description: 'Whether the group is archived', type: boolean, example: 'true', nullable: false }, public: { description: 'Should the group be visible to all student?', type: boolean, example: 'true', nullable: false }, directlyArchived: { description: 'Whether the group was explicitly marked as archived', type: boolean, example: 'true', nullable: false }, localizedTexts: { description: 'Localized names and descriptions', type: array, items: { }, nullable: false }, primaryAdminsIds: { description: 'IDs of users which are explicitly listed as direct admins of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, parentGroupId: { description: 'Identifier of the parent group (absent for a top-level group)', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, parentGroupsIds: { description: 'Identifications of groups in descending order.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, childGroups: { description: 'Identifications of child groups.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, privateData: { description: '', properties: { admins: { description: 'IDs of all users that have admin privileges to this group (including inherited)', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, supervisors: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, observers: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, students: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, instanceId: { description: '', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, hasValidLicence: { description: '', type: boolean, example: 'true', nullable: false }, assignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, shadowAssignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, publicStats: { description: '', type: boolean, example: 'true', nullable: false }, detaining: { description: '', type: boolean, example: 'true', nullable: false }, threshold: { description: '', type: number, example: '0.1', nullable: false }, pointsLimit: { description: '', type: integer, example: '0', nullable: false }, bindings: { description: '', type: array, items: { }, nullable: false }, examBegin: { description: '', type: integer, example: '1740135333', nullable: false }, examEnd: { description: '', type: integer, example: '1740135333', nullable: false }, examLockStrict: { description: '', type: boolean, example: 'true', nullable: false }, exams: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false }, permissionHints: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false } + type: object delete: summary: 'Change the group back to regular group (remove information about an exam).' description: 'Change the group back to regular group (remove information about an exam).' @@ -2497,7 +2557,19 @@ paths: nullable: false responses: '200': - description: 'Placeholder response' + description: 'Response data' + content: + application/json: + schema: + required: + - success + - code + - payload + properties: + success: { description: '', type: boolean, example: 'true', nullable: false } + code: { description: '', type: integer, example: '0', nullable: false } + payload: { description: '', properties: { id: { description: 'An identifier of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, externalId: { description: 'An informative, human readable identifier of the group', type: string, example: text, nullable: true }, organizational: { description: 'Whether the group is organizational (no assignments nor students).', type: boolean, example: 'true', nullable: false }, exam: { description: 'Whether the group is an exam group.', type: boolean, example: 'true', nullable: false }, archived: { description: 'Whether the group is archived', type: boolean, example: 'true', nullable: false }, public: { description: 'Should the group be visible to all student?', type: boolean, example: 'true', nullable: false }, directlyArchived: { description: 'Whether the group was explicitly marked as archived', type: boolean, example: 'true', nullable: false }, localizedTexts: { description: 'Localized names and descriptions', type: array, items: { }, nullable: false }, primaryAdminsIds: { description: 'IDs of users which are explicitly listed as direct admins of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, parentGroupId: { description: 'Identifier of the parent group (absent for a top-level group)', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, parentGroupsIds: { description: 'Identifications of groups in descending order.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, childGroups: { description: 'Identifications of child groups.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, privateData: { description: '', properties: { admins: { description: 'IDs of all users that have admin privileges to this group (including inherited)', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, supervisors: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, observers: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, students: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, instanceId: { description: '', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, hasValidLicence: { description: '', type: boolean, example: 'true', nullable: false }, assignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, shadowAssignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, publicStats: { description: '', type: boolean, example: 'true', nullable: false }, detaining: { description: '', type: boolean, example: 'true', nullable: false }, threshold: { description: '', type: number, example: '0.1', nullable: false }, pointsLimit: { description: '', type: integer, example: '0', nullable: false }, bindings: { description: '', type: array, items: { }, nullable: false }, examBegin: { description: '', type: integer, example: '1740135333', nullable: false }, examEnd: { description: '', type: integer, example: '1740135333', nullable: false }, examLockStrict: { description: '', type: boolean, example: 'true', nullable: false }, exams: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false }, permissionHints: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false } + type: object '/v1/groups/{id}/exam/{examId}': get: summary: 'Retrieve a list of locks for given exam' @@ -2618,7 +2690,19 @@ paths: nullable: false responses: '200': - description: 'Placeholder response' + description: 'Response data' + content: + application/json: + schema: + required: + - success + - code + - payload + properties: + success: { description: '', type: boolean, example: 'true', nullable: false } + code: { description: '', type: integer, example: '0', nullable: false } + payload: { description: '', properties: { id: { description: 'An identifier of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, externalId: { description: 'An informative, human readable identifier of the group', type: string, example: text, nullable: true }, organizational: { description: 'Whether the group is organizational (no assignments nor students).', type: boolean, example: 'true', nullable: false }, exam: { description: 'Whether the group is an exam group.', type: boolean, example: 'true', nullable: false }, archived: { description: 'Whether the group is archived', type: boolean, example: 'true', nullable: false }, public: { description: 'Should the group be visible to all student?', type: boolean, example: 'true', nullable: false }, directlyArchived: { description: 'Whether the group was explicitly marked as archived', type: boolean, example: 'true', nullable: false }, localizedTexts: { description: 'Localized names and descriptions', type: array, items: { }, nullable: false }, primaryAdminsIds: { description: 'IDs of users which are explicitly listed as direct admins of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, parentGroupId: { description: 'Identifier of the parent group (absent for a top-level group)', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, parentGroupsIds: { description: 'Identifications of groups in descending order.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, childGroups: { description: 'Identifications of child groups.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, privateData: { description: '', properties: { admins: { description: 'IDs of all users that have admin privileges to this group (including inherited)', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, supervisors: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, observers: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, students: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, instanceId: { description: '', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, hasValidLicence: { description: '', type: boolean, example: 'true', nullable: false }, assignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, shadowAssignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, publicStats: { description: '', type: boolean, example: 'true', nullable: false }, detaining: { description: '', type: boolean, example: 'true', nullable: false }, threshold: { description: '', type: number, example: '0.1', nullable: false }, pointsLimit: { description: '', type: integer, example: '0', nullable: false }, bindings: { description: '', type: array, items: { }, nullable: false }, examBegin: { description: '', type: integer, example: '1740135333', nullable: false }, examEnd: { description: '', type: integer, example: '1740135333', nullable: false }, examLockStrict: { description: '', type: boolean, example: 'true', nullable: false }, exams: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false }, permissionHints: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false } + type: object delete: summary: 'Remove a student from a group' description: 'Remove a student from a group' @@ -2643,7 +2727,19 @@ paths: nullable: false responses: '200': - description: 'Placeholder response' + description: 'Response data' + content: + application/json: + schema: + required: + - success + - code + - payload + properties: + success: { description: '', type: boolean, example: 'true', nullable: false } + code: { description: '', type: integer, example: '0', nullable: false } + payload: { description: '', properties: { id: { description: 'An identifier of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, externalId: { description: 'An informative, human readable identifier of the group', type: string, example: text, nullable: true }, organizational: { description: 'Whether the group is organizational (no assignments nor students).', type: boolean, example: 'true', nullable: false }, exam: { description: 'Whether the group is an exam group.', type: boolean, example: 'true', nullable: false }, archived: { description: 'Whether the group is archived', type: boolean, example: 'true', nullable: false }, public: { description: 'Should the group be visible to all student?', type: boolean, example: 'true', nullable: false }, directlyArchived: { description: 'Whether the group was explicitly marked as archived', type: boolean, example: 'true', nullable: false }, localizedTexts: { description: 'Localized names and descriptions', type: array, items: { }, nullable: false }, primaryAdminsIds: { description: 'IDs of users which are explicitly listed as direct admins of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, parentGroupId: { description: 'Identifier of the parent group (absent for a top-level group)', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, parentGroupsIds: { description: 'Identifications of groups in descending order.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, childGroups: { description: 'Identifications of child groups.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, privateData: { description: '', properties: { admins: { description: 'IDs of all users that have admin privileges to this group (including inherited)', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, supervisors: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, observers: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, students: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, instanceId: { description: '', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, hasValidLicence: { description: '', type: boolean, example: 'true', nullable: false }, assignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, shadowAssignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, publicStats: { description: '', type: boolean, example: 'true', nullable: false }, detaining: { description: '', type: boolean, example: 'true', nullable: false }, threshold: { description: '', type: number, example: '0.1', nullable: false }, pointsLimit: { description: '', type: integer, example: '0', nullable: false }, bindings: { description: '', type: array, items: { }, nullable: false }, examBegin: { description: '', type: integer, example: '1740135333', nullable: false }, examEnd: { description: '', type: integer, example: '1740135333', nullable: false }, examLockStrict: { description: '', type: boolean, example: 'true', nullable: false }, exams: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false }, permissionHints: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false } + type: object '/v1/groups/{id}/students/{userId}/solutions': get: summary: 'Get all solutions of a single student from all assignments in a group' @@ -2778,7 +2874,19 @@ paths: type: object responses: '200': - description: 'Placeholder response' + description: 'Response data' + content: + application/json: + schema: + required: + - success + - code + - payload + properties: + success: { description: '', type: boolean, example: 'true', nullable: false } + code: { description: '', type: integer, example: '0', nullable: false } + payload: { description: '', properties: { id: { description: 'An identifier of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, externalId: { description: 'An informative, human readable identifier of the group', type: string, example: text, nullable: true }, organizational: { description: 'Whether the group is organizational (no assignments nor students).', type: boolean, example: 'true', nullable: false }, exam: { description: 'Whether the group is an exam group.', type: boolean, example: 'true', nullable: false }, archived: { description: 'Whether the group is archived', type: boolean, example: 'true', nullable: false }, public: { description: 'Should the group be visible to all student?', type: boolean, example: 'true', nullable: false }, directlyArchived: { description: 'Whether the group was explicitly marked as archived', type: boolean, example: 'true', nullable: false }, localizedTexts: { description: 'Localized names and descriptions', type: array, items: { }, nullable: false }, primaryAdminsIds: { description: 'IDs of users which are explicitly listed as direct admins of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, parentGroupId: { description: 'Identifier of the parent group (absent for a top-level group)', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, parentGroupsIds: { description: 'Identifications of groups in descending order.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, childGroups: { description: 'Identifications of child groups.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, privateData: { description: '', properties: { admins: { description: 'IDs of all users that have admin privileges to this group (including inherited)', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, supervisors: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, observers: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, students: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, instanceId: { description: '', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, hasValidLicence: { description: '', type: boolean, example: 'true', nullable: false }, assignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, shadowAssignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, publicStats: { description: '', type: boolean, example: 'true', nullable: false }, detaining: { description: '', type: boolean, example: 'true', nullable: false }, threshold: { description: '', type: number, example: '0.1', nullable: false }, pointsLimit: { description: '', type: integer, example: '0', nullable: false }, bindings: { description: '', type: array, items: { }, nullable: false }, examBegin: { description: '', type: integer, example: '1740135333', nullable: false }, examEnd: { description: '', type: integer, example: '1740135333', nullable: false }, examLockStrict: { description: '', type: boolean, example: 'true', nullable: false }, exams: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false }, permissionHints: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false } + type: object delete: summary: 'Remove a member (other than student) from a group' description: 'Remove a member (other than student) from a group' @@ -2803,7 +2911,19 @@ paths: nullable: false responses: '200': - description: 'Placeholder response' + description: 'Response data' + content: + application/json: + schema: + required: + - success + - code + - payload + properties: + success: { description: '', type: boolean, example: 'true', nullable: false } + code: { description: '', type: integer, example: '0', nullable: false } + payload: { description: '', properties: { id: { description: 'An identifier of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, externalId: { description: 'An informative, human readable identifier of the group', type: string, example: text, nullable: true }, organizational: { description: 'Whether the group is organizational (no assignments nor students).', type: boolean, example: 'true', nullable: false }, exam: { description: 'Whether the group is an exam group.', type: boolean, example: 'true', nullable: false }, archived: { description: 'Whether the group is archived', type: boolean, example: 'true', nullable: false }, public: { description: 'Should the group be visible to all student?', type: boolean, example: 'true', nullable: false }, directlyArchived: { description: 'Whether the group was explicitly marked as archived', type: boolean, example: 'true', nullable: false }, localizedTexts: { description: 'Localized names and descriptions', type: array, items: { }, nullable: false }, primaryAdminsIds: { description: 'IDs of users which are explicitly listed as direct admins of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, parentGroupId: { description: 'Identifier of the parent group (absent for a top-level group)', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, parentGroupsIds: { description: 'Identifications of groups in descending order.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, childGroups: { description: 'Identifications of child groups.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, privateData: { description: '', properties: { admins: { description: 'IDs of all users that have admin privileges to this group (including inherited)', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, supervisors: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, observers: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, students: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, instanceId: { description: '', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, hasValidLicence: { description: '', type: boolean, example: 'true', nullable: false }, assignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, shadowAssignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, publicStats: { description: '', type: boolean, example: 'true', nullable: false }, detaining: { description: '', type: boolean, example: 'true', nullable: false }, threshold: { description: '', type: number, example: '0.1', nullable: false }, pointsLimit: { description: '', type: integer, example: '0', nullable: false }, bindings: { description: '', type: array, items: { }, nullable: false }, examBegin: { description: '', type: integer, example: '1740135333', nullable: false }, examEnd: { description: '', type: integer, example: '1740135333', nullable: false }, examLockStrict: { description: '', type: boolean, example: 'true', nullable: false }, exams: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false }, permissionHints: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false } + type: object '/v1/groups/{id}/assignments': get: summary: 'Get all exercise assignments for a group' From 30d4f96e67063af38444e441ba933c027cfcf2d6 Mon Sep 17 00:00:00 2001 From: Vojtech Kloda Date: Sun, 20 Jul 2025 15:43:33 +0200 Subject: [PATCH 12/19] changed filters validation --- app/V1Module/presenters/UsersPresenter.php | 4 +- .../FormatDefinitions/UserFilterFormat.php | 60 +++++++++++++++++++ .../MetaFormats/Validators/VObject.php | 5 ++ .../Swagger/AnnotationParameterData.php | 1 + docs/swagger.yaml | 21 ++++++- 5 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 app/helpers/MetaFormats/FormatDefinitions/UserFilterFormat.php diff --git a/app/V1Module/presenters/UsersPresenter.php b/app/V1Module/presenters/UsersPresenter.php index aa7c07a66..0e5d663ca 100644 --- a/app/V1Module/presenters/UsersPresenter.php +++ b/app/V1Module/presenters/UsersPresenter.php @@ -5,6 +5,7 @@ use App\Helpers\MetaFormats\Attributes\Post; use App\Helpers\MetaFormats\Attributes\Query; use App\Helpers\MetaFormats\Attributes\Path; +use App\Helpers\MetaFormats\FormatDefinitions\UserFilterFormat; use App\Helpers\MetaFormats\Validators\VArray; use App\Helpers\MetaFormats\Validators\VBool; use App\Helpers\MetaFormats\Validators\VEmail; @@ -29,6 +30,7 @@ use App\Exceptions\BadRequestException; use App\Helpers\EmailVerificationHelper; use App\Helpers\AnonymizationHelper; +use App\Helpers\MetaFormats\Validators\VObject; use App\Model\View\GroupViewFactory; use App\Model\View\InstanceViewFactory; use App\Model\View\UserViewFactory; @@ -130,7 +132,7 @@ public function checkDefault() required: false, nullable: true, )] - #[Query("filters", new VArray(), "Named filters that prune the result.", required: false, nullable: true)] + #[Query("filters", new VObject(UserFilterFormat::class), "Named filters that prune the result.", required: false, nullable: true)] #[Query( "locale", new VString(), diff --git a/app/helpers/MetaFormats/FormatDefinitions/UserFilterFormat.php b/app/helpers/MetaFormats/FormatDefinitions/UserFilterFormat.php new file mode 100644 index 000000000..2f68d45db --- /dev/null +++ b/app/helpers/MetaFormats/FormatDefinitions/UserFilterFormat.php @@ -0,0 +1,60 @@ +$offset); + } + + /** + * Offset to retrieve + * @param mixed $offset The offset to retrieve. + * @return mixed Can return all value types. + */ + public function offsetGet(mixed $offset): mixed + { + return $this->$offset ?? null; + } + + /** + * Offset to set + * @param mixed $offset The offset to assign the value to. + * @param mixed $value The value to set. + */ + public function offsetSet(mixed $offset, mixed $value): void + { + $this->$offset = $value; + } + + /** + * Offset to unset + * @param mixed $offset The offset to unset. + */ + public function offsetUnset(mixed $offset): void + { + $this->$offset = null; + } +} diff --git a/app/helpers/MetaFormats/Validators/VObject.php b/app/helpers/MetaFormats/Validators/VObject.php index 892233b9c..9bee783be 100644 --- a/app/helpers/MetaFormats/Validators/VObject.php +++ b/app/helpers/MetaFormats/Validators/VObject.php @@ -28,6 +28,11 @@ public function __construct(string $format, bool $strict = true) public function validate(mixed $value): bool { + // query parameters can contain arrays + if (!$this->strict && is_array($value)) { + return true; + } + // fine-grained checking is done in the properties return $value instanceof MetaFormat; } diff --git a/app/helpers/Swagger/AnnotationParameterData.php b/app/helpers/Swagger/AnnotationParameterData.php index 8d22599a1..390f0d14f 100644 --- a/app/helpers/Swagger/AnnotationParameterData.php +++ b/app/helpers/Swagger/AnnotationParameterData.php @@ -136,6 +136,7 @@ private function generateSchemaAnnotation(): string } $this->addArrayItemsIfArray($body); + $this->addObjectParamsIfObject($body); return $head . $body->toString(); } diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 25effac28..d857b6774 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -4624,9 +4624,26 @@ paths: description: 'Named filters that prune the result.' required: false schema: - type: array - items: { } + properties: + search: + description: '' + type: string + example: text + nullable: false + instanceId: + description: '' + type: string + example: text + nullable: false + roles: + description: '' + type: string + example: text + nullable: false + type: object nullable: true + style: deepObject + explode: true - name: locale in: query From 13f81a17090481295bc5d9fe6dc2d79d24c02d5e Mon Sep 17 00:00:00 2001 From: Vojtech Kloda Date: Sun, 20 Jul 2025 15:57:49 +0200 Subject: [PATCH 13/19] fixed tests --- tests/Validation/Validators.phpt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/Validation/Validators.phpt b/tests/Validation/Validators.phpt index 5d56ac2a1..f5f58e58b 100644 --- a/tests/Validation/Validators.phpt +++ b/tests/Validation/Validators.phpt @@ -248,10 +248,13 @@ class TestValidators extends Tester\TestCase public function testVObject() { // accepts all formats (content is not validated, that is done with the checkedAssign method) + // permissive validation accepts arrays as well $validator = new VObject(UserFormat::class); - $valid = [new UserFormat()]; - $invalid = [0, 1.2, -1, "", false, [], new VMixed()]; - self::validatorTester($validator, $valid, $invalid, $valid, $invalid); + $strictValid = [new UserFormat()]; + $strictInvalid = [0, 1.2, -1, "", false, [], new VMixed()]; + $permissiveValid = [new UserFormat(), [], [[]]]; + $permissiveInvalid = [0, 1.2, -1, "", false, new VMixed()]; + self::validatorTester($validator, $strictValid, $strictInvalid, $permissiveValid, $permissiveInvalid); } } From 0687948db9b24368cbed6a1261918ed18f9c1cd7 Mon Sep 17 00:00:00 2001 From: Vojtech Kloda Date: Sun, 20 Jul 2025 17:29:03 +0200 Subject: [PATCH 14/19] updated swagger generation --- app/helpers/Swagger/AnnotationParameterData.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/helpers/Swagger/AnnotationParameterData.php b/app/helpers/Swagger/AnnotationParameterData.php index 390f0d14f..cd9526ab6 100644 --- a/app/helpers/Swagger/AnnotationParameterData.php +++ b/app/helpers/Swagger/AnnotationParameterData.php @@ -153,6 +153,12 @@ public function toParameterAnnotation(): string $body->addKeyValue("in", $this->location); $body->addKeyValue("required", $this->required); + // add flags for query parameters with objects that make the client generate correct endpoints + if ($this->nestedObjectParameterData !== null) { + $body->addKeyValue("style", "deepObject"); + $body->addKeyValue("explode", true); + } + if ($this->description !== null) { $body->addKeyValue("description", $this->description); } From add0904730138ba56ebc56c9666b0c2660226596 Mon Sep 17 00:00:00 2001 From: Vojtech Kloda Date: Wed, 23 Jul 2025 11:13:59 +0200 Subject: [PATCH 15/19] loose parameters can now use VObject to reference Format classes for validation --- .../presenters/base/BasePresenter.php | 18 ++++++- .../FormatDefinitions/UserFilterFormat.php | 50 +++---------------- app/helpers/MetaFormats/RequestParamData.php | 10 +++- .../MetaFormats/Validators/VObject.php | 5 -- tests/Validation/Validators.phpt | 9 ++-- 5 files changed, 34 insertions(+), 58 deletions(-) diff --git a/app/V1Module/presenters/base/BasePresenter.php b/app/V1Module/presenters/base/BasePresenter.php index a0f62c712..b076881be 100644 --- a/app/V1Module/presenters/base/BasePresenter.php +++ b/app/V1Module/presenters/base/BasePresenter.php @@ -3,6 +3,8 @@ namespace App\V1Module\Presenters; use App\Helpers\MetaFormats\MetaFormatHelper; +use App\Helpers\MetaFormats\Validators\VArray; +use App\Helpers\MetaFormats\Validators\VObject; use App\Helpers\Pagination; use App\Model\Entity\User; use App\Security\AccessToken; @@ -241,8 +243,20 @@ private function processParamsLoose(array $paramData) foreach ($paramData as $param) { $paramValue = $this->getValueFromParamData($param); - // this throws when it does not conform - $param->conformsToDefinition($paramValue); + // special case when the request parameter is an object (the raw request data is an array that needs to + // be mapped to the Format definition) + $mainValidator = $param->validators[0]; + if ($mainValidator instanceof VObject) { + // first check whether the raw request data is an array + $param->conformsToDefinition($paramValue, [new VArray(strict: false)]); + + // map the content of the array to the format + // (the created format instance is not used, because that feature is reserved for POST bodies) + $format = $mainValidator->format; + $this->processParamsFormat($format, $paramValue); + } else { + $param->conformsToDefinition($paramValue); + } } } diff --git a/app/helpers/MetaFormats/FormatDefinitions/UserFilterFormat.php b/app/helpers/MetaFormats/FormatDefinitions/UserFilterFormat.php index 2f68d45db..b192199e8 100644 --- a/app/helpers/MetaFormats/FormatDefinitions/UserFilterFormat.php +++ b/app/helpers/MetaFormats/FormatDefinitions/UserFilterFormat.php @@ -4,57 +4,21 @@ use App\Helpers\MetaFormats\Attributes\Format; use App\Helpers\MetaFormats\MetaFormat; -use App\Helpers\MetaFormats\Attributes\FPost; use App\Helpers\MetaFormats\Attributes\FQuery; -use App\Helpers\MetaFormats\Validators\VBool; -use App\Helpers\MetaFormats\Validators\VInt; -use App\Helpers\MetaFormats\Validators\VMixed; use App\Helpers\MetaFormats\Validators\VString; -use ArrayAccess; +/** + * Format definition used by the GroupsPresenter. + */ #[Format(UserFilterFormat::class)] -class UserFilterFormat extends MetaFormat implements ArrayAccess +class UserFilterFormat extends MetaFormat { - #[FQuery(new VString(), required: false)] + #[FQuery(new VString(), "A search pattern", required: false)] public ?string $search; - #[FQuery(new VString(), required: false)] + #[FQuery(new VString(), "The instance ID of the user", required: false)] public ?string $instanceId; - #[FQuery(new VString(), required: false)] + #[FQuery(new VString(), "The roles of the user", required: false)] public ?string $roles; - - public function offsetExists(mixed $offset): bool - { - return isset($this->$offset); - } - - /** - * Offset to retrieve - * @param mixed $offset The offset to retrieve. - * @return mixed Can return all value types. - */ - public function offsetGet(mixed $offset): mixed - { - return $this->$offset ?? null; - } - - /** - * Offset to set - * @param mixed $offset The offset to assign the value to. - * @param mixed $value The value to set. - */ - public function offsetSet(mixed $offset, mixed $value): void - { - $this->$offset = $value; - } - - /** - * Offset to unset - * @param mixed $offset The offset to unset. - */ - public function offsetUnset(mixed $offset): void - { - $this->$offset = null; - } } diff --git a/app/helpers/MetaFormats/RequestParamData.php b/app/helpers/MetaFormats/RequestParamData.php index c5b87ac79..e89825edb 100644 --- a/app/helpers/MetaFormats/RequestParamData.php +++ b/app/helpers/MetaFormats/RequestParamData.php @@ -45,10 +45,16 @@ public function __construct( * Checks whether a value meets this definition. If the definition is not met, an exception is thrown. * The method has no return value. * @param mixed $value The value to be checked. + * @param ?array $validators If set, these validators will be used instead of the ones defined for the parameter. * @throws InvalidApiArgumentException Thrown when the value does not meet the definition. */ - public function conformsToDefinition(mixed $value) + public function conformsToDefinition(mixed $value, ?array $validators = null) { + // use custom validators if provided + if ($validators == null) { + $validators = $this->validators; + } + // check if null if ($value === null) { // optional parameters can be null @@ -70,7 +76,7 @@ public function conformsToDefinition(mixed $value) } // use every provided validator - foreach ($this->validators as $validator) { + foreach ($validators as $validator) { if (!$validator->validate($value)) { $type = $validator::SWAGGER_TYPE; throw new InvalidApiArgumentException( diff --git a/app/helpers/MetaFormats/Validators/VObject.php b/app/helpers/MetaFormats/Validators/VObject.php index 9bee783be..892233b9c 100644 --- a/app/helpers/MetaFormats/Validators/VObject.php +++ b/app/helpers/MetaFormats/Validators/VObject.php @@ -28,11 +28,6 @@ public function __construct(string $format, bool $strict = true) public function validate(mixed $value): bool { - // query parameters can contain arrays - if (!$this->strict && is_array($value)) { - return true; - } - // fine-grained checking is done in the properties return $value instanceof MetaFormat; } diff --git a/tests/Validation/Validators.phpt b/tests/Validation/Validators.phpt index f5f58e58b..5d56ac2a1 100644 --- a/tests/Validation/Validators.phpt +++ b/tests/Validation/Validators.phpt @@ -248,13 +248,10 @@ class TestValidators extends Tester\TestCase public function testVObject() { // accepts all formats (content is not validated, that is done with the checkedAssign method) - // permissive validation accepts arrays as well $validator = new VObject(UserFormat::class); - $strictValid = [new UserFormat()]; - $strictInvalid = [0, 1.2, -1, "", false, [], new VMixed()]; - $permissiveValid = [new UserFormat(), [], [[]]]; - $permissiveInvalid = [0, 1.2, -1, "", false, new VMixed()]; - self::validatorTester($validator, $strictValid, $strictInvalid, $permissiveValid, $permissiveInvalid); + $valid = [new UserFormat()]; + $invalid = [0, 1.2, -1, "", false, [], new VMixed()]; + self::validatorTester($validator, $valid, $invalid, $valid, $invalid); } } From 50523791624b62cba85025230d93e278c5fbcac5 Mon Sep 17 00:00:00 2001 From: Vojtech Kloda Date: Wed, 23 Jul 2025 11:19:05 +0200 Subject: [PATCH 16/19] changed roles type to array --- .../MetaFormats/FormatDefinitions/UserFilterFormat.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/helpers/MetaFormats/FormatDefinitions/UserFilterFormat.php b/app/helpers/MetaFormats/FormatDefinitions/UserFilterFormat.php index b192199e8..f7a5d6baa 100644 --- a/app/helpers/MetaFormats/FormatDefinitions/UserFilterFormat.php +++ b/app/helpers/MetaFormats/FormatDefinitions/UserFilterFormat.php @@ -5,6 +5,7 @@ use App\Helpers\MetaFormats\Attributes\Format; use App\Helpers\MetaFormats\MetaFormat; use App\Helpers\MetaFormats\Attributes\FQuery; +use App\Helpers\MetaFormats\Validators\VArray; use App\Helpers\MetaFormats\Validators\VString; /** @@ -19,6 +20,6 @@ class UserFilterFormat extends MetaFormat #[FQuery(new VString(), "The instance ID of the user", required: false)] public ?string $instanceId; - #[FQuery(new VString(), "The roles of the user", required: false)] - public ?string $roles; + #[FQuery(new VArray(new VString()), "The roles of the user", required: false)] + public ?array $roles; } From 95080cdb0b46977cfad34d0836f63e24d3146709 Mon Sep 17 00:00:00 2001 From: Vojtech Kloda Date: Wed, 23 Jul 2025 11:22:43 +0200 Subject: [PATCH 17/19] fixed too long line --- app/V1Module/presenters/UsersPresenter.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/V1Module/presenters/UsersPresenter.php b/app/V1Module/presenters/UsersPresenter.php index 0e5d663ca..37a6594b5 100644 --- a/app/V1Module/presenters/UsersPresenter.php +++ b/app/V1Module/presenters/UsersPresenter.php @@ -132,7 +132,13 @@ public function checkDefault() required: false, nullable: true, )] - #[Query("filters", new VObject(UserFilterFormat::class), "Named filters that prune the result.", required: false, nullable: true)] + #[Query( + "filters", + new VObject(UserFilterFormat::class), + "Named filters that prune the result.", + required: false, + nullable: true, + )] #[Query( "locale", new VString(), From fc7e7d16b82ced0a291da42f5c8eec6161fc9f2c Mon Sep 17 00:00:00 2001 From: Vojtech Kloda Date: Wed, 23 Jul 2025 11:35:03 +0200 Subject: [PATCH 18/19] added descriptions to formats --- .../GroupPrivateDataFormat.php | 32 ++++---- .../SuccessResponseFormat.php | 6 +- docs/swagger.yaml | 82 ++++++++++--------- 3 files changed, 61 insertions(+), 59 deletions(-) diff --git a/app/helpers/MetaFormats/FormatDefinitions/GroupPrivateDataFormat.php b/app/helpers/MetaFormats/FormatDefinitions/GroupPrivateDataFormat.php index 9414ff85b..97280e11e 100644 --- a/app/helpers/MetaFormats/FormatDefinitions/GroupPrivateDataFormat.php +++ b/app/helpers/MetaFormats/FormatDefinitions/GroupPrivateDataFormat.php @@ -21,51 +21,51 @@ class GroupPrivateDataFormat extends MetaFormat #[FPost(new VArray(new VUuid()), "IDs of all users that have admin privileges to this group (including inherited)")] public array $admins; - #[FPost(new VArray(new VUuid()))] + #[FPost(new VArray(new VUuid()), "IDs of all group supervisors")] public array $supervisors; - #[FPost(new VArray(new VUuid()))] + #[FPost(new VArray(new VUuid()), "IDs of all group observers")] public array $observers; - #[FPost(new VArray(new VUuid()))] + #[FPost(new VArray(new VUuid()), "IDs of the students of this group")] public array $students; - #[FPost(new VUuid(), required: false)] + #[FPost(new VUuid(), "The instance ID of the group", required: false)] public string $instanceId; - #[FPost(new VBool())] + #[FPost(new VBool(), "Whether the group has a valid license")] public bool $hasValidLicence; - #[FPost(new VArray(new VUuid()))] + #[FPost(new VArray(new VUuid()), "IDs of all group assignments")] public array $assignments; - #[FPost(new VArray(new VUuid()))] + #[FPost(new VArray(new VUuid()), "IDs of all group shadow assignments")] public array $shadowAssignments; - #[FPost(new VBool())] + #[FPost(new VBool(), "Whether the group statistics are public")] public bool $publicStats; - #[FPost(new VBool())] + #[FPost(new VBool(), "Whether the group is detaining")] public bool $detaining; - #[FPost(new VDouble(), required: false)] + #[FPost(new VDouble(), "The group assignment point threshold", required: false)] public ?float $threshold; - #[FPost(new VInt(), required: false)] + #[FPost(new VInt(), "The group points limit", required: false)] public ?int $pointsLimit; - #[FPost(new VArray())] + #[FPost(new VArray(), "Entities bound to the group")] public array $bindings; - #[FPost(new VTimestamp(), required: false)] + #[FPost(new VTimestamp(), "The time when the exam starts if there is an exam", required: false)] public ?int $examBegin; - #[FPost(new VTimestamp(), required: false)] + #[FPost(new VTimestamp(), "The time when the exam ends if there is an exam", required: false)] public ?int $examEnd; - #[FPost(new VBool(), required: false)] + #[FPost(new VBool(), "Whether there is a strict exam lock", required: false)] public ?bool $examLockStrict; - #[FPost(new VArray())] + #[FPost(new VArray(), "All group exams")] public array $exams; } diff --git a/app/helpers/MetaFormats/FormatDefinitions/SuccessResponseFormat.php b/app/helpers/MetaFormats/FormatDefinitions/SuccessResponseFormat.php index c3966d03f..9aa72c47b 100644 --- a/app/helpers/MetaFormats/FormatDefinitions/SuccessResponseFormat.php +++ b/app/helpers/MetaFormats/FormatDefinitions/SuccessResponseFormat.php @@ -16,12 +16,12 @@ #[Format(SuccessResponseFormat::class)] class SuccessResponseFormat extends MetaFormat { - #[FPost(new VBool())] + #[FPost(new VBool(), "Whether the request was processed successfully.")] public bool $success; - #[FPost(new VInt())] + #[FPost(new VInt(), "HTTP response code.")] public int $code; - #[FPost(new VMixed())] + #[FPost(new VMixed(), "The payload of the response.")] public mixed $payload; } diff --git a/docs/swagger.yaml b/docs/swagger.yaml index d857b6774..aa4086e65 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -2226,9 +2226,9 @@ paths: - code - payload properties: - success: { description: '', type: boolean, example: 'true', nullable: false } - code: { description: '', type: integer, example: '0', nullable: false } - payload: { description: '', properties: { id: { description: 'An identifier of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, externalId: { description: 'An informative, human readable identifier of the group', type: string, example: text, nullable: true }, organizational: { description: 'Whether the group is organizational (no assignments nor students).', type: boolean, example: 'true', nullable: false }, exam: { description: 'Whether the group is an exam group.', type: boolean, example: 'true', nullable: false }, archived: { description: 'Whether the group is archived', type: boolean, example: 'true', nullable: false }, public: { description: 'Should the group be visible to all student?', type: boolean, example: 'true', nullable: false }, directlyArchived: { description: 'Whether the group was explicitly marked as archived', type: boolean, example: 'true', nullable: false }, localizedTexts: { description: 'Localized names and descriptions', type: array, items: { }, nullable: false }, primaryAdminsIds: { description: 'IDs of users which are explicitly listed as direct admins of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, parentGroupId: { description: 'Identifier of the parent group (absent for a top-level group)', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, parentGroupsIds: { description: 'Identifications of groups in descending order.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, childGroups: { description: 'Identifications of child groups.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, privateData: { description: '', properties: { admins: { description: 'IDs of all users that have admin privileges to this group (including inherited)', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, supervisors: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, observers: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, students: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, instanceId: { description: '', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, hasValidLicence: { description: '', type: boolean, example: 'true', nullable: false }, assignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, shadowAssignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, publicStats: { description: '', type: boolean, example: 'true', nullable: false }, detaining: { description: '', type: boolean, example: 'true', nullable: false }, threshold: { description: '', type: number, example: '0.1', nullable: false }, pointsLimit: { description: '', type: integer, example: '0', nullable: false }, bindings: { description: '', type: array, items: { }, nullable: false }, examBegin: { description: '', type: integer, example: '1740135333', nullable: false }, examEnd: { description: '', type: integer, example: '1740135333', nullable: false }, examLockStrict: { description: '', type: boolean, example: 'true', nullable: false }, exams: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false }, permissionHints: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false } + success: { description: 'Whether the request was processed successfully.', type: boolean, example: 'true', nullable: false } + code: { description: 'HTTP response code.', type: integer, example: '0', nullable: false } + payload: { description: 'The payload of the response.', properties: { id: { description: 'An identifier of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, externalId: { description: 'An informative, human readable identifier of the group', type: string, example: text, nullable: true }, organizational: { description: 'Whether the group is organizational (no assignments nor students).', type: boolean, example: 'true', nullable: false }, exam: { description: 'Whether the group is an exam group.', type: boolean, example: 'true', nullable: false }, archived: { description: 'Whether the group is archived', type: boolean, example: 'true', nullable: false }, public: { description: 'Should the group be visible to all student?', type: boolean, example: 'true', nullable: false }, directlyArchived: { description: 'Whether the group was explicitly marked as archived', type: boolean, example: 'true', nullable: false }, localizedTexts: { description: 'Localized names and descriptions', type: array, items: { }, nullable: false }, primaryAdminsIds: { description: 'IDs of users which are explicitly listed as direct admins of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, parentGroupId: { description: 'Identifier of the parent group (absent for a top-level group)', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, parentGroupsIds: { description: 'Identifications of groups in descending order.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, childGroups: { description: 'Identifications of child groups.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, privateData: { description: '', properties: { admins: { description: 'IDs of all users that have admin privileges to this group (including inherited)', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, supervisors: { description: 'IDs of all group supervisors', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, observers: { description: 'IDs of all group observers', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, students: { description: 'IDs of the students of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, instanceId: { description: 'The instance ID of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, hasValidLicence: { description: 'Whether the group has a valid license', type: boolean, example: 'true', nullable: false }, assignments: { description: 'IDs of all group assignments', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, shadowAssignments: { description: 'IDs of all group shadow assignments', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, publicStats: { description: 'Whether the group statistics are public', type: boolean, example: 'true', nullable: false }, detaining: { description: 'Whether the group is detaining', type: boolean, example: 'true', nullable: false }, threshold: { description: 'The group assignment point threshold', type: number, example: '0.1', nullable: false }, pointsLimit: { description: 'The group points limit', type: integer, example: '0', nullable: false }, bindings: { description: 'Entities bound to the group', type: array, items: { }, nullable: false }, examBegin: { description: 'The time when the exam starts if there is an exam', type: integer, example: '1740135333', nullable: false }, examEnd: { description: 'The time when the exam ends if there is an exam', type: integer, example: '1740135333', nullable: false }, examLockStrict: { description: 'Whether there is a strict exam lock', type: boolean, example: 'true', nullable: false }, exams: { description: 'All group exams', type: array, items: { }, nullable: false } }, type: object, nullable: false }, permissionHints: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false } type: object /v1/groups/validate-add-group-data: post: @@ -2290,9 +2290,9 @@ paths: - code - payload properties: - success: { description: '', type: boolean, example: 'true', nullable: false } - code: { description: '', type: integer, example: '0', nullable: false } - payload: { description: '', properties: { id: { description: 'An identifier of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, externalId: { description: 'An informative, human readable identifier of the group', type: string, example: text, nullable: true }, organizational: { description: 'Whether the group is organizational (no assignments nor students).', type: boolean, example: 'true', nullable: false }, exam: { description: 'Whether the group is an exam group.', type: boolean, example: 'true', nullable: false }, archived: { description: 'Whether the group is archived', type: boolean, example: 'true', nullable: false }, public: { description: 'Should the group be visible to all student?', type: boolean, example: 'true', nullable: false }, directlyArchived: { description: 'Whether the group was explicitly marked as archived', type: boolean, example: 'true', nullable: false }, localizedTexts: { description: 'Localized names and descriptions', type: array, items: { }, nullable: false }, primaryAdminsIds: { description: 'IDs of users which are explicitly listed as direct admins of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, parentGroupId: { description: 'Identifier of the parent group (absent for a top-level group)', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, parentGroupsIds: { description: 'Identifications of groups in descending order.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, childGroups: { description: 'Identifications of child groups.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, privateData: { description: '', properties: { admins: { description: 'IDs of all users that have admin privileges to this group (including inherited)', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, supervisors: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, observers: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, students: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, instanceId: { description: '', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, hasValidLicence: { description: '', type: boolean, example: 'true', nullable: false }, assignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, shadowAssignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, publicStats: { description: '', type: boolean, example: 'true', nullable: false }, detaining: { description: '', type: boolean, example: 'true', nullable: false }, threshold: { description: '', type: number, example: '0.1', nullable: false }, pointsLimit: { description: '', type: integer, example: '0', nullable: false }, bindings: { description: '', type: array, items: { }, nullable: false }, examBegin: { description: '', type: integer, example: '1740135333', nullable: false }, examEnd: { description: '', type: integer, example: '1740135333', nullable: false }, examLockStrict: { description: '', type: boolean, example: 'true', nullable: false }, exams: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false }, permissionHints: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false } + success: { description: 'Whether the request was processed successfully.', type: boolean, example: 'true', nullable: false } + code: { description: 'HTTP response code.', type: integer, example: '0', nullable: false } + payload: { description: 'The payload of the response.', properties: { id: { description: 'An identifier of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, externalId: { description: 'An informative, human readable identifier of the group', type: string, example: text, nullable: true }, organizational: { description: 'Whether the group is organizational (no assignments nor students).', type: boolean, example: 'true', nullable: false }, exam: { description: 'Whether the group is an exam group.', type: boolean, example: 'true', nullable: false }, archived: { description: 'Whether the group is archived', type: boolean, example: 'true', nullable: false }, public: { description: 'Should the group be visible to all student?', type: boolean, example: 'true', nullable: false }, directlyArchived: { description: 'Whether the group was explicitly marked as archived', type: boolean, example: 'true', nullable: false }, localizedTexts: { description: 'Localized names and descriptions', type: array, items: { }, nullable: false }, primaryAdminsIds: { description: 'IDs of users which are explicitly listed as direct admins of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, parentGroupId: { description: 'Identifier of the parent group (absent for a top-level group)', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, parentGroupsIds: { description: 'Identifications of groups in descending order.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, childGroups: { description: 'Identifications of child groups.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, privateData: { description: '', properties: { admins: { description: 'IDs of all users that have admin privileges to this group (including inherited)', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, supervisors: { description: 'IDs of all group supervisors', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, observers: { description: 'IDs of all group observers', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, students: { description: 'IDs of the students of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, instanceId: { description: 'The instance ID of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, hasValidLicence: { description: 'Whether the group has a valid license', type: boolean, example: 'true', nullable: false }, assignments: { description: 'IDs of all group assignments', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, shadowAssignments: { description: 'IDs of all group shadow assignments', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, publicStats: { description: 'Whether the group statistics are public', type: boolean, example: 'true', nullable: false }, detaining: { description: 'Whether the group is detaining', type: boolean, example: 'true', nullable: false }, threshold: { description: 'The group assignment point threshold', type: number, example: '0.1', nullable: false }, pointsLimit: { description: 'The group points limit', type: integer, example: '0', nullable: false }, bindings: { description: 'Entities bound to the group', type: array, items: { }, nullable: false }, examBegin: { description: 'The time when the exam starts if there is an exam', type: integer, example: '1740135333', nullable: false }, examEnd: { description: 'The time when the exam ends if there is an exam', type: integer, example: '1740135333', nullable: false }, examLockStrict: { description: 'Whether there is a strict exam lock', type: boolean, example: 'true', nullable: false }, exams: { description: 'All group exams', type: array, items: { }, nullable: false } }, type: object, nullable: false }, permissionHints: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false } type: object post: summary: 'Update group info' @@ -2363,9 +2363,9 @@ paths: - code - payload properties: - success: { description: '', type: boolean, example: 'true', nullable: false } - code: { description: '', type: integer, example: '0', nullable: false } - payload: { description: '', properties: { id: { description: 'An identifier of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, externalId: { description: 'An informative, human readable identifier of the group', type: string, example: text, nullable: true }, organizational: { description: 'Whether the group is organizational (no assignments nor students).', type: boolean, example: 'true', nullable: false }, exam: { description: 'Whether the group is an exam group.', type: boolean, example: 'true', nullable: false }, archived: { description: 'Whether the group is archived', type: boolean, example: 'true', nullable: false }, public: { description: 'Should the group be visible to all student?', type: boolean, example: 'true', nullable: false }, directlyArchived: { description: 'Whether the group was explicitly marked as archived', type: boolean, example: 'true', nullable: false }, localizedTexts: { description: 'Localized names and descriptions', type: array, items: { }, nullable: false }, primaryAdminsIds: { description: 'IDs of users which are explicitly listed as direct admins of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, parentGroupId: { description: 'Identifier of the parent group (absent for a top-level group)', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, parentGroupsIds: { description: 'Identifications of groups in descending order.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, childGroups: { description: 'Identifications of child groups.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, privateData: { description: '', properties: { admins: { description: 'IDs of all users that have admin privileges to this group (including inherited)', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, supervisors: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, observers: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, students: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, instanceId: { description: '', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, hasValidLicence: { description: '', type: boolean, example: 'true', nullable: false }, assignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, shadowAssignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, publicStats: { description: '', type: boolean, example: 'true', nullable: false }, detaining: { description: '', type: boolean, example: 'true', nullable: false }, threshold: { description: '', type: number, example: '0.1', nullable: false }, pointsLimit: { description: '', type: integer, example: '0', nullable: false }, bindings: { description: '', type: array, items: { }, nullable: false }, examBegin: { description: '', type: integer, example: '1740135333', nullable: false }, examEnd: { description: '', type: integer, example: '1740135333', nullable: false }, examLockStrict: { description: '', type: boolean, example: 'true', nullable: false }, exams: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false }, permissionHints: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false } + success: { description: 'Whether the request was processed successfully.', type: boolean, example: 'true', nullable: false } + code: { description: 'HTTP response code.', type: integer, example: '0', nullable: false } + payload: { description: 'The payload of the response.', properties: { id: { description: 'An identifier of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, externalId: { description: 'An informative, human readable identifier of the group', type: string, example: text, nullable: true }, organizational: { description: 'Whether the group is organizational (no assignments nor students).', type: boolean, example: 'true', nullable: false }, exam: { description: 'Whether the group is an exam group.', type: boolean, example: 'true', nullable: false }, archived: { description: 'Whether the group is archived', type: boolean, example: 'true', nullable: false }, public: { description: 'Should the group be visible to all student?', type: boolean, example: 'true', nullable: false }, directlyArchived: { description: 'Whether the group was explicitly marked as archived', type: boolean, example: 'true', nullable: false }, localizedTexts: { description: 'Localized names and descriptions', type: array, items: { }, nullable: false }, primaryAdminsIds: { description: 'IDs of users which are explicitly listed as direct admins of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, parentGroupId: { description: 'Identifier of the parent group (absent for a top-level group)', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, parentGroupsIds: { description: 'Identifications of groups in descending order.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, childGroups: { description: 'Identifications of child groups.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, privateData: { description: '', properties: { admins: { description: 'IDs of all users that have admin privileges to this group (including inherited)', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, supervisors: { description: 'IDs of all group supervisors', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, observers: { description: 'IDs of all group observers', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, students: { description: 'IDs of the students of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, instanceId: { description: 'The instance ID of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, hasValidLicence: { description: 'Whether the group has a valid license', type: boolean, example: 'true', nullable: false }, assignments: { description: 'IDs of all group assignments', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, shadowAssignments: { description: 'IDs of all group shadow assignments', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, publicStats: { description: 'Whether the group statistics are public', type: boolean, example: 'true', nullable: false }, detaining: { description: 'Whether the group is detaining', type: boolean, example: 'true', nullable: false }, threshold: { description: 'The group assignment point threshold', type: number, example: '0.1', nullable: false }, pointsLimit: { description: 'The group points limit', type: integer, example: '0', nullable: false }, bindings: { description: 'Entities bound to the group', type: array, items: { }, nullable: false }, examBegin: { description: 'The time when the exam starts if there is an exam', type: integer, example: '1740135333', nullable: false }, examEnd: { description: 'The time when the exam ends if there is an exam', type: integer, example: '1740135333', nullable: false }, examLockStrict: { description: 'Whether there is a strict exam lock', type: boolean, example: 'true', nullable: false }, exams: { description: 'All group exams', type: array, items: { }, nullable: false } }, type: object, nullable: false }, permissionHints: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false } type: object delete: summary: 'Delete a group' @@ -2441,9 +2441,9 @@ paths: - code - payload properties: - success: { description: '', type: boolean, example: 'true', nullable: false } - code: { description: '', type: integer, example: '0', nullable: false } - payload: { description: '', properties: { id: { description: 'An identifier of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, externalId: { description: 'An informative, human readable identifier of the group', type: string, example: text, nullable: true }, organizational: { description: 'Whether the group is organizational (no assignments nor students).', type: boolean, example: 'true', nullable: false }, exam: { description: 'Whether the group is an exam group.', type: boolean, example: 'true', nullable: false }, archived: { description: 'Whether the group is archived', type: boolean, example: 'true', nullable: false }, public: { description: 'Should the group be visible to all student?', type: boolean, example: 'true', nullable: false }, directlyArchived: { description: 'Whether the group was explicitly marked as archived', type: boolean, example: 'true', nullable: false }, localizedTexts: { description: 'Localized names and descriptions', type: array, items: { }, nullable: false }, primaryAdminsIds: { description: 'IDs of users which are explicitly listed as direct admins of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, parentGroupId: { description: 'Identifier of the parent group (absent for a top-level group)', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, parentGroupsIds: { description: 'Identifications of groups in descending order.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, childGroups: { description: 'Identifications of child groups.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, privateData: { description: '', properties: { admins: { description: 'IDs of all users that have admin privileges to this group (including inherited)', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, supervisors: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, observers: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, students: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, instanceId: { description: '', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, hasValidLicence: { description: '', type: boolean, example: 'true', nullable: false }, assignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, shadowAssignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, publicStats: { description: '', type: boolean, example: 'true', nullable: false }, detaining: { description: '', type: boolean, example: 'true', nullable: false }, threshold: { description: '', type: number, example: '0.1', nullable: false }, pointsLimit: { description: '', type: integer, example: '0', nullable: false }, bindings: { description: '', type: array, items: { }, nullable: false }, examBegin: { description: '', type: integer, example: '1740135333', nullable: false }, examEnd: { description: '', type: integer, example: '1740135333', nullable: false }, examLockStrict: { description: '', type: boolean, example: 'true', nullable: false }, exams: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false }, permissionHints: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false } + success: { description: 'Whether the request was processed successfully.', type: boolean, example: 'true', nullable: false } + code: { description: 'HTTP response code.', type: integer, example: '0', nullable: false } + payload: { description: 'The payload of the response.', properties: { id: { description: 'An identifier of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, externalId: { description: 'An informative, human readable identifier of the group', type: string, example: text, nullable: true }, organizational: { description: 'Whether the group is organizational (no assignments nor students).', type: boolean, example: 'true', nullable: false }, exam: { description: 'Whether the group is an exam group.', type: boolean, example: 'true', nullable: false }, archived: { description: 'Whether the group is archived', type: boolean, example: 'true', nullable: false }, public: { description: 'Should the group be visible to all student?', type: boolean, example: 'true', nullable: false }, directlyArchived: { description: 'Whether the group was explicitly marked as archived', type: boolean, example: 'true', nullable: false }, localizedTexts: { description: 'Localized names and descriptions', type: array, items: { }, nullable: false }, primaryAdminsIds: { description: 'IDs of users which are explicitly listed as direct admins of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, parentGroupId: { description: 'Identifier of the parent group (absent for a top-level group)', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, parentGroupsIds: { description: 'Identifications of groups in descending order.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, childGroups: { description: 'Identifications of child groups.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, privateData: { description: '', properties: { admins: { description: 'IDs of all users that have admin privileges to this group (including inherited)', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, supervisors: { description: 'IDs of all group supervisors', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, observers: { description: 'IDs of all group observers', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, students: { description: 'IDs of the students of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, instanceId: { description: 'The instance ID of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, hasValidLicence: { description: 'Whether the group has a valid license', type: boolean, example: 'true', nullable: false }, assignments: { description: 'IDs of all group assignments', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, shadowAssignments: { description: 'IDs of all group shadow assignments', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, publicStats: { description: 'Whether the group statistics are public', type: boolean, example: 'true', nullable: false }, detaining: { description: 'Whether the group is detaining', type: boolean, example: 'true', nullable: false }, threshold: { description: 'The group assignment point threshold', type: number, example: '0.1', nullable: false }, pointsLimit: { description: 'The group points limit', type: integer, example: '0', nullable: false }, bindings: { description: 'Entities bound to the group', type: array, items: { }, nullable: false }, examBegin: { description: 'The time when the exam starts if there is an exam', type: integer, example: '1740135333', nullable: false }, examEnd: { description: 'The time when the exam ends if there is an exam', type: integer, example: '1740135333', nullable: false }, examLockStrict: { description: 'Whether there is a strict exam lock', type: boolean, example: 'true', nullable: false }, exams: { description: 'All group exams', type: array, items: { }, nullable: false } }, type: object, nullable: false }, permissionHints: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false } type: object '/v1/groups/{id}/archived': post: @@ -2484,9 +2484,9 @@ paths: - code - payload properties: - success: { description: '', type: boolean, example: 'true', nullable: false } - code: { description: '', type: integer, example: '0', nullable: false } - payload: { description: '', properties: { id: { description: 'An identifier of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, externalId: { description: 'An informative, human readable identifier of the group', type: string, example: text, nullable: true }, organizational: { description: 'Whether the group is organizational (no assignments nor students).', type: boolean, example: 'true', nullable: false }, exam: { description: 'Whether the group is an exam group.', type: boolean, example: 'true', nullable: false }, archived: { description: 'Whether the group is archived', type: boolean, example: 'true', nullable: false }, public: { description: 'Should the group be visible to all student?', type: boolean, example: 'true', nullable: false }, directlyArchived: { description: 'Whether the group was explicitly marked as archived', type: boolean, example: 'true', nullable: false }, localizedTexts: { description: 'Localized names and descriptions', type: array, items: { }, nullable: false }, primaryAdminsIds: { description: 'IDs of users which are explicitly listed as direct admins of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, parentGroupId: { description: 'Identifier of the parent group (absent for a top-level group)', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, parentGroupsIds: { description: 'Identifications of groups in descending order.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, childGroups: { description: 'Identifications of child groups.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, privateData: { description: '', properties: { admins: { description: 'IDs of all users that have admin privileges to this group (including inherited)', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, supervisors: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, observers: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, students: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, instanceId: { description: '', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, hasValidLicence: { description: '', type: boolean, example: 'true', nullable: false }, assignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, shadowAssignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, publicStats: { description: '', type: boolean, example: 'true', nullable: false }, detaining: { description: '', type: boolean, example: 'true', nullable: false }, threshold: { description: '', type: number, example: '0.1', nullable: false }, pointsLimit: { description: '', type: integer, example: '0', nullable: false }, bindings: { description: '', type: array, items: { }, nullable: false }, examBegin: { description: '', type: integer, example: '1740135333', nullable: false }, examEnd: { description: '', type: integer, example: '1740135333', nullable: false }, examLockStrict: { description: '', type: boolean, example: 'true', nullable: false }, exams: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false }, permissionHints: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false } + success: { description: 'Whether the request was processed successfully.', type: boolean, example: 'true', nullable: false } + code: { description: 'HTTP response code.', type: integer, example: '0', nullable: false } + payload: { description: 'The payload of the response.', properties: { id: { description: 'An identifier of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, externalId: { description: 'An informative, human readable identifier of the group', type: string, example: text, nullable: true }, organizational: { description: 'Whether the group is organizational (no assignments nor students).', type: boolean, example: 'true', nullable: false }, exam: { description: 'Whether the group is an exam group.', type: boolean, example: 'true', nullable: false }, archived: { description: 'Whether the group is archived', type: boolean, example: 'true', nullable: false }, public: { description: 'Should the group be visible to all student?', type: boolean, example: 'true', nullable: false }, directlyArchived: { description: 'Whether the group was explicitly marked as archived', type: boolean, example: 'true', nullable: false }, localizedTexts: { description: 'Localized names and descriptions', type: array, items: { }, nullable: false }, primaryAdminsIds: { description: 'IDs of users which are explicitly listed as direct admins of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, parentGroupId: { description: 'Identifier of the parent group (absent for a top-level group)', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, parentGroupsIds: { description: 'Identifications of groups in descending order.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, childGroups: { description: 'Identifications of child groups.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, privateData: { description: '', properties: { admins: { description: 'IDs of all users that have admin privileges to this group (including inherited)', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, supervisors: { description: 'IDs of all group supervisors', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, observers: { description: 'IDs of all group observers', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, students: { description: 'IDs of the students of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, instanceId: { description: 'The instance ID of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, hasValidLicence: { description: 'Whether the group has a valid license', type: boolean, example: 'true', nullable: false }, assignments: { description: 'IDs of all group assignments', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, shadowAssignments: { description: 'IDs of all group shadow assignments', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, publicStats: { description: 'Whether the group statistics are public', type: boolean, example: 'true', nullable: false }, detaining: { description: 'Whether the group is detaining', type: boolean, example: 'true', nullable: false }, threshold: { description: 'The group assignment point threshold', type: number, example: '0.1', nullable: false }, pointsLimit: { description: 'The group points limit', type: integer, example: '0', nullable: false }, bindings: { description: 'Entities bound to the group', type: array, items: { }, nullable: false }, examBegin: { description: 'The time when the exam starts if there is an exam', type: integer, example: '1740135333', nullable: false }, examEnd: { description: 'The time when the exam ends if there is an exam', type: integer, example: '1740135333', nullable: false }, examLockStrict: { description: 'Whether there is a strict exam lock', type: boolean, example: 'true', nullable: false }, exams: { description: 'All group exams', type: array, items: { }, nullable: false } }, type: object, nullable: false }, permissionHints: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false } type: object '/v1/groups/{id}/examPeriod': post: @@ -2537,9 +2537,9 @@ paths: - code - payload properties: - success: { description: '', type: boolean, example: 'true', nullable: false } - code: { description: '', type: integer, example: '0', nullable: false } - payload: { description: '', properties: { id: { description: 'An identifier of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, externalId: { description: 'An informative, human readable identifier of the group', type: string, example: text, nullable: true }, organizational: { description: 'Whether the group is organizational (no assignments nor students).', type: boolean, example: 'true', nullable: false }, exam: { description: 'Whether the group is an exam group.', type: boolean, example: 'true', nullable: false }, archived: { description: 'Whether the group is archived', type: boolean, example: 'true', nullable: false }, public: { description: 'Should the group be visible to all student?', type: boolean, example: 'true', nullable: false }, directlyArchived: { description: 'Whether the group was explicitly marked as archived', type: boolean, example: 'true', nullable: false }, localizedTexts: { description: 'Localized names and descriptions', type: array, items: { }, nullable: false }, primaryAdminsIds: { description: 'IDs of users which are explicitly listed as direct admins of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, parentGroupId: { description: 'Identifier of the parent group (absent for a top-level group)', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, parentGroupsIds: { description: 'Identifications of groups in descending order.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, childGroups: { description: 'Identifications of child groups.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, privateData: { description: '', properties: { admins: { description: 'IDs of all users that have admin privileges to this group (including inherited)', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, supervisors: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, observers: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, students: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, instanceId: { description: '', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, hasValidLicence: { description: '', type: boolean, example: 'true', nullable: false }, assignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, shadowAssignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, publicStats: { description: '', type: boolean, example: 'true', nullable: false }, detaining: { description: '', type: boolean, example: 'true', nullable: false }, threshold: { description: '', type: number, example: '0.1', nullable: false }, pointsLimit: { description: '', type: integer, example: '0', nullable: false }, bindings: { description: '', type: array, items: { }, nullable: false }, examBegin: { description: '', type: integer, example: '1740135333', nullable: false }, examEnd: { description: '', type: integer, example: '1740135333', nullable: false }, examLockStrict: { description: '', type: boolean, example: 'true', nullable: false }, exams: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false }, permissionHints: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false } + success: { description: 'Whether the request was processed successfully.', type: boolean, example: 'true', nullable: false } + code: { description: 'HTTP response code.', type: integer, example: '0', nullable: false } + payload: { description: 'The payload of the response.', properties: { id: { description: 'An identifier of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, externalId: { description: 'An informative, human readable identifier of the group', type: string, example: text, nullable: true }, organizational: { description: 'Whether the group is organizational (no assignments nor students).', type: boolean, example: 'true', nullable: false }, exam: { description: 'Whether the group is an exam group.', type: boolean, example: 'true', nullable: false }, archived: { description: 'Whether the group is archived', type: boolean, example: 'true', nullable: false }, public: { description: 'Should the group be visible to all student?', type: boolean, example: 'true', nullable: false }, directlyArchived: { description: 'Whether the group was explicitly marked as archived', type: boolean, example: 'true', nullable: false }, localizedTexts: { description: 'Localized names and descriptions', type: array, items: { }, nullable: false }, primaryAdminsIds: { description: 'IDs of users which are explicitly listed as direct admins of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, parentGroupId: { description: 'Identifier of the parent group (absent for a top-level group)', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, parentGroupsIds: { description: 'Identifications of groups in descending order.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, childGroups: { description: 'Identifications of child groups.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, privateData: { description: '', properties: { admins: { description: 'IDs of all users that have admin privileges to this group (including inherited)', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, supervisors: { description: 'IDs of all group supervisors', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, observers: { description: 'IDs of all group observers', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, students: { description: 'IDs of the students of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, instanceId: { description: 'The instance ID of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, hasValidLicence: { description: 'Whether the group has a valid license', type: boolean, example: 'true', nullable: false }, assignments: { description: 'IDs of all group assignments', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, shadowAssignments: { description: 'IDs of all group shadow assignments', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, publicStats: { description: 'Whether the group statistics are public', type: boolean, example: 'true', nullable: false }, detaining: { description: 'Whether the group is detaining', type: boolean, example: 'true', nullable: false }, threshold: { description: 'The group assignment point threshold', type: number, example: '0.1', nullable: false }, pointsLimit: { description: 'The group points limit', type: integer, example: '0', nullable: false }, bindings: { description: 'Entities bound to the group', type: array, items: { }, nullable: false }, examBegin: { description: 'The time when the exam starts if there is an exam', type: integer, example: '1740135333', nullable: false }, examEnd: { description: 'The time when the exam ends if there is an exam', type: integer, example: '1740135333', nullable: false }, examLockStrict: { description: 'Whether there is a strict exam lock', type: boolean, example: 'true', nullable: false }, exams: { description: 'All group exams', type: array, items: { }, nullable: false } }, type: object, nullable: false }, permissionHints: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false } type: object delete: summary: 'Change the group back to regular group (remove information about an exam).' @@ -2566,9 +2566,9 @@ paths: - code - payload properties: - success: { description: '', type: boolean, example: 'true', nullable: false } - code: { description: '', type: integer, example: '0', nullable: false } - payload: { description: '', properties: { id: { description: 'An identifier of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, externalId: { description: 'An informative, human readable identifier of the group', type: string, example: text, nullable: true }, organizational: { description: 'Whether the group is organizational (no assignments nor students).', type: boolean, example: 'true', nullable: false }, exam: { description: 'Whether the group is an exam group.', type: boolean, example: 'true', nullable: false }, archived: { description: 'Whether the group is archived', type: boolean, example: 'true', nullable: false }, public: { description: 'Should the group be visible to all student?', type: boolean, example: 'true', nullable: false }, directlyArchived: { description: 'Whether the group was explicitly marked as archived', type: boolean, example: 'true', nullable: false }, localizedTexts: { description: 'Localized names and descriptions', type: array, items: { }, nullable: false }, primaryAdminsIds: { description: 'IDs of users which are explicitly listed as direct admins of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, parentGroupId: { description: 'Identifier of the parent group (absent for a top-level group)', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, parentGroupsIds: { description: 'Identifications of groups in descending order.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, childGroups: { description: 'Identifications of child groups.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, privateData: { description: '', properties: { admins: { description: 'IDs of all users that have admin privileges to this group (including inherited)', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, supervisors: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, observers: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, students: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, instanceId: { description: '', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, hasValidLicence: { description: '', type: boolean, example: 'true', nullable: false }, assignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, shadowAssignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, publicStats: { description: '', type: boolean, example: 'true', nullable: false }, detaining: { description: '', type: boolean, example: 'true', nullable: false }, threshold: { description: '', type: number, example: '0.1', nullable: false }, pointsLimit: { description: '', type: integer, example: '0', nullable: false }, bindings: { description: '', type: array, items: { }, nullable: false }, examBegin: { description: '', type: integer, example: '1740135333', nullable: false }, examEnd: { description: '', type: integer, example: '1740135333', nullable: false }, examLockStrict: { description: '', type: boolean, example: 'true', nullable: false }, exams: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false }, permissionHints: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false } + success: { description: 'Whether the request was processed successfully.', type: boolean, example: 'true', nullable: false } + code: { description: 'HTTP response code.', type: integer, example: '0', nullable: false } + payload: { description: 'The payload of the response.', properties: { id: { description: 'An identifier of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, externalId: { description: 'An informative, human readable identifier of the group', type: string, example: text, nullable: true }, organizational: { description: 'Whether the group is organizational (no assignments nor students).', type: boolean, example: 'true', nullable: false }, exam: { description: 'Whether the group is an exam group.', type: boolean, example: 'true', nullable: false }, archived: { description: 'Whether the group is archived', type: boolean, example: 'true', nullable: false }, public: { description: 'Should the group be visible to all student?', type: boolean, example: 'true', nullable: false }, directlyArchived: { description: 'Whether the group was explicitly marked as archived', type: boolean, example: 'true', nullable: false }, localizedTexts: { description: 'Localized names and descriptions', type: array, items: { }, nullable: false }, primaryAdminsIds: { description: 'IDs of users which are explicitly listed as direct admins of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, parentGroupId: { description: 'Identifier of the parent group (absent for a top-level group)', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, parentGroupsIds: { description: 'Identifications of groups in descending order.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, childGroups: { description: 'Identifications of child groups.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, privateData: { description: '', properties: { admins: { description: 'IDs of all users that have admin privileges to this group (including inherited)', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, supervisors: { description: 'IDs of all group supervisors', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, observers: { description: 'IDs of all group observers', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, students: { description: 'IDs of the students of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, instanceId: { description: 'The instance ID of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, hasValidLicence: { description: 'Whether the group has a valid license', type: boolean, example: 'true', nullable: false }, assignments: { description: 'IDs of all group assignments', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, shadowAssignments: { description: 'IDs of all group shadow assignments', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, publicStats: { description: 'Whether the group statistics are public', type: boolean, example: 'true', nullable: false }, detaining: { description: 'Whether the group is detaining', type: boolean, example: 'true', nullable: false }, threshold: { description: 'The group assignment point threshold', type: number, example: '0.1', nullable: false }, pointsLimit: { description: 'The group points limit', type: integer, example: '0', nullable: false }, bindings: { description: 'Entities bound to the group', type: array, items: { }, nullable: false }, examBegin: { description: 'The time when the exam starts if there is an exam', type: integer, example: '1740135333', nullable: false }, examEnd: { description: 'The time when the exam ends if there is an exam', type: integer, example: '1740135333', nullable: false }, examLockStrict: { description: 'Whether there is a strict exam lock', type: boolean, example: 'true', nullable: false }, exams: { description: 'All group exams', type: array, items: { }, nullable: false } }, type: object, nullable: false }, permissionHints: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false } type: object '/v1/groups/{id}/exam/{examId}': get: @@ -2699,9 +2699,9 @@ paths: - code - payload properties: - success: { description: '', type: boolean, example: 'true', nullable: false } - code: { description: '', type: integer, example: '0', nullable: false } - payload: { description: '', properties: { id: { description: 'An identifier of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, externalId: { description: 'An informative, human readable identifier of the group', type: string, example: text, nullable: true }, organizational: { description: 'Whether the group is organizational (no assignments nor students).', type: boolean, example: 'true', nullable: false }, exam: { description: 'Whether the group is an exam group.', type: boolean, example: 'true', nullable: false }, archived: { description: 'Whether the group is archived', type: boolean, example: 'true', nullable: false }, public: { description: 'Should the group be visible to all student?', type: boolean, example: 'true', nullable: false }, directlyArchived: { description: 'Whether the group was explicitly marked as archived', type: boolean, example: 'true', nullable: false }, localizedTexts: { description: 'Localized names and descriptions', type: array, items: { }, nullable: false }, primaryAdminsIds: { description: 'IDs of users which are explicitly listed as direct admins of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, parentGroupId: { description: 'Identifier of the parent group (absent for a top-level group)', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, parentGroupsIds: { description: 'Identifications of groups in descending order.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, childGroups: { description: 'Identifications of child groups.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, privateData: { description: '', properties: { admins: { description: 'IDs of all users that have admin privileges to this group (including inherited)', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, supervisors: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, observers: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, students: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, instanceId: { description: '', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, hasValidLicence: { description: '', type: boolean, example: 'true', nullable: false }, assignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, shadowAssignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, publicStats: { description: '', type: boolean, example: 'true', nullable: false }, detaining: { description: '', type: boolean, example: 'true', nullable: false }, threshold: { description: '', type: number, example: '0.1', nullable: false }, pointsLimit: { description: '', type: integer, example: '0', nullable: false }, bindings: { description: '', type: array, items: { }, nullable: false }, examBegin: { description: '', type: integer, example: '1740135333', nullable: false }, examEnd: { description: '', type: integer, example: '1740135333', nullable: false }, examLockStrict: { description: '', type: boolean, example: 'true', nullable: false }, exams: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false }, permissionHints: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false } + success: { description: 'Whether the request was processed successfully.', type: boolean, example: 'true', nullable: false } + code: { description: 'HTTP response code.', type: integer, example: '0', nullable: false } + payload: { description: 'The payload of the response.', properties: { id: { description: 'An identifier of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, externalId: { description: 'An informative, human readable identifier of the group', type: string, example: text, nullable: true }, organizational: { description: 'Whether the group is organizational (no assignments nor students).', type: boolean, example: 'true', nullable: false }, exam: { description: 'Whether the group is an exam group.', type: boolean, example: 'true', nullable: false }, archived: { description: 'Whether the group is archived', type: boolean, example: 'true', nullable: false }, public: { description: 'Should the group be visible to all student?', type: boolean, example: 'true', nullable: false }, directlyArchived: { description: 'Whether the group was explicitly marked as archived', type: boolean, example: 'true', nullable: false }, localizedTexts: { description: 'Localized names and descriptions', type: array, items: { }, nullable: false }, primaryAdminsIds: { description: 'IDs of users which are explicitly listed as direct admins of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, parentGroupId: { description: 'Identifier of the parent group (absent for a top-level group)', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, parentGroupsIds: { description: 'Identifications of groups in descending order.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, childGroups: { description: 'Identifications of child groups.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, privateData: { description: '', properties: { admins: { description: 'IDs of all users that have admin privileges to this group (including inherited)', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, supervisors: { description: 'IDs of all group supervisors', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, observers: { description: 'IDs of all group observers', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, students: { description: 'IDs of the students of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, instanceId: { description: 'The instance ID of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, hasValidLicence: { description: 'Whether the group has a valid license', type: boolean, example: 'true', nullable: false }, assignments: { description: 'IDs of all group assignments', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, shadowAssignments: { description: 'IDs of all group shadow assignments', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, publicStats: { description: 'Whether the group statistics are public', type: boolean, example: 'true', nullable: false }, detaining: { description: 'Whether the group is detaining', type: boolean, example: 'true', nullable: false }, threshold: { description: 'The group assignment point threshold', type: number, example: '0.1', nullable: false }, pointsLimit: { description: 'The group points limit', type: integer, example: '0', nullable: false }, bindings: { description: 'Entities bound to the group', type: array, items: { }, nullable: false }, examBegin: { description: 'The time when the exam starts if there is an exam', type: integer, example: '1740135333', nullable: false }, examEnd: { description: 'The time when the exam ends if there is an exam', type: integer, example: '1740135333', nullable: false }, examLockStrict: { description: 'Whether there is a strict exam lock', type: boolean, example: 'true', nullable: false }, exams: { description: 'All group exams', type: array, items: { }, nullable: false } }, type: object, nullable: false }, permissionHints: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false } type: object delete: summary: 'Remove a student from a group' @@ -2736,9 +2736,9 @@ paths: - code - payload properties: - success: { description: '', type: boolean, example: 'true', nullable: false } - code: { description: '', type: integer, example: '0', nullable: false } - payload: { description: '', properties: { id: { description: 'An identifier of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, externalId: { description: 'An informative, human readable identifier of the group', type: string, example: text, nullable: true }, organizational: { description: 'Whether the group is organizational (no assignments nor students).', type: boolean, example: 'true', nullable: false }, exam: { description: 'Whether the group is an exam group.', type: boolean, example: 'true', nullable: false }, archived: { description: 'Whether the group is archived', type: boolean, example: 'true', nullable: false }, public: { description: 'Should the group be visible to all student?', type: boolean, example: 'true', nullable: false }, directlyArchived: { description: 'Whether the group was explicitly marked as archived', type: boolean, example: 'true', nullable: false }, localizedTexts: { description: 'Localized names and descriptions', type: array, items: { }, nullable: false }, primaryAdminsIds: { description: 'IDs of users which are explicitly listed as direct admins of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, parentGroupId: { description: 'Identifier of the parent group (absent for a top-level group)', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, parentGroupsIds: { description: 'Identifications of groups in descending order.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, childGroups: { description: 'Identifications of child groups.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, privateData: { description: '', properties: { admins: { description: 'IDs of all users that have admin privileges to this group (including inherited)', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, supervisors: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, observers: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, students: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, instanceId: { description: '', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, hasValidLicence: { description: '', type: boolean, example: 'true', nullable: false }, assignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, shadowAssignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, publicStats: { description: '', type: boolean, example: 'true', nullable: false }, detaining: { description: '', type: boolean, example: 'true', nullable: false }, threshold: { description: '', type: number, example: '0.1', nullable: false }, pointsLimit: { description: '', type: integer, example: '0', nullable: false }, bindings: { description: '', type: array, items: { }, nullable: false }, examBegin: { description: '', type: integer, example: '1740135333', nullable: false }, examEnd: { description: '', type: integer, example: '1740135333', nullable: false }, examLockStrict: { description: '', type: boolean, example: 'true', nullable: false }, exams: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false }, permissionHints: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false } + success: { description: 'Whether the request was processed successfully.', type: boolean, example: 'true', nullable: false } + code: { description: 'HTTP response code.', type: integer, example: '0', nullable: false } + payload: { description: 'The payload of the response.', properties: { id: { description: 'An identifier of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, externalId: { description: 'An informative, human readable identifier of the group', type: string, example: text, nullable: true }, organizational: { description: 'Whether the group is organizational (no assignments nor students).', type: boolean, example: 'true', nullable: false }, exam: { description: 'Whether the group is an exam group.', type: boolean, example: 'true', nullable: false }, archived: { description: 'Whether the group is archived', type: boolean, example: 'true', nullable: false }, public: { description: 'Should the group be visible to all student?', type: boolean, example: 'true', nullable: false }, directlyArchived: { description: 'Whether the group was explicitly marked as archived', type: boolean, example: 'true', nullable: false }, localizedTexts: { description: 'Localized names and descriptions', type: array, items: { }, nullable: false }, primaryAdminsIds: { description: 'IDs of users which are explicitly listed as direct admins of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, parentGroupId: { description: 'Identifier of the parent group (absent for a top-level group)', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, parentGroupsIds: { description: 'Identifications of groups in descending order.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, childGroups: { description: 'Identifications of child groups.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, privateData: { description: '', properties: { admins: { description: 'IDs of all users that have admin privileges to this group (including inherited)', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, supervisors: { description: 'IDs of all group supervisors', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, observers: { description: 'IDs of all group observers', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, students: { description: 'IDs of the students of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, instanceId: { description: 'The instance ID of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, hasValidLicence: { description: 'Whether the group has a valid license', type: boolean, example: 'true', nullable: false }, assignments: { description: 'IDs of all group assignments', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, shadowAssignments: { description: 'IDs of all group shadow assignments', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, publicStats: { description: 'Whether the group statistics are public', type: boolean, example: 'true', nullable: false }, detaining: { description: 'Whether the group is detaining', type: boolean, example: 'true', nullable: false }, threshold: { description: 'The group assignment point threshold', type: number, example: '0.1', nullable: false }, pointsLimit: { description: 'The group points limit', type: integer, example: '0', nullable: false }, bindings: { description: 'Entities bound to the group', type: array, items: { }, nullable: false }, examBegin: { description: 'The time when the exam starts if there is an exam', type: integer, example: '1740135333', nullable: false }, examEnd: { description: 'The time when the exam ends if there is an exam', type: integer, example: '1740135333', nullable: false }, examLockStrict: { description: 'Whether there is a strict exam lock', type: boolean, example: 'true', nullable: false }, exams: { description: 'All group exams', type: array, items: { }, nullable: false } }, type: object, nullable: false }, permissionHints: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false } type: object '/v1/groups/{id}/students/{userId}/solutions': get: @@ -2883,9 +2883,9 @@ paths: - code - payload properties: - success: { description: '', type: boolean, example: 'true', nullable: false } - code: { description: '', type: integer, example: '0', nullable: false } - payload: { description: '', properties: { id: { description: 'An identifier of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, externalId: { description: 'An informative, human readable identifier of the group', type: string, example: text, nullable: true }, organizational: { description: 'Whether the group is organizational (no assignments nor students).', type: boolean, example: 'true', nullable: false }, exam: { description: 'Whether the group is an exam group.', type: boolean, example: 'true', nullable: false }, archived: { description: 'Whether the group is archived', type: boolean, example: 'true', nullable: false }, public: { description: 'Should the group be visible to all student?', type: boolean, example: 'true', nullable: false }, directlyArchived: { description: 'Whether the group was explicitly marked as archived', type: boolean, example: 'true', nullable: false }, localizedTexts: { description: 'Localized names and descriptions', type: array, items: { }, nullable: false }, primaryAdminsIds: { description: 'IDs of users which are explicitly listed as direct admins of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, parentGroupId: { description: 'Identifier of the parent group (absent for a top-level group)', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, parentGroupsIds: { description: 'Identifications of groups in descending order.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, childGroups: { description: 'Identifications of child groups.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, privateData: { description: '', properties: { admins: { description: 'IDs of all users that have admin privileges to this group (including inherited)', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, supervisors: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, observers: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, students: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, instanceId: { description: '', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, hasValidLicence: { description: '', type: boolean, example: 'true', nullable: false }, assignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, shadowAssignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, publicStats: { description: '', type: boolean, example: 'true', nullable: false }, detaining: { description: '', type: boolean, example: 'true', nullable: false }, threshold: { description: '', type: number, example: '0.1', nullable: false }, pointsLimit: { description: '', type: integer, example: '0', nullable: false }, bindings: { description: '', type: array, items: { }, nullable: false }, examBegin: { description: '', type: integer, example: '1740135333', nullable: false }, examEnd: { description: '', type: integer, example: '1740135333', nullable: false }, examLockStrict: { description: '', type: boolean, example: 'true', nullable: false }, exams: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false }, permissionHints: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false } + success: { description: 'Whether the request was processed successfully.', type: boolean, example: 'true', nullable: false } + code: { description: 'HTTP response code.', type: integer, example: '0', nullable: false } + payload: { description: 'The payload of the response.', properties: { id: { description: 'An identifier of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, externalId: { description: 'An informative, human readable identifier of the group', type: string, example: text, nullable: true }, organizational: { description: 'Whether the group is organizational (no assignments nor students).', type: boolean, example: 'true', nullable: false }, exam: { description: 'Whether the group is an exam group.', type: boolean, example: 'true', nullable: false }, archived: { description: 'Whether the group is archived', type: boolean, example: 'true', nullable: false }, public: { description: 'Should the group be visible to all student?', type: boolean, example: 'true', nullable: false }, directlyArchived: { description: 'Whether the group was explicitly marked as archived', type: boolean, example: 'true', nullable: false }, localizedTexts: { description: 'Localized names and descriptions', type: array, items: { }, nullable: false }, primaryAdminsIds: { description: 'IDs of users which are explicitly listed as direct admins of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, parentGroupId: { description: 'Identifier of the parent group (absent for a top-level group)', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, parentGroupsIds: { description: 'Identifications of groups in descending order.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, childGroups: { description: 'Identifications of child groups.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, privateData: { description: '', properties: { admins: { description: 'IDs of all users that have admin privileges to this group (including inherited)', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, supervisors: { description: 'IDs of all group supervisors', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, observers: { description: 'IDs of all group observers', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, students: { description: 'IDs of the students of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, instanceId: { description: 'The instance ID of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, hasValidLicence: { description: 'Whether the group has a valid license', type: boolean, example: 'true', nullable: false }, assignments: { description: 'IDs of all group assignments', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, shadowAssignments: { description: 'IDs of all group shadow assignments', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, publicStats: { description: 'Whether the group statistics are public', type: boolean, example: 'true', nullable: false }, detaining: { description: 'Whether the group is detaining', type: boolean, example: 'true', nullable: false }, threshold: { description: 'The group assignment point threshold', type: number, example: '0.1', nullable: false }, pointsLimit: { description: 'The group points limit', type: integer, example: '0', nullable: false }, bindings: { description: 'Entities bound to the group', type: array, items: { }, nullable: false }, examBegin: { description: 'The time when the exam starts if there is an exam', type: integer, example: '1740135333', nullable: false }, examEnd: { description: 'The time when the exam ends if there is an exam', type: integer, example: '1740135333', nullable: false }, examLockStrict: { description: 'Whether there is a strict exam lock', type: boolean, example: 'true', nullable: false }, exams: { description: 'All group exams', type: array, items: { }, nullable: false } }, type: object, nullable: false }, permissionHints: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false } type: object delete: summary: 'Remove a member (other than student) from a group' @@ -2920,9 +2920,9 @@ paths: - code - payload properties: - success: { description: '', type: boolean, example: 'true', nullable: false } - code: { description: '', type: integer, example: '0', nullable: false } - payload: { description: '', properties: { id: { description: 'An identifier of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, externalId: { description: 'An informative, human readable identifier of the group', type: string, example: text, nullable: true }, organizational: { description: 'Whether the group is organizational (no assignments nor students).', type: boolean, example: 'true', nullable: false }, exam: { description: 'Whether the group is an exam group.', type: boolean, example: 'true', nullable: false }, archived: { description: 'Whether the group is archived', type: boolean, example: 'true', nullable: false }, public: { description: 'Should the group be visible to all student?', type: boolean, example: 'true', nullable: false }, directlyArchived: { description: 'Whether the group was explicitly marked as archived', type: boolean, example: 'true', nullable: false }, localizedTexts: { description: 'Localized names and descriptions', type: array, items: { }, nullable: false }, primaryAdminsIds: { description: 'IDs of users which are explicitly listed as direct admins of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, parentGroupId: { description: 'Identifier of the parent group (absent for a top-level group)', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, parentGroupsIds: { description: 'Identifications of groups in descending order.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, childGroups: { description: 'Identifications of child groups.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, privateData: { description: '', properties: { admins: { description: 'IDs of all users that have admin privileges to this group (including inherited)', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, supervisors: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, observers: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, students: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, instanceId: { description: '', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, hasValidLicence: { description: '', type: boolean, example: 'true', nullable: false }, assignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, shadowAssignments: { description: '', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, publicStats: { description: '', type: boolean, example: 'true', nullable: false }, detaining: { description: '', type: boolean, example: 'true', nullable: false }, threshold: { description: '', type: number, example: '0.1', nullable: false }, pointsLimit: { description: '', type: integer, example: '0', nullable: false }, bindings: { description: '', type: array, items: { }, nullable: false }, examBegin: { description: '', type: integer, example: '1740135333', nullable: false }, examEnd: { description: '', type: integer, example: '1740135333', nullable: false }, examLockStrict: { description: '', type: boolean, example: 'true', nullable: false }, exams: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false }, permissionHints: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false } + success: { description: 'Whether the request was processed successfully.', type: boolean, example: 'true', nullable: false } + code: { description: 'HTTP response code.', type: integer, example: '0', nullable: false } + payload: { description: 'The payload of the response.', properties: { id: { description: 'An identifier of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, externalId: { description: 'An informative, human readable identifier of the group', type: string, example: text, nullable: true }, organizational: { description: 'Whether the group is organizational (no assignments nor students).', type: boolean, example: 'true', nullable: false }, exam: { description: 'Whether the group is an exam group.', type: boolean, example: 'true', nullable: false }, archived: { description: 'Whether the group is archived', type: boolean, example: 'true', nullable: false }, public: { description: 'Should the group be visible to all student?', type: boolean, example: 'true', nullable: false }, directlyArchived: { description: 'Whether the group was explicitly marked as archived', type: boolean, example: 'true', nullable: false }, localizedTexts: { description: 'Localized names and descriptions', type: array, items: { }, nullable: false }, primaryAdminsIds: { description: 'IDs of users which are explicitly listed as direct admins of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, parentGroupId: { description: 'Identifier of the parent group (absent for a top-level group)', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, parentGroupsIds: { description: 'Identifications of groups in descending order.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, childGroups: { description: 'Identifications of child groups.', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, privateData: { description: '', properties: { admins: { description: 'IDs of all users that have admin privileges to this group (including inherited)', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, supervisors: { description: 'IDs of all group supervisors', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, observers: { description: 'IDs of all group observers', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, students: { description: 'IDs of the students of this group', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, instanceId: { description: 'The instance ID of the group', type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000, nullable: false }, hasValidLicence: { description: 'Whether the group has a valid license', type: boolean, example: 'true', nullable: false }, assignments: { description: 'IDs of all group assignments', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, shadowAssignments: { description: 'IDs of all group shadow assignments', type: array, items: { type: string, pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', example: 10000000-2000-4000-8000-160000000000 }, nullable: false }, publicStats: { description: 'Whether the group statistics are public', type: boolean, example: 'true', nullable: false }, detaining: { description: 'Whether the group is detaining', type: boolean, example: 'true', nullable: false }, threshold: { description: 'The group assignment point threshold', type: number, example: '0.1', nullable: false }, pointsLimit: { description: 'The group points limit', type: integer, example: '0', nullable: false }, bindings: { description: 'Entities bound to the group', type: array, items: { }, nullable: false }, examBegin: { description: 'The time when the exam starts if there is an exam', type: integer, example: '1740135333', nullable: false }, examEnd: { description: 'The time when the exam ends if there is an exam', type: integer, example: '1740135333', nullable: false }, examLockStrict: { description: 'Whether there is a strict exam lock', type: boolean, example: 'true', nullable: false }, exams: { description: 'All group exams', type: array, items: { }, nullable: false } }, type: object, nullable: false }, permissionHints: { description: '', type: array, items: { }, nullable: false } }, type: object, nullable: false } type: object '/v1/groups/{id}/assignments': get: @@ -4623,27 +4623,29 @@ paths: in: query description: 'Named filters that prune the result.' required: false + style: deepObject + explode: true schema: properties: search: - description: '' + description: 'A search pattern' type: string example: text nullable: false instanceId: - description: '' + description: 'The instance ID of the user' type: string example: text nullable: false roles: - description: '' - type: string - example: text + description: 'The roles of the user' + type: array + items: + type: string + example: text nullable: false type: object nullable: true - style: deepObject - explode: true - name: locale in: query From 24f4e4f47ff893b8ea8fd47051d686f6f9353c74 Mon Sep 17 00:00:00 2001 From: Vojtech Kloda Date: Wed, 23 Jul 2025 16:23:11 +0200 Subject: [PATCH 19/19] improved descriptions --- .../GroupPrivateDataFormat.php | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/app/helpers/MetaFormats/FormatDefinitions/GroupPrivateDataFormat.php b/app/helpers/MetaFormats/FormatDefinitions/GroupPrivateDataFormat.php index 97280e11e..454e69617 100644 --- a/app/helpers/MetaFormats/FormatDefinitions/GroupPrivateDataFormat.php +++ b/app/helpers/MetaFormats/FormatDefinitions/GroupPrivateDataFormat.php @@ -30,10 +30,10 @@ class GroupPrivateDataFormat extends MetaFormat #[FPost(new VArray(new VUuid()), "IDs of the students of this group")] public array $students; - #[FPost(new VUuid(), "The instance ID of the group", required: false)] + #[FPost(new VUuid(), "ID of an instance in which the group belongs", required: false)] public string $instanceId; - #[FPost(new VBool(), "Whether the group has a valid license")] + #[FPost(new VBool(), "Whether the instance where the group belongs has a valid license")] public bool $hasValidLicence; #[FPost(new VArray(new VUuid()), "IDs of all group assignments")] @@ -42,30 +42,38 @@ class GroupPrivateDataFormat extends MetaFormat #[FPost(new VArray(new VUuid()), "IDs of all group shadow assignments")] public array $shadowAssignments; - #[FPost(new VBool(), "Whether the group statistics are public")] + #[FPost(new VBool(), "Whether the student's results are visible to other students")] public bool $publicStats; - #[FPost(new VBool(), "Whether the group is detaining")] + #[FPost(new VBool(), "Whether the group detains the students (so they can be released only by the teacher)")] public bool $detaining; - #[FPost(new VDouble(), "The group assignment point threshold", required: false)] + #[FPost( + new VDouble(), + "A relative number of points a student must receive from assignments to fulfill the requirements of the group", + required: false, + )] public ?float $threshold; - #[FPost(new VInt(), "The group points limit", required: false)] + #[FPost( + new VInt(), + "A minimal number of points that a student must receive to fulfill the group's requirements", + required: false, + )] public ?int $pointsLimit; #[FPost(new VArray(), "Entities bound to the group")] public array $bindings; - #[FPost(new VTimestamp(), "The time when the exam starts if there is an exam", required: false)] + #[FPost(new VTimestamp(), "The time when the exam starts if there is an exam scheduled", required: false)] public ?int $examBegin; - #[FPost(new VTimestamp(), "The time when the exam ends if there is an exam", required: false)] + #[FPost(new VTimestamp(), "The time when the exam ends if there is an exam scheduled", required: false)] public ?int $examEnd; - #[FPost(new VBool(), "Whether there is a strict exam lock", required: false)] + #[FPost(new VBool(), "Whether the scheduled exam requires a strict access lock", required: false)] public ?bool $examLockStrict; - #[FPost(new VArray(), "All group exams")] + #[FPost(new VArray(), "All past exams (with at least one student locked)")] public array $exams; }