| | |
| | | 'limit' => 'integer|default:50|min:1|max:100', |
| | | ]) |
| | | ->auth('user') |
| | | ->rateLimit(30) |
| | | ->rateLimit() |
| | | ->post([$this, 'handleAction']) |
| | | ->args([ |
| | | 'ids' => 'array|required', |
| | | 'action' => 'string|required|enum:dismiss,retry,cancel', |
| | | ]) |
| | | ->auth('user') |
| | | ->rateLimit(30); |
| | | ->rateLimit() |
| | | ->register(); |
| | | |
| | | // Poll endpoint |
| | | Route::for('queue/poll') |
| | |
| | | 'ids' => 'string', |
| | | ]) |
| | | ->auth('user') |
| | | ->rateLimit(15); |
| | | ->rateLimit() |
| | | ->register(); |
| | | |
| | | // Errors endpoint |
| | | Route::for('queue/errors') |
| | | ->get([$this, 'getOperationErrors']) |
| | | ->auth('user') |
| | | ->rateLimit(15); |
| | | ->rateLimit() |
| | | ->register(); |
| | | |
| | | // Single operation with dynamic ID |
| | | Route::for(Route::pattern('queue/{id}')) |
| | | ->get([$this, 'getOperation']) |
| | | ->arg('id', 'string|required') |
| | | ->auth('user') |
| | | ->rateLimit(15); |
| | | ->rateLimit() |
| | | ->register(); |
| | | } |
| | | |
| | | /** |
| | |
| | | { |
| | | $params = $request->get_params(); |
| | | $user_id = absint($params['user']); |
| | | $this->cache = Cache::for($user_id.'_queue'); |
| | | $status = sanitize_text_field($params['status']); |
| | | $ids = array_map('trim', array_map('sanitize_text_field', explode(',', $params['ids']))); |
| | | $ids = !empty($params['ids']) |
| | | ? array_map('trim', array_map('sanitize_text_field', explode(',', $params['ids']))) |
| | | : []; |
| | | $limit = absint($params['limit']); |
| | | |
| | | $cacheKey = $this->cache->generateKey(compact('user_id', 'status', 'ids', 'limit')); |
| | |
| | | return $cached; |
| | | } |
| | | |
| | | $data = $this->cache->remember($cacheKey, function() use ($user_id, $params) { |
| | | $data = $this->cache->tag("user:{$user_id}")->remember($cacheKey, function() use ($user_id, $params) { |
| | | $filters = $this->buildFilters($params); |
| | | $operations = JVB()->queue()->getUserOperations($user_id, $filters); |
| | | |
| | |
| | | public function handleAction(WP_REST_Request $request): WP_REST_Response |
| | | { |
| | | $data = $request->get_params(); |
| | | $ids = array_map('trim', array_map('sanitize_text_field', explode(',', $data['ids']))); |
| | | $ids = is_array($data['ids']) |
| | | ? array_map('trim', array_map('sanitize_text_field', $data['ids'])) |
| | | : array_map('trim', array_map('sanitize_text_field', explode(',', $data['ids']))); |
| | | |
| | | $action = sanitize_text_field($data['action'] ?? ''); |
| | | $user_id = absint($data['user']); |
| | | |
| | | $this->cache = Cache::for($user_id.'_queue'); |
| | | |
| | | // Validate input |
| | | if (empty($ids)) { |
| | | return Response::validationError(['ids' => 'Missing or invalid operation IDs']); |
| | |
| | | public function pollQueue(WP_REST_Request $request): WP_REST_Response |
| | | { |
| | | $userId = $request->get_param('user'); |
| | | $this->cache = Cache::for($userId.'_queue'); |
| | | $since = $request->get_param('since'); |
| | | $ids = $request->get_param('ids'); |
| | | |
| | |
| | | $items = array_map(fn($op) => [ |
| | | 'id' => $op->id, |
| | | 'status' => $this->mapStateToStatus($op->state, $op->outcome), |
| | | 'progress_percentage' => $this->formatPercentage($op), |
| | | 'progress_count' => $op->processedItems, |
| | | 'count' => $op->totalItems, |
| | | 'updated_at' => $this->formatTimestamp($op->completedAt ?? $op->startedAt ?? $op->scheduledAt), |
| | |
| | | public function getOperationErrors(WP_REST_Request $request): WP_REST_Response |
| | | { |
| | | $user_id = absint($request->get_param('user')); |
| | | $this->cache = Cache::for($user_id.'_queue'); |
| | | $operations = JVB()->queue()->getUserOperations($user_id, [ |
| | | 'state' => 'completed', |
| | | 'outcome' => ['failed', 'failed_permanent', 'partial'], |
| | |
| | | { |
| | | $id = $request->get_param('id'); |
| | | $userId = $request->get_param('user'); |
| | | $this->cache = Cache::for($userId.'_queue'); |
| | | |
| | | $op = JVB()->queue()->get($id); |
| | | |
| | |
| | | ); |
| | | } |
| | | |
| | | private function formatOperation(Operation $op, bool $full = false): array |
| | | private function formatOperation(Operation $op, bool $full = true): array |
| | | { |
| | | $formatted = [ |
| | | 'id' => $op->id, |
| | | 'type' => $op->type, |
| | | 'status' => $this->mapStateToStatus($op->state, $op->outcome), |
| | | 'progress_count' => $op->processedItems, |
| | | 'progress_percentage' => $this->formatPercentage($op), |
| | | 'count' => $op->totalItems, |
| | | 'title' => $this->getOperationTitle($op->type, $op->requestData), |
| | | 'created_at' => $this->formatTimestamp($op->scheduledAt), |
| | | 'updated_at' => $this->formatTimestamp($op->completedAt ?? $op->startedAt ?? $op->scheduledAt), |
| | | ]; |
| | | |
| | | if ($op->processedItems > 0 && $op->totalItems > 0) { |
| | | $formatted['progress_percentage'] = round(($op->processedItems / $op->totalItems) * 100); |
| | | if (!empty($op->result['merged_into'])) { |
| | | $formatted['merged_into'] = $op->result['merged_into']; |
| | | } |
| | | |
| | | if ($op->errorMessage) { |
| | |
| | | return $formatted; |
| | | } |
| | | |
| | | protected function formatPercentage(Operation $op):int |
| | | { |
| | | if ($op->totalItems > 0){ |
| | | return round(($op->processedItems / $op->totalItems) * 100); |
| | | } else { |
| | | return match($op->state) { |
| | | 'pending','scheduled=' => 0, |
| | | 'processing' => 45, |
| | | 'completed' => 100 |
| | | }; |
| | | } |
| | | } |
| | | /** |
| | | * Map backend state/outcome to frontend status |
| | | * Backend uses: state (pending, scheduled, processing, completed) + outcome (pending, success, partial, failed, failed_permanent) |