TextToImageApiController.php 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
  5. * SPDX-License-Identifier: AGPL-3.0-or-later
  6. */
  7. namespace OC\Core\Controller;
  8. use OC\Core\ResponseDefinitions;
  9. use OC\Files\AppData\AppData;
  10. use OCP\AppFramework\Http;
  11. use OCP\AppFramework\Http\Attribute\AnonRateLimit;
  12. use OCP\AppFramework\Http\Attribute\ApiRoute;
  13. use OCP\AppFramework\Http\Attribute\BruteForceProtection;
  14. use OCP\AppFramework\Http\Attribute\NoAdminRequired;
  15. use OCP\AppFramework\Http\Attribute\PublicPage;
  16. use OCP\AppFramework\Http\Attribute\UserRateLimit;
  17. use OCP\AppFramework\Http\DataResponse;
  18. use OCP\AppFramework\Http\FileDisplayResponse;
  19. use OCP\DB\Exception;
  20. use OCP\Files\NotFoundException;
  21. use OCP\IL10N;
  22. use OCP\IRequest;
  23. use OCP\PreConditionNotMetException;
  24. use OCP\TextToImage\Exception\TaskFailureException;
  25. use OCP\TextToImage\Exception\TaskNotFoundException;
  26. use OCP\TextToImage\IManager;
  27. use OCP\TextToImage\Task;
  28. /**
  29. * @psalm-import-type CoreTextToImageTask from ResponseDefinitions
  30. */
  31. class TextToImageApiController extends \OCP\AppFramework\OCSController {
  32. public function __construct(
  33. string $appName,
  34. IRequest $request,
  35. private IManager $textToImageManager,
  36. private IL10N $l,
  37. private ?string $userId,
  38. private AppData $appData,
  39. ) {
  40. parent::__construct($appName, $request);
  41. }
  42. /**
  43. * Check whether this feature is available
  44. *
  45. * @return DataResponse<Http::STATUS_OK, array{isAvailable: bool}, array{}>
  46. *
  47. * 200: Returns availability status
  48. */
  49. #[PublicPage]
  50. #[ApiRoute(verb: 'GET', url: '/is_available', root: '/text2image')]
  51. public function isAvailable(): DataResponse {
  52. return new DataResponse([
  53. 'isAvailable' => $this->textToImageManager->hasProviders(),
  54. ]);
  55. }
  56. /**
  57. * This endpoint allows scheduling a text to image task
  58. *
  59. * @param string $input Input text
  60. * @param string $appId ID of the app that will execute the task
  61. * @param string $identifier An arbitrary identifier for the task
  62. * @param int $numberOfImages The number of images to generate
  63. *
  64. * @return DataResponse<Http::STATUS_OK, array{task: CoreTextToImageTask}, array{}>|DataResponse<Http::STATUS_PRECONDITION_FAILED|Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
  65. *
  66. * 200: Task scheduled successfully
  67. * 412: Scheduling task is not possible
  68. */
  69. #[PublicPage]
  70. #[UserRateLimit(limit: 20, period: 120)]
  71. #[AnonRateLimit(limit: 5, period: 120)]
  72. #[ApiRoute(verb: 'POST', url: '/schedule', root: '/text2image')]
  73. public function schedule(string $input, string $appId, string $identifier = '', int $numberOfImages = 8): DataResponse {
  74. $task = new Task($input, $appId, $numberOfImages, $this->userId, $identifier);
  75. try {
  76. try {
  77. $this->textToImageManager->runOrScheduleTask($task);
  78. } catch (TaskFailureException) {
  79. // Task status was already updated by the manager, nothing to do here
  80. }
  81. $json = $task->jsonSerialize();
  82. return new DataResponse([
  83. 'task' => $json,
  84. ]);
  85. } catch (PreConditionNotMetException) {
  86. return new DataResponse(['message' => $this->l->t('No text to image provider is available')], Http::STATUS_PRECONDITION_FAILED);
  87. } catch (Exception) {
  88. return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
  89. }
  90. }
  91. /**
  92. * This endpoint allows checking the status and results of a task.
  93. * Tasks are removed 1 week after receiving their last update.
  94. *
  95. * @param int $id The id of the task
  96. *
  97. * @return DataResponse<Http::STATUS_OK, array{task: CoreTextToImageTask}, array{}>|DataResponse<Http::STATUS_NOT_FOUND|Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
  98. *
  99. * 200: Task returned
  100. * 404: Task not found
  101. */
  102. #[PublicPage]
  103. #[BruteForceProtection(action: 'text2image')]
  104. #[ApiRoute(verb: 'GET', url: '/task/{id}', root: '/text2image')]
  105. public function getTask(int $id): DataResponse {
  106. try {
  107. $task = $this->textToImageManager->getUserTask($id, $this->userId);
  108. $json = $task->jsonSerialize();
  109. return new DataResponse([
  110. 'task' => $json,
  111. ]);
  112. } catch (TaskNotFoundException) {
  113. $res = new DataResponse(['message' => $this->l->t('Task not found')], Http::STATUS_NOT_FOUND);
  114. $res->throttle(['action' => 'text2image']);
  115. return $res;
  116. } catch (\RuntimeException) {
  117. return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
  118. }
  119. }
  120. /**
  121. * This endpoint allows downloading the resulting image of a task
  122. *
  123. * @param int $id The id of the task
  124. * @param int $index The index of the image to retrieve
  125. *
  126. * @return FileDisplayResponse<Http::STATUS_OK, array{'Content-Type': string}>|DataResponse<Http::STATUS_NOT_FOUND|Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
  127. *
  128. * 200: Image returned
  129. * 404: Task or image not found
  130. */
  131. #[PublicPage]
  132. #[BruteForceProtection(action: 'text2image')]
  133. #[ApiRoute(verb: 'GET', url: '/task/{id}/image/{index}', root: '/text2image')]
  134. public function getImage(int $id, int $index): DataResponse|FileDisplayResponse {
  135. try {
  136. $task = $this->textToImageManager->getUserTask($id, $this->userId);
  137. try {
  138. $folder = $this->appData->getFolder('text2image');
  139. } catch(NotFoundException) {
  140. $res = new DataResponse(['message' => $this->l->t('Image not found')], Http::STATUS_NOT_FOUND);
  141. $res->throttle(['action' => 'text2image']);
  142. return $res;
  143. }
  144. $file = $folder->getFolder((string) $task->getId())->getFile((string) $index);
  145. $info = getimagesizefromstring($file->getContent());
  146. return new FileDisplayResponse($file, Http::STATUS_OK, ['Content-Type' => image_type_to_mime_type($info[2])]);
  147. } catch (TaskNotFoundException) {
  148. $res = new DataResponse(['message' => $this->l->t('Task not found')], Http::STATUS_NOT_FOUND);
  149. $res->throttle(['action' => 'text2image']);
  150. return $res;
  151. } catch (\RuntimeException) {
  152. return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
  153. } catch (NotFoundException) {
  154. $res = new DataResponse(['message' => $this->l->t('Image not found')], Http::STATUS_NOT_FOUND);
  155. $res->throttle(['action' => 'text2image']);
  156. return $res;
  157. }
  158. }
  159. /**
  160. * This endpoint allows to delete a scheduled task for a user
  161. *
  162. * @param int $id The id of the task
  163. *
  164. * @return DataResponse<Http::STATUS_OK, array{task: CoreTextToImageTask}, array{}>|DataResponse<Http::STATUS_NOT_FOUND|Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
  165. *
  166. * 200: Task returned
  167. * 404: Task not found
  168. */
  169. #[NoAdminRequired]
  170. #[BruteForceProtection(action: 'text2image')]
  171. #[ApiRoute(verb: 'DELETE', url: '/task/{id}', root: '/text2image')]
  172. public function deleteTask(int $id): DataResponse {
  173. try {
  174. $task = $this->textToImageManager->getUserTask($id, $this->userId);
  175. $this->textToImageManager->deleteTask($task);
  176. $json = $task->jsonSerialize();
  177. return new DataResponse([
  178. 'task' => $json,
  179. ]);
  180. } catch (TaskNotFoundException) {
  181. $res = new DataResponse(['message' => $this->l->t('Task not found')], Http::STATUS_NOT_FOUND);
  182. $res->throttle(['action' => 'text2image']);
  183. return $res;
  184. } catch (\RuntimeException) {
  185. return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
  186. }
  187. }
  188. /**
  189. * This endpoint returns a list of tasks of a user that are related
  190. * with a specific appId and optionally with an identifier
  191. *
  192. * @param string $appId ID of the app
  193. * @param string|null $identifier An arbitrary identifier for the task
  194. * @return DataResponse<Http::STATUS_OK, array{tasks: CoreTextToImageTask[]}, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
  195. *
  196. * 200: Task list returned
  197. */
  198. #[NoAdminRequired]
  199. #[AnonRateLimit(limit: 5, period: 120)]
  200. #[ApiRoute(verb: 'GET', url: '/tasks/app/{appId}', root: '/text2image')]
  201. public function listTasksByApp(string $appId, ?string $identifier = null): DataResponse {
  202. try {
  203. $tasks = $this->textToImageManager->getUserTasksByApp($this->userId, $appId, $identifier);
  204. /** @var CoreTextToImageTask[] $json */
  205. $json = array_map(static function (Task $task) {
  206. return $task->jsonSerialize();
  207. }, $tasks);
  208. return new DataResponse([
  209. 'tasks' => $json,
  210. ]);
  211. } catch (\RuntimeException) {
  212. return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
  213. }
  214. }
  215. }