]> source.dussan.org Git - nextcloud-server.git/commitdiff
allow anon text processing scheduling
authorJulien Veyssier <julien-nc@posteo.net>
Thu, 3 Aug 2023 21:11:29 +0000 (23:11 +0200)
committerJulien Veyssier <julien-nc@posteo.net>
Wed, 9 Aug 2023 12:58:02 +0000 (14:58 +0200)
add a textprocessing_tasks index
convert anotations to method attributes
refactor TP manager
add mapper methods

Signed-off-by: Julien Veyssier <julien-nc@posteo.net>
(cherry picked from commit 41b19cf969956fe57fcb35e3dee0200d5c29b6d7)
Signed-off-by: Julien Veyssier <julien-nc@posteo.net>
core/Controller/TextProcessingApiController.php
core/Migrations/Version28000Date20230803221055.php [new file with mode: 0644]
lib/composer/composer/autoload_classmap.php
lib/composer/composer/autoload_static.php
lib/private/TextProcessing/Db/Task.php
lib/private/TextProcessing/Db/TaskMapper.php
lib/private/TextProcessing/Manager.php
lib/public/TextProcessing/FreePromptTaskType.php
lib/public/TextProcessing/IManager.php

index 0f4c8c40b293020177dedd5648d8d2aaf757a425..c713a70481c0716b5ed8df5b870ca6821c2872a5 100644 (file)
@@ -27,7 +27,12 @@ declare(strict_types=1);
 namespace OC\Core\Controller;
 
 use InvalidArgumentException;
+use OCA\Core\ResponseDefinitions;
 use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\Attribute\AnonRateLimit;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
+use OCP\AppFramework\Http\Attribute\PublicPage;
+use OCP\AppFramework\Http\Attribute\UserRateLimit;
 use OCP\AppFramework\Http\DataResponse;
 use OCP\Common\Exception\NotFoundException;
 use OCP\IL10N;
@@ -41,15 +46,18 @@ use Psr\Container\ContainerInterface;
 use Psr\Container\NotFoundExceptionInterface;
 use Psr\Log\LoggerInterface;
 
+/**
+ * @psalm-import-type CoreTextProcessingTask from ResponseDefinitions
+ */
 class TextProcessingApiController extends \OCP\AppFramework\OCSController {
        public function __construct(
-               string           $appName,
-               IRequest         $request,
-               private IManager $languageModelManager,
-               private IL10N    $l,
-               private ?string  $userId,
+               string                     $appName,
+               IRequest                   $request,
+               private IManager           $textProcessingManager,
+               private IL10N              $l,
+               private ?string            $userId,
                private ContainerInterface $container,
-               private LoggerInterface $logger,
+               private LoggerInterface    $logger,
        ) {
                parent::__construct($appName, $request);
        }
@@ -57,11 +65,13 @@ class TextProcessingApiController extends \OCP\AppFramework\OCSController {
        /**
         * This endpoint returns all available LanguageModel task types
         *
-        * @PublicPage
+        * @return DataResponse<Http::STATUS_OK, array{types: array{id: string, name: string, description: string}[]}, array{}>
         */
+       #[PublicPage]
        public function taskTypes(): DataResponse {
-               $typeClasses = $this->languageModelManager->getAvailableTaskTypes();
+               $typeClasses = $this->textProcessingManager->getAvailableTaskTypes();
                $types = [];
+               /** @var string $typeClass */
                foreach ($typeClasses as $typeClass) {
                        try {
                                /** @var ITaskType $object */
@@ -85,10 +95,20 @@ class TextProcessingApiController extends \OCP\AppFramework\OCSController {
        /**
         * This endpoint allows scheduling a language model task
         *
-        * @PublicPage
-        * @UserRateThrottle(limit=20, period=120)
-        * @AnonRateThrottle(limit=5, period=120)
+        * @param string $input Input text
+        * @param string $type Type of the task
+        * @param string $appId ID of the app that will execute the task
+        * @param string $identifier An arbitrary identifier for the task
+        *
+        * @return DataResponse<Http::STATUS_OK, array{task: CoreTextProcessingTask}, array{}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_PRECONDITION_FAILED, array{message: string}, array{}>
+        *
+        * 200: Task scheduled successfully
+        * 400: Scheduling task is not possible
+        * 412: Scheduling task is not possible
         */
+       #[PublicPage]
+       #[UserRateLimit(limit: 20, period: 120)]
+       #[AnonRateLimit(limit: 5, period: 120)]
        public function schedule(string $input, string $type, string $appId, string $identifier = ''): DataResponse {
                try {
                        $task = new Task($type, $input, $appId, $this->userId, $identifier);
@@ -96,7 +116,7 @@ class TextProcessingApiController extends \OCP\AppFramework\OCSController {
                        return new DataResponse(['message' => $this->l->t('Requested task type does not exist')], Http::STATUS_BAD_REQUEST);
                }
                try {
-                       $this->languageModelManager->scheduleTask($task);
+                       $this->textProcessingManager->scheduleTask($task);
 
                        $json = $task->jsonSerialize();
 
@@ -112,16 +132,17 @@ class TextProcessingApiController extends \OCP\AppFramework\OCSController {
         * This endpoint allows checking the status and results of a task.
         * Tasks are removed 1 week after receiving their last update.
         *
-        * @PublicPage
         * @param int $id The id of the task
+        *
+        * @return DataResponse<Http::STATUS_OK, array{task: CoreTextProcessingTask}, array{}>|DataResponse<Http::STATUS_NOT_FOUND|Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
+        *
+        * 200: Task returned
+        * 404: Task not found
         */
+       #[PublicPage]
        public function getTask(int $id): DataResponse {
                try {
-                       $task = $this->languageModelManager->getTask($id);
-
-                       if ($this->userId !== $task->getUserId()) {
-                               return new DataResponse(['message' => $this->l->t('Task not found')], Http::STATUS_NOT_FOUND);
-                       }
+                       $task = $this->textProcessingManager->getUserTask($id, $this->userId);
 
                        $json = $task->jsonSerialize();
 
@@ -136,10 +157,8 @@ class TextProcessingApiController extends \OCP\AppFramework\OCSController {
        }
 
        /**
-        * This endpoint returns a list of tasks related with a specific appId and identifier
-        *
-        * @PublicPage
-        * @UserRateThrottle(limit=20, period=120)
+        * This endpoint returns a list of tasks of a user that are related
+        * with a specific appId and optionally with an identifier
         *
         * @param string $appId
         * @param string|null $identifier
@@ -147,14 +166,10 @@ class TextProcessingApiController extends \OCP\AppFramework\OCSController {
         *
         *  200: Task list returned
         */
+       #[NoAdminRequired]
        public function listTasksByApp(string $appId, ?string $identifier = null): DataResponse {
-               if ($this->userId === null) {
-                       return new DataResponse([
-                               'tasks' => [],
-                       ]);
-               }
                try {
-                       $tasks = $this->languageModelManager->getTasksByApp($this->userId, $appId, $identifier);
+                       $tasks = $this->textProcessingManager->getUserTasksByApp($this->userId, $appId, $identifier);
                        $json = array_map(static function (Task $task) {
                                return $task->jsonSerialize();
                        }, $tasks);
diff --git a/core/Migrations/Version28000Date20230803221055.php b/core/Migrations/Version28000Date20230803221055.php
new file mode 100644 (file)
index 0000000..8878a6f
--- /dev/null
@@ -0,0 +1,66 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2023 Julien Veyssier <julien-nc@posteo.net>
+ *
+ * @author Julien Veyssier <julien-nc@posteo.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\Core\Migrations;
+
+use Closure;
+use OCP\DB\ISchemaWrapper;
+use OCP\Migration\IOutput;
+use OCP\Migration\SimpleMigrationStep;
+
+/**
+ * Adjust textprocessing_tasks table
+ */
+class Version28000Date20230803221055 extends SimpleMigrationStep {
+       /**
+        * @param IOutput $output
+        * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
+        * @param array $options
+        * @return null|ISchemaWrapper
+        */
+       public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
+               /** @var ISchemaWrapper $schema */
+               $schema = $schemaClosure();
+               $changed = false;
+
+               if ($schema->hasTable('textprocessing_tasks')) {
+                       $table = $schema->getTable('textprocessing_tasks');
+
+                       $column = $table->getColumn('user_id');
+                       $column->setNotnull(false);
+
+                       $table->addIndex(['user_id', 'app_id', 'identifier'], 'tp_tasks_uid_appid_ident');
+
+                       $changed = true;
+               }
+
+               if ($changed) {
+                       return $schema;
+               }
+
+               return null;
+       }
+}
index 7de1df8e5fc2d79dddc77fd1b673d9cc806c8df5..ecf3466392f7d6153e7c081fb0a798de82f14fb2 100644 (file)
@@ -1133,6 +1133,7 @@ return array(
     'OC\\Core\\Migrations\\Version27000Date20230309104802' => $baseDir . '/core/Migrations/Version27000Date20230309104802.php',
     'OC\\Core\\Migrations\\Version28000Date20230616104802' => $baseDir . '/core/Migrations/Version28000Date20230616104802.php',
     'OC\\Core\\Migrations\\Version28000Date20230728104802' => $baseDir . '/core/Migrations/Version28000Date20230728104802.php',
+    'OC\\Core\\Migrations\\Version28000Date20230803221055' => $baseDir . '/core/Migrations/Version28000Date20230803221055.php',
     'OC\\Core\\Notification\\CoreNotifier' => $baseDir . '/core/Notification/CoreNotifier.php',
     'OC\\Core\\Service\\LoginFlowV2Service' => $baseDir . '/core/Service/LoginFlowV2Service.php',
     'OC\\DB\\Adapter' => $baseDir . '/lib/private/DB/Adapter.php',
index ecd68f980b2a0edfc94a4320c1a76947d8b2c28b..cbfb6bc018982543d6bd9ef285e56467a2c0d2e7 100644 (file)
@@ -1166,6 +1166,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
         'OC\\Core\\Migrations\\Version27000Date20230309104802' => __DIR__ . '/../../..' . '/core/Migrations/Version27000Date20230309104802.php',
         'OC\\Core\\Migrations\\Version28000Date20230616104802' => __DIR__ . '/../../..' . '/core/Migrations/Version28000Date20230616104802.php',
         'OC\\Core\\Migrations\\Version28000Date20230728104802' => __DIR__ . '/../../..' . '/core/Migrations/Version28000Date20230728104802.php',
+        'OC\\Core\\Migrations\\Version28000Date20230803221055' => __DIR__ . '/../../..' . '/core/Migrations/Version28000Date20230803221055.php',
         'OC\\Core\\Notification\\CoreNotifier' => __DIR__ . '/../../..' . '/core/Notification/CoreNotifier.php',
         'OC\\Core\\Service\\LoginFlowV2Service' => __DIR__ . '/../../..' . '/core/Service/LoginFlowV2Service.php',
         'OC\\DB\\Adapter' => __DIR__ . '/../../..' . '/lib/private/DB/Adapter.php',
index 8c2ddb74f1fb76ae287bda5ffe004e0426a2e659..9c6f16d11ae3beea557f65ee58e4376142f567fe 100644 (file)
@@ -39,11 +39,11 @@ use OCP\TextProcessing\Task as OCPTask;
  * @method string getOutput()
  * @method setStatus(int $type)
  * @method int getStatus()
- * @method setUserId(string $type)
- * @method string getuserId()
+ * @method setUserId(?string $userId)
+ * @method string|null getUserId()
  * @method setAppId(string $type)
  * @method string getAppId()
- * @method setIdentifier(string $type)
+ * @method setIdentifier(string $identifier)
  * @method string getIdentifier()
  */
 class Task extends Entity {
index cf78006d6d9e85f7214ebecc79f015b87fd64e27..6fde1e510b58e7e3c868f4fa108b97bb87391941 100644 (file)
@@ -59,6 +59,27 @@ class TaskMapper extends QBMapper {
                return $this->findEntity($qb);
        }
 
+       /**
+        * @param int $id
+        * @param string|null $userId
+        * @return Task
+        * @throws DoesNotExistException
+        * @throws Exception
+        * @throws MultipleObjectsReturnedException
+        */
+       public function findByIdAndUser(int $id, ?string $userId): Task {
+               $qb = $this->db->getQueryBuilder();
+               $qb->select(Task::$columns)
+                       ->from($this->tableName)
+                       ->where($qb->expr()->eq('id', $qb->createPositionalParameter($id)));
+               if ($userId === null) {
+                       $qb->andWhere($qb->expr()->isNull('user_id'));
+               } else {
+                       $qb->andWhere($qb->expr()->eq('user_id', $qb->createPositionalParameter($userId)));
+               }
+               return $this->findEntity($qb);
+       }
+
        /**
         * @param string $userId
         * @param string $appId
@@ -66,7 +87,7 @@ class TaskMapper extends QBMapper {
         * @return array
         * @throws Exception
         */
-       public function findByApp(string $userId, string $appId, ?string $identifier = null): array {
+       public function findUserTasksByApp(string $userId, string $appId, ?string $identifier = null): array {
                $qb = $this->db->getQueryBuilder();
                $qb->select(Task::$columns)
                        ->from($this->tableName)
index da38dc876a786853b1533a4d5b43ea7e144ae3c4..c8302f1e8df58ff56a19957ecd53643d342a1a1f 100644 (file)
@@ -178,6 +178,8 @@ class Manager implements IManager {
        }
 
        /**
+        * Get a task from its id
+        *
         * @param int $id The id of the task
         * @return OCPTask
         * @throws RuntimeException If the query failed
@@ -197,14 +199,41 @@ class Manager implements IManager {
        }
 
        /**
+        * Get a task from its user id and task id
+        * If userId is null, this can only get a task that was scheduled anonymously
+        *
+        * @param int $id The id of the task
+        * @param string|null $userId The user id that scheduled the task
+        * @return OCPTask
+        * @throws RuntimeException If the query failed
+        * @throws NotFoundException If the task could not be found
+        */
+       public function getUserTask(int $id, ?string $userId): OCPTask {
+               try {
+                       $taskEntity = $this->taskMapper->findByIdAndUser($id, $userId);
+                       return $taskEntity->toPublicTask();
+               } catch (DoesNotExistException $e) {
+                       throw new NotFoundException('Could not find task with the provided id and user id');
+               } catch (MultipleObjectsReturnedException $e) {
+                       throw new RuntimeException('Could not uniquely identify task with given id and user id', 0, $e);
+               } catch (Exception $e) {
+                       throw new RuntimeException('Failure while trying to find task by id and user id: ' . $e->getMessage(), 0, $e);
+               }
+       }
+
+       /**
+        * Get a list of tasks scheduled by a specific user for a specific app
+        * and optionally with a specific identifier.
+        * This cannot be used to get anonymously scheduled tasks
+        *
         * @param string $userId
         * @param string $appId
         * @param string|null $identifier
         * @return array
         */
-       public function getTasksByApp(string $userId, string $appId, ?string $identifier = null): array {
+       public function getUserTasksByApp(string $userId, string $appId, ?string $identifier = null): array {
                try {
-                       $taskEntities = $this->taskMapper->findByApp($userId, $appId, $identifier);
+                       $taskEntities = $this->taskMapper->findUserTasksByApp($userId, $appId, $identifier);
                        return array_map(static function (DbTask $taskEntity) {
                                return $taskEntity->toPublicTask();
                        }, $taskEntities);
index a1e52e268a37c22683cfc4165fb5f6731e93a2d3..2cb8d6b794647a47e6c8f76d6dccf88a384be3f0 100644 (file)
@@ -61,6 +61,6 @@ class FreePromptTaskType implements ITaskType {
         * @since 27.1.0
         */
        public function getDescription(): string {
-               return $this->l->t('Runs an arbitrary prompt through the built-in language model.');
+               return $this->l->t('Runs an arbitrary prompt through the language model.');
        }
 }
index c7b101ad51082b71157ed345475637b24a6024b8..00646528e68acb63cef73e08975c2b278fb0194e 100644 (file)
@@ -81,11 +81,22 @@ interface IManager {
         */
        public function getTask(int $id): Task;
 
+       /**
+        * @param int $id The id of the task
+        * @param string|null $userId The user id that scheduled the task
+        * @return Task
+        * @throws RuntimeException If the query failed
+        * @throws NotFoundException If the task could not be found
+        * @since 27.1.0
+        */
+       public function getUserTask(int $id, ?string $userId): Task;
+
        /**
         * @param string $userId
         * @param string $appId
         * @param string|null $identifier
         * @return array
+        * @since 27.1.0
         */
-       public function getTasksByApp(string $userId, string $appId, ?string $identifier = null): array;
+       public function getUserTasksByApp(string $userId, string $appId, ?string $identifier = null): array;
 }