From 56a9a1ccf764ff7a6af8f8a2292cb07443cb4aa7 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Thu, 28 May 2026 18:19:57 +0000
Subject: [PATCH] =New Gitbit setpu

---
 inc/rest/routes/NotificationsRoutes.php | 2932 +++++++++++++++++++++++++++--------------------------------
 1 files changed, 1,337 insertions(+), 1,595 deletions(-)

diff --git a/inc/rest/routes/NotificationsRoutes.php b/inc/rest/routes/NotificationsRoutes.php
index ea6fa2c..ff84cdc 100644
--- a/inc/rest/routes/NotificationsRoutes.php
+++ b/inc/rest/routes/NotificationsRoutes.php
@@ -1,1754 +1,1496 @@
 <?php
 namespace JVBase\rest\routes;
 
-use JVBase\JVB;
-use JVBase\rest\RestRouteManager;
+use JVBase\managers\Cache;
+use JVBase\managers\CustomTable;
+use JVBase\registrar\Registrar;
+use JVBase\rest\Rest;
+use JVBase\rest\Route;
 use WP_REST_Request;
 use WP_REST_Response;
 use WP_Error;
 use Exception;
 
 if (!defined('ABSPATH')) {
-    exit; // Exit if accessed directly
+	exit; // Exit if accessed directly
 }
 
 /**
- * Step 1: Build status/order/filter params
- * Step 2: Get all regular notifications
- * Step 3: Get all Content notifications
- * Step 4: Get all Approval notifications
- * Step 5: Merge in order of created date
- * Step 6: Return result
+ * Notification Routes Handler
+ *
+ * Manages user notifications including regular notifications, content notifications,
+ * and approval notifications. Provides endpoints for reading, marking, and dismissing.
  */
-class NotificationsRoutes extends RestRouteManager
+class NotificationsRoutes extends Rest
 {
-    protected int $user_id;
-    protected array $notification_types = [];
-    protected object $manager;
-    protected array $typeMap = [
-        'favourite' => [
-            'new_favourite',
-            'list_shared',
-        ],
-        'artist' => [
-            'new_artist',
-            'new_tattoo',
-            'new_piercing',
-            'new_event',
-            'new_update',
+	protected int $user_id;
+	protected array $notification_types = [];
+	protected object $manager;
 
-        ],
-        'partner'   => [
-            'new_partner',
-            'new_offer',
-        ],
-        'shop' => [
-            'new_shop',
-            'shop_update',
-            'shop_accepted',
-            'artist_request',
-        ],
-        'event' => [
-            'new_event',
-            'event_reminder',
-        ],
-        'news' => [
-            'new_update',
-        ],
-        'system' => [
-            'system_message',
-            'artist_approved',
-            'artist_invitation',
-            'artist_request',
-            'shop_accepted',
-            'shop_rejected',
-            'new_term',
-            'term_approved',
-            'term_rejected',
-        ],
-    ];
-    protected array $notificationTableMap = [
-        // Regular notifications
-        'notifications' => [
-            'new_favourite',
-            'artist_approved',
-            'artist_rejected',
-            'artist_invitation',
-            'shop_invitation',
-            'artist_request',
-            'shop_accepted',
-            'shop_rejected',
-            'new_term',
-            'term_approved',
-            'term_rejected',
-            'list_shared',
-            'system_message'
-        ],
+	protected array $typeMap = [
+		'favourite' => [
+			'new_favourite',
+			'list_shared',
+		],
+		'artist' => [
+			'new_artist',
+			'new_tattoo',
+			'new_piercing',
+			'new_event',
+			'new_update',
+		],
+		'partner' => [
+			'new_partner',
+			'new_offer',
+		],
+		'shop' => [
+			'new_shop',
+			'shop_update',
+			'shop_accepted',
+			'artist_request',
+		],
+		'event' => [
+			'new_event',
+			'event_reminder',
+		],
+		'news' => [
+			'new_update',
+		],
+		'system' => [
+			'system_message',
+			'artist_approved',
+			'artist_invitation',
+			'artist_request',
+			'shop_accepted',
+			'shop_rejected',
+			'new_term',
+			'term_approved',
+			'term_rejected',
+		],
+	];
 
-        // Content notifications (from artists the user follows)
-        'content_notifications' => [
-            'new_artist',
-            'new_tattoo',
-            'new_piercing',
-            'new_event',
-            'new_update',
-            'new_partner',
-            'new_offer',
-            'new_shop',
-            'shop_update',
-            'event_reminder'
-        ]
-    ];
+	protected array $notificationTableMap = [
+		'notifications' => [
+			'new_favourite',
+			'artist_approved',
+			'artist_rejected',
+			'artist_invitation',
+			'shop_invitation',
+			'artist_request',
+			'shop_accepted',
+			'shop_rejected',
+			'new_term',
+			'term_approved',
+			'term_rejected',
+			'list_shared',
+			'system_message'
+		],
+		'content_notifications' => [
+			'new_artist',
+			'new_tattoo',
+			'new_piercing',
+			'new_event',
+			'new_update',
+			'new_partner',
+			'new_offer',
+			'new_shop',
+			'shop_update',
+			'event_reminder'
+		]
+	];
 
-    public function __construct()
-    {
-        $this->cache_name = 'notifications';
-        parent::__construct();
+	protected CustomTable $notifications;
+	protected CustomTable $metrics;
 
-        $allTypes = [];
-        foreach ($this->typeMap as $key => $values) {
-            $allTypes = array_unique(array_merge($allTypes, $values));
-        }
+	public function __construct()
+	{
+		$this->cacheName = 'notifications';
+		$this->cacheTtl = HOUR_IN_SECONDS;
+		parent::__construct();
 
-        $this->typeMap['all'] = $allTypes;
+		// Connect notifications cache to user cache
+		// When user data changes, notification cache should invalidate
+		$this->cache->connect('user');
 
-        $this->user_id = get_current_user_id();
-        $this->action = 'notifications-';
+		$this->notifications = CustomTable::for('notifications');
+		$this->metrics = CustomTable::for('notification_metrics');
 
-        add_action('init', [$this, 'init']);
+		// Build complete type map
+		$allTypes = [];
+		foreach ($this->typeMap as $key => $values) {
+			$allTypes = array_unique(array_merge($allTypes, $values));
+		}
+		$this->typeMap['all'] = $allTypes;
 
-        add_filter(BASE.'handle_bulk_operation', [$this, 'processOperation'], 10, 3);
-    }
-    /**
-     * Format a notification for display
-     *
-     * @param object $notification Notification object
-     *
-     * @return array Formatted notification
-     */
-    protected function formatNotification(object $notification):array
-    {
-        $config = $this->notification_types[$notification->type] ?? [];
-        $context = json_decode($notification->context ?? '{}', true);
+		$this->user_id = get_current_user_id();
 
-        // Get action user's name if available
-        $acting_user_name = null;
-        if ($notification->action_user_id) {
-            $acting_user_name = jvbShareName($notification->action_user_id);
-        }
+		add_action('init', [$this, 'init']);
+		add_filter(BASE.'handle_bulk_operation', [$this, 'processOperation'], 10, 3);
+	}
 
-        return [
-            'id' => $notification->id,
-            'type' => $notification->type,
-            'message' => $notification->message,
-            'created_at' => $notification->created_at,
-            'status' => $notification->status,
-            'requires_action' => (bool)$notification->requires_action,
-            'action_taken' => (bool)$notification->action_taken,
-            'icon' => $config['icon'] ?? 'info',
-            'priority' => $notification->priority,
-            'target' => [
-                'id' => $notification->target_id,
-                'type' => $notification->target_type
-            ],
-            'context' => $context,
-            'acting_user' => $notification->action_user_id ? [
-                'id' => $notification->action_user_id,
-                'name' => $acting_user_name
-            ] : null
-        ];
-    }
+	/**
+	 * Set up required parameters
+	 */
+	public function init(): void
+	{
+		$this->manager = JVB()->notification();
+		$this->notification_types = $this->manager->getNotificationTypes(true);
+	}
 
-    /**
-     * Set up required paramaters
-     * @return void
-     */
-    public function init()
-    {
-        $this->manager = JVB()->notification();
-        $this->notification_types = $this->manager->getNotificationTypes();
-    }
+	/**
+	 * Register notification routes
+	 */
+	public function registerRoutes(): void
+	{
+		// Get user notifications
+		Route::for('notifications')
+			->get([$this, 'getNotifications'])
+			->args([
+				'user' => 'integer|required',
+				'type' => 'string',
+				'status' => 'string|enum:unread,read,actioned,dismissed',
+				'limit' => 'integer|default:20|min:1|max:100',
+				'offset' => 'integer|default:0',
+			])
+			->auth('user')
+			->rateLimit(30)
+			->register();
 
-    /**
-     * Registers notification routes
-     * @return void
-     */
-    public function registerRoutes():void
-    {
-        register_rest_route($this->namespace, '/notifications', [
-            [
-                'methods' => 'GET',
-                'callback' => [$this, 'getNotifications'],
-                'permission_callback' => [$this, 'checkPermission']
-            ],
-            [
-                'methods' => 'POST',
-                'callback' => [$this, 'updateNotifications'],
-                'permission_callback' => [$this, 'checkPermission']
-            ]
-        ]);
-    }
+		// Mark as read
+		Route::for('notifications/read')
+			->post([$this, 'markRead'])
+			->args([
+				'user' => 'integer|required',
+				'notification_id' => 'integer|required',
+			])
+			->auth('user')
+			->rateLimit(30)
+			->register();
 
+		// Mark all as read
+		Route::for('notifications/read-all')
+			->post([$this, 'markAllRead'])
+			->args([
+				'user' => 'integer|required',
+				'type' => 'string',
+			])
+			->auth('user')
+			->rateLimit(10)
+			->register();
 
-    /**
-     * @param int $ID
-     * @param string $content
-     *
-     * @return string
-     */
-    protected function getItemLink(int $ID, string $content):string
-    {
-        error_log('Type: '.print_r($content, true));
-        switch ($content) {
-            case BASE.'artist':
-            case BASE.'artwork':
-            case BASE.'event':
-            case BASE.'news':
-            case BASE.'offer':
-            case BASE.'partner':
-            case BASE.'piercing':
-            case BASE.'tattoo':
-                return get_permalink($ID);
-            default:
-                return get_term_link($ID, BASE.$content);
-        }
-    }
+		// Mark as actioned
+		Route::for('notifications/action')
+			->post([$this, 'markActioned'])
+			->args([
+				'user' => 'integer|required',
+				'notification_id' => 'integer|required',
+			])
+			->auth('user')
+			->rateLimit(30)
+			->register();
 
-    /**
-     * Get notification actions
-     *
-     * @param string $type Notification type
-     * @param array $data Notification data
-     * @param object $notification Full notification object
-     *
-     * @return array Actions available for this notification
-     */
-    protected function getNotificationActions(string $type, array $data, object $notification = null):array
-    {
-        error_log('Data for actions: '.print_r($data, true));
+		// Dismiss notification
+		Route::for('notifications/dismiss')
+			->post([$this, 'markDismissed'])
+			->args([
+				'user' => 'integer|required',
+				'notification_id' => 'integer|required',
+			])
+			->auth('user')
+			->rateLimit(30)
+			->register();
 
-        $actions = [];
+		// Get unread count
+		Route::for('notifications/count')
+			->get([$this, 'getUnreadCount'])
+			->args([
+				'user' => 'integer|required',
+				'type' => 'string',
+			])
+			->auth('user')
+			->rateLimit()
+			->register();
+	}
 
-        switch ($type) {
-            case 'artist_approved':
-            case 'artist_rejected':
-            case 'shop_approved':
-            case 'shop_rejected':
-            case 'term_approved':
-            case 'term_rejected':
-            case 'system_message':
-                //No extra action needed
-                break;
-            case 'artist_invitation':
-                $actions[] = [
-                    'icon'      => 'upvote',
-                    'label'     => 'Approve',
-                    'action'    => 'acceptInvitation',
-                ];
-                $actions[] = [
-                    'icon'      => 'downvote',
-                    'label'     => 'Reject',
-                    'action'    => 'reject_invitation',
-                ];
-                break;
-            case 'artist_request':
-                $actions[] = [
-                    'icon'      => 'upvote',
-                    'label'     => 'Approve',
-                    'action'    => 'accept_to_shop',
-                ];
-                $actions[] = [
-                    'icon'      => 'downvote',
-                    'label'     => 'Reject',
-                    'action'    => 'reject_to_shop',
-                ];
-                break;
-            case 'artist_approval':
-                $actions[] = [
-                    'icon'      => 'upvote',
-                    'label'     => 'Approve',
-                    'action'    => 'accept_artist',
-                ];
-                $actions[] = [
-                    'icon'      => 'downvote',
-                    'label'     => 'Reject',
-                    'action'    => 'reject_artist',
-                ];
-                break;
+	// =========================================================================
+	// GET OPERATIONS
+	// =========================================================================
 
-            case 'new_term':
-                $actions[] = [
-                    'icon'      => 'upvote',
-                    'label'     => 'Approve',
-                    'action'    => 'approve_term',
-                ];
-                $actions[] = [
-                    'icon'      => 'downvote',
-                    'label'     => 'Reject',
-                    'action'    => 'reject_term',
-                ];
-                break;
+	/**
+	 * Get notifications for a user
+	 */
+	public function getNotifications(WP_REST_Request $request): WP_REST_Response
+	{
+		$user_id = absint($request->get_param('user'));
+		$type = sanitize_text_field($request->get_param('type') ?? '');
+		$status = sanitize_text_field($request->get_param('status') ?? '');
+		$limit = absint($request->get_param('limit'));
+		$offset = absint($request->get_param('offset'));
 
-            case 'list_shared':
-                if (!empty($data['list_id'])) {
-                    $actions[] = [
-                        'icon'      => 'list-heart',
-                        'label'     => 'View List',
-                        'url'       => home_url("/dash/favourites/{$data['list_id']}"),
-                    ];
-                }
-                break;
-            default:
-                $actions[] = [
-                    'icon'      => 'link',
-                    'label'     => 'View',
-                    'url'       => $this->getItemLink($data['target_id'], $data['target_type']),
-                ];
-                break;
-        }
-
-        $actions[] = [
-            'icon'      => 'close',
-            'label'     => 'Dismiss',
-            'action'    => 'dismiss_notification'
-        ];
-        // Allow customization via filter
-        return apply_filters('jvb_notification_actions', $actions, $type, $data, $notification);
-    }
-
-    /**
-     * @param int $user_id
-     * @param array $data
-     *
-     * @return array
-     */
-    protected function getSanitizedData(int $user_id, array $data):array
-    {
-        $status = (array_key_exists('status', $data)) ? $data['status'] : 'unread';
-        $limit = (array_key_exists('limit', $data)) ? $data['limit'] : 20;
-        $offset = (array_key_exists('page', $data)) ? $data['page'] : 1;
-        $type = (array_key_exists('type', $data)) ? $data['type'] : 'all';
-
-        // Validate and sanitize status
-        $allowed_statuses = ['unread', 'read', 'actioned', 'dismissed', 'all'];
-        if (!in_array($status, $allowed_statuses)) {
-            $this->logError("Invalid notification status", [
-                'status' => $status,
-                'user' => $user_id
-            ], 'warning');
-            $status = 'unread'; // Default to unread if invalid
-        }
-
-        if (!in_array($type, array_keys($this->typeMap))) {
-            $this->logError("Invalid notification type", [
-                'type' => $type,
-                'user' => $user_id
-            ], 'warning');
-            $type = 'all';
-        }
-
-        // Validate and sanitize limit and offset
-        $limit = absint($limit);
-        if ($limit <= 0 || $limit > 100) {
-            $limit = 20; // Use reasonable default if invalid
-        }
-
-        $offset = absint($offset);
-        if ($offset < 0) {
-            $offset = 1;
-        }
-
-        $return = [
-            'status'    => $status,
-            'limit'     => $limit,
-            'page'      => $offset,
-            'type'      => $type
-        ];
-        if (array_key_exists('grouped', $data)) {
-            $return['grouped'] = $data['grouped'];
-        }
-        return $return;
-    }
-    /**
-     * Get notifications for a user
-     *
-     * @param WP_REST_Request $request
-     * @return WP_REST_Response
-     */
-    public function getNotifications(WP_REST_Request $request): WP_REST_Response
-    {
-        $data = $request->get_params();
-        $user_id = $data['user'];
-		if (!$this->userCheck($user_id)) {
-			$this->logError("Invalid user ID for notifications", ['user' => $user_id], 'warning');
-			return new WP_REST_Response([
-				'success' => false,
-				'message' => 'User doesn\'t match. Are you a bot?'
-			]);
+		if (!$this->checkUser($user_id)) {
+			return $this->unauthorized();
 		}
 
-		// Check HTTP cache headers (includes notification types in timestamp check)
-		$cache_check = $this->checkUserHeaders($request, $user_id, 'notifications');
-		if ($cache_check) {
-			return $cache_check;
+		$cacheKey = compact('user_id', 'type', 'status', 'limit', 'offset');
+
+		$result = $this->cache->remember($cacheKey, function() use ($user_id, $type, $status, $limit, $offset) {
+			// Build where conditions
+			$where = ['owner_id' => $user_id];
+			if ($type) $where['type'] = $type;
+			if ($status) $where['status'] = $status;
+
+			$items = $this->notifications
+				->where($where)
+				->orderBy('created_at', 'DESC')
+				->limit($limit, $offset)
+				->getResults();
+
+			$total = $this->notifications->where($where)->countResults();
+
+			// Format notifications
+			$formatted = array_map([$this, 'formatNotification'], $items);
+
+			return [
+				'items' => $formatted,
+				'total' => $total,
+				'has_more' => ($offset + $limit) < $total
+			];
+		});
+
+		return $this->success($result);
+	}
+
+	/**
+	 * Get unread notification count
+	 */
+	public function getUnreadCount(WP_REST_Request $request): WP_REST_Response
+	{
+		$user_id = absint($request->get_param('user'));
+		$type = sanitize_text_field($request->get_param('type') ?? '');
+
+		if (!$this->checkUser($user_id)) {
+			return $this->unauthorized();
 		}
 
-        // Step 1: Build status/order/filter params
-        $params = $this->getSanitizedData($user_id, $data);
-        $status = $params['status'];
-        $limit = $params['limit'];
-        $offset = $params['page'];
-        $type = $params['type'];
+		$cacheKey = compact('user_id', 'type');
 
-        // Try cache first with validated parameters
-        $cache_key = "user_{$user_id}_merged_notifications_{$status}_{$type}_{$limit}_{$offset}";
-        $cached = $this->cache->get($cache_key);
-        if ($cached) {
-            $response = new WP_REST_Response($cached);
-			return $this->addCacheHeaders($response);
-        }
+		$count = $this->cache->remember($cacheKey, function() use ($user_id, $type) {
+			$where = ['owner_id' => $user_id, 'status' => 'unread'];
+			if ($type) $where['type'] = $type;
 
-        try {
-            // Step 2: Get regular notifications
-            $regular_notifications = $this->getRegularNotifications($user_id, $params);
+			return $this->notifications->where($where)->countResults();
+		});
 
-            // Step 3: Get content notifications
-            $content_notifications = $this->getContentNotifications($user_id, $status, $limit, $offset);
+		return $this->success(['count' => $count]);
+	}
 
-            // Step 4: Get approval notifications
-            $approval_notifications = $this->getApprovalNotifications($user_id, $status);
+	/**
+	 * Get grouped notifications for a user
+	 */
+	public function getGroupedNotifications(
+		int $user_id,
+		string $status,
+		int $limit,
+		int $offset,
+		string $type
+	): array {
+		$cacheKey = compact('user_id', 'status', 'limit', 'offset', 'type');
 
-            // Step 5: Merge in order of created date
-            $notifications = array_merge(
-                $regular_notifications,
-                $content_notifications,
-                $approval_notifications
-            );
+		return $this->cache->remember($cacheKey, function() use ($user_id, $status, $limit, $offset, $type) {
+			$time_window = '24 HOUR';
 
-            usort($notifications, function ($a, $b) {
-                $date_a = strtotime($a['created_at'] ?? $a['date'] ?? date('Y-m-d H:i:s'));
-                $date_b = strtotime($b['created_at'] ?? $b['date'] ?? date('Y-m-d H:i:s'));
-                return $date_b - $date_a; // Sort from newest to oldest
-            });
+			// Build type filter
+			$typeFilter = '';
+			$typeValues = [];
 
-            // Apply pagination
-            $total_count = count($notifications);
-            $notifications = array_slice($notifications, 0, $limit);
+			if ($type !== 'all' && isset($this->typeMap[$type])) {
+				$types = $this->typeMap[$type];
+				if (!empty($types)) {
+					$placeholders = implode(',', array_fill(0, count($types), '%s'));
+					$typeFilter = "AND type IN ({$placeholders})";
+					$typeValues = $types;
+				}
+			}
 
-            // Step 6: Return result
-            $response = [
-                'notifications' => $notifications,
-                'pagination' => [
-                    'total' => $total_count,
-                    'page' => $offset,
-                    'per_page' => $limit,
-                    'pages' => ceil($total_count / $limit),
-                    'has_more' => ($offset * $limit + count($notifications)) < $total_count
-                ]
-            ];
+			// Build status filter
+			$statusFilter = '';
+			if ($status === 'read') {
+				$statusFilter = "AND status IN ('read', 'actioned')";
+			} elseif ($status !== 'all') {
+				$statusFilter = "AND status = %s";
+				$typeValues[] = $status;
+			}
 
-            // Cache the result
-            $this->cache->set($cache_key, $response, 'notifications_' . $user_id);
-            $response = new WP_REST_Response($response);
-			return $this->addCacheHeaders($response);
-        } catch (Exception $e) {
-            $this->logError("Error retrieving notifications", [
-                'user_id' => $user_id,
-                'error' => $e->getMessage()
-            ]);
-
-            return new WP_REST_Response([
-                'notifications' => [],
-                'pagination' => [
-                    'total' => 0,
-                    'page' => $offset,
-                    'per_page' => $limit,
-                    'pages' => 0,
-                    'has_more' => false
-                ]
-            ]);
-        }
-    }
-
-    /**
-     * Get regular notifications from the notifications table
-     *
-     * @param int $user_id User ID
-     * @param array $params Filter parameters
-     * @return array Array of formatted notifications
-     */
-    protected function getRegularNotifications(int $user_id, array $params): array
-    {
-        $status = $params['status'];
-        $limit = $params['limit'];
-        $offset = $params['page'];
-        $type = $params['type'];
-
-        // Try to get from cache first with validated parameters
-        $cache_key = "user_{$user_id}_regular_notifications_{$status}_{$type}_{$limit}_{$offset}";
-        $cached = $this->cache->get($cache_key);
-        if ($cached) {
-            return $cached;
-        }
-
-        global $wpdb;
-        $notifications_table = $wpdb->prefix . BASE . 'notifications';
-
-        // Build status condition
-        $status_condition = "1=1";
-        if ($status === 'unread') {
-            $status_condition = "status = 'unread'";
-        } elseif ($status === 'read') {
-            $status_condition = "status IN ('read', 'actioned')";
-        } elseif ($status !== 'all') {
-            $status_condition = $wpdb->prepare("status = %s", $status);
-        }
-
-        // Build type condition
-        $type_condition = "1=1";
-        if ($type !== 'all' && isset($this->typeMap[$type])) {
-            $types = $this->typeMap[$type];
-            if (!empty($types)) {
-                $placeholders = implode(',', array_fill(0, count($types), '%s'));
-                $type_condition = $wpdb->prepare("type IN ($placeholders)", $types);
-            }
-        }
-
-        // Get notifications
-        $notifications = $wpdb->get_results(
-            $wpdb->prepare(
-                "SELECT * FROM {$notifications_table}
-             WHERE owner_id = %d AND {$status_condition} AND {$type_condition}
-             ORDER BY created_at DESC",
-                $user_id
-            )
-        );
-
-        // Format notifications
-        $formatted = [];
-        foreach ($notifications as $notification) {
-            $formatted[] = $this->formatNotification($notification);
-        }
-
-        // Cache the results
-        $this->cache->set($cache_key, $formatted, 'notifications_' . $user_id);
-
-        return $formatted;
-    }
-
-    /**
-     * Get approval notifications from the approval_requests table
-     *
-     * @param int $user_id User ID
-     * @param string $status Filter by status
-     * @return array Array of formatted approval notifications
-     */
-    protected function getApprovalNotifications(int $user_id, string $status): array
-    {
-        // Try to get from cache first
-        $cache_key = "user_{$user_id}_approval_notifications_{$status}";
-        $cached = $this->cache->get($cache_key);
-
-        if ($cached) {
-            return $cached;
-        }
-
-        global $wpdb;
-        $formatted = [];
-
-        // Build status condition
-        $status_condition = "1=1";
-        if ($status === 'unread') {
-            $status_condition = "a.status = 'pending'";
-        } elseif ($status === 'read') {
-            $status_condition = "a.status IN ('approved', 'rejected')";
-        } elseif ($status !== 'all') {
-            $status_condition = $wpdb->prepare("a.status = %s", $status);
-        }
-
-        if ($this->isVerifiedUser($user_id)) {
-            $approvals = jvbApprovalTypes();
-            foreach ($approvals as $type => $config) {
-                $table = $wpdb->prefix.BASE.'approval_'.$type.'requests';
-                $votes = $wpdb->prefix.BASE.'approval_'.$type.'votes';
-
-                $approvals = $wpdb->get_results(
-                    $wpdb->prepare(
-                        "SELECT a.*,
-                COALESCE(v.vote, 'none') as user_vote
-                FROM {$table} a
-                LEFT JOIN {$votes} v ON a.id = v.request_id AND v.user_id = %d
-                WHERE a.user_id != %d
-                AND {$status_condition}
-                ORDER BY a.created_at DESC",
-                        $user_id,
-                        $user_id
-                    )
-                );
-                // Now filter out requests created by the current user
-                foreach ($approvals as $approval) {
-                    $requested_by = json_decode($approval->requested_by, true);
-
-                    // Skip if the current user is the requester
-                    if (is_array($requested_by) && in_array($user_id, $requested_by)) {
-                        continue;
-                    }
-
-                    $formatted[] = $this->formatApprovalNotification($approval);
-                }
-            }
-        }
-
-        // Cache the results
-        $this->cache->set($cache_key, $formatted, 'approvals');
-
-        return $formatted;
-    }
-
-    /**
-     * Format an approval request as a notification
-     *
-     * @param object $approval Approval request object
-     * @return array Formatted notification
-     */
-    protected function formatApprovalNotification(object $approval): array
-    {
-        $data = json_decode($approval->data ?? '{}', true);
-        $type_labels = [
-            'artist_approval' => 'Artist Verification',
-            'term_suggestion' => 'Term Suggestion'
-        ];
-
-        $status_labels = [
-            'pending' => 'Pending',
-            'approved' => 'Approved',
-            'rejected' => 'Rejected',
-            'expired' => 'Expired'
-        ];
-
-        $icon = ($approval->type === 'artist_approval') ? 'artist' : 'style';
-
-        $message = '';
-        if ($approval->type === 'artist_approval') {
-            if ($approval->requested_by == $approval->target_id) {
-                $message = "Your artist verification is {$status_labels[$approval->status]}";
-            } else {
-                $name = isset($data['display_name']) ? $data['display_name'] : 'An artist';
-                $message = "{$name} is requesting verification";
-            }
-        } elseif ($approval->type === 'term_suggestion') {
-            $term_name = $data['term_name'] ?? 'A term';
-            $taxonomy = $data['taxonomy'] ?? '';
-            $taxonomy_name = str_replace(BASE, '', $taxonomy);
-
-            if ($approval->requested_by == get_current_user_id()) {
-                $message = "Your {$taxonomy_name} suggestion '{$term_name}' is {$status_labels[$approval->status]}";
-            } else {
-                $message = "New {$taxonomy_name} suggestion: '{$term_name}'";
-            }
-        }
-
-        return [
-            'id' => 'approval_' . $approval->id,
-            'type' => $approval->type,
-            'message' => $message,
-            'created_at' => $approval->created_at,
-            'status' => $approval->status,
-            'requires_action' => ($approval->status === 'pending' && $approval->requested_by != get_current_user_id()),
-            'action_taken' => !empty($approval->user_vote) && $approval->user_vote !== 'none',
-            'icon' => $icon,
-            'priority' => 'high',
-            'target' => [
-                'id' => $approval->target_id,
-                'type' => $approval->target_type
-            ],
-            'context' => $data,
-            'approval_data' => [
-                'required_approvals' => $approval->required_approvals,
-                'current_approvals' => $approval->current_approvals,
-                'expires_at' => $approval->expires_at
-            ]
-        ];
-    }
-
-    /**
-     * Determine which table a notification type belongs to
-     *
-     * @param string $notificationType The notification type
-     * @return string The table name ('notifications' or 'content_notifications')
-     */
-    protected function getTableForNotificationType(string $notificationType): string
-    {
-        foreach ($this->notificationTableMap as $table => $types) {
-            if (in_array($notificationType, $types)) {
-                return $table;
-            }
-        }
-        // Default to the main notifications table if type is unknown
-        return 'notifications';
-    }
-
-    /**
-     * Get grouped notifications for a user
-     * @param int $user_id User ID
-     * @param string $status notification status
-     * @param int $limit number of notifications to fetch
-     * @param int $offset page to fetch
-     * @param string $type notification type to fetch
-     * @return array Grouped notifications with pagination info
-     */
-    public function getGroupedNotifications(int $user_id, string $status, int $limit, int $offset, string $type):array
-    {
-        $cache_key = "user_{$user_id}_grouped_notifications_{$status}_{$type}_{$limit}_{$offset}";
-        $cached = $this->cache->get($cache_key);
-        if ($cached !== false) {
-            return $cached;
-        }
-
-        global $wpdb;
-
-        // Build status condition
-        $status_condition = "1=1";
-        if ($status === 'unread') {
-            $status_condition = "status = 'unread'";
-        } elseif ($status === 'read') {
-            $status_condition = "status IN ('read', 'actioned')";
-        } elseif ($status !== 'all') {
-            $status_condition = $wpdb->prepare("status = %s", $status);
-        }
-
-        // Build type condition
-        $type_condition = "1=1";
-        if ($type !== 'all' && isset($this->typeMap[$type])) {
-            $types = $this->typeMap[$type];
-            if (!empty($types)) {
-                $placeholders = implode(',', array_fill(0, count($types), '%s'));
-                $type_condition = $wpdb->prepare("type IN ($placeholders)", $types);
-            }
-        }
-        try {
-            // Time window for grouping (e.g., last 24 hours)
-            $time_window = '24 HOUR';
-
-            $table = $wpdb->prefix.BASE.'notifications';
-            // Count notifications by action_user_id and type
-            $grouped_counts = $wpdb->get_results(
-                $wpdb->prepare(
-                    "SELECT
+			// Get grouped notifications
+			$grouped = $this->notifications->queryResults(
+				"SELECT
                     action_user_id,
                     type,
                     COUNT(*) as count,
                     MAX(created_at) as latest_time,
                     MIN(id) as first_id,
                     GROUP_CONCAT(target_type) as target_types
-                 FROM {$table}
+                 FROM {table}
                  WHERE owner_id = %d
                  AND action_user_id IS NOT NULL
-                 AND {$status_condition}
-                 AND {$type_condition}
                  AND created_at > DATE_SUB(NOW(), INTERVAL {$time_window})
+                 {$statusFilter}
+                 {$typeFilter}
                  GROUP BY action_user_id, type
                  ORDER BY latest_time DESC
                  LIMIT %d OFFSET %d",
-                    $user_id,
-                    $limit,
-                    $offset
-                )
-            );
+				array_merge([$user_id], $typeValues, [$limit, $offset])
+			);
 
-
-            // Get total count for pagination
-            $total_count = $wpdb->get_var(
-                $wpdb->prepare(
-                    "SELECT COUNT(DISTINCT CONCAT(action_user_id, '_', type))
-                 FROM {$table}
+			// Get total count
+			$total = $this->notifications->queryVar(
+				"SELECT COUNT(DISTINCT CONCAT(action_user_id, '_', type))
+                 FROM {table}
                  WHERE owner_id = %d
                  AND action_user_id IS NOT NULL
-                 AND {$status_condition}
-                 AND {$type_condition}
-                 AND created_at > DATE_SUB(NOW(), INTERVAL {$time_window})",
-                    $user_id
-                )
-            );
+                 AND created_at > DATE_SUB(NOW(), INTERVAL {$time_window})
+                 {$statusFilter}
+                 {$typeFilter}",
+				array_merge([$user_id], $typeValues)
+			);
 
-            // Format the grouped notifications
-            $formatted = [];
-            foreach ($grouped_counts as $group) {
-                // Get acting user name
-                $acting_user_name = jvbGetUsername($group->action_user_id);
+			// Format results
+			$formatted = [];
+			foreach ($grouped as $group) {
+				if ($group->count > 1) {
+					// Grouped notification
+					$target_types = explode(',', $group->target_types);
+					$formatted[] = [
+						'id' => 'group_' . $group->first_id,
+						'type' => $group->type,
+						'message' => $this->buildGroupedMessage(
+							jvbGetUsername($group->action_user_id),
+							$group->type,
+							$group->count,
+							$target_types
+						),
+						'created_at' => $group->latest_time,
+						'is_grouped' => true,
+						'group_count' => $group->count,
+					];
+				} else {
+					// Single notification
+					$notification = $this->notifications
+						->where([
+							'owner_id' => $user_id,
+							'action_user_id' => $group->action_user_id,
+							'type' => $group->type
+						])
+						->orderBy('created_at', 'DESC')
+						->first();
 
-                if ($group->count > 1) {
-                    // Get unique target types for better message formatting
-                    $target_types = array_unique(explode(',', $group->target_types));
+					if ($notification) {
+						$formatted[] = $this->formatNotification($notification);
+					}
+				}
+			}
 
-                    // Build a grouped notification
-                    $message = $this->buildGroupedMessage(
-                        $acting_user_name,
-                        $group->type,
-                        $group->count,
-                        $target_types
-                    );
+			return [
+				'notifications' => $formatted,
+				'pagination' => [
+					'total' => (int)$total,
+					'page' => $offset,
+					'per_page' => $limit,
+					'pages' => ceil($total / $limit)
+				],
+				'has_more' => ($offset + $limit) < $total
+			];
+		});
+	}
 
-                    $formatted[] = [
-                        'id' => 'group_' . $group->first_id,
-                        'type' => $group->type,
-                        'message' => $message,
-                        'created_at' => $group->latest_time,
-                        'timestamp' => strtotime($group->latest_time),
-                        'status' => $status,
-                        'icon' => $this->notification_types[$group->type]['icon'] ?? 'info',
-                        'priority' => $this->notification_types[$group->type]['priority'] ?? 'normal',
-                        'is_grouped' => true,
-                        'group_count' => $group->count,
-                        'acting_user' => [
-                            'id' => $group->action_user_id,
-                            'name' => $acting_user_name
-                        ],
-                        'target_types' => $target_types
-                    ];
-                } else {
-                    // Get the single notification details
-                    $table = $wpdb->prefix.BASE.'notifications';
-                    $notification = $wpdb->get_row(
-                        $wpdb->prepare(
-                            "SELECT * FROM {$table}
-                         WHERE owner_id = %d
-                         AND action_user_id = %d
-                         AND type = %s
-                         ORDER BY created_at DESC
-                         LIMIT 1",
-                            $user_id,
-                            $group->action_user_id,
-                            $group->type
-                        )
-                    );
+	/**
+	 * Get regular notifications from the notifications table
+	 */
+	protected function getRegularNotifications(int $user_id, array $params): array
+	{
+		$cacheKey = compact('user_id', 'params');
 
-                    if ($notification) {
-                        $formatted[] = $this->formatNotification($notification);
-                    }
-                }
-            }
+		return $this->cache->remember($cacheKey, function() use ($user_id, $params) {
+			$status = $params['status'];
+			$type = $params['type'];
+			$limit = $params['limit'];
+			$offset = $params['page'];
 
-            // Prepare response with pagination info
-            $response = [
-                'notifications' => $formatted,
-                'pagination' => [
-                    'total' => (int)$total_count,
-                    'page' => $offset,
-                    'per_page' => $limit,
-                    'pages' => ceil($total_count / $limit),
-                    'has_more' => ($offset + $limit) < $total_count
-                ]
-            ];
+			// Build base query
+			$where = ['owner_id' => $user_id];
 
-            // Cache the results
-            $this->cache->set($cache_key, $response, 'notifications_' . $user_id);
+			// Handle status filter
+			if ($status === 'read') {
+				// For multiple statuses, use raw query
+				$notifications = $this->getNotificationsWithMultipleStatuses(
+					$user_id,
+					['read', 'actioned'],
+					$type,
+					$limit,
+					$offset
+				);
+			} else {
+				// Single status - use fluent builder
+				if ($status !== 'all') {
+					$where['status'] = $status;
+				}
 
-            return $response;
-        } catch (Exception $e) {
-            $this->logError("Error retrieving grouped notifications", [
-                'user_id' => $user_id,
-                'status' => $status,
-                'error' => $e->getMessage()
-            ]);
+				// Handle type filter
+				if ($type !== 'all' && isset($this->typeMap[$type])) {
+					$types = $this->typeMap[$type];
+					if (!empty($types)) {
+						// Multiple types - use raw query
+						return $this->getNotificationsByTypes(
+							$user_id,
+							$types,
+							$status,
+							$limit,
+							$offset
+						);
+					}
+				}
 
-            return [
-                'notifications' => [],
-                'pagination' => [
-                    'total' => 0,
-                    'page' => $offset,
-                    'per_page' => $limit,
-                    'pages' => 0,
-                    'has_more' => false
-                ]
-            ];
-        }
-    }
+				// Simple query - use fluent builder
+				$notifications = $this->notifications
+					->where($where)
+					->orderBy('created_at', 'DESC')
+					->limit($limit, $offset)
+					->getResults();
+			}
 
-    /**
-     * Build a message for grouped notifications
-     *
-     * @param string $user_name Acting user's name
-     * @param string $type Notification type
-     * @param int $count Number of grouped notifications
-     * @param array $target_types Types of targets involved
-     * @return string Formatted message
-     */
-    protected function buildGroupedMessage(string $user_name, string $type, int $count, array $target_types = []):string
-    {
-        switch ($type) {
-            case 'new_favourite':
-                // If we have a single target type
-                if (count($target_types) === 1) {
-                    $content_type = $this->manager->getContentTypeLabel($target_types[0]);
-                    return "{$user_name} favourited {$count} of your {$content_type}";
-                }
-                return "{$user_name} favourited {$count} of your items";
+			// Format notifications
+			return array_map([$this, 'formatNotification'], $notifications);
+		});
+	}
 
-            case 'artist_request':
-                return "{$user_name} wants to join your shop";
+	/**
+	 * Get notifications with multiple status values
+	 */
+	protected function getNotificationsWithMultipleStatuses(
+		int $user_id,
+		array $statuses,
+		string $type,
+		int $limit,
+		int $offset
+	): array {
+		$placeholders = implode(',', array_fill(0, count($statuses), '%s'));
+		$params = array_merge([$user_id], $statuses);
 
-            // Add more cases for other notification types
-            default:
-                return "{$user_name} has {$count} notifications for you";
-        }
-    }
+		$typeCondition = '';
+		if ($type !== 'all' && isset($this->typeMap[$type])) {
+			$types = $this->typeMap[$type];
+			if (!empty($types)) {
+				$typePlaceholders = implode(',', array_fill(0, count($types), '%s'));
+				$typeCondition = "AND type IN ({$typePlaceholders})";
+				$params = array_merge($params, $types);
+			}
+		}
 
+		$params[] = $limit;
+		$params[] = $offset;
 
-    /**
-     * Build Notification request
-     *
-     * @param WP_REST_Request $request
-     * @return array Sanitized parameters for checking the cache
-     */
-    protected function buildParams(WP_REST_Request $request):array
-    {
-        $request = $request->get_params();
-        return [
-            'status'        => (array_key_exists('status', $request) && in_array($request['status'], ['all', 'unread', 'expired'])) ? $request['status'] : 'unread',
-            'user_id'       => (array_key_exists('user', $request) && is_int($request['user'])) ? $request['user'] : get_current_user_id(),
-            'page'          => (array_key_exists('page', $request) && is_numeric($request['page'])) ? $request['page'] : 1,
-            'type'          => (array_key_exists('type', $request) && in_array($request['type'], array_keys($this->manager->notification_types))) ? $request['type'] : 'all',
-        ];
-    }
+		return $this->notifications->queryResults(
+			"SELECT * FROM {table}
+             WHERE owner_id = %d
+             AND status IN ({$placeholders})
+             {$typeCondition}
+             ORDER BY created_at DESC
+             LIMIT %d OFFSET %d",
+			$params
+		);
+	}
 
-    /**
-     * @param WP_REST_Request $request
-     *
-     * @return WP_REST_Response
-     */
-    public function updateNotifications(WP_REST_Request $request):WP_REST_Response
-    {
-        $data = $request->get_params();
-        $action = $request->get_param('action');
-        $notificationID = (array_key_exists('notification', $data) && is_int($data['notification'])) ? $data['notification'] : false;
-        $args = $this->buildParams($request);
-        $data = [];
-        $error = '';
-        switch ($action) {
-            case 'mark_as_read':
-                if (is_null($notificationID)) {
-                    return new WP_REST_Response([
-                        'success'   => false,
-                        'message'   => 'Notification ID is required'
-                    ]);
-                }
-                $data = [
-                    'user_id'   => $args['user_id'],
-                    'notification_id' => $notificationID,
-                ];
-                break;
-            case 'mark_all_as_read':
-                $data = [
-                    'user_id'   => $args['user_id'],
-                ];
-                if ($request->get_param('notification_ids')) {
-                    //To bulk mark all ids
-                    $data['notification_ids'] = array_map('intval', $request->get_param('notification_ids'));
-                }
-                if ($request->get_param('type')) {
-                    //To bulk select all items by type
-                    $data['type'] = $request->get_param('type');
-                }
-                break;
-            case 'approve_artist':
-            case 'reject_artist':
-                //TODO: hook into the approval routes already set up
-                $handler = JVB()->routes('approvals');
-                $ID = $handler->getVerificationDetails($args['user_id']);
-                $notes = isset($request['notes']) ? sanitize_text_field($request['notes']) : '';
-                $handler->voteForArtist($args['user_id'], $ID['request'], $action==='approve_artist', $notes);
+	/**
+	 * Get notifications by multiple types
+	 */
+	protected function getNotificationsByTypes(
+		int $user_id,
+		array $types,
+		string $status,
+		int $limit,
+		int $offset
+	): array {
+		$placeholders = implode(',', array_fill(0, count($types), '%s'));
+		$params = array_merge([$user_id], $types, [$limit, $offset]);
 
-                $this->markActioned($notificationID, $args['user_id']);
-                break;
-            case 'acceptInvitation':
-            case 'reject_invitation':
-                //TODO: hook into the shop routes already set up
-                $handler = JVB()->routes('shop');
-                $notification_data = $this->getNotification($notificationID);
+		$statusCondition = '';
+		if ($status !== 'all') {
+			$statusCondition = "AND status = %s";
+			array_splice($params, -2, 0, [$status]);
+		}
 
-                if ($action === 'acceptInvitation') {
-                    $result = $handler->acceptShopInvitation($args['user_id'], $args['shop_id']);
-                } else {
-                    $result = $handler->declineShopInvitation($args['user_id'], $args['shop_id']);
-                }
+		return $this->notifications->queryResults(
+			"SELECT * FROM {table}
+             WHERE owner_id = %d
+             AND type IN ({$placeholders})
+             {$statusCondition}
+             ORDER BY created_at DESC
+             LIMIT %d OFFSET %d",
+			$params
+		);
+	}
 
-                $this->markActioned($notificationID, $args['user_id']);
-                break;
-            case 'accept_to_shop':
-            case 'reject_to_shop':
-                $handler = JVB()->routes('shop');
+	/**
+	 * Get approval notifications from the approval_requests table
+	 */
+	protected function getApprovalNotifications(int $user_id, string $status): array
+	{
+		$cacheKey = compact('user_id', 'status');
 
-                if ($action === 'accept_to_shop') {
-                    $result = $handler->addArtistToShop($args['user_id'], $args['shop_id']);
-                } else {
-                    //TODO: notify requester that their request has been denied
-                }
+		return $this->cache->remember($cacheKey, function() use ($user_id, $status) {
+			if (!$this->isVerifiedUser($user_id)) {
+				return [];
+			}
 
-                $this->markActioned($notificationID, $args['user_id']);
-                break;
-            case 'approve_term':
-            case 'reject_term':
-                $handler = JVB()->routes('approvals');
-                $notification_data = $this->getNotification($notificationID);
-                $notes = isset($request['notes']) ? sanitize_text_field($request['notes']) : '';
+			global $wpdb;
+			$formatted = [];
 
-                if ($action === 'approve_term') {
-                    $result = $handler->approveTerm($args['user_id'], $notification_data, $notes);
-                } else {
-                    $result = $handler->rejectTerm($args['user_id'], $notification_data, $notes);
-                }
+			// Build status condition
+			$statusCondition = "1=1";
+			if ($status === 'unread') {
+				$statusCondition = "a.status = 'pending'";
+			} elseif ($status === 'read') {
+				$statusCondition = "a.status IN ('approved', 'rejected')";
+			} elseif ($status !== 'all') {
+				$statusCondition = $wpdb->prepare("a.status = %s", $status);
+			}
 
-                $this->markActioned($notificationID, $args['user_id']);
-                break;
-            case 'dismiss_notification':
-                $data = [
-                    'user_id'   => $args['user_id'],
-                    'notification_id' => $notificationID,
-                ];
-                break;
+			$approvals = Registrar::getFeatured('approve_new');
+			foreach ($approvals as $type => $config) {
+				$table = $wpdb->prefix . BASE . 'approval_' . $type . 'requests';
+				$votes = $wpdb->prefix . BASE . 'approval_' . $type . 'votes';
 
-            default:
-                $error = 'Invalid action';
+				$approvalRequests = $wpdb->get_results(
+					$wpdb->prepare(
+						"SELECT a.*,
+                        COALESCE(v.vote, 'none') as user_vote
+                        FROM {$table} a
+                        LEFT JOIN {$votes} v ON a.id = v.request_id AND v.user_id = %d
+                        WHERE a.user_id != %d
+                        AND {$statusCondition}
+                        ORDER BY a.created_at DESC",
+						$user_id,
+						$user_id
+					)
+				);
 
-        }
+				// Filter out requests created by current user
+				foreach ($approvalRequests as $approval) {
+					$requested_by = json_decode($approval->requested_by, true);
 
-        if (!empty($data)) {
-            JVB()->queue()->queueOperation(
-                'notification_'.$action,
-                $args['user_id'],
-                $data
-            );
-            return new WP_REST_Response([
-                'success'   => true,
-                'message'   => __('Notification queued for processing', 'jvb')
-            ]);
-        } else {
-            return new WP_REST_Response([
-                'success'   => false,
-                'message'   => __('Error: '.$error, 'jvb')
-            ]);
-        }
-    }
+					if (is_array($requested_by) && in_array($user_id, $requested_by)) {
+						continue;
+					}
 
-    /**
-     * Get notification data by ID
-     * @param int $notification_id Notification ID
-     * @return array|false Notification data or false if not found
-     */
-    protected function getNotification(int $notification_id):array|false
-    {
-        global $wpdb;
-        $table = $wpdb->prefix.BASE.'notifications';
-        $notification = $wpdb->get_row(
-            $wpdb->prepare(
-                "SELECT * FROM {$table} WHERE id = %d",
-                $notification_id
-            ),
-            ARRAY_A
-        );
+					$formatted[] = $this->formatApprovalNotification($approval);
+				}
+			}
 
-        if (!$notification) {
-            return false;
-        }
+			return $formatted;
+		});
+	}
 
-        // Parse context data
-        $context = !empty($notification['context'])
-            ? json_decode($notification['context'], true)
-            : [];
+	/**
+	 * Get content notifications for a user
+	 */
+	public function getContentNotifications(
+		int $user_id,
+		string $status = 'unread',
+		int $limit = 20,
+		int $offset = 0
+	): array {
+		if (!$this->checkUser($user_id)) {
+			return [];
+		}
 
-        return array_merge($notification, $context);
-    }
+		$cacheKey = compact('user_id', 'status', 'limit', 'offset');
 
-    /**
-     * @param WP_Error|array $result
-     * @param object $operation
-     * @param array $data
-     *
-     * @return int|mixed
-     */
-    public function processOperation(WP_Error|array $result, object $operation, array $data):WP_Error|array
-    {
-        switch ($operation->type) {
-            case 'notification_mark_as_read':
-                $result = $this->markRead($data['notification_id'], $operation->user_id);
-                break;
-            case 'notification_mark_all_as_read':
-                $result = $this->markAllRead($data);
-                break;
-            case 'notification_dismiss_notification':
-                $result = $this->markDismissed($data['notification_id'], $operation->user_id);
-                break;
-        }
-        return $result;
-    }
+		return $this->cache->remember($cacheKey, function() use ($user_id, $status, $limit, $offset) {
+			global $wpdb;
 
-    /**
-     * Mark all notifications as read for a user
-     *
-     * @param array $data Data containing user_id and optional filters
-     *
-     * @return array  Number of notifications marked as read
-     */
-    public function markAllRead(array $data):array
-    {
-        $user_id = $data['user_id'];
-        $type = $data['type'] ?? null;
-        $notification_ids = $data['notification_ids'] ?? null;
+			// Build status condition
+			$statusCondition = "1=1";
+			if ($status === 'unread') {
+				$statusCondition = "seen.status = 'unread'";
+			} elseif ($status === 'read') {
+				$statusCondition = "seen.status = 'read'";
+			} elseif ($status !== 'all') {
+				$statusCondition = $wpdb->prepare("seen.status = %s", $status);
+			}
 
-        global $wpdb;
-        $table = $wpdb->prefix . BASE . 'notifications';
+			$notifications = $wpdb->get_results(
+				$wpdb->prepare(
+					"SELECT seen.*, content.*
+                     FROM {$wpdb->prefix}" . BASE . "notifications_user_seen AS seen
+                     JOIN {$wpdb->prefix}" . BASE . "notifications_content AS content
+                         ON seen.content_notification_id = content.id
+                     WHERE seen.user_id = %d AND {$statusCondition}
+                     ORDER BY content.date DESC, content.created_at DESC
+                     LIMIT %d OFFSET %d",
+					$user_id,
+					$limit,
+					$offset
+				)
+			);
 
-        $where = [
-            'owner_id = %d',
-            "status = 'unread'"
-        ];
-        $params = [$user_id];
+			// Format content notifications
+			return array_map([$this, 'formatContentNotification'], $notifications);
+		});
+	}
 
-        // Add type filter if specified
-        if ($type) {
-            $where[] = "type = %s";
-            $params[] = $type;
-        }
+	// =========================================================================
+	// UPDATE OPERATIONS
+	// =========================================================================
 
-        // Add specific IDs filter if provided
-        if ($notification_ids && is_array($notification_ids)) {
-            $id_placeholders = implode(',', array_fill(0, count($notification_ids), '%d'));
-            $where[] = "id IN ($id_placeholders)";
-            $params = array_merge($params, $notification_ids);
-        }
+	/**
+	 * Mark notification as read
+	 */
+	public function markRead(WP_REST_Request $request): WP_REST_Response
+	{
+		$user_id = absint($request->get_param('user'));
+		$notification_id = absint($request->get_param('notification_id'));
 
-        $where_clause = implode(' AND ', $where);
+		if (!$this->checkUser($user_id)) {
+			return $this->unauthorized();
+		}
 
-        // Update all matching notifications
-        $updated = $wpdb->query($wpdb->prepare(
-            "UPDATE $table
-         SET status = 'read',
-             read_at = %s,
-             updated_at = %s
-         WHERE $where_clause",
-            array_merge(
-                [current_time('mysql'), current_time('mysql')],
-                $params
-            )
-        ));
+		try {
+			$result = $this->notifications->transaction(function($table) use ($notification_id, $user_id) {
+				// Verify ownership
+				$notification = $table
+					->where(['id' => $notification_id, 'owner_id' => $user_id])
+					->first();
 
-        if ($updated) {
-            $this->trackNotificationMetrics(0, $user_id, 'batch_read', ['count' => $updated]);
-            $this->clearNotificationCache($user_id);
-        }
+				if (!$notification) {
+					throw new Exception('Invalid notification');
+				}
 
-        return [
-            'success'   => true,
-            'result'   => $updated
-        ];
-    }
+				$updated = $table->update(
+					[
+						'status' => 'read',
+						'read_at' => current_time('mysql')
+					],
+					['id' => $notification_id]
+				);
 
-    /**
-     * Mark a notification as read
-     *
-     * @param int $notification_id Notification ID
-     * @param int $user_id User ID making the request (for security)
-     *
-     * @return array Success or failure
-     */
-    public function markRead(int $notification_id, int $user_id):array
-    {
-        if (!$this->checkUser($user_id)) {
-            return [
-                'success'   => false,
-                'result'   => 'Invalid user'
-            ];
-        }
-        global $wpdb;
+				if (!$updated) {
+					throw new Exception('Failed to update notification');
+				}
+
+				return $updated;
+			});
+
+			$this->trackMetrics($notification_id, $user_id, 'read');
+			$this->clearUserCache($user_id);
+
+			return $this->success(['updated' => $result]);
+
+		} catch (Exception $e) {
+			return $this->error($e->getMessage());
+		}
+	}
+
+	/**
+	 * Mark all notifications as read
+	 */
+	public function markAllRead(WP_REST_Request $request): WP_REST_Response
+	{
+		$user_id = absint($request->get_param('user'));
+		$type = sanitize_text_field($request->get_param('type') ?? '');
+
+		if (!$this->checkUser($user_id)) {
+			return $this->unauthorized();
+		}
+
+		try {
+			$where = ['owner_id' => $user_id, 'status' => 'unread'];
+			if ($type) $where['type'] = $type;
+
+			$updated = $this->notifications
+				->where($where)
+				->updateResults([
+					'status' => 'read',
+					'read_at' => current_time('mysql')
+				]);
+
+			if ($updated) {
+				$this->trackMetrics(0, $user_id, 'batch_read', ['count' => $updated]);
+				$this->clearUserCache($user_id);
+			}
+
+			return $this->success(['updated' => $updated]);
+
+		} catch (Exception $e) {
+			return $this->error($e->getMessage());
+		}
+	}
+
+	/**
+	 * Mark notification as actioned
+	 */
+	public function markActioned(WP_REST_Request $request): WP_REST_Response
+	{
+		$user_id = absint($request->get_param('user'));
+		$notification_id = absint($request->get_param('notification_id'));
+
+		if (!$this->checkUser($user_id)) {
+			return $this->unauthorized();
+		}
+
+		try {
+			$result = $this->notifications->transaction(function($table) use ($notification_id, $user_id) {
+				// Verify ownership and requires action
+				$notification = $table
+					->where(['id' => $notification_id, 'owner_id' => $user_id, 'requires_action' => 1])
+					->first();
+
+				if (!$notification) {
+					throw new Exception('Invalid notification or does not require action');
+				}
+
+				$updated = $table->update(
+					[
+						'status' => 'actioned',
+						'action_taken' => 1,
+						'actioned_at' => current_time('mysql')
+					],
+					['id' => $notification_id]
+				);
+
+				if (!$updated) {
+					throw new Exception('Failed to update notification');
+				}
+
+				return $updated;
+			});
+
+			$this->trackMetrics($notification_id, $user_id, 'actioned');
+			$this->clearUserCache($user_id);
+
+			return $this->success(['message' => 'Notification actioned']);
+
+		} catch (Exception $e) {
+			return $this->error($e->getMessage());
+		}
+	}
+
+	/**
+	 * Dismiss notification
+	 */
+	public function markDismissed(WP_REST_Request $request): WP_REST_Response
+	{
+		$user_id = absint($request->get_param('user'));
+		$notification_id = absint($request->get_param('notification_id'));
+
+		if (!$this->checkUser($user_id)) {
+			return $this->unauthorized();
+		}
+
+		try {
+			$result = $this->notifications->transaction(function($table) use ($notification_id, $user_id) {
+				// Verify ownership
+				$notification = $table
+					->where(['id' => $notification_id, 'owner_id' => $user_id])
+					->first();
+
+				if (!$notification) {
+					throw new Exception('Invalid notification');
+				}
+
+				$updated = $table->update(
+					[
+						'status' => 'dismissed',
+						'dismissed_at' => current_time('mysql')
+					],
+					['id' => $notification_id]
+				);
+
+				if (!$updated) {
+					throw new Exception('Failed to update notification');
+				}
+
+				return $updated;
+			});
+
+			$this->trackMetrics($notification_id, $user_id, 'dismissed');
+			$this->clearUserCache($user_id);
+
+			return $this->success(['updated' => $result]);
+
+		} catch (Exception $e) {
+			return $this->error($e->getMessage());
+		}
+	}
+
+	/**
+	 * Mark content notification as read
+	 */
+	public function markContentNotificationRead(int $seen_id, int $user_id): array
+	{
+		if (!$this->checkUser($seen_id) || !$this->checkUser($user_id)) {
+			return [
+				'success' => false,
+				'message' => 'Invalid User ID'
+			];
+		}
+
+		try {
+			global $wpdb;
+			$table = $wpdb->prefix . BASE . 'notifications_user_seen';
+
+			return $wpdb->query('START TRANSACTION') &&
+				$this->updateContentNotificationStatus($table, $seen_id, $user_id, 'read');
+
+		} catch (Exception $e) {
+			global $wpdb;
+			$wpdb->query('ROLLBACK');
+			$this->logError('markContentNotificationRead exception', [
+				'seen_id' => $seen_id,
+				'user_id' => $user_id,
+				'error' => $e->getMessage()
+			]);
+			return [
+				'success' => false,
+				'message' => $e->getMessage()
+			];
+		}
+	}
+
+	/**
+	 * Dismiss content notification
+	 */
+	public function dismissContentNotification(int $seen_id, int $user_id): array
+	{
+		if (!$this->checkUser($seen_id) || !$this->checkUser($user_id)) {
+			return [
+				'success' => false,
+				'message' => 'Invalid User ID'
+			];
+		}
+
+		try {
+			global $wpdb;
+			$table = $wpdb->prefix . BASE . 'notifications_user_seen';
+
+			// Verify ownership
+			$seen_record = $wpdb->get_row(
+				$wpdb->prepare(
+					"SELECT * FROM {$table} WHERE id = %d AND user_id = %d",
+					$seen_id,
+					$user_id
+				)
+			);
+
+			if (!$seen_record) {
+				return [
+					'success' => false,
+					'message' => 'No notification found'
+				];
+			}
+
+			$result = $wpdb->update(
+				$table,
+				['status' => 'dismissed'],
+				['id' => $seen_id]
+			);
+
+			if ($result !== false) {
+				$this->clearUserCache($user_id);
+			}
+
+			return [
+				'success' => $result !== false,
+				'message' => 'Operation completed',
+			];
+
+		} catch (Exception $e) {
+			return [
+				'success' => false,
+				'message' => $e->getMessage()
+			];
+		}
+	}
+
+	// =========================================================================
+	// QUEUE OPERATIONS
+	// =========================================================================
+
+	/**
+	 * Process queued notification operations
+	 */
+	public function processOperation(WP_Error|array $result, object $operation, array $data): WP_Error|array
+	{
+		switch ($operation->type) {
+			case 'notification_mark_as_read':
+				$result = $this->markReadQueued($data['notification_id'], $operation->user_id);
+				break;
+			case 'notification_mark_all_as_read':
+				$result = $this->markAllReadQueued($data);
+				break;
+			case 'notification_dismiss_notification':
+				$result = $this->markDismissedQueued($data['notification_id'], $operation->user_id);
+				break;
+		}
+		return $result;
+	}
+
+	/**
+	 * Update notification operations (legacy endpoint)
+	 */
+	public function updateNotifications(WP_REST_Request $request): WP_REST_Response
+	{
+		$data = $request->get_params();
+		$action = $request->get_param('action');
+		$notificationID = absint($data['notification'] ?? 0);
+		$args = $this->buildParams($request);
+		$queueData = [];
+		$error = '';
+
+		switch ($action) {
+			case 'mark_as_read':
+				if (!$notificationID) {
+					return $this->validationError(['notification' => 'Notification ID is required']);
+				}
+				$queueData = [
+					'user_id' => $args['user_id'],
+					'notification_id' => $notificationID,
+				];
+				break;
+
+			case 'mark_all_as_read':
+				$queueData = ['user_id' => $args['user_id']];
+				if ($request->get_param('notification_ids')) {
+					$queueData['notification_ids'] = array_map('intval', $request->get_param('notification_ids'));
+				}
+				if ($request->get_param('type')) {
+					$queueData['type'] = $request->get_param('type');
+				}
+				break;
+
+			case 'dismiss_notification':
+				$queueData = [
+					'user_id' => $args['user_id'],
+					'notification_id' => $notificationID,
+				];
+				break;
+
+			default:
+				$error = 'Invalid action';
+		}
+
+		if (!empty($queueData)) {
+			JVB()->queue()->queueOperation(
+				'notification_' . $action,
+				$args['user_id'],
+				$queueData
+			);
+			return $this->success(['message' => __('Notification queued for processing', 'jvb')]);
+		}
+
+		return $this->error($error ?: 'Unknown error');
+	}
+
+	// =========================================================================
+	// FORMATTING HELPERS
+	// =========================================================================
+
+	/**
+	 * Format a notification for display
+	 */
+	protected function formatNotification(object $notification): array
+	{
+		$config = $this->notification_types[$notification->type] ?? [];
+		$context = json_decode($notification->context ?? '{}', true);
+
+		// Get action user's name if available
+		$acting_user_name = null;
+		if ($notification->action_user_id) {
+			$acting_user_name = jvbShareName($notification->action_user_id);
+		}
 
-        //TODO: We need to set up a system to check the main notification table, but also the content notification table
-        // Verify ownership
-        $table = $wpdb->prefix . BASE . 'notifications';
-        $notification = $wpdb->get_row(
-            $wpdb->prepare(
-                "SELECT * FROM {$table}
-             WHERE id = %d AND owner_id = %d",
-                $notification_id,
-                $user_id
-            )
-        );
+		return [
+			'id' => $notification->id,
+			'type' => $notification->type,
+			'message' => $notification->message,
+			'created_at' => $notification->created_at,
+			'status' => $notification->status,
+			'requires_action' => (bool)$notification->requires_action,
+			'action_taken' => (bool)$notification->action_taken,
+			'icon' => $config['icon'] ?? 'info',
+			'priority' => $notification->priority,
+			'target' => [
+				'id' => $notification->target_id,
+				'type' => $notification->target_type
+			],
+			'context' => $context,
+			'acting_user' => $notification->action_user_id ? [
+				'id' => $notification->action_user_id,
+				'name' => $acting_user_name
+			] : null,
+			'actions' => $this->getNotificationActions($notification->type, (array)$notification, $notification)
+		];
+	}
 
-        if (!$notification) {
-            return [
-                'success'   => false,
-                'result'   => 'Invalid notification'
-            ];
-        }
+	/**
+	 * Format an approval request as a notification
+	 */
+	protected function formatApprovalNotification(object $approval): array
+	{
+		$data = json_decode($approval->data ?? '{}', true);
+		$type_labels = [
+			'artist_approval' => 'Artist Verification',
+			'term_suggestion' => 'Term Suggestion'
+		];
 
-        // Mark as read
-        $result = $wpdb->update(
-            $wpdb->prefix . BASE . 'notifications',
-            [
-                'status' => 'read',
-                'read_at' => current_time('mysql'),
-                'updated_at' => current_time('mysql')
-            ],
-            ['id' => $notification_id]
-        );
+		$status_labels = [
+			'pending' => 'Pending',
+			'approved' => 'Approved',
+			'rejected' => 'Rejected',
+			'expired' => 'Expired'
+		];
 
-        if ($result) {
-            $this->clearNotificationCache($user_id);
-        }
+		$icon = ($approval->type === 'artist_approval') ? 'artist' : 'style';
 
-        return [
-            'success'   => $result !== false,
-            'result'   => $result
-        ];
-    }
+		$message = '';
+		if ($approval->type === 'artist_approval') {
+			if ($approval->requested_by == $approval->target_id) {
+				$message = "Your artist verification is {$status_labels[$approval->status]}";
+			} else {
+				$name = $data['display_name'] ?? 'An artist';
+				$message = "{$name} is requesting verification";
+			}
+		} elseif ($approval->type === 'term_suggestion') {
+			$term_name = $data['term_name'] ?? 'A term';
+			$taxonomy = $data['taxonomy'] ?? '';
+			$taxonomy_name = str_replace(BASE, '', $taxonomy);
 
-    /**
-     * Mark a notification as actioned
-     *
-     * @param int $notification_id Notification ID
-     * @param int $user_id User ID making the request
-     *
-     * @return array
-     */
-    public function markActioned(int $notification_id, int $user_id):array
-    {
-        if (!$this->checkUser($user_id)) {
-            return [
-                'success'   => false,
-                'message'   => 'Invalid user'
-            ];
-        }
-        global $wpdb;
+			if ($approval->requested_by == get_current_user_id()) {
+				$message = "Your {$taxonomy_name} suggestion '{$term_name}' is {$status_labels[$approval->status]}";
+			} else {
+				$message = "New {$taxonomy_name} suggestion: '{$term_name}'";
+			}
+		}
 
-        //TODO: We need to set up a system to check the main notification table, but also the content notification table
-        // Start a transaction
-        $wpdb->query('START TRANSACTION');
+		return [
+			'id' => 'approval_' . $approval->id,
+			'type' => $approval->type,
+			'message' => $message,
+			'created_at' => $approval->created_at,
+			'status' => $approval->status,
+			'requires_action' => ($approval->status === 'pending' && $approval->requested_by != get_current_user_id()),
+			'action_taken' => !empty($approval->user_vote) && $approval->user_vote !== 'none',
+			'icon' => $icon,
+			'priority' => 'high',
+			'target' => [
+				'id' => $approval->target_id,
+				'type' => $approval->target_type
+			],
+			'context' => $data,
+			'approval_data' => [
+				'required_approvals' => $approval->required_approvals,
+				'current_approvals' => $approval->current_approvals,
+				'expires_at' => $approval->expires_at
+			]
+		];
+	}
 
-        try {
-            $table = $wpdb->prefix.BASE.'notifications';
-            // Verify ownership and that notification requires action
-            $notification = $wpdb->get_row(
-                $wpdb->prepare(
-                    "SELECT * FROM {$table}
-                 WHERE id = %d AND user_id = %d AND requires_action = 1
-                 FOR UPDATE", // Lock the row
-                    $notification_id,
-                    $user_id
-                )
-            );
+	/**
+	 * Format a content notification for display
+	 */
+	protected function formatContentNotification(object $notification): array
+	{
+		// Get artist data
+		$artist_data = jvbContentFromUser($notification->user_id);
 
-            if (!$notification) {
-                $wpdb->query('ROLLBACK');
-                $this->logError("Failed to action notification - not found or not owned", [
-                    'notification_id' => $notification_id,
-                    'user_id' => $user_id
-                ], 'warning');
-                return [
-                    'success'   => false,
-                    'message'   => 'No notification found, or invalid owner'
-                ];
-            }
+		// Parse JSON data
+		$new_items = json_decode($notification->new_items, true) ?: [];
+		$updated_items = json_decode($notification->updated_items, true) ?: [];
 
-            // Mark as actioned
-            $result = $wpdb->update(
-                $wpdb->prefix . BASE . 'notifications',
-                [
-                    'status' => 'actioned',
-                    'action_taken' => 1,
-                    'actioned_at' => current_time('mysql'),
-                    'updated_at' => current_time('mysql')
-                ],
-                ['id' => $notification_id]
-            );
+		// Count items by type
+		$counts_by_type = [];
+		$total_new = 0;
 
-            if ($result !== false) {
-                $wpdb->query('COMMIT');
-                $this->clearNotificationCache($user_id);
-                return [
-                    'success'   => true,
-                    'message'   => 'Notification actioned'
-                ];
-            } else {
-                $wpdb->query('ROLLBACK');
-                $this->logError("Database error marking notification as actioned", [
-                    'notification_id' => $notification_id,
-                    'user_id' => $user_id,
-                    'db_error' => $wpdb->last_error
-                ]);
-                return [
-                    'success'   => false,
-                    'message'   => 'Error'
-                ];
-            }
-        } catch (Exception $e) {
-            $wpdb->query('ROLLBACK');
-            $this->logError("Exception marking notification as actioned", [
-                'notification_id' => $notification_id,
-                'user_id' => $user_id,
-                'error' => $e->getMessage()
-            ]);
-            return [
-                'success'   => false,
-                'message'   => $e->getMessage(),
-            ];
-        }
-    }
+		foreach ($new_items as $type => $ids) {
+			$clean_type = str_replace(BASE, '', $type);
+			$counts_by_type[$clean_type] = [
+				'new' => count($ids),
+				'updated' => 0
+			];
+			$total_new += count($ids);
+		}
 
-    /**
-     * Dismiss a notification
-     *
-     * @param int $notification_id Notification ID
-     * @param int $user_id User ID making the request
-     *
-     * @return array Success or failure
-     */
-    public function markDismissed(int $notification_id, int $user_id):array{
-        if (!$this->checkUser($user_id)) {
-            return [
-                'success'   => false,
-                'result'   => 'Invalid User',
-            ];
-        }
-        global $wpdb;
+		foreach ($updated_items as $type => $ids) {
+			$clean_type = str_replace(BASE, '', $type);
+			if (!isset($counts_by_type[$clean_type])) {
+				$counts_by_type[$clean_type] = ['new' => 0];
+			}
+			$counts_by_type[$clean_type]['updated'] = count($ids);
+		}
 
-        // Verify ownership
-        $table = $wpdb->prefix.BASE.'notifications';
-        $notification = $wpdb->get_row(
-            $wpdb->prepare(
-                "SELECT * FROM {$table}
-                 WHERE id = %d AND user_id = %d",
-                $notification_id,
-                $user_id
-            )
-        );
-//TODO: We need to set up a system to check the main notification table, but also the content notification table
-        if (!$notification) {
-            return [
-                'success'   => false,
-                'result'   => 'Invalid notification'
-            ];
-        }
+		// Build summary text
+		$summary = [];
+		foreach ($counts_by_type as $type => $counts) {
+			if ($counts['new'] > 0) {
+				$label = $counts['new'] === 1 ? $type : $this->pluralize($type);
+				$summary[] = "{$counts['new']} new {$label}";
+			}
+			if ($counts['updated'] > 0) {
+				$label = $counts['updated'] === 1 ? $type : $this->pluralize($type);
+				$summary[] = "{$counts['updated']} updated {$label}";
+			}
+		}
 
-        // Mark as dismissed
-        $result = $wpdb->update(
-            $wpdb->prefix . BASE . 'notifications',
-            [
-                'status'     => 'dismissed',
-                'updated_at' => current_time('mysql')
-            ],
-            [ 'id' => $notification_id ]
-        );
+		return [
+			'id' => $notification->id,
+			'seen_id' => $notification->content_notification_id,
+			'status' => $notification->status,
+			'date' => $notification->date,
+			'artist' => $artist_data,
+			'new_items' => $new_items,
+			'updated_items' => $updated_items,
+			'summary' => implode(', ', $summary),
+			'total_new' => $total_new,
+			'total_updated' => $notification->total_items - $total_new,
+			'counts_by_type' => $counts_by_type,
+			'has_profile_update' => (bool)$notification->has_profile_update,
+			'created_at' => $notification->created_at
+		];
+	}
 
-        if ($result) {
-            $this->clearNotificationCache($user_id);
-        }
+	/**
+	 * Build a message for grouped notifications
+	 */
+	protected function buildGroupedMessage(string $user_name, string $type, int $count, array $target_types = []): string
+	{
+		switch ($type) {
+			case 'new_favourite':
+				if (count($target_types) === 1) {
+					$content_type = $this->manager->getContentTypeLabel($target_types[0]);
+					return "{$user_name} favourited {$count} of your {$content_type}";
+				}
+				return "{$user_name} favourited {$count} of your items";
 
-        return [
-            'success'   => $result !== false,
-            'result'   => $result
-        ];
-    }
+			case 'artist_request':
+				return "{$user_name} wants to join your shop";
 
-    /***
-     * Content notifications (in a separate database))
-     */
-    /**
-     * Get content notifications for a user
-     *
-     * @param int $user_id User ID
-     * @param string $status Status filter (unread, read, all)
-     * @param int $limit Maximum number of notifications to return
-     * @param int $offset Pagination offset
-     *
-     * @return array Content notifications
-     */
-    public function getContentNotifications(
-        int $user_id,
-        string $status = 'unread',
-        int $limit = 20,
-        int $offset = 0
-    ):array {
-        if (!$this->checkUser($user_id)) {
-            return [];
-        }
-        // Try cache first
-        $cache_key = "user_{$user_id}_content_notifications_{$status}_{$limit}_{$offset}";
-        $cached    = $this->cache->get($cache_key);
+			default:
+				return "{$user_name} has {$count} notifications for you";
+		}
+	}
 
-        if ($cached !== false) {
-            return $cached;
-        }
+	// =========================================================================
+	// HELPER METHODS
+	// =========================================================================
 
-        global $wpdb;
+	/**
+	 * Get notification actions
+	 */
+	protected function getNotificationActions(string $type, array $data, ?object $notification = null): array
+	{
+		$actions = [];
 
-        // Build status condition
-        $status_condition = "1=1";
-        if ($status === 'unread') {
-            $status_condition = "seen.status = 'unread'";
-        } elseif ($status === 'read') {
-            $status_condition = "seen.status = 'read'";
-        } elseif ($status !== 'all') {
-            $status_condition = $wpdb->prepare("seen.status = %s", $status);
-        }
+		switch ($type) {
+			case 'artist_approved':
+			case 'artist_rejected':
+			case 'shop_approved':
+			case 'shop_rejected':
+			case 'term_approved':
+			case 'term_rejected':
+			case 'system_message':
+				// No extra action needed
+				break;
 
-        // Get content notifications this user has seen records for
+			case 'artist_invitation':
+				$actions[] = [
+					'icon' => 'upvote',
+					'label' => 'Approve',
+					'action' => 'acceptInvitation',
+				];
+				$actions[] = [
+					'icon' => 'downvote',
+					'label' => 'Reject',
+					'action' => 'reject_invitation',
+				];
+				break;
 
-        $notifications = $wpdb->get_results(
-            $wpdb->prepare(
-                "SELECT seen.*, content.*
-                 FROM {$wpdb->prefix}{$this->base}notifications_user_seen AS seen
-                 JOIN {$wpdb->prefix}{$this->base}notifications_content AS content
-                     ON seen.content_notification_id = content.id
-                 WHERE seen.user_id = %d AND {$status_condition}
-                 ORDER BY content.date DESC, content.created_at DESC
-                 LIMIT %d OFFSET %d",
-                $user_id,
-                $limit,
-                $offset
-            )
-        );
+			case 'artist_request':
+				$actions[] = [
+					'icon' => 'upvote',
+					'label' => 'Approve',
+					'action' => 'accept_to_shop',
+				];
+				$actions[] = [
+					'icon' => 'downvote',
+					'label' => 'Reject',
+					'action' => 'reject_to_shop',
+				];
+				break;
 
-        // Format content notifications
-        $formatted = [];
-        foreach ($notifications as $notification) {
-            $formatted[] = $this->formatContentNotification($notification);
-        }
+			case 'new_term':
+				$actions[] = [
+					'icon' => 'upvote',
+					'label' => 'Approve',
+					'action' => 'approve_term',
+				];
+				$actions[] = [
+					'icon' => 'downvote',
+					'label' => 'Reject',
+					'action' => 'reject_term',
+				];
+				break;
 
-        // Cache the results
-        $this->cache->set($cache_key, $formatted, 'notifications_' . $user_id);
+			case 'list_shared':
+				if (!empty($data['list_id'])) {
+					$actions[] = [
+						'icon' => 'list-heart',
+						'label' => 'View List',
+						'url' => home_url("/dash/favourites/{$data['list_id']}"),
+					];
+				}
+				break;
 
-        return $formatted;
-    }
+			default:
+				if (!empty($data['target_id']) && !empty($data['target_type'])) {
+					$actions[] = [
+						'icon' => 'link',
+						'label' => 'View',
+						'url' => $this->getItemLink($data['target_id'], $data['target_type']),
+					];
+				}
+				break;
+		}
 
-    /**
-     * Format a content notification for display
-     *
-     * @param object $notification Content notification object
-     *
-     * @return array Formatted content notification
-     */
-    protected function formatContentNotification(object $notification):array
-    {
-        // Get artist data
-        $artist_data = jvbContentFromUser($notification->user_id);
+		$actions[] = [
+			'icon' => 'close',
+			'label' => 'Dismiss',
+			'action' => 'dismiss_notification'
+		];
 
-        // Parse JSON data
-        $new_items     = json_decode($notification->new_items, true) ?: [];
-        $updated_items = json_decode($notification->updated_items, true) ?: [];
+		return apply_filters('jvb_notification_actions', $actions, $type, $data, $notification);
+	}
 
-        // Count items by type
-        $counts_by_type = [];
-        $total_new      = 0;
+	/**
+	 * Get item link for notification target
+	 */
+	protected function getItemLink(int $ID, string $content): string
+	{
+		switch ($content) {
+			case BASE.'artist':
+			case BASE.'artwork':
+			case BASE.'event':
+			case BASE.'news':
+			case BASE.'offer':
+			case BASE.'partner':
+			case BASE.'piercing':
+			case BASE.'tattoo':
+				return get_permalink($ID);
+			default:
+				return get_term_link($ID, BASE.$content);
+		}
+	}
 
-        foreach ($new_items as $type => $ids) {
-            $clean_type                    = str_replace(BASE, '', $type);
-            $counts_by_type[ $clean_type ] = [
-                'new'     => count($ids),
-                'updated' => 0
-            ];
-            $total_new += count($ids);
-        }
+	/**
+	 * Get notification data by ID
+	 */
+	protected function getNotification(int $notification_id): array|false
+	{
+		$notification = $this->notifications->where(['id' => $notification_id])->first();
 
-        foreach ($updated_items as $type => $ids) {
-            $clean_type = str_replace(BASE, '', $type);
-            if (!isset($counts_by_type[ $clean_type ])) {
-                $counts_by_type[ $clean_type ] = [
-                    'new'     => 0
-                ];
-            }
-            $counts_by_type[ $clean_type ]['updated'] = count($ids);
-        }
+		if (!$notification) {
+			return false;
+		}
 
-        // Build summary text
-        $summary = [];
-        foreach ($counts_by_type as $type => $counts) {
-            if ($counts['new'] > 0) {
-                $label = $counts['new'] === 1 ? $type : $this->pluralize($type);
-                $summary[] = "{$counts['new']} new {$label}";
-            }
-            if ($counts['updated'] > 0) {
-                $label     = $counts['updated'] === 1 ? $type : $this->pluralize($type);
-                $summary[] = "{$counts['updated']} updated {$label}";
-            }
-        }
+		$context = !empty($notification->context)
+			? json_decode($notification->context, true)
+			: [];
 
-        return [
-            'id'                 => $notification->id,
-            'seen_id'            => $notification->content_notification_id,
-            'status'             => $notification->status,
-            'date'               => $notification->date,
-            'artist'             => $artist_data,
-            'new_items'          => $new_items,
-            'updated_items'      => $updated_items,
-            'summary'            => implode(', ', $summary),
-            'total_new'          => $total_new,
-            'total_updated'      => $notification->total_items - $total_new,
-            'counts_by_type'     => $counts_by_type,
-            'has_profile_update' => (bool) $notification->has_profile_update,
-            'created_at'         => $notification->created_at
-        ];
-    }
+		return array_merge((array)$notification, $context);
+	}
 
-    /**
-     * Mark a content notification as read
-     *
-     * @param int $seen_id User seen record ID
-     * @param int $user_id User ID making the request
-     *
-     * @return array Success or failure
-     */
-    public function markContentNotificationRead(int $seen_id, int $user_id):array
-    {
-        // Validate input parameters
-        if (!$this->checkUser($seen_id)) {
-            $this->logError("Invalid seen ID", [
-                'seen_id' => $seen_id,
-                'user_id' => $user_id
-            ], 'warning');
-            return [
-                'success'   => false,
-                'message'   => 'Invalid User ID'
-            ];
-        }
+	/**
+	 * Build notification request parameters
+	 */
+	protected function buildParams(WP_REST_Request $request): array
+	{
+		$params = $request->get_params();
 
-        if (!$this->checkUser($user_id)) {
-            $this->logError("Invalid user ID", [
-                'seen_id' => $seen_id,
-                'user_id' => $user_id
-            ], 'warning');
-            return [
-                'success'   => false,
-                'message'   => 'Invalid User ID'
-            ];
-        }
+		return [
+			'status' => in_array($params['status'] ?? '', ['all', 'unread', 'expired'])
+				? $params['status']
+				: 'unread',
+			'user_id' => absint($params['user'] ?? get_current_user_id()),
+			'page' => absint($params['page'] ?? 1),
+			'type' => in_array($params['type'] ?? '', array_keys($this->manager->notification_types ?? []))
+				? $params['type']
+				: 'all',
+		];
+	}
 
-        global $wpdb;
-        $table = $wpdb->prefix . BASE . 'notifications_user_seen';
+	/**
+	 * Determine which table a notification type belongs to
+	 */
+	protected function getTableForNotificationType(string $notificationType): string
+	{
+		foreach ($this->notificationTableMap as $table => $types) {
+			if (in_array($notificationType, $types)) {
+				return $table;
+			}
+		}
+		return 'notifications';
+	}
 
-        // Start transaction
-        $wpdb->query('START TRANSACTION');
+	/**
+	 * Simple pluralization helper
+	 */
+	protected function pluralize(string $word): string
+	{
+		$irregular = [
+			'tattoo' => 'tattoos',
+			'piercing' => 'piercings',
+			'artwork' => 'artwork',
+			'news' => 'news',
+			'offer' => 'offers',
+			'event' => 'events'
+		];
 
-        try {
-            // Verify ownership with row locking
-            $seen_record = $wpdb->get_row(
-                $wpdb->prepare(
-                    "SELECT * FROM {$table}
-                 WHERE id = %d AND user_id = %d
-                 FOR UPDATE",
-                    $seen_id,
-                    $user_id
-                )
-            );
+		if (isset($irregular[$word])) {
+			return $irregular[$word];
+		}
 
-            if (!$seen_record) {
-                $wpdb->query('ROLLBACK');
-                $this->logError("Seen record not found or not owned by user", [
-                    'seen_id' => $seen_id,
-                    'user_id' => $user_id
-                ], 'warning');
-                return [
-                    'success'   => false,
-                    'message'   => 'Seen record not found or not owned by user'
-                ];
-            }
+		if (substr($word, -1) === 'y') {
+			return substr($word, 0, -1) . 'ies';
+		}
 
-            // Mark as read
-            $result = $wpdb->update(
-                $table,
-                [
-                    'status' => 'read',
-                    'read_at' => current_time('mysql')
-                ],
-                ['id' => $seen_id]
-            );
+		return $word . 's';
+	}
 
-            if ($result !== false) {
-                $wpdb->query('COMMIT');
-                $this->clearNotificationCache($user_id);
-                return [
-                    'success'   => true,
-                    'message'   => 'Successfully marked as seen'
-                ];
-            } else {
-                $wpdb->query('ROLLBACK');
-                $this->logError("Database error marking content notification as read", [
-                    'seen_id' => $seen_id,
-                    'user_id' => $user_id,
-                    'db_error' => $wpdb->last_error
-                ]);
-                return [
-                    'success'   => false,
-                    'message'   => 'Something went wrong...'
-                ];
-            }
-        } catch (Exception $e) {
-            $wpdb->query('ROLLBACK');
-            $this->logError("Exception marking content notification as read", [
-                'seen_id' => $seen_id,
-                'user_id' => $user_id,
-                'error' => $e->getMessage()
-            ]);
-            return [
-                'success'   => false,
-                'message'   => $e->getMessage()
-            ];
-        }
-    }
+	/**
+	 * Track notification metrics
+	 */
+	protected function trackMetrics(int $notification_id, int $user_id, string $action, array $details = []): void
+	{
+		try {
+			$this->metrics->create([
+				'notification_id' => $notification_id,
+				'user_id' => $user_id,
+				'action' => $action,
+				'action_source' => 'web',
+				'action_details' => !empty($details) ? json_encode($details) : null,
+			]);
+		} catch (Exception $e) {
+			// Don't fail the request if metrics tracking fails
+			$this->logError('trackMetrics failed', [
+				'error' => $e->getMessage(),
+				'user_id' => $user_id,
+				'action' => $action
+			]);
+		}
+	}
 
+	/**
+	 * Clear notification cache for a user
+	 */
+	protected function clearUserCache(int $user_id): void
+	{
+		Cache::invalidateItem('notifications', $user_id);
+	}
 
-    /**
-     * Dismiss a content notification
-     *
-     * @param int $seen_id User seen record ID
-     * @param int $user_id User ID making the request
-     *
-     * @return array Success or failure
-     */
-    public function dismissContentNotification( int $seen_id, int $user_id ):array
-    {
-        // Validate input parameters
-        if (!$this->checkUser($seen_id)) {
-            $this->logError("Invalid seen ID", [
-                'seen_id' => $seen_id,
-                'user_id' => $user_id
-            ], 'warning');
-            return [
-                'success'   => false,
-                'message'   => 'Invalid User ID'
-            ];
-        }
+	/**
+	 * Update content notification status (helper for transactions)
+	 */
+	private function updateContentNotificationStatus(string $table, int $seen_id, int $user_id, string $status): array
+	{
+		global $wpdb;
 
-        if (!$this->checkUser($user_id)) {
-            $this->logError("Invalid user ID", [
-                'seen_id' => $seen_id,
-                'user_id' => $user_id
-            ], 'warning');
-            return [
-                'success'   => false,
-                'message'   => 'Invalid User ID'
-            ];
-        }
-        global $wpdb;
+		// Verify ownership with row locking
+		$seen_record = $wpdb->get_row(
+			$wpdb->prepare(
+				"SELECT * FROM {$table} WHERE id = %d AND user_id = %d FOR UPDATE",
+				$seen_id,
+				$user_id
+			)
+		);
 
-        // Verify ownership
-        $seen_record = $wpdb->get_row(
-            $wpdb->prepare(
-                "SELECT * FROM {$wpdb->prefix}{$this->base}notifications_user_seen
-                 WHERE id = %d AND user_id = %d",
-                $seen_id,
-                $user_id
-            )
-        );
+		if (!$seen_record) {
+			$wpdb->query('ROLLBACK');
+			return [
+				'success' => false,
+				'message' => 'Seen record not found or not owned by user'
+			];
+		}
 
-        if (!$seen_record) {
-            return [
-                'success'   => false,
-                'message'   => 'No notification found'
-            ];
-        }
+		// Update status
+		$result = $wpdb->update(
+			$table,
+			[
+				'status' => $status,
+				'read_at' => current_time('mysql')
+			],
+			['id' => $seen_id]
+		);
 
-        // Mark as dismissed
-        $result = $wpdb->update(
-            $wpdb->prefix . BASE . 'notifications_user_seen',
-            [
-                'status' => 'dismissed'
-            ],
-            [ 'id' => $seen_id ]
-        );
+		if ($result !== false) {
+			$wpdb->query('COMMIT');
+			$this->clearUserCache($user_id);
+			return [
+				'success' => true,
+				'message' => 'Successfully marked as ' . $status
+			];
+		}
 
-        if ($result) {
-            $this->clearNotificationCache($user_id);
-        }
+		$wpdb->query('ROLLBACK');
+		return [
+			'success' => false,
+			'message' => 'Database error'
+		];
+	}
 
-        return [
-            'success' => $result !== false,
-            'message'   => 'Operation completed',
-        ];
-    }
+	/**
+	 * Queued operation helpers (for backwards compatibility)
+	 */
+	private function markReadQueued(int $notification_id, int $user_id): array
+	{
+		$updated = $this->notifications->update(
+			['status' => 'read', 'read_at' => current_time('mysql')],
+			['id' => $notification_id, 'owner_id' => $user_id]
+		);
 
-    /**
-     * Simple pluralization helper
-     *
-     * @param string $word Word to pluralize
-     * @return string Pluralized word
-     */
-    protected function pluralize(string $word): string
-    {
-        $irregular = [
-            'tattoo' => 'tattoos',
-            'piercing' => 'piercings',
-            'artwork' => 'artwork',
-            'news' => 'news',
-            'offer' => 'offers',
-            'event' => 'events'
-        ];
+		if ($updated) {
+			$this->clearUserCache($user_id);
+		}
 
-        if (isset($irregular[$word])) {
-            return $irregular[$word];
-        }
+		return ['success' => $updated !== false];
+	}
 
-        // Simple pluralization rules
-        if (substr($word, -1) === 'y') {
-            return substr($word, 0, -1) . 'ies';
-        }
+	private function markAllReadQueued(array $data): array
+	{
+		$where = ['owner_id' => $data['user_id'], 'status' => 'unread'];
+		if (!empty($data['type'])) {
+			$where['type'] = $data['type'];
+		}
 
-        return $word . 's';
-    }
-    /**
-     * Track notification metrics for analytics
-     *
-     * @param int $notification_id Notification ID (0 for batch operations)
-     * @param int $user_id User ID
-     * @param string $action Action taken (read, dismiss, etc.)
-     * @param array $details Additional details
-     * @return bool Success or failure
-     */
-    protected function trackNotificationMetrics(int $notification_id, int $user_id, string $action, array $details = []): bool
-    {
-        global $wpdb;
-        $metrics_table = $wpdb->prefix . BASE . 'notification_metrics';
+		$updated = $this->notifications
+			->where($where)
+			->updateResults(['status' => 'read', 'read_at' => current_time('mysql')]);
 
-        try {
-            return $wpdb->insert(
-                $metrics_table,
-                [
-                    'notification_id' => $notification_id,
-                    'user_id' => $user_id,
-                    'action' => $action,
-                    'action_source' => 'web',
-                    'action_details' => json_encode($details),
-                    'created_at' => current_time('mysql')
-                ]
-            ) !== false;
-        } catch (Exception $e) {
-            $this->logError("Failed to track notification metrics", [
-                'user_id' => $user_id,
-                'action' => $action,
-                'error' => $e->getMessage()
-            ]);
-            return false;
-        }
-    }
+		if ($updated) {
+			$this->clearUserCache($data['user_id']);
+		}
 
-    /**
-     * Clear notification cache for a user, for all notification types
-     *
-     * @param int $user_id User ID
-     * @return void
-     */
-    protected function clearNotificationCache(int $user_id): void
-    {
-        // Clear regular notifications cache
-        $this->cache->invalidate("user_{$user_id}_notifications_");
-        $this->cache->invalidate("user_{$user_id}_regular_notifications_");
+		return ['success' => $updated !== false, 'count' => $updated];
+	}
 
-        // Clear content notifications cache
-        $this->cache->invalidate("user_{$user_id}_content_notifications_");
+	private function markDismissedQueued(int $notification_id, int $user_id): array
+	{
+		$updated = $this->notifications->update(
+			['status' => 'dismissed', 'dismissed_at' => current_time('mysql')],
+			['id' => $notification_id, 'owner_id' => $user_id]
+		);
 
-        // Clear approval notifications cache
-        $this->cache->invalidate("user_{$user_id}_approval_notifications_");
+		if ($updated) {
+			$this->clearUserCache($user_id);
+		}
 
-        // Clear merged notifications cache
-        $this->cache->invalidate("user_{$user_id}_merged_notifications_");
-    }
+		return ['success' => $updated !== false];
+	}
 }

--
Gitblit v1.10.0