diff --git a/src/API/Tasks/Index.php b/src/API/Tasks/Index.php index 8a7fa7cd..3f3398cb 100644 --- a/src/API/Tasks/Index.php +++ b/src/API/Tasks/Index.php @@ -6,14 +6,22 @@ namespace App\API\Tasks; use App\Commands\System\TasksCommand; use App\Libs\Attributes\Route\Get; +use App\Libs\Attributes\Route\Route; use App\Libs\HTTP_STATUS; +use DateInterval; use Psr\Http\Message\ResponseInterface as iResponse; use Psr\Http\Message\ServerRequestInterface as iRequest; +use Psr\SimpleCache\CacheInterface as iCache; +use Psr\SimpleCache\InvalidArgumentException; final class Index { public const string URL = '%{api.prefix}/tasks'; + public function __construct(private readonly iCache $cache) + { + } + #[Get(self::URL . '[/]', name: 'tasks.index')] public function tasksIndex(iRequest $request): iResponse { @@ -36,6 +44,7 @@ final class Index $task['links'] = [ 'self' => (string)$apiUrl->withPath($urlPath . '/' . ag($task, 'name')), + 'queue' => (string)$apiUrl->withPath($urlPath . '/' . ag($task, 'name') . '/queue'), ]; $response['tasks'][] = $task; @@ -44,8 +53,46 @@ final class Index return api_response(HTTP_STATUS::HTTP_OK, $response); } - #[Get(self::URL . '/{id:[a-zA-Z0-9_-]+}[/]', name: 'tasks.view')] - public function __invoke(iRequest $request, array $args = []): iResponse + /** + * @throws InvalidArgumentException + */ + #[Route(['GET', 'POST'], self::URL . '/{id:[a-zA-Z0-9_-]+}/queue[/]', name: 'tasks.task.queue')] + public function taskQueue(iRequest $request, array $args = []): iResponse + { + if (null === ($id = ag($args, 'id'))) { + return api_error('No id was given.', HTTP_STATUS::HTTP_BAD_REQUEST); + } + + $task = TasksCommand::getTasks($id); + + if (empty($task)) { + return api_error('Task not found.', HTTP_STATUS::HTTP_NOT_FOUND); + } + + $queuedTasks = $this->cache->get('queued_tasks', []); + + if ('POST' === $request->getMethod()) { + $queuedTasks[] = $id; + $this->cache->set('queued_tasks', $queuedTasks, new DateInterval('P3D')); + return api_response(HTTP_STATUS::HTTP_ACCEPTED, ['queue' => $queuedTasks]); + } + + $apiUrl = $request->getUri()->withHost('')->withPort(0)->withScheme('')->withUserInfo(''); + $urlPath = parseConfigValue(Index::URL); + + return api_response(HTTP_STATUS::HTTP_OK, [ + 'task' => $id, + 'is_queued' => in_array($id, $queuedTasks), + 'links' => [ + 'self' => (string)$apiUrl, + 'task' => (string)$apiUrl->withPath($urlPath . '/' . $id), + 'tasks' => (string)$apiUrl->withPath($urlPath), + ], + ]); + } + + #[Get(self::URL . '/{id:[a-zA-Z0-9_-]+}[/]', name: 'tasks.task.view')] + public function taskView(iRequest $request, array $args = []): iResponse { if (null === ($id = ag($args, 'id'))) { return api_error('No id was given.', HTTP_STATUS::HTTP_BAD_REQUEST); diff --git a/src/Commands/System/TasksCommand.php b/src/Commands/System/TasksCommand.php index b7b04cff..92432b20 100644 --- a/src/Commands/System/TasksCommand.php +++ b/src/Commands/System/TasksCommand.php @@ -11,6 +11,8 @@ use App\Libs\Extends\ConsoleOutput; use App\Libs\Stream; use Cron\CronExpression; use Exception; +use Psr\SimpleCache\CacheInterface as iCache; +use Psr\SimpleCache\InvalidArgumentException; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Input\InputInterface as iInput; @@ -28,7 +30,7 @@ use Throwable; #[Cli(command: self::ROUTE)] final class TasksCommand extends Command { - public const ROUTE = 'system:tasks'; + public const string ROUTE = 'system:tasks'; private array $logs = []; private array $taskOutput = []; @@ -36,7 +38,7 @@ final class TasksCommand extends Command /** * Class Constructor. */ - public function __construct() + public function __construct(private readonly iCache $cache) { set_time_limit(0); ini_set('memory_limit', '-1'); @@ -128,6 +130,7 @@ final class TasksCommand extends Command * @param iOutput $output The output instance. * * @return int Returns the exit code of the command. + * @throws InvalidArgumentException if cache key name is invalid. */ protected function runCommand(iInput $input, iOutput $output): int { @@ -162,6 +165,7 @@ final class TasksCommand extends Command * @param iOutput $output The output object. * * @return int The exit code of the command. + * @throws InvalidArgumentException if cache key name is invalid. */ private function runTasks(iInput $input, iOutput $output): int { @@ -182,6 +186,21 @@ final class TasksCommand extends Command } $run[] = ag($tasks, $task); + } elseif (null !== ($queued = $this->cache->get('queued_tasks', null))) { + foreach ($queued as $taskName) { + $task = strtolower($taskName); + if (false === ag_exists($tasks, $task)) { + $output->writeln( + r('There are no task named [{task}].', [ + 'task' => $task + ]) + ); + continue; + } + + $run[] = ag($tasks, $task); + } + $this->cache->delete('queued_tasks'); } else { foreach ($tasks as $task) { if (false === (bool)ag($task, 'enabled')) {