From 2127b1bdd73ecd2423e443992da4b442f5a3c1a3 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Wed, 04 Feb 2026 21:19:25 +0000
Subject: [PATCH] =Major overhaul of MetaManager.php -> Meta.php and RestRouteManager.php -> Rest.php. Seems to work for JakeVan

---
 inc/rest/routes/ResponseRoutes.php | 1052 +++++++++++++++++++++++----------------------------------
 1 files changed, 424 insertions(+), 628 deletions(-)

diff --git a/inc/rest/routes/ResponseRoutes.php b/inc/rest/routes/ResponseRoutes.php
index 31cbd9d..00c15a3 100644
--- a/inc/rest/routes/ResponseRoutes.php
+++ b/inc/rest/routes/ResponseRoutes.php
@@ -2,714 +2,510 @@
 namespace JVBase\rest\routes;
 
 use JVBase\JVB;
-use JVBase\rest\RestRouteManager;
-use JVBase\managers\Cache;
+use JVBase\rest\Rest;
+use JVBase\managers\CustomTable;
 use WP_REST_Request;
 use WP_REST_Response;
 use WP_Error;
 
 if (!defined('ABSPATH')) {
-    exit; // Exit if accessed directly
+	exit;
 }
-class ResponseRoutes extends RestRouteManager
+
+/**
+ * Response Routes
+ *
+ * Handles threaded responses/comments for news items
+ */
+class ResponseRoutes extends Rest
 {
-    protected int $per_page;
-    protected false|object $manager = false;
+	protected int $perPage = 20;
+	protected CustomTable $table;
+	protected CustomTable $karmaTable;
 
-    public function __construct()
-    {
-        $this->cache_name = 'responses';
-        parent::__construct();
-        $this->action = 'dash-';
-        $this->per_page = 20;
+	public function __construct()
+	{
+		$this->cacheName = 'responses';
+		$this->cacheTtl = 1800; // 30 minutes
+		parent::__construct();
 
-        add_filter(BASE.'handle_bulk_operation', [$this, 'processOperation'], 10, 3);
-        add_action('deleted_user', [$this, 'handleUserDeletion'], 10, 1);
-    }
+		$this->table = CustomTable::for('responses');
+		$this->karmaTable = CustomTable::for('karma_response');
 
-    /**
-     * Registers response routes
-     * @return void
-     */
-    public function registerRoutes():void
-    {
-        register_rest_route($this->namespace, '/response', [
-            [
-                'methods'   => 'GET',
-                'callback'  => [$this, 'getResponses'],
-                'permission_callback'   => [$this, 'checkPermission']
-            ],
-            [
-                'methods'   => 'POST',
-                'callback'  => [$this, 'handleResponseActions'],
-                'permission_callback'   => [$this, 'checkPermission']
-            ]
-        ]);
-    }
+		add_filter(BASE.'handle_bulk_operation', [$this, 'processOperation'], 10, 3);
+		add_action('deleted_user', [$this, 'handleUserDeletion'], 10, 1);
+	}
 
-    /**
-     * Get responses for a news post
-     * @param WP_REST_Request $request
-     * @return WP_REST_Response
-     */
-    public function getResponses(WP_REST_Request $request):WP_REST_Response
-    {
-        $item_id = (int) $request->get_param('item_id');
-        error_log('Item ID: '.print_r($item_id, true));
-        if (!$item_id) {
-            return new WP_REST_Response([
-                'success'=> false,
-                'message'   => 'Missing item ID'
-            ]);
-        }
+	public function registerRoutes(): void
+	{
+		register_rest_route($this->namespace, '/response', [
+			[
+				'methods'   => 'GET',
+				'callback'  => [$this, 'getResponses'],
+				'permission_callback' => 'is_user_logged_in'
+			],
+			[
+				'methods'   => 'POST',
+				'callback'  => [$this, 'handleResponseActions'],
+				'permission_callback' => 'is_user_logged_in'
+			]
+		]);
+	}
 
-        // Build query args
-        $args = $this->buildQueryArgs($request);
-        error_log('Args: '.print_r($args, true));
-        $responses = $this->getItemResponse($item_id, $args);
-        error_log('Responses: '.print_r($responses, true));
+	/**
+	 * Get responses for an item
+	 */
+	public function getResponses(WP_REST_Request $request): WP_REST_Response
+	{
+		$item_id = (int) $request->get_param('item_id');
 
-        // Return formatted response
-        return new WP_REST_Response($responses);
-    }
+		if (!$item_id) {
+			return $this->error('Missing item ID', 'missing_item_id');
+		}
 
-    /**
-     * @param int $ID
-     * @param string $postType
-     *
-     * @return void
-     */
-    protected function clearItemCache(int $ID, string $postType):void
-    {
-        $args = [
+		// Build query args
+		$args = $this->buildQueryArgs($request);
+		$cacheKey = $this->cache->generateKey(array_merge(['item_id' => $item_id], $args));
 
-            BASE.'news' => $ID,
-            'post_type' => BASE.$postType,
-            'page'      => 1,
-            'per_page'  => 20,
-            'orderby'   => 'created_at',
-            'order'     => 'DESC'
-        ];
-        $key = $this->cache->generateKey($args);
-        $this->cache->invalidate($key);
-    }
+		// Check headers for 304 Not Modified
+		$headerCheck = $this->checkHeaders($request, $cacheKey);
+		if ($headerCheck) {
+			return $headerCheck;
+		}
 
-    /**
-     * @param int $ID
-     * @param array $args
-     *
-     * @return array|WP_Error
-     */
-    public function getItemResponse(int $ID, array $args = []):array|WP_Error
-    {
+		// Check cache
+		$cached = $this->cache->get($cacheKey);
+		if ($cached) {
+			return $this->success($cached);
+		}
 
-        $default = [
-            'post_type' => BASE.'news',
-            'page' => 1,
-            'per_page' => 20,
-            'orderby' => 'created_at',
-            'order' => 'DESC'
-        ];
+		// Get responses
+		$responses = $this->getItemResponse($item_id, $args);
 
-        $args = wp_parse_args($default, $args);
+		if (is_wp_error($responses)) {
+			return $this->notFound($responses->get_error_message());
+		}
 
-        $key = $this->cache->generateKey(array_merge([$args['post_type'] => $ID], $args));
-        $check = $this->cache->get($key);
+		// Cache and return
+		$this->cache->set($cacheKey, $responses);
+		return $this->success($responses);
+	}
 
-        if ($check) {
-            return $check;
-        }
+	/**
+	 * Handle response actions (create/update/delete)
+	 */
+	public function handleResponseActions(WP_REST_Request $request): WP_REST_Response
+	{
+		$data = $request->get_params();
+		$user_id = (int) ($data['user'] ?? 0);
 
-        // Verify post exists and is of correct type
-        $post = get_post($ID);
-        if (!$post || $post->post_type !== $args['post_type']) {
-            return new WP_Error(
-                self::ERROR_NOT_FOUND,
-                'Item not found',
-                ['status' => 404]
-            );
-        }
+		if (!$this->userCheck($user_id)) {
+			return $this->unauthorized('User verification failed');
+		}
 
-        // Execute the query
-        global $wpdb;
-        $table = $wpdb->prefix . BASE . 'responses';
+		$operation_id = $data['id'] ?? uniqid('response_');
+		$action = sanitize_text_field($data['action'] ?? '');
 
-        // Get total count
-        $total_query = "SELECT COUNT(*) FROM $table WHERE item_id = %d";
-        $total_args = [$ID];
+		if (!in_array($action, ['create', 'delete', 'update'])) {
+			return $this->error('Invalid action', 'invalid_action');
+		}
 
-        // Add parent filter if specified
-        if (isset($args['parent_id'])) {
-            $total_query .= " AND parent_id " . ($args['parent_id'] === null ? "IS NULL" : "= %d");
-            if ($args['parent_id'] !== null) {
-                $total_args[] = $args['parent_id'];
-            }
-        }
+		// Validate required fields for create
+		if ($action === 'create') {
+			if (!isset($data['item_id'], $data['response'], $data['content'])) {
+				return $this->error('Missing required fields', 'missing_fields');
+			}
+		}
 
-        $total_items = $wpdb->get_var($wpdb->prepare($total_query, $total_args));
+		// Prepare data for queue
+		$queue_data = match($action) {
+			'create' => [
+				'item_id' => (int) $data['item_id'],
+				'parent_id' => isset($data['parent_id']) ? (int) $data['parent_id'] : null,
+				'response' => wp_kses_post($data['response']),
+				'content' => sanitize_text_field($data['content'])
+			],
+			'update' => [
+				'response_id' => (int) $data['response_id'],
+				'response' => isset($data['response']) ? wp_kses_post($data['response']) : null,
+				'status' => isset($data['status']) && in_array($data['status'], ['published', 'hidden', 'flagged'])
+					? $data['status']
+					: null
+			],
+			'delete' => [
+				'response_id' => (int) $data['response_id']
+			]
+		};
 
-        // Build main query
-        $query = "SELECT r.*,
-                  COALESCE((SELECT COUNT(*) FROM $table WHERE parent_id = r.id), 0) as reply_count,
-                  COALESCE((SELECT SUM(CASE WHEN vote = 'up' THEN 1 ELSE 0 END) FROM {$wpdb->prefix}" . BASE . "karma_response WHERE item_id = r.id), 0) as upvotes,
-                  COALESCE((SELECT SUM(CASE WHEN vote = 'down' THEN 1 ELSE 0 END) FROM {$wpdb->prefix}" . BASE . "karma_response WHERE item_id = r.id), 0) as downvotes
-                  FROM $table r
-                  WHERE r.item_id = %d";
+		// Queue the operation
+		JVB()->queue()->queueOperation(
+			$action . '_response',
+			$user_id,
+			$queue_data,
+			[
+				'operation_id' => 'u' . $user_id . '_' . $operation_id,
+				'priority' => 'high'
+			]
+		);
 
-        $query_args = [$ID];
+		return $this->queued($operation_id);
+	}
 
-        // Apply parent filter
-        if (isset($args['parent_id'])) {
-            $query .= " AND r.parent_id " . ($args['parent_id'] === null ? "IS NULL" : "= %d");
-            if ($args['parent_id'] !== null) {
-                $query_args[] = $args['parent_id'];
-            }
-        }
+	/**
+	 * Get responses with karma calculations
+	 */
+	protected function getItemResponse(int $item_id, array $args = []): array|WP_Error
+	{
+		$defaults = [
+			'page' => 1,
+			'per_page' => $this->perPage,
+			'orderby' => 'created_at',
+			'order' => 'DESC',
+			'parent_id' => null
+		];
 
-        // Apply order
-        $order_column = in_array($args['orderby'], ['created_at', 'updated_at'])
-            ? "r." . $args['orderby']
-            : $args['orderby'];
+		$args = wp_parse_args($args, $defaults);
 
-        $query .= " ORDER BY $order_column " . $args['order'];
+		// Verify post exists
+		$post = get_post($item_id);
+		if (!$post) {
+			return new WP_Error('not_found', 'Item not found');
+		}
 
-        // Apply pagination
-        $query .= " LIMIT %d OFFSET %d";
-        $query_args[] = $args['per_page'];
-        $query_args[] = ($args['page'] - 1) * $args['per_page'];
+		// Build query with karma calculations
+		$query = "
+            SELECT r.*,
+                COALESCE((SELECT COUNT(*) FROM {$this->table->getFullTableName()} WHERE parent_id = r.id), 0) as reply_count,
+                COALESCE((SELECT COUNT(*) FROM {$this->karmaTable->getFullTableName()} WHERE item_id = r.id AND vote = 'up'), 0) as upvotes,
+                COALESCE((SELECT COUNT(*) FROM {$this->karmaTable->getFullTableName()} WHERE item_id = r.id AND vote = 'down'), 0) as downvotes
+            FROM {$this->table->getFullTableName()} r
+            WHERE r.item_id = %d
+        ";
 
-        // Get responses
-        $responses = $wpdb->get_results($wpdb->prepare($query, $query_args));
+		$query_args = [$item_id];
 
-        // Format responses
-        $items = array_map([$this, 'formatItem'], $responses);
+		// Filter by parent_id
+		if (isset($args['parent_id'])) {
+			$query .= " AND r.parent_id " . ($args['parent_id'] === null ? "IS NULL" : "= %d");
+			if ($args['parent_id'] !== null) {
+				$query_args[] = $args['parent_id'];
+			}
+		}
 
-        // Calculate pagination
-        $total_pages = ceil($total_items / $args['per_page']);
+		// Get total count for pagination
+		$count_query = str_replace(
+			['SELECT r.*,', 'COALESCE((SELECT COUNT(*) FROM', 'as reply_count,', 'as upvotes,', 'as downvotes'],
+			['SELECT COUNT(*)', '', '', '', ''],
+			$query
+		);
+		$count_query = preg_replace('/COALESCE\([^)]+\)[^,]*,?/', '', $count_query);
+		$total_items = (int) $this->table->queryVar($count_query, $query_args);
 
-        $return =  [
-            'items' => $items,
-            'has_more' => $args['page'] < $total_pages,
-            'total_items' => (int) $total_items,
-            'total_pages' => $total_pages
-        ];
+		// Add ordering
+		$order_column = in_array($args['orderby'], ['created_at', 'updated_at'])
+			? "r.{$args['orderby']}"
+			: $args['orderby'];
 
-        $this->cache->set($key, $return);
-        return $return;
-    }
+		$query .= " ORDER BY {$order_column} {$args['order']}";
 
-    /**
-     * Create a new response
-     * @param WP_REST_Request $request
-     * @return WP_REST_Response
-     */
-    public function handleResponseActions(WP_REST_Request $request):WP_REST_Response
-    {
-        $data = $request->get_params();
-        $user_id = (int)$data['user'];
-        if (!$this->userCheck($data['user'])) {
-            return new WP_REST_Response([
-                'success'   => false,
-                'message'   => 'User doesn\'t match. Bot?'
-            ]);
-        }
-        $operation_id = $data['id'] ?? uniqid('response_');
+		// Add pagination
+		$query .= " LIMIT %d OFFSET %d";
+		$query_args[] = $args['per_page'];
+		$query_args[] = ($args['page'] - 1) * $args['per_page'];
 
+		// Get responses
+		$responses = $this->table->queryResults($query, $query_args);
 
-        // Validate required fields
-        if (!array_key_exists('item_id', $data) || !array_key_exists('response', $data) || !array_key_exists('content', $data)) {
-            error_log('Not enough data');
-            return new WP_REST_Response([
-                'success'   => false,
-                'message'   => 'Missing required information.'
-            ]);
-        }
+		// Format responses
+		$items = array_map([$this, 'formatItem'], $responses);
 
-        // Prepare data for queue
-        $queue_data = [
-            'item_id' => (int) $data['item_id'],
-            'parent_id' => array_key_exists('parent_id', $data) ? (int) $data['parent_id'] : null,
-            'response' => wp_kses_post($data['response']),
-            'content'=> sanitize_text_field($data['content'])
-        ];
+		return [
+			'items' => $items,
+			'has_more' => $args['page'] < ceil($total_items / $args['per_page']),
+			'total_items' => $total_items,
+			'total_pages' => (int) ceil($total_items / $args['per_page'])
+		];
+	}
 
-        error_log('Queue Data: '.print_r($queue_data, true));
+	/**
+	 * Process queue operations
+	 */
+	public function processOperation(WP_Error|array $result, object $operation, array $data): WP_Error|array
+	{
+		if (!in_array($operation->type, ['create_response', 'update_response', 'delete_response'])) {
+			return $result;
+		}
 
-        $action = sanitize_text_field($data['action']);
-        error_log('Action: '.print_r($action, true));
-        if (!in_array($action, ['create', 'delete', 'update'])) {
-            return new WP_REST_Response([
-                'success'   => false,
-                'message'   => 'Invalid action'
-            ]);
-        }
-        error_log('Sanitized action. Here we go!');
+		return match($operation->type) {
+			'create_response' => $this->createResponse($operation, $data),
+			'update_response' => $this->updateResponse($data),
+			'delete_response' => $this->deleteResponse($data),
+			default => $result
+		};
+	}
 
-      	// Add to queue
-        $operation = JVB()->queue()->queueOperation(
-            $action.'_response',
-            $user_id,
-            $queue_data,
-            [
-                'operation_id' => 'u' . $user_id . '_' . $operation_id,
-                'priority'     => 'high'
-            ]
-        );
-        error_log('Queued for processing');
+	/**
+	 * Create a new response
+	 */
+	protected function createResponse(object $operation, array $data): array
+	{
+		$response_id = $this->table->insert([
+			'item_id' => $data['item_id'],
+			'content' => $data['content'],
+			'user_id' => $operation->user_id,
+			'parent_id' => $data['parent_id'],
+			'response' => $data['response'],
+			'status' => 'published'
+		]);
 
-        return new WP_REST_Response([
-            'success'   => true,
-            'message'   => 'Item queued for processing'
-        ]);
-    }
+		if (!$response_id) {
+			$this->logError('Failed to insert response', [
+				'data' => $data,
+				'error' => $this->table->getLastError()
+			]);
 
-    /**
-     * Update a response
-     * @param WP_REST_Request $request
-     * @return WP_REST_Response
-     */
-    public function updateResponse(WP_REST_Request $request):WP_REST_Response
-    {
-        $id = (int) $request->get_param('id');
-        $data = $request->get_params();
-        $user_id = (int) $data['user'] ?? get_current_user_id();
-        $operation_id = $data['id'] ?? uniqid('response_update_');
+			return ['success' => false, 'result' => 'Failed to create response'];
+		}
 
+		// Send notifications
+		$this->sendNotifications($data['item_id'], $data['parent_id'], $response_id, $operation->user_id);
 
-        // Verify response exists
-        global $wpdb;
-        $table = $wpdb->prefix . BASE . 'responses';
-        $response = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table WHERE id = %d", $id));
+		// Clear cache
+		$this->cache->flush();
 
-        if (!$response) {
-            return new WP_REST_Response([
-                'success'   => false,
-                'message'   => 'Item not found.'
-            ]);
-        }
+		return ['success' => true, 'result' => $response_id];
+	}
 
-        // Check ownership or admin rights
-        if ($response->user_id != $user_id) {
-            return new WP_REST_Response([
-                'success'   => false,
-                'message'   => 'You do not have permission to delete this response'
-            ]);
-        }
+	/**
+	 * Update a response
+	 */
+	protected function updateResponse(array $data): array
+	{
+		$update_data = [];
 
-        // Prepare data for queue
-        $queue_data = [
-            'response_id' => $id,
-            'response' => !empty($data['response']) ? wp_kses_post($data['response']) : null,
-            'status' => !empty($data['status']) && in_array($data['status'], ['published', 'hidden', 'flagged'])
-                ? $data['status']
-                : null
-        ];
+		if (isset($data['response'])) {
+			$update_data['response'] = $data['response'];
+			$update_data['updated_at'] = current_time('mysql');
+		}
 
-        // Add to queue
-        $operation = JVB()->queue()->queueOperation(
-            'update_response',
-            $user_id,
-            $queue_data,
-            [
-                'operation_id' => 'u' . $user_id . '_' . $operation_id,
-                'priority'     => 'high',
-                'notification' => false,
-            ]
-        );
+		if (isset($data['status'])) {
+			$update_data['status'] = $data['status'];
+		}
 
-        return new WP_REST_Response($operation);
-    }
+		if (empty($update_data)) {
+			return ['success' => false, 'result' => 'No fields to update'];
+		}
 
-    /**
-     * Delete a response
-     * @param WP_REST_Request $request
-     * @return WP_REST_Response
-     */
-    public function deleteResponse(WP_REST_Request $request):WP_REST_Response
-    {
-        $id = (int) $request->get_param('id');
-        $user_id = get_current_user_id();
-        $operation_id = $request->get_param('id') ?? uniqid('response_delete_');
+		$updated = $this->table->update(
+			$update_data,
+			['id' => $data['response_id']]
+		);
 
-        // Verify response exists
-        global $wpdb;
-        $table = $wpdb->prefix . BASE . 'responses';
-        $response = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table WHERE id = %d", $id));
+		if ($updated === false) {
+			$this->logError('Failed to update response', [
+				'data' => $data,
+				'error' => $this->table->getLastError()
+			]);
 
-        if (!$response) {
-            return new WP_REST_Response([
-                'success'       => false,
-                'message'       => 'Response not found',
-            ]);
-        }
-
-        // Check ownership or admin rights
-        if ($response->user_id != $user_id) {
-            return new WP_REST_Response([
-                'success'   => false,
-                'msesage'   => 'You do not have permission to delete this resposne',
-            ]);
-        }
-
-        // Add to queue
-        $operation = JVB()->queue()->queueOperation(
-            'delete_response',
-            $user_id,
-            ['response_id' => $id],
-            [
-                'operation_id' => 'u' . $user_id . '_' . $operation_id,
-                'priority'     => 'high',
-                'notification' => false,
-            ]
-        );
-
-        return new WP_REST_Response([
-            'success'   => true,
-            'message'   => 'Queued for processing'
-        ]);
-    }
-
-    /**
-     * Process operations from the queue
-     * @param WP_Error|array $result
-     * @param object $operation
-     * @param array $data
-     * @return WP_Error|array
-     */
-    public function processOperation(WP_Error|array $result, object $operation, array $data):WP_Error|array
-    {
-        if (!in_array($operation->type, ['create_response', 'update_response', 'delete_response'])) {
-            return $result;
-        }
-
-        global $wpdb;
-        $table = $wpdb->prefix . BASE . 'responses';
-
-        switch ($operation->type) {
-            case 'create_response':
-                // Create new response
-                $inserted = $wpdb->insert(
-                    $table,
-                    [
-                        'item_id'   => $data['item_id'],
-                        'content' => $data['content'],
-                        'user_id'   => $operation->user_id,
-                        'parent_id' => $data['parent_id'],
-                        'response'   => $data['response'],
-                        'status'    => 'published',
-                        'created_at' => current_time('mysql'),
-                        'updated_at' => current_time('mysql')
-                    ],
-                    ['%d', '%d', '%d', '%s', '%s', '%s', '%s']
-                );
-
-                if (!$inserted) {
-                    error_log('Did not insert'.print_r($wpdb->last_error, true));
-                    JVB()->error()->log(
-                        '[ResponseRoutes]:processOperation',
-                        'Failed to insert response',
-                        ['data' => $data, 'error' => $wpdb->last_error],
-                        'error'
-                    );
-                    return [
-						'success'	=> false,
-						'result'	=> 'Failed to create response'
-					];
-                }
-
-                $response_id = $wpdb->insert_id;
-                error_log('Response ID: '.print_r($response_id, true));
-
-                // Send notification to post author
-                $post = get_post($data['item_id']);
-                if ($post && $post->post_author != $operation->user_id) {
-                    JVB()->notification()->addNotification(
-                        $post->post_author,
-                        'new_response',
-                        [
-                            'message' => 'Someone responded to your post',
-                            'item_id' => $data['item_id'],
-                            'response_id' => $response_id
-                        ]
-                    );
-                }
-
-                // Send notification to parent response author if this is a reply
-                if ($data['parent_id']) {
-                    $parent = $wpdb->get_row($wpdb->prepare(
-                        "SELECT user_id FROM $table WHERE id = %d",
-                        $data['parent_id']
-                    ));
-
-                    if ($parent && $parent->user_id != $operation->user_id) {
-                        JVB()->notification()->addNotification(
-                            $parent->user_id,
-                            'response_reply',
-                            [
-                                'message' => 'Someone replied to your response',
-                                'item_id' => $data['item_id'],
-                                'response_id' => $response_id
-                            ]
-                        );
-                    }
-                }
-
-                $this->cache->forget($data['item_id']);
-                return ['success' => true, 'result' => $response_id];
-
-            case 'update_response':
-                $update_data = [];
-                $update_format = [];
-
-                if (array_key_exists('response', $data)) {
-                    $update_data['response'] = $data['response'];
-                    $update_data['updated_at'] = current_time('mysql');
-                    $update_format[] = '%s';
-                    $update_format[] = '%s';
-                }
-
-                if (array_key_exists('status', $data)) {
-                    $update_data['status'] = $data['status'];
-                    $update_format[] = '%s';
-                }
-
-                if (empty($update_data)) {
-                    return ['success' => false, 'result' => 'No fields to update'];
-                }
+			return ['success' => false, 'result' => 'Failed to update response'];
+		}
 
-                $updated = $wpdb->update(
-                    $table,
-                    $update_data,
-                    ['id' => $data['response_id']],
-                    $update_format,
-                    ['%d']
-                );
+		$this->cache->flush();
+		return ['success' => true, 'result' => $updated];
+	}
 
-                if ($updated === false) {
-                    JVB()->error()->log(
-                        '[ResponseRoutes]:processOperation',
-                        'Failed to update response',
-                        ['data' => $data, 'error' => $wpdb->last_error],
-                        'error'
-                    );
-                    return [
-						'success'	=> false,
-						'result'	=> 'Failed to update response'
-					];
-                }
+	/**
+	 * Delete a response (or mark as deleted if it has replies)
+	 */
+	protected function deleteResponse(array $data): array
+	{
+		$response = $this->table->get(['id' => $data['response_id']]);
 
-                $this->cache->forget($data['item_id']);
-                $this->cache->flush();
-                return ['success' => true, 'result' => $updated];
+		if (!$response) {
+			return ['success' => false, 'result' => 'Response not found'];
+		}
 
-            case 'delete_response':
-                // Get response info before deleting
-                $response = $wpdb->get_row($wpdb->prepare(
-                    "SELECT * FROM $table WHERE id = %d",
-                    $data['response_id']
-                ));
+		// Check if it has replies
+		$has_replies = $this->table->where(['parent_id' => $data['response_id']])->countResults() > 0;
 
-                if (!$response) {
-                    return ['success' => false, 'result' => 'Response not found'];
-                }
+		if ($has_replies) {
+			// Don't delete, just mark as deleted
+			$updated = $this->table->update(
+				[
+					'response' => '[ deleted ]',
+					'status' => 'deleted',
+					'updated_at' => current_time('mysql')
+				],
+				['id' => $data['response_id']]
+			);
 
-                // Check if this response has replies
-                $has_replies = $wpdb->get_var($wpdb->prepare(
-                    "SELECT COUNT(*) FROM $table WHERE parent_id = %d",
-                    $data['response_id']
-                )) > 0;
+			$this->cache->flush();
+			return ['success' => true, 'result' => $updated];
+		}
 
-                if ($has_replies) {
-                    // Don't delete, just mark as deleted and replace content
-                    $updated = $wpdb->update(
-                        $table,
-                        [
-                            'response' => '[ deleted ]',
-                            'status' => 'deleted',
-                            'updated_at' => current_time('mysql')
-                        ],
-                        ['id' => $data['response_id']],
-                        ['%s', '%s', '%s'],
-                        ['%d']
-                    );
-                    $this->cache->flush();
-                    return ['success' => true, 'result' => $updated ];
-                } else {
-                    // No replies, safe to actually delete
-                    $deleted = $wpdb->delete(
-                        $table,
-                        ['id' => $data['response_id']],
-                        ['%d']
-                    );
+		// No replies, safe to delete
+		$deleted = $this->table->delete(['id' => $data['response_id']]);
 
-                    if ($deleted === false) {
-                        JVB()->error()->log(
-                            '[ResponseRoutes]:processOperation',
-                            'Failed to delete response',
-                            ['data' => $data, 'error' => $wpdb->last_error],
-                            'error'
-                        );
-                        return [
-							'success'	=> false,
-							'result'	=> 'Failed to delete response'
-						];
-                    }
+		if ($deleted === false) {
+			$this->logError('Failed to delete response', [
+				'data' => $data,
+				'error' => $this->table->getLastError()
+			]);
 
-                    $this->cache->forget($data['item_id']);
-                    $this->cache->flush();
-                    return ['success' => true, 'result' => $deleted];
-                }
-        }
+			return ['success' => false, 'result' => 'Failed to delete response'];
+		}
 
+		$this->cache->flush();
+		return ['success' => true, 'result' => $deleted];
+	}
 
-        return $result;
-    }
+	/**
+	 * Send notifications for new responses
+	 */
+	protected function sendNotifications(int $item_id, ?int $parent_id, int $response_id, int $user_id): void
+	{
+		// Notify post author
+		$post = get_post($item_id);
+		if ($post && $post->post_author != $user_id) {
+			JVB()->notification()->addNotification(
+				$post->post_author,
+				'new_response',
+				[
+					'message' => 'Someone responded to your post',
+					'item_id' => $item_id,
+					'response_id' => $response_id
+				]
+			);
+		}
 
-    /**
-     * Build query arguments from request parameters
-     * @param WP_REST_Request $request
-     * @return array
-     */
-    protected function buildQueryArgs(WP_REST_Request $request):array
-    {
-        $page = max(1, (int) $request->get_param('page') ?? 1);
-        $per_page = min(100, max(1, (int) $request->get_param('per_page') ?? $this->per_page));
+		// Notify parent response author if this is a reply
+		if ($parent_id) {
+			$parent = $this->table->get(['id' => $parent_id]);
 
-        $args = [
-            'page' => $page,
-            'per_page' => $per_page,
-            'orderby' => 'created_at',
-            'order' => 'DESC'
-        ];
+			if ($parent && $parent->user_id != $user_id) {
+				JVB()->notification()->addNotification(
+					$parent->user_id,
+					'response_reply',
+					[
+						'message' => 'Someone replied to your response',
+						'item_id' => $item_id,
+						'response_id' => $response_id
+					]
+				);
+			}
+		}
+	}
 
-        // Apply parent filter (null means top-level responses)
-        if ($request->has_param('parent_id')) {
-            $parent_id = $request->get_param('parent_id');
-            $args['parent_id'] = $parent_id === '0' ? null : (int) $parent_id;
-        }
+	/**
+	 * Build query arguments from request
+	 */
+	protected function buildQueryArgs(WP_REST_Request $request): array
+	{
+		$page = max(1, (int) ($request->get_param('page') ?? 1));
+		$per_page = min(100, max(1, (int) ($request->get_param('per_page') ?? $this->perPage)));
 
-        // Apply ordering
-        if ($request->has_param('orderby')) {
-            $orderby = $request->get_param('orderby');
-            $valid_orderby = ['created_at', 'updated_at', 'upvotes', 'downvotes'];
+		$args = [
+			'page' => $page,
+			'per_page' => $per_page,
+			'orderby' => 'created_at',
+			'order' => 'DESC'
+		];
 
-            if (in_array($orderby, $valid_orderby)) {
-                $args['orderby'] = $orderby;
-            }
-        }
+		// Parent filter (null = top-level only)
+		if ($request->has_param('parent_id')) {
+			$parent_id = $request->get_param('parent_id');
+			$args['parent_id'] = $parent_id === '0' ? null : (int) $parent_id;
+		}
 
-        if ($request->has_param('order')) {
-            $order = strtoupper($request->get_param('order'));
-            if (in_array($order, ['ASC', 'DESC'])) {
-                $args['order'] = $order;
-            }
-        }
+		// Ordering
+		if ($request->has_param('orderby')) {
+			$orderby = $request->get_param('orderby');
+			$valid = ['created_at', 'updated_at', 'upvotes', 'downvotes'];
 
-        return $args;
-    }
+			if (in_array($orderby, $valid)) {
+				$args['orderby'] = $orderby;
+			}
+		}
 
-    /**
-     * @param int $itemID
-     * @param int $ID
-     *
-     * @return array
-     */
-    protected function getChildren(int $itemID, int $ID):array
-    {
-        return $this->getItemResponse($itemID, ['parent_id' => $ID]);
-    }
-    /**
-     * Format a response object for API output
-     * @param object $response
-     * @return array
-     */
-    protected function formatItem(object $response):array
-    {
-        if ($response->is_user_deleted) {
-            // For deleted users, show anonymous info
-            $formatted = [
-                'id' => (int) $response->id,
-                'item_id' => (int) $response->item_id,
-                'parent_id' => $response->parent_id ? (int) $response->parent_id : null,
-                'response' => $response->response,
-                'status' => $response->status,
-                'created_at' => $response->created_at,
-                'updated_at' => $response->updated_at,
-                'children'  => $this->getChildren($response->item_id, $response->id),
-                'user' => [
-                    'id' => null,
-                    'name' => '[deleted user]',
-                    'is_deleted' => true
-                ],
-                'upvotes' => (int) ($response->upvotes ?? 0),
-                'downvotes' => (int) ($response->downvotes ?? 0),
-                'karma' => (int) ($response->upvotes ?? 0) - (int) ($response->downvotes ?? 0)
-            ];
-        } else {
-            // Normal user processing as before
-            error_log('Response: '.print_r($response, true));
-            $artist = jvbContentFromUser($response->user_id);
+		if ($request->has_param('order')) {
+			$order = strtoupper($request->get_param('order'));
+			if (in_array($order, ['ASC', 'DESC'])) {
+				$args['order'] = $order;
+			}
+		}
 
-            $formatted = [
-                'id' => (int) $response->id,
-                'item_id' => (int) $response->item_id,
-                'parent_id' => $response->parent_id ? (int) $response->parent_id : null,
-                'response' => $response->response,
-                'status' => $response->status,
-                'created_at' => $response->created_at,
-                'updated_at' => $response->updated_at,
-                'children'  => $this->getChildren($response->item_id, $response->id),
-                'upvotes' => (int) ($response->upvotes ?? 0),
-                'downvotes' => (int) ($response->downvotes ?? 0),
-                'karma' => (int) ($response->upvotes ?? 0) - (int) ($response->downvotes ?? 0)
+		return $args;
+	}
 
-            ];
+	/**
+	 * Get child responses recursively
+	 */
+	protected function getChildren(int $item_id, int $parent_id): array
+	{
+		return $this->getItemResponse($item_id, ['parent_id' => $parent_id]);
+	}
 
-            // Add artist info if available
-            if (!empty($artist)) {
-                $formatted['artist'] = [
-                    'name' => $artist['name'],
-                    'url' => $artist['url'],
-                    'shop' => $artist['shop'] ?? null
-                ];
-            }
-        }
+	/**
+	 * Format response for API output
+	 */
+	protected function formatItem(object $response): array
+	{
+		$formatted = [
+			'id' => (int) $response->id,
+			'item_id' => (int) $response->item_id,
+			'parent_id' => $response->parent_id ? (int) $response->parent_id : null,
+			'response' => $response->response,
+			'status' => $response->status,
+			'created_at' => $response->created_at,
+			'updated_at' => $response->updated_at,
+			'children' => $this->getChildren($response->item_id, $response->id),
+			'upvotes' => (int) ($response->upvotes ?? 0),
+			'downvotes' => (int) ($response->downvotes ?? 0),
+			'karma' => (int) ($response->upvotes ?? 0) - (int) ($response->downvotes ?? 0),
+			'reply_count' => (int) ($response->reply_count ?? 0)
+		];
 
-        // Add reply count if available (for both deleted and non-deleted users)
-        if (isset($response->reply_count)) {
-            $formatted['reply_count'] = (int) $response->reply_count;
-        }
+		// Handle deleted users
+		if ($response->is_user_deleted) {
+			$formatted['user'] = [
+				'id' => null,
+				'name' => '[deleted user]',
+				'is_deleted' => true
+			];
+		} else {
+			// Add artist info
+			$artist = jvbContentFromUser($response->user_id);
+			if (!empty($artist)) {
+				$formatted['artist'] = [
+					'name' => $artist['name'],
+					'url' => $artist['url'],
+					'shop' => $artist['shop'] ?? null
+				];
+			}
+		}
 
-        return $formatted;
-    }
+		return $formatted;
+	}
 
-    /**
-     * Handle user deletion by anonymizing their responses
-     * @param int $user_id The deleted user ID
-     */
-    public function handleUserDeletion(int $user_id):void
-    {
-        global $wpdb;
-        $table = $wpdb->prefix . BASE . 'news_responses';
+	/**
+	 * Handle user deletion by anonymizing responses
+	 */
+	public function handleUserDeletion(int $user_id): void
+	{
+		$updated = $this->table->update(
+			[
+				'is_user_deleted' => 1,
+				'updated_at' => current_time('mysql')
+			],
+			['user_id' => $user_id]
+		);
 
-        // Anonymize all responses by this user
-        $wpdb->update(
-            $table,
-            [
-                'is_user_deleted' => 1,
-                // Keep user_id intact for internal tracking, but mark as deleted
-                'updated_at' => current_time('mysql')
-            ],
-            ['user_id' => $user_id],
-            ['%d', '%s'],
-            ['%d']
-        );
+		$this->logError(
+			'Anonymized responses for deleted user',
+			['user_id' => $user_id, 'count' => $updated],
+			'info'
+		);
 
-        JVB()->error()->log(
-            'news_responses',
-            'Anonymized responses for deleted user',
-            ['user_id' => $user_id, 'count' => $wpdb->rows_affected],
-            'info'
-        );
-    }
+		$this->cache->flush();
+	}
 }

--
Gitblit v1.10.0