Jake Vanderwerf
2026-08-25 3d25ad7de4fdbb3eba2cde2a62422e6be5d79a0a
inc/rest/routes/FavouritesRoutes.php
@@ -1,8 +1,10 @@
<?php
namespace JVBase\rest\routes;
use JVBase\Instance;
use JVBase\managers\Cache;
use JVBase\managers\CustomTable;
use JVBase\managers\Favourites;
use JVBase\registrar\Registrar;
use JVBase\rest\PermissionHandler;
use JVBase\rest\Response;
@@ -18,6 +20,7 @@
class FavouritesRoutes extends Rest
{
   use Instance;
   protected array $valid_types;
   protected Cache $listsCache;
   protected Cache $sharedListsCache;
@@ -32,20 +35,16 @@
      $this->cacheName = 'favourites';
      $this->cacheTtl = HOUR_IN_SECONDS;
      parent::__construct();
      $this->cache = Cache::for($this->cacheName)->user();
      // Set up cache connections
      $this->cache->connect('post')->connect('user')->connect('taxonomy')->user();
      $this->listsCache = Cache::for('lists')->connect('favourites', true)->user();
      $this->sharedListsCache = Cache::for('sharedLists')->connect('favourites', true)->user();
      $this->favouritesCache = Cache::for('allFavourites')->connect('favourites', true)->user();
      $this->listsCache = Cache::for('fav_lists')->user();
      $this->sharedListsCache = Cache::for('fav_shared_list')->user();
      $this->favouritesCache = Cache::for('allFavourites')->user();
      $this->valid_types = array_merge(Registrar::getRegistered('post'), Registrar::getRegistered('term'));
      // Initialize CustomTable instances
      $this->favourites = CustomTable::for('favourites');
      $this->lists = CustomTable::for('favourites_lists');
      $this->listItems = CustomTable::for('favourites_list_items');
      $this->listShares = CustomTable::for('favourites_list_shares');
   }
   public function registerRoutes(): void
@@ -108,17 +107,17 @@
      }
      $args = $this->buildParams($request);
      $key = $this->cache->generateKey($args);
      $result = $this->cache->remember(
         $args,
         function() use ($user_id, $args) {
            return Favourites::getInstance()->getFavourites($args);
         }
      );
      // Check cache headers
      $cache_check = $this->checkHeaders($request, $key);
      if ($cache_check) {
         return $cache_check;
      }
      $result = JVB()->favourites()->getFavourites($args);
      $result['items'] = $this->formatItems($result['items']);
      return $this->addCacheHeaders(Response::success($result));
      $response = [
         'items' => $this->formatItems($result['items'])
      ];
      return Response::success($response);
   }
@@ -131,29 +130,65 @@
      $user_id = get_current_user_id();
      $action = strtolower(sanitize_text_field($params['action']));
      $action = in_array($action, ['add', 'remove']) ? $action : false;
      $action = in_array($action, ['add', 'remove', 'toggle', 'batch', 'notes']) ? $action : false;
      if (!$action) {
         return $this->error('Invalid favourite action');
      }
      $target_id = absint($params['target_id']??0);
      if ($target_id === 0) {
         return $this->error('Invalid target id');
      }
      $type = sanitize_text_field($params['type']??'');
      if (empty($type)) {
         return $this->error('No type provided');
      }
      $result = JVB()->favourites()->toggleFavourite(
         $action === 'add',
         $user_id,
         $target_id,
         $type
      );
      $target_id = absint($params['target_id']??0);
      if ($action !== 'batch' && $target_id === 0) {
         return $this->error('Invalid target id');
      }
      $favourites = Favourites::getInstance();
      $result = false;
      switch ($action) {
         case 'notes':
            $note = sanitize_text_field($params['note']??'');
            $result = $favourites->addNoteFromTarget($user_id, $target_id, $type, $note);
            break;
         case 'toggle':
            $isFavourited = $favourites->hasFavourited($user_id, $target_id, $type);
            $result = $favourites->toggleFavourite(!$isFavourited, $user_id, $target_id, $type);
            break;
         case 'add':
            $result = $favourites->favourite($user_id, $target_id, $type);
            break;
         case 'remove':
            $result = $favourites->unfavourite($user_id, $target_id, $type);
            break;
         case 'batch':
            $items = $params['items']??[];
            $items = array_filter($items, function ($item) {
               $keys = ['target_id', 'type', 'action'];
               foreach ($keys as $key) {
                  if (!array_key_exists($key, $item)) {
                     return false;
                  }
                  if (empty($item[$key])) {
                     return false;
                  }
               }
               if (!in_array($item['action'], ['add', 'remove'])) {
                  return false;
               }
               return true;
            });
            if (empty($items)) {
               $result = $this->success(['message' => 'No items to update']);
            } else {
               $result = $favourites->batchToggleFavourites($user_id, $items);
            }
            break;
      }
      if ($result) {
         return $this->success();
         return $this->success(is_bool($result) ? [] : ['result' => $result]);
      }
      return $this->error('Something went wrong');
   }
@@ -163,10 +198,8 @@
    */
   public function getLists(WP_REST_Request $request): WP_REST_Response
   {
      $params = $request->get_params();
      $user_id = get_current_user_id();
      $args = $this->buildParams($request);
      $args['per_page'] = 20;
      $listId = $request->get_param('id');
@@ -174,21 +207,13 @@
         $args['where']['id'] = sanitize_text_field($listId);
      }
      $key = $this->listsCache->generateKey($args);
      // Check cache headers
      $cache_check = $this->checkHeaders($request, $key);
      if ($cache_check) {
         return $cache_check;
      }
      $includeShares = !empty($request->get_param('include_shares'));
      $response = !empty($listId)
         ? JVB()->favourites()->getListDetails($listId, $user_id)
         : JVB()->favourites()->getAvailableLists($args, $includeShares);
         ? Favourites::getInstance()->getListDetails($listId, $user_id)
         : Favourites::getInstance()->getAvailableLists($args, $includeShares);
      return $this->addCacheHeaders(Response::success($response));
      return Response::success($response);
   }
@@ -202,131 +227,12 @@
      $user_id = get_current_user_id();
      $counts = JVB()->favourites()->getFavouriteCounts($user_id);
      $counts = Favourites::getInstance()->getFavouriteCounts($user_id);
      return Response::success(['counts' => $counts]);
   }
   /**
    * Process favourite operations using transactions
    */
   public function processOperation($result, object $operation, array $data)
   {
      $action_map = [
         'favourite_add' => 'addFavourite',
         'favourite_remove' => 'removeFavourite',
         'favourite_batch' => 'batchFavourites',
         'favourite_note' => 'processNote',
         'favourite_list_create' => 'createList',
         'favourite_list_update' => 'updateList',
         'favourite_list_delete' => 'deleteList',
         'favourite_list_add_items' => 'addToList',
         'favourite_list_remove_items' => 'removeFromList',
         'favourite_list_share' => 'shareList',
         'favourite_list_unshare' => 'unshareList',
      ];
      if (!isset($action_map[$operation->type])) {
         return $result;
      }
      try {
         $method = $action_map[$operation->type];
         $response = $this->$method($operation->user_id, $data);
         // Clear cache on success
         if ($response['success'] ?? false) {
            Cache::invalidateItem('favourites', $operation->user_id);
            $this->listsCache->flush();
            $this->sharedListsCache->flush();
         }
         return $response;
      } catch (Exception $e) {
         $this->logError('processOperation', [
            'error' => $e->getMessage(),
            'operation_id' => $operation->id,
            'type' => $operation->type
         ]);
         return [
            'success' => false,
            'result' => $e->getMessage()
         ];
      }
   }
   /**
    * Add a favourite using findOrCreate pattern
    */
   protected function addFavourite(int $user_id, array $data): array
   {
      $type = $data['type'];
      $target_id = $data['target_id'];
      if (!str_starts_with($type, BASE)) {
         $type = BASE . $type;
      }
      if (!isset($this->valid_types[$type])) {
         return ['success' => false, 'result' => 'Invalid type'];
      }
      // Use findOrCreate pattern
      $result = $this->favourites->findOrCreate([
         'user_id' => $user_id,
         'type' => $type,
         'target_id' => $target_id
      ]);
      if ((bool)$result) {
         $this->updateFavouriteCount($type, $target_id);
         $this->maybeNotifyOwner($type, $target_id, $user_id);
      }
      return [
         'success' => true,
         'result' => [
            'action' => $result['created'] ? 'added' : 'already_exists',
            'favourite_id' => $result['id'],
            'count' => $this->getFavouriteCount($type, $target_id)
         ]
      ];
   }
   /**
    * Remove a favourite
    */
   protected function removeFavourite(int $user_id, array $data): array
   {
      $type = $data['type'];
      $target_id = $data['target_id'];
      if (!str_starts_with($type, BASE)) {
         $type = BASE . $type;
      }
      $deleted = $this->favourites->where([
         'user_id' => $user_id,
         'type' => $type,
         'target_id' => $target_id
      ])->deleteResults();
      if ($deleted) {
         $this->updateFavouriteCount($type, $target_id);
         $this->removeRelatedNotifications($type, $target_id, $user_id);
      }
      return [
         'success' => true,
         'result' => [
            'action' => $deleted ? 'removed' : 'not_found',
            'count' => $this->getFavouriteCount($type, $target_id)
         ]
      ];
   }
   /**
    * Batch favourites using transaction
    */
   protected function batchFavourites(int $user_id, array $data): array
@@ -334,7 +240,7 @@
      $items = $data['items'] ?? [];
      if (empty($items)) {
         return ['success' => false, 'result' => 'No items provided'];
         return $this->success(['result' => 'No items provided']);
      }
      return $this->favourites->transaction(function($table) use ($user_id, $items) {
@@ -605,7 +511,7 @@
   public function maybeAcceptListInvite(int $user_id, string $email, array $data):void
   {
      if (array_key_exists('list_token', $data) && !empty($data['list_token'])) {
         JVB()->favourites()->acceptListShare($data['list_token'], $user_id);
         Favourites::getInstance()->acceptListShare($data['list_token'], $user_id);
      }
   }
@@ -615,8 +521,8 @@
   protected function getListDetails(int $list_id, int $user_id): array
   {
      // Check access - either owner or has share
      $is_owner = JVB()->favourites()->userOwnsList($list_id, $user_id);
      $is_shared = JVB()->favourites()->userCanViewList($list_id, $user_id);
      $is_owner = Favourites::getInstance()->userOwnsList($list_id, $user_id);
      $is_shared = Favourites::getInstance()->userCanViewList($list_id, $user_id);
      if (!$is_owner && !$is_shared) {
@@ -626,7 +532,7 @@
         ];
      }
      $list = JVB()->favourites()->getListDetails($list_id, $user_id);
      $list = Favourites::getInstance()->getListDetails($list_id, $user_id);
      if (empty($list)) {
         return [
@@ -1121,7 +1027,7 @@
            ]);
            // Send notification
            JVB()->notification()->addNotification(
            NotificationManager::getInstance()->addNotification(
               $share_user->ID,
               'list_shared',
               $user_id,
@@ -1241,7 +1147,7 @@
            $list = $this->lists->where(['id' => $list_id])->first();
            if ($list) {
               JVB()->notification()->addNotification(
               NotificationManager::getInstance()->addNotification(
                  $existing_share->user_id,
                  'list_share_revoked',
                  $user_id,
@@ -1315,7 +1221,7 @@
         $invite_url
      );
      return JVB()->email()->sendEmail($email, $subject, $message);
      return Emails::getInstance()->sendEmail($email, $subject, $message);
   }
   /**
@@ -1389,7 +1295,7 @@
         $user = get_userdata($user_id);
         $display_name = $user ? $user->display_name : $email;
         JVB()->notification()->addNotification(
         NotificationManager::getInstance()->addNotification(
            $list->user_id,
            'list_share_accepted',
            $user_id,
@@ -1676,7 +1582,7 @@
         return $this->error('Invalid action', 'invalid_action', 400);
      }
      JVB()->queue()->queueOperation(
      Queue::getInstance()->queueOperation(
         $operation_type,
         $user_id,
         $data,