From 7a9054bb3f033c98067b3196378311dae54c5fbf Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Tue, 20 Jan 2026 01:31:53 +0000
Subject: [PATCH] =OperationQueue refactor to the JVBase/managers/queue namespace

---
 inc/rest/routes/UploadRoutes.php |  240 ++++++++++++++++++++++++++++++++++++-----------------------
 1 files changed, 146 insertions(+), 94 deletions(-)

diff --git a/inc/rest/routes/UploadRoutes.php b/inc/rest/routes/UploadRoutes.php
index 742e295..eb3c211 100644
--- a/inc/rest/routes/UploadRoutes.php
+++ b/inc/rest/routes/UploadRoutes.php
@@ -2,6 +2,8 @@
 namespace JVBase\rest\routes;
 
 use JVBase\JVB;
+use JVBase\managers\queue\executors\UploadExecutor;
+use JVBase\managers\queue\TypeConfig;
 use JVBase\rest\RestRouteManager;
 use JVBase\meta\MetaManager;
 use JVBase\managers\UploadManager;
@@ -21,9 +23,62 @@
     {
 		$this->action = 'dash-';
         parent::__construct();
-        add_filter(BASE.'handle_bulk_operation', [$this, 'processOperation'], 10, 3);
+
+		add_action('init', [$this, 'registerUploadExecutors'], 5);
     }
 
+	/**
+	 * Register upload operation types with the queue's TypeRegistry
+	 */
+	public function registerUploadExecutors(): void
+	{
+		$registry = JVB()->queue()->registry();
+		$executor = new UploadExecutor();
+
+		// Image uploads - chunked at 5 files
+		$registry->register('image_upload', new TypeConfig(
+			executor: $executor,
+			chunkKey: 'secured_files',
+			chunkSize: 5
+		));
+
+		// Video uploads - one at a time (heavy processing)
+		$registry->register('video_upload', new TypeConfig(
+			executor: $executor,
+			chunkKey: 'secured_files',
+			chunkSize: 1
+		));
+
+		// Document uploads - chunked at 10
+		$registry->register('document_upload', new TypeConfig(
+			executor: $executor,
+			chunkKey: 'secured_files',
+			chunkSize: 10
+		));
+
+		// Metadata updates
+		$registry->register('update_metadata', new TypeConfig(
+			executor: $executor
+		));
+
+		// Cleanup - chunked at 5
+		$registry->register('temporary_cleanup', new TypeConfig(
+			executor: $executor,
+			chunkKey: 'files',
+			chunkSize: 5
+		));
+
+		// Attach to content (depends on upload completing)
+		$registry->register('attach_upload_to_content', new TypeConfig(
+			executor: $executor
+		));
+
+		// Process upload groups into posts
+		$registry->register('process_upload_groups', new TypeConfig(
+			executor: $executor
+		));
+	}
+
     /**
      * Registers upload routes
      * @return void
@@ -270,27 +325,27 @@
 	{
 		$uploader = new UploadManager();
 		$secured_files = [];
-		$upload_map = [];
 		$errors = [];
 
 		$context = $args;
 		unset($context['upload_ids']);
 
-		$config = $this->getFieldConfig($args);
-		error_log('secureFiles: '.print_r($files, true));
 		$file_array = $files['files'] ?? $files;
-		$tmp_names = isset($file_array['tmp_name'][0]) && is_array($file_array['tmp_name'][0])
-			? $file_array['tmp_name'][0]
-			: $file_array['tmp_name'];
+		if (!is_array($file_array['tmp_name'])) {
+			$file_array = [
+				'name' => [$file_array['name']],
+				'type' => [$file_array['type']],
+				'tmp_name' => [$file_array['tmp_name']],
+				'error' => [$file_array['error']],
+				'size' => [$file_array['size']]
+			];
+		}
+		$tmp_names = $file_array['tmp_name'] ?? [];
+
+
 
 		foreach ($tmp_names as $index => $tmp_name) {
-			$file_data = isset($file_array['tmp_name'][0]) && is_array($file_array['tmp_name'][0]) ? [
-				'name' => $file_array['name'][0][$index],
-				'type' => $file_array['type'][0][$index],
-				'tmp_name' => $tmp_name,
-				'error' => $file_array['error'][0][$index],
-				'size' => $file_array['size'][0][$index]
-			] : [
+			$file_data = [
 				'name' => $file_array['name'][$index],
 				'type' => $file_array['type'][$index],
 				'tmp_name' => $tmp_name,
@@ -310,8 +365,8 @@
 					throw new Exception('Failed to secure file');
 				}
 
-				$frontend_id = $args['upload_ids'][$index] ?? 'upload_' . $index;
-				$upload_map[$index] = $frontend_id;
+				// Embed upload_id directly in the secured file data
+				$secured['upload_id'] = $args['upload_ids'][$index] ?? 'upload_' . $index;
 
 				$secured_files[] = $secured;
 			} catch (Exception $e) {
@@ -321,7 +376,6 @@
 
 		return [
 			'files' => $secured_files,
-			'upload_map' => $upload_map,
 			'errors' => $errors
 		];
 	}
@@ -344,7 +398,7 @@
 	/**
 	 * Queue files for processing
 	 */
-	protected function queueProcessing(array $secured_data, array $args):string
+	protected function queueProcessing(array $secured_data, array $args): string
 	{
 		$operation_type = $this->determineOperationType($secured_data['files'][0] ?? []);
 
@@ -354,20 +408,18 @@
 		} elseif ($operation_type === 'document') {
 			$chunkSize = 10;
 		}
+
 		JVB()->queue()->queueOperation(
 			$operation_type,
 			$args['user'],
 			array_merge(
-				[
-					'secured_files' 	=> $secured_data['files'],
-					'upload_map' 		=> $secured_data['upload_map'],
-				],
+				['secured_files' => $secured_data['files']],
 				$args
 			),
 			[
-				'operation_id'	=> $args['upload'],
-				'chunk_key'		=> 'secured_files',
-				'chunk_size'	=> $chunkSize
+				'operation_id'  => $args['upload'],
+				'chunk_key'     => 'secured_files',
+				'chunk_size'    => $chunkSize
 			]
 		);
 
@@ -377,9 +429,9 @@
 				$args['user'],
 				$args,
 				[
-					'priority'		=> 'high',
-					'operation_id'	=> $args['id'],
-					'depends_on'	=> $args['upload']
+					'priority'      => 'high',
+					'operation_id'  => $args['id'],
+					'depends_on'    => $args['upload']
 				]
 			);
 		}
@@ -388,16 +440,17 @@
 			'temporary_cleanup',
 			$args['user'],
 			[
-				'files'		=> $secured_data['files'],
-				'context'	=> $args,
+				'files'     => $secured_data['files'],
+				'context'   => $args,
 			],
 			[
-				'priority'		=> 'low',
-				'chunk_size'	=> 5,
-				'chunk_key'		=> 'files',
-				'depends_on'	=> $args['upload']
+				'priority'      => 'low',
+				'chunk_size'    => 5,
+				'chunk_key'     => 'files',
+				'depends_on'    => $args['upload']
 			]
 		);
+
 		return $args['id'];
 	}
 
@@ -580,9 +633,8 @@
 
 			$args = $data;
 			unset($args['secured_files']);
-			unset($args['upload_map']);
 
-			foreach ($data['secured_files'] as $index => $secured_file) {
+			foreach ($data['secured_files'] as $secured_file) {
 				$result = $uploader->processUpload(
 					$secured_file['temp_path'],
 					array_merge(
@@ -594,7 +646,7 @@
 							'term_id' => (int)($data['term_id'] ?? 0),
 							'original_name' => $secured_file['original_name'],
 							'mime_type' => $secured_file['mime_type'],
-							'content' => $data['content'] ?? '', // For directory pattern
+							'content' => $data['content'] ?? '',
 						]
 					)
 				);
@@ -602,20 +654,19 @@
 				if (!is_wp_error($result)) {
 					$standardized = $this->standardizeResult($result);
 
-					// Map to frontend upload ID
-					if (isset($data['upload_map'][$index])) {
-						$standardized['upload_id'] = $data['upload_map'][$index];
+					// Use embedded upload_id from the secured file itself
+					$standardized['upload_id'] = $secured_file['upload_id'] ?? null;
+
+					if ($standardized['upload_id']) {
+						$processed_results[$standardized['upload_id']] = $standardized;
+					} else {
+						$processed_results[] = $standardized;
 					}
 
 					// Apply frontend metadata if provided
-					if (!empty($data['frontend_metadata'][$index])) {
-						$this->applyMeta(
-							$standardized['attachment_id'],
-							$data['frontend_metadata'][$index]
-						);
+					if (!empty($secured_file['metadata'])) {
+						$this->applyMeta($standardized['attachment_id'], $secured_file['metadata']);
 					}
-
-					$processed_results[$standardized['upload_id']] = $standardized;
 				}
 			}
 
@@ -640,8 +691,8 @@
 				]
 			);
 			return [
-				'success'	=> false,
-				'result'	=> $e->getMessage()
+				'success'   => false,
+				'result'    => $e->getMessage()
 			];
 		}
 	}
@@ -1090,22 +1141,12 @@
 		try {
 			$files = $request->get_file_params();
 			$args = $this->buildUploadArgs($request);
-			$data = $request->get_params();
-
-
-
-			error_log('[UploadRoutes]:handleGroupingRequest: data'.print_r($data, true));
-			error_log('[UploadRoutes]:handleGroupingRequest: args'.print_r($args, true));
-			error_log('get_file_params: '.print_r($files, true));
-			global $_FILES;
-			error_log('Global Files: '.print_r($_FILES, true));
 
 			if (!$args['content'] || !$args['user'] || !$args['posts']) {
-
 				$this->logError('Missing required data');
 				return new WP_REST_Response([
-					'success'	=> false,
-					'message'	=> 'Missing required data'
+					'success'   => false,
+					'message'   => 'Missing required data'
 				]);
 			}
 
@@ -1121,7 +1162,7 @@
 			}
 
 			// Queue file upload operation
-			$operation_type = $this->determineOperationType($secured_data['files'][0] ?? []);
+			$operation_type = $this->determineOperationType($secured_files['files'][0] ?? []);
 			$chunkSize = 5;
 			if ($operation_type === 'video') {
 				$chunkSize = 1;
@@ -1133,10 +1174,7 @@
 				$operation_type,
 				$args['user'],
 				array_merge(
-					[
-						'secured_files' => $secured_files['files'],
-						'upload_map' => $secured_files['upload_map'],
-					],
+					['secured_files' => $secured_files['files']],
 					$args
 				),
 				[
@@ -1192,40 +1230,55 @@
 			$queue = JVB()->queue();
 			$all_uploaded_images = [];
 
-			// Get the upload operation ID from data
+			// Get the upload operation ID from dependencies
 			$dependencies = json_decode($operation->dependencies, true);
-			$uploads = [];
-			foreach($dependencies as $dependency) {
-				$op = $queue->getOperation($dependency);
 
-				$res = json_decode($op->result, true);
-				if ($op->status === 'completed') {
-					$uploads = array_merge($uploads, $res);
+			if (empty($dependencies)) {
+				return [
+					'success'   => false,
+					'result'    => 'No dependencies found'
+				];
+			}
+
+			// Collect uploads from all dependency operations
+			$uploads = [];
+			foreach ($dependencies as $dependency) {
+				// Use getOperationValue like ContentRoutes does
+				$res = $queue->getOperationValue($dependency, 'result');
+
+				if (empty($res)) {
+					continue;
+				}
+
+				// Results are stored at root level, keyed by upload_id
+				// Filter to only include actual upload results (arrays with attachment_id)
+				foreach ($res as $key => $value) {
+					if (is_array($value) && isset($value['attachment_id'])) {
+						$uploads[$key] = $value;
+					}
 				}
 			}
 
-
 			if (empty($uploads)) {
 				return [
-					'success'	=> false,
-					'result'	=> 'No uploads to process'
+					'success'   => false,
+					'result'    => 'No uploads to process'
 				];
 			}
 
 			// Build map of upload_id => attachment_id
-			foreach ($uploads as $img) {
-				if (isset($img['upload_id'], $img['attachment_id'])) {
-					$all_uploaded_images[$img['upload_id']] = [
-						'upload_id' => $img['upload_id'],
-						'attachment_id' => (int)$img['attachment_id']
-					];
-				}
+			foreach ($uploads as $upload_id => $img) {
+				$all_uploaded_images[$upload_id] = [
+					'upload_id' => $upload_id,
+					'attachment_id' => (int)$img['attachment_id']
+				];
 			}
 
 			$content = jvbCheckBase($data['content']);
 			if (Features::forContent($data['content'])->has('is_timeline')) {
 				return $this->processTimelineUploads($data, $uploads, $all_uploaded_images, $operation);
 			}
+
 			$user = (int)$data['user'];
 			$created_posts = [];
 			$used_upload_ids = [];
@@ -1234,13 +1287,13 @@
 			foreach ($data['posts'] as $index => $post) {
 				$post_title = !empty($post['fields']['post_title'])
 					? sanitize_text_field($post['fields']['post_title'])
-					: 'New ' . JVB_CONTENT[$content]['singular'] . ' ' . ($index + 1);
+					: 'New ' . JVB_CONTENT[$data['content']]['singular'] . ' ' . ($index + 1);
 
 				$post_excerpt = !empty($post['fields']['post_excerpt'])
 					? sanitize_textarea_field($post['fields']['post_excerpt'])
 					: '';
 
-				$args =[
+				$args = [
 					'post_type' => $content,
 					'post_author' => $user,
 					'post_status' => 'draft',
@@ -1253,18 +1306,18 @@
 				if ($new_post_id && !is_wp_error($new_post_id)) {
 					$created_posts[] = $new_post_id;
 
-					// Get featured image upload_id
-					$featured_upload_id = (int)$post['fields']['featured'] ?? null;
+					// Get featured image upload_id - string, not int!
+					$featured_upload_id = $post['fields']['featured'] ?? null;
 					$featured_attachment_id = null;
 					$gallery_attachment_ids = [];
 
 					// Process all images for this post
-					foreach ($post['images'] as $key => $img) {
+					foreach ($post['images'] as $img) {
 						$upload_id = $img['upload_id'];
 						$used_upload_ids[] = $upload_id;
 
 						if (isset($all_uploaded_images[$upload_id])) {
-							$attachment_id = (int)$all_uploaded_images[$upload_id]['attachment_id'];
+							$attachment_id = $all_uploaded_images[$upload_id]['attachment_id'];
 
 							if ($upload_id === $featured_upload_id) {
 								$featured_attachment_id = $attachment_id;
@@ -1276,10 +1329,9 @@
 
 					// Set featured image
 					if ($featured_attachment_id) {
-						set_post_thumbnail($new_post_id, (int)$featured_attachment_id);
+						set_post_thumbnail($new_post_id, $featured_attachment_id);
 					} elseif (!empty($gallery_attachment_ids)) {
-						// If no featured set, use first image
-						set_post_thumbnail($new_post_id, (int)$gallery_attachment_ids[0]);
+						set_post_thumbnail($new_post_id, $gallery_attachment_ids[0]);
 						array_shift($gallery_attachment_ids);
 					}
 
@@ -1333,9 +1385,9 @@
 			$used_upload_ids = [];
 
 			$content = jvbCheckBase($data['content']);
-			error_log('[processTimelineUploads]Got content: '.print_r($content, true));
+
 			$config = Features::getConfig($content);
-			error_log('[processTimelineUploads]Got config: '.print_r($config, true));
+
 			$defaultTitle = 'New '.$config['singular']. ' ';
 			foreach ($data['posts'] as $index=> $post) {
 				$title = !empty($post['fields']['post_title'])

--
Gitblit v1.10.0