From 747d741293e064a979d7bf6c143ef969ea6d7629 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Sun, 24 May 2026 20:49:44 +0000
Subject: [PATCH] =GMBReview block minor tweaks. Refactored ReferralManager.php and ReferralRoutes.php to utilize the manager for all logic, and CustomTable for table interactions.

---
 inc/rest/routes/ReferralRoutes.php |  625 ++++++++++++++++++--------------------------------------
 1 files changed, 203 insertions(+), 422 deletions(-)

diff --git a/inc/rest/routes/ReferralRoutes.php b/inc/rest/routes/ReferralRoutes.php
index aec6c55..a1e3f3e 100644
--- a/inc/rest/routes/ReferralRoutes.php
+++ b/inc/rest/routes/ReferralRoutes.php
@@ -1,10 +1,12 @@
 <?php
 namespace JVBase\rest\routes;
 
+use JVBase\base\Site;
 use JVBase\importers\JaneAppClientImporter;
-use JVBase\managers\JaneSalesImporter;
-use JVBase\managers\MagicLinkManager;
-use JVBase\rest\RestRouteManager;
+use JVBase\importers\JaneAppSalesImporter;
+use JVBase\managers\CustomTable;
+use JVBase\rest\Rest;
+use JVBase\rest\Route;
 use WP_REST_Request;
 use WP_REST_Response;
 use WP_Error;
@@ -16,130 +18,80 @@
 /**
  * REST API routes for referral system
  */
-class ReferralRoutes extends RestRouteManager
+class ReferralRoutes extends Rest
 {
-	protected string $referrals_table;
-	protected string $rewards_table;
-	protected $wpdb;
-
 	public function __construct()
 	{
-		$this->route = 'referrals';
-		$this->cache_name = 'referrals';
+		$this->cacheName = 'referrals';
+		$this->cacheTtl = (int)HOUR_IN_SECONDS;
 		parent::__construct();
 
-		global $wpdb;
-		$this->wpdb = $wpdb;
-		$this->referrals_table = $wpdb->prefix . BASE . 'referrals';
-		$this->rewards_table = $wpdb->prefix . BASE . 'referral_rewards';
-
 		add_filter(BASE.'handle_bulk_operation', [$this, 'processOperation'], 10, 3);
 	}
 
 	public function registerRoutes(): void
 	{
-		/**
-		 * Main referrals endpoint
-		 * GET: List referrals with filters
-		 * POST: Perform actions (invite, consulted, treated, remove, resend)
-		 */
-		register_rest_route($this->namespace, "/{$this->route}", [
-			[
-				'methods' => 'GET',
-				'callback' => [$this, 'getReferrals'],
-				'permission_callback' => [$this, 'checkPermission'],
-				'args' => [
-					'user' => ['type' => 'integer', 'sanitize_callback' => 'absint'],
-					'status' => ['type' => 'string', 'enum' => ['all', 'pending', 'consulted', 'treated', 'unused', 'registered', 'completed']],
-					'date_start' => ['type' => 'string'],
-					'date_end' => ['type' => 'string'],
-					'limit' => ['type' => 'integer', 'default' => 50],
-					'offset' => ['type' => 'integer', 'default' => 0],
-					'format' => ['type' => 'string', 'enum' => ['simple', 'formatted'], 'default' => 'formatted'],
-					'search' => ['type' => 'string']
-				]
-			],
-			[
-				'methods' => 'POST',
-				'callback' => [$this, 'handleAction'],
-				'permission_callback' => [$this, 'checkPermission'],
-				'args' => [
-					'action' => [
-						'required' => true,
-						'type' => 'string',
-						'enum' => ['invite', 'consulted', 'treated', 'remove', 'resend']
-					]
-				]
-			]
-		]);
+		// Main referrals endpoint - list and manage referrals
+		Route::for('referrals')
+			->get([$this, 'getReferrals'])
+			->args([
+				'user' => 'integer',
+				'status' => 'string|enum:all,pending,consulted,treated,unused,registered,completed|default:all',
+				'date_start' => 'string',
+				'date_end' => 'string',
+				'limit' => 'integer|default:50',
+				'offset' => 'integer|default:0',
+				'search' => 'string'
+			])
+			->rateLimit()
+			->post([$this, 'handleAction'])
+			->args([
+				'action' => 'string|required|enum:invite,consulted,treated,remove,resend'
+			])
+			->auth('user')
+			->rateLimit(10)
+			->register();
 
-		/**
-		 * Referral code endpoint
-		 * GET: Get user's referral code
-		 * POST: Validate a referral code
-		 */
-		register_rest_route($this->namespace, "/{$this->route}/code", [
-			[
-				'methods' => 'GET',
-				'callback' => [$this, 'getCode'],
-				'permission_callback' => [$this, 'checkPermission'],
-				'args' => [
-					'user' => ['type' => 'integer', 'sanitize_callback' => 'absint']
-				]
-			],
-			[
-				'methods' => 'POST',
-				'callback' => [$this, 'validateCode'],
-				'permission_callback' => '__return_true', // Public endpoint
-				'args' => [
-					'code' => ['required' => true, 'type' => 'string']
-				]
-			]
-		]);
+		// Referral code endpoint
+		Route::for('referrals/code')
+			->get([$this, 'getCode'])
+			->args(['user' => 'integer'])
+			->auth('user')
+			->rateLimit(30)
+			->post([$this, 'validateCode'])
+			->args(['code' => 'string|required'])
+			->auth('public')
+			->rateLimit(10)
+			->register();
 
-		/**
-		 * Stats endpoint
-		 * GET: Get user's referral statistics
-		 */
-		register_rest_route($this->namespace, "/{$this->route}/stats", [
-			'methods' => 'GET',
-			'callback' => [$this, 'getStats'],
-			'permission_callback' => [$this, 'checkPermission'],
-			'args' => [
-				'user' => ['type' => 'integer', 'sanitize_callback' => 'absint'],
-			]
-		]);
+		// Stats endpoint
+		Route::for('referrals/stats')
+			->get([$this, 'getStats'])
+			->args(['user' => 'integer'])
+			->auth('user')
+			->rateLimit(30)
+			->register();
 
-		/**
-		 * Settings endpoint (admin only)
-		 */
-		register_rest_route($this->namespace, "/{$this->route}/settings", [
-			[
-				'methods' => 'GET',
-				'callback' => [$this, 'getSettings'],
-				'permission_callback' => [$this, 'checkAdminPermission']
-			],
-			[
-				'methods' => 'POST',
-				'callback' => [$this, 'updateSettings'],
-				'permission_callback' => [$this, 'checkAdminPermission']
-			]
-		]);
+		// Settings endpoint (admin only)
+		Route::for('referrals/settings')
+			->get([$this, 'getSettings'])
+			->post([$this, 'updateSettings'])
+			->auth('admin')
+			->rateLimit(10)
+			->register();
 
-		/**
-		 * CSV Upload endpoints (admin only)
-		 */
-		register_rest_route($this->namespace, "/{$this->route}/upload-clients", [
-			'methods' => 'POST',
-			'callback' => [$this, 'handleClientUpload'],
-			'permission_callback' => [$this, 'checkAdminPermission']
-		]);
+		// CSV Upload endpoints (admin only)
+		Route::for('referrals/upload-clients')
+			->post([$this, 'handleClientUpload'])
+			->auth('admin')
+			->rateLimit(3)
+			->register();
 
-		register_rest_route($this->namespace, "/{$this->route}/upload-sales", [
-			'methods' => 'POST',
-			'callback' => [$this, 'handleSalesUpload'],
-			'permission_callback' => [$this, 'checkAdminPermission']
-		]);
+		Route::for('referrals/upload-sales')
+			->post([$this, 'handleSalesUpload'])
+			->auth('admin')
+			->rateLimit(3)
+			->register();
 	}
 
 	/**
@@ -152,19 +104,16 @@
 	{
 		$user_id = $request->get_param('user');
 
-		// Determine scope
+		// Determine scope: admin without user param gets all referrals
 		if (!$user_id) {
 			$current_user_id = get_current_user_id();
-			$is_admin = current_user_can('manage_options');
-			if ($is_admin) {
-				// Admin with no user param = get all referrals
+			if (current_user_can('manage_options')) {
 				return $this->getAllReferrals($request);
 			}
 			$user_id = $current_user_id;
 		}
 
-
-		// Get user's referrals
+		// Build cache key
 		$args = [
 			'status' => $request->get_param('status') ?? 'all',
 			'limit' => $request->get_param('limit') ?? 50,
@@ -172,14 +121,15 @@
 			'date_start' => $request->get_param('date_start'),
 			'date_end' => $request->get_param('date_end'),
 		];
+		$cache_key = "user_{$user_id}_" . $this->cache->generateKey($args);
 
-		$cache_key = "ref_{$user_id}_" . md5(serialize($args));
-		// Check headers for 304 Not Modified
+		// Check 304 Not Modified
 		$cache_check = $this->checkHeaders($request, $cache_key);
 		if ($cache_check instanceof WP_REST_Response) {
-			return $cache_check; // Returns 304 if not modified
+			return $cache_check;
 		}
 
+		// Get referrals from manager
 		$referrals = JVB()->referrals()->getUserReferrals($user_id, $args);
 
 		$data = [
@@ -187,11 +137,8 @@
 			'total' => count($referrals)
 		];
 
-		// Create response with cache headers
 		$response = $this->success($data);
-
-		// Add ETag and Last-Modified headers
-		return $this->addCacheHeaders($response, $cache_key, $data);
+		return $this->addCacheHeaders($response);
 	}
 
 	/**
@@ -217,51 +164,53 @@
 	 */
 	protected function actionInvite(WP_REST_Request $request): WP_REST_Response
 	{
-		$data = $request->get_params();
-		error_log('Send Referral Invitations:'.print_r($data, true));
 		$user = absint($request->get_param('user'));
-		if (!$this->checkUser($user)) {
-			return new WP_REST_Response([
-				'success'	=> false,
-				'message'	=> 'No user found'
-			]);
+		if (!$user || !get_userdata($user)) {
+			return $this->error('Invalid user', 'invalid_user', 400);
 		}
+
+		//Additional check to not send too many emails in an hour
+		$user = absint($request->get_param('user'));
+		$transient_key = "referral_invite_limit_{$user}";
+		$recent_invites = get_transient($transient_key) ?: 0;
+
+		if ($recent_invites >= 20) { // Max 5 batch invites per hour
+			return $this->error('Too many invitations sent. Please try again later.', 'rate_limit', 429);
+		}
+		set_transient($transient_key, $recent_invites + 1, HOUR_IN_SECONDS);
+
 		$subject = sanitize_text_field($request->get_param('subject'));
 		$message = sanitize_textarea_field($request->get_param('message'));
 		$invitations = $request->get_param('invite');
 
-		// Validate invitation format
-		foreach ($invitations as $key => $invite) {
-			if (!array_key_exists('name', $invite) || !array_key_exists('email', $invite)) {
-				unset($invitations[$key]);
-			} else {
-				$temp = [
-					'name'	=> sanitize_text_field($invite['name']),
-					'email'	=> sanitize_email($invite['email'])
+		// Validate and sanitize invitations
+		$sanitized_invitations = [];
+		foreach ($invitations as $invite) {
+			if (isset($invite['name'], $invite['email'])) {
+				$sanitized_invitations[] = [
+					'name' => sanitize_text_field($invite['name']),
+					'email' => sanitize_email($invite['email'])
 				];
-				$invitations[$key] = $temp;
 			}
 		}
 
+		if (empty($sanitized_invitations)) {
+			return $this->error('No valid invitations provided', 'no_invitations', 400);
+		}
+
 		$operationID = sanitize_text_field($request->get_param('id'));
-		$operation = JVB()->queue()->queueOperation(
+		JVB()->queue()->queueOperation(
 			'referral_invite',
 			$user,
 			[
 				'subject' => $subject,
 				'message' => $message,
-				'invitations' => $invitations
+				'invitations' => $sanitized_invitations
 			],
-			[
-				'operation_id'	=> $operationID
-			]
+			['operation_id' => $operationID]
 		);
 
-		return new WP_REST_Response([
-			'success'	=> true,
-			'message'	=> 'Queued for Processing',
-			'operation'	=> $operationID
-		]);
+		return $this->queued($operationID, 'Referral invitations queued');
 	}
 
 	/**
@@ -270,7 +219,7 @@
 	protected function actionUpdateStatus(WP_REST_Request $request, string $status): WP_REST_Response
 	{
 		if (!current_user_can('manage_options')) {
-			return $this->error('Admin permission required', 'unauthorized', 403);
+			return $this->forbidden('Admin permission required');
 		}
 
 		$referral_id = $request->get_param('referral_id');
@@ -278,42 +227,16 @@
 			return $this->error('referral_id required', 'missing_id', 400);
 		}
 
-		$referral = $this->wpdb->get_row($this->wpdb->prepare(
-			"SELECT * FROM {$this->referrals_table} WHERE id = %d",
-			$referral_id
-		));
+		$result = JVB()->referrals()->updateStatus($referral_id, $status);
 
-		if (!$referral) {
-			return $this->error('Referral not found', 'not_found', 404);
+		if (is_wp_error($result)) {
+			$code = $result->get_error_code();
+			return $code === 'not_found'
+				? $this->notFound($result->get_error_message())
+				: $this->error($result->get_error_message(), $code, 500);
 		}
 
-		// Update status
-		$update_data = ['status' => $status];
-		$update_data["{$status}_at"] = current_time('mysql');
-
-		if ($status === 'treated') {
-			$update_data['treatment_count'] = ($referral->treatment_count ?? 0) + 1;
-		}
-
-		$updated = $this->wpdb->update(
-			$this->referrals_table,
-			$update_data,
-			['id' => $referral_id],
-			array_fill(0, count($update_data), '%s'),
-			['%d']
-		);
-
-
-
-		if ($updated) {
-			// Also create rewards if treated
-			if ($status === 'treated') {
-				$this->createRewards($referral);
-			}
-		}
-
-		$this->cache->clear();
-
+		$this->cache->flush();
 		return $this->success(['message' => "Referral marked as {$status}"]);
 	}
 
@@ -327,29 +250,20 @@
 			return $this->error('referral_id required', 'missing_id', 400);
 		}
 
-		$referral = $this->wpdb->get_row($this->wpdb->prepare(
-			"SELECT * FROM {$this->referrals_table} WHERE id = %d",
-			$referral_id
-		));
+		$result = JVB()->referrals()->removeReferral($referral_id, get_current_user_id());
 
-		if (!$referral) {
-			return $this->error('Referral not found', 'not_found', 404);
+		if (is_wp_error($result)) {
+			$code = $result->get_error_code();
+			$status = match($code) {
+				'not_found'      => 404,
+				'unauthorized'   => 403,
+				'invalid_status' => 400,
+				default          => 500
+			};
+			return $this->error($result->get_error_message(), $code, $status);
 		}
 
-		// Check ownership
-		$current_user_id = get_current_user_id();
-		if ($referral->referrer_id != $current_user_id && !current_user_can('manage_options')) {
-			return $this->error('Unauthorized', 'unauthorized', 403);
-		}
-
-		// Can only remove pending referrals
-		if ($referral->status !== 'pending') {
-			return $this->error('Can only remove pending referrals', 'invalid_status', 400);
-		}
-
-		$this->wpdb->delete($this->referrals_table, ['id' => $referral_id], ['%d']);
-		$this->cache->clear();
-
+		$this->cache->flush();
 		return $this->success(['message' => 'Referral removed']);
 	}
 
@@ -363,45 +277,18 @@
 			return $this->error('referral_id required', 'missing_id', 400);
 		}
 
-		$current_user_id = get_current_user_id();
-		$referral = $this->wpdb->get_row($this->wpdb->prepare(
-			"SELECT * FROM {$this->referrals_table} WHERE id = %d AND referrer_id = %d",
-			$referral_id,
-			$current_user_id
-		));
-
-		if (!$referral) {
-			return $this->error('Referral not found', 'not_found', 404);
-		}
-
-		// Check rate limit (once per week)
-		$transient_key = 'referral_last_invite_' . md5($referral->referee_email);
-		$last_invite = get_transient($transient_key);
-
-		if ($last_invite && (time() - $last_invite) < WEEK_IN_SECONDS) {
-			return $this->error(
-				'Can only resend once per week',
-				'rate_limit',
-				429
-			);
-		}
-
-		// Resend via referral manager
-		$result = JVB()->referrals()->sendReferralInvitation(
-			$current_user_id,
-			$referral->referee_email,
-			$referral->referee_name,
-			sprintf('Reminder: Join %s', get_bloginfo('name')),
-			'Just a friendly reminder about my invitation!'
-		);
+		$result = JVB()->referrals()->resendInvitation($referral_id, get_current_user_id());
 
 		if (is_wp_error($result)) {
-			return $this->error($result->get_error_message(), 'send_failed', 500);
+			$code = $result->get_error_code();
+			$status = match($code) {
+				'not_found'  => 404,
+				'rate_limit' => 429,
+				default      => 500
+			};
+			return $this->error($result->get_error_message(), $code, $status);
 		}
 
-		// Set rate limit
-		set_transient($transient_key, time(), WEEK_IN_SECONDS);
-
 		return $this->success(['message' => 'Invitation resent']);
 	}
 
@@ -415,7 +302,7 @@
 
 		// Check permission
 		if ($user_id != get_current_user_id() && !current_user_can('manage_options')) {
-			return $this->error('Unauthorized', 'unauthorized', 403);
+			return $this->forbidden('Unauthorized');
 		}
 
 		$code = JVB()->referrals()->getUserReferralCode($user_id);
@@ -466,11 +353,10 @@
 	 */
 	public function getStats(WP_REST_Request $request): WP_REST_Response
 	{
-		$user_id = $request->get_param('user');
-
+		$user_id = $request->get_param('user') ?? get_current_user_id();
 		$cache_key = "stats_{$user_id}";
 
-		// Check for 304 Not Modified
+		// Check 304 Not Modified
 		$cache_check = $this->checkHeaders($request, $cache_key);
 		if ($cache_check instanceof WP_REST_Response) {
 			return $cache_check;
@@ -479,9 +365,7 @@
 		$stats = JVB()->referrals()->getUserStats($user_id);
 
 		$response = $this->success(['items' => [$stats]]);
-
-		// Add cache headers (5 minutes for stats)
-		return $this->addCacheHeaders($response, $cache_key, $stats, 5 * MINUTE_IN_SECONDS);
+		return $this->addCacheHeaders($response);
 	}
 
 	/**
@@ -508,7 +392,7 @@
 		];
 
 		update_option(BASE . 'referral_settings', $settings);
-		$this->cache->clear();
+		$this->cache->flush();
 
 		return $this->success([
 			'message' => 'Settings updated',
@@ -521,106 +405,18 @@
 	 */
 	protected function getAllReferrals(WP_REST_Request $request): WP_REST_Response
 	{
-		$where = ['1=1'];
-		$where_params = [];
-
-		$status = $request->get_param('status');
-		if ($status && $status !== 'all') {
-			$where[] = 'status = %s';
-			$where_params[] = $status;
-		}
-
-		if ($date_start = $request->get_param('date_start')) {
-			$where[] = 'referred_at >= %s';
-			$where_params[] = $date_start;
-		}
-
-		if ($date_end = $request->get_param('date_end')) {
-			$where[] = 'referred_at <= %s';
-			$where_params[] = $date_end;
-		}
-
-		$search = $request->get_param('search');
-		if (!empty($search)) {
-			$search_term = '%' . $this->wpdb->esc_like($search) . '%';
-			$where[] = '(r.referee_name LIKE %s OR r.referee_email LIKE %s OR r.referral_code LIKE %s OR u.display_name LIKE %s OR ru.display_name LIKE %s OR ru.user_email LIKE %s)';
-			$where_params[] = $search_term;
-			$where_params[] = $search_term;
-			$where_params[] = $search_term;
-			$where_params[] = $search_term;
-			$where_params[] = $search_term;
-			$where_params[] = $search_term;
-		}
-
-		$limit = $request->get_param('limit') ?? 50;
-		$offset = $request->get_param('offset') ?? 0;
-
-		$where_params[] = $limit;
-		$where_params[] = $offset;
-
-		$query = "SELECT r.*, u.display_name as referrer_name
-			FROM {$this->referrals_table} r
-			LEFT JOIN {$this->wpdb->users} u ON r.referrer_id = u.ID
-			WHERE " . implode(' AND ', $where) . "
-			ORDER BY referred_at DESC
-			LIMIT %d OFFSET %d";
-
-		$items = $this->wpdb->get_results($this->wpdb->prepare($query, $where_params));
-
-		error_log('All Referrals result: '.print_r($items, true));
-		return $this->success([
-			'items' => $items,
-			'total' => count($items)
+		$items = JVB()->referrals()->getAllReferrals([
+			'status'     => $request->get_param('status') ?? 'all',
+			'search'     => $request->get_param('search'),
+			'date_start' => $request->get_param('date_start'),
+			'date_end'   => $request->get_param('date_end'),
+			'limit'      => $request->get_param('limit') ?? 50,
+			'offset'     => $request->get_param('offset') ?? 0,
 		]);
+
+		return $this->success(['items' => $items, 'total' => count($items)]);
 	}
 
-	/**
-	 * Helper: Create rewards for completed referral
-	 */
-	protected function createRewards(object $referral): void
-	{
-		$settings = JVB()->referrals()->getRewardSettings();
-
-		// Referrer reward
-		$this->wpdb->insert(
-			$this->rewards_table,
-			[
-				'referral_id' => $referral->id,
-				'user_id' => $referral->referrer_id,
-				'reward_type' => 'referrer',
-				'amount' => $settings['referrer_reward_amount'],
-				'reward_calculation' => $settings['referrer_reward_type'],
-				'status' => 'available',
-				'created_at' => current_time('mysql')
-			],
-			['%d', '%d', '%s', '%f', '%s', '%s', '%s']
-		);
-
-		// Referee reward
-		if ($referral->referee_id) {
-			$this->wpdb->insert(
-				$this->rewards_table,
-				[
-					'referral_id' => $referral->id,
-					'user_id' => $referral->referee_id,
-					'reward_type' => 'referee',
-					'amount' => $settings['referee_reward_amount'],
-					'reward_calculation' => $settings['referee_reward_type'],
-					'status' => 'available',
-					'created_at' => current_time('mysql')
-				],
-				['%d', '%d', '%s', '%f', '%s', '%s', '%s']
-			);
-		}
-	}
-
-	/**
-	 * Check admin permission
-	 */
-	public function checkAdminPermission(WP_REST_Request $request): bool
-	{
-		return current_user_can('manage_options') && parent::checkPermission($request);
-	}
 
 	/**
 	 * Process queued referral operations
@@ -637,11 +433,24 @@
 			$data['subject'],
 			$data['message']
 		);
+
 		if ($result['success']) {
-			$this->cache->clear();
+			$this->cache->flush();
 		}
-		error_log('Result: '.print_r($result, true));
-		return $result;
+
+		return [
+			'success' => true,
+			'message' => sprintf(
+				'Sent invitations. Success: %d. Failed: %d.',
+				count($result['result']['success']),
+				count($result['result']['failed'])
+			),
+			'details' => [
+				'successful' => $result['result']['success'],
+				'failed' => $result['result']['failed'],
+				'total' => count($data['invitations'])
+			]
+		];
 	}
 
 	/**
@@ -649,37 +458,34 @@
 	 */
 	public function handleClientUpload(WP_REST_Request $request): WP_REST_Response
 	{
-		$files = $request->get_file_params();
-
-		if (!isset($files['file'])) {
-			return new WP_REST_Response([
-				'success' => false,
-				'message' => 'No file uploaded'
-			], 400);
+		if (empty($_FILES['file'])) {
+			return $this->error('No file uploaded', 'no_file', 400);
 		}
 
-		$file = $files['file'];
+		$file = $_FILES['file'];
+
+		if ($file['error'] !== UPLOAD_ERR_OK) {
+			return $this->error('File upload error: ' . $file['error'], 'upload_error', 400);
+		}
 
 		// Validate file type
 		$allowed_types = ['text/csv', 'application/vnd.ms-excel', 'text/plain'];
-		if (!in_array($file['type'], $allowed_types)) {
-			return new WP_REST_Response([
-				'success' => false,
-				'message' => 'File must be a CSV'
-			], 400);
+		$finfo = finfo_open(FILEINFO_MIME_TYPE);
+		$mime_type = finfo_file($finfo, $file['tmp_name']);
+		finfo_close($finfo);
+
+		if (!in_array($mime_type, $allowed_types) && !in_array($file['type'], $allowed_types)) {
+			return $this->error('File must be a CSV', 'invalid_file_type', 400);
 		}
 
 		// Validate file size (10MB max)
 		if ($file['size'] > 10 * 1024 * 1024) {
-			return new WP_REST_Response([
-				'success' => false,
-				'message' => 'File size exceeds 10MB limit'
-			], 400);
+			return $this->error('File size exceeds 10MB limit', 'file_too_large', 400);
 		}
 
 		// Import using JaneAppClientImporter
 		$importer = new JaneAppClientImporter();
-		$default_role = get_option(BASE . 'client_import_role', JVB_USER);
+		$default_role = get_option(BASE . 'referral_role', Site::getDefaultReferralRole());
 
 		$options = [
 			'update_existing' => true,
@@ -691,33 +497,20 @@
 		$result = $importer->importFromCSV($file['tmp_name'], $options);
 
 		if (is_wp_error($result)) {
-			return new WP_REST_Response([
-				'success' => false,
-				'message' => $result->get_error_message()
-			], 500);
+			return $this->error($result->get_error_message(), 'import_failed', 500);
 		}
 
-		// Build detailed message
-		$message = sprintf(
-			'Import complete: %d created, %d updated, %d skipped',
-			$result['created'],
-			$result['updated'],
-			$result['skipped']
-		);
+		$this->cache->flush();
 
-		$details = [];
-		if (!empty($result['skipped_details'])) {
-			$details = $result['skipped_details'];
-		}
-
-		// Clear cache
-		$this->cache->clear();
-
-		return new WP_REST_Response([
-			'success' => true,
-			'message' => $message,
-			'items' => $result,
-			'skipped_details' => $details
+		return $this->success([
+			'message' => sprintf(
+				'Import complete: %d created, %d updated, %d skipped',
+				$result['created'],
+				$result['updated'],
+				$result['skipped']
+			),
+			'stats' => $result,
+			'skipped_details' => $result['skipped_details'] ?? []
 		]);
 	}
 
@@ -726,54 +519,42 @@
 	 */
 	public function handleSalesUpload(WP_REST_Request $request): WP_REST_Response
 	{
-		$files = $request->get_file_params();
-
-		if (!isset($files['file'])) {
-			return new WP_REST_Response([
-				'success' => false,
-				'message' => 'No file uploaded'
-			], 400);
+		if (empty($_FILES['file'])) {
+			return $this->error('No file uploaded', 'no_file', 400);
 		}
 
-		$file = $files['file'];
+		$file = $_FILES['file'];
+
+		if ($file['error'] !== UPLOAD_ERR_OK) {
+			return $this->error('File upload error: ' . $file['error'], 'upload_error', 400);
+		}
 
 		// Validate file type
 		$allowed_types = ['text/csv', 'application/vnd.ms-excel', 'text/plain'];
-		if (!in_array($file['type'], $allowed_types)) {
-			return new WP_REST_Response([
-				'success' => false,
-				'message' => 'File must be a CSV'
-			], 400);
+		$finfo = finfo_open(FILEINFO_MIME_TYPE);
+		$mime_type = finfo_file($finfo, $file['tmp_name']);
+		finfo_close($finfo);
+
+		if (!in_array($mime_type, $allowed_types) && !in_array($file['type'], $allowed_types)) {
+			return $this->error('File must be a CSV', 'invalid_file_type', 400);
 		}
 
 		// Validate file size (10MB max)
 		if ($file['size'] > 10 * 1024 * 1024) {
-			return new WP_REST_Response([
-				'success' => false,
-				'message' => 'File size exceeds 10MB limit'
-			], 400);
+			return $this->error('File size exceeds 10MB limit', 'file_too_large', 400);
 		}
 
-		// Import using JaneSalesImporter
-		$importer = new JaneSalesImporter();
-		$options = [
-			'skip_existing' => true
-		];
-
-		$result = $importer->importFromCSV($file['tmp_name'], $options);
+		// Import using JaneAppSalesImporter
+		$importer = new JaneAppSalesImporter();
+		$result = $importer->importFromCSV($file['tmp_name'], ['skip_existing' => true]);
 
 		if (is_wp_error($result)) {
-			return new WP_REST_Response([
-				'success' => false,
-				'message' => $result->get_error_message()
-			], 500);
+			return $this->error($result->get_error_message(), 'import_failed', 500);
 		}
 
-		// Clear cache
-		$this->cache->clear();
+		$this->cache->flush();
 
-		return new WP_REST_Response([
-			'success' => true,
+		return $this->success([
 			'message' => 'Sales imported successfully',
 			'stats' => $result
 		]);

--
Gitblit v1.10.0