db = Database::getInstance(); } public function create(array $data): int { $data['created_at'] = date('Y-m-d H:i:s'); return $this->db->insert('notifications', $data); } public function findById(int $id): ?array { return $this->db->fetch('SELECT * FROM notifications WHERE id = ?', [$id]); } public function getForUser(int $userId, int $page = 1, int $perPage = 20): array { $offset = ($page - 1) * $perPage; $total = $this->db->fetch( "SELECT COUNT(*) as total FROM notifications n WHERE (n.user_id IS NULL OR n.user_id = ?)", [$userId] ); $items = $this->db->fetchAll( "SELECT n.*, CASE WHEN nr.id IS NOT NULL THEN 1 ELSE 0 END as is_read, nr.read_at as read_at, (SELECT COUNT(*) FROM notification_reads WHERE notification_id = n.id) as total_reads, (SELECT COUNT(*) FROM users WHERE status = 'active') as total_users FROM notifications n LEFT JOIN notification_reads nr ON nr.notification_id = n.id AND nr.user_id = ? WHERE n.user_id IS NULL OR n.user_id = ? ORDER BY n.created_at DESC LIMIT ? OFFSET ?", [$userId, $userId, $perPage, $offset] ); return [ 'data' => $items, 'total' => (int) ($total['total'] ?? 0), 'page' => $page, 'per_page' => $perPage, 'total_pages' => (int) ceil((int) ($total['total'] ?? 0) / $perPage), ]; } public function getUnreadCount(int $userId): int { $result = $this->db->fetch( "SELECT COUNT(*) as total FROM notifications n WHERE (n.user_id IS NULL OR n.user_id = ?) AND NOT EXISTS ( SELECT 1 FROM notification_reads nr WHERE nr.notification_id = n.id AND nr.user_id = ? )", [$userId, $userId] ); return (int) ($result['total'] ?? 0); } public function markAsRead(int $id, int $userId): void { try { $this->db->insert('notification_reads', [ 'notification_id' => $id, 'user_id' => $userId, 'read_at' => date('Y-m-d H:i:s'), ]); } catch (\Throwable $e) { // Already read, ignore } $this->db->update( 'notifications', ['is_read' => 1, 'read_at' => date('Y-m-d H:i:s')], 'id = ? AND (user_id IS NULL OR user_id = ?)', [$id, $userId] ); } public function markAllAsRead(int $userId): void { $unread = $this->db->fetchAll( "SELECT n.id FROM notifications n WHERE (n.user_id IS NULL OR n.user_id = ?) AND NOT EXISTS ( SELECT 1 FROM notification_reads nr WHERE nr.notification_id = n.id AND nr.user_id = ? )", [$userId, $userId] ); $now = date('Y-m-d H:i:s'); foreach ($unread as $note) { try { $this->db->insert('notification_reads', [ 'notification_id' => (int) $note['id'], 'user_id' => $userId, 'read_at' => $now, ]); } catch (\Throwable $e) { // Duplicate entry — already read, skip } } $this->db->update( 'notifications', ['is_read' => 1, 'read_at' => $now], '(user_id IS NULL OR user_id = ?) AND is_read = 0', [$userId] ); } public function getAll(int $page = 1, int $perPage = 20): array { $offset = ($page - 1) * $perPage; $total = $this->db->fetch("SELECT COUNT(*) as total FROM notifications"); $items = $this->db->fetchAll( "SELECT n.*, u.username as target_username, (SELECT COUNT(*) FROM notification_reads nr WHERE nr.notification_id = n.id) as read_count FROM notifications n LEFT JOIN users u ON n.user_id = u.id ORDER BY n.created_at DESC LIMIT ? OFFSET ?", [$perPage, $offset] ); return [ 'data' => $items, 'total' => (int) ($total['total'] ?? 0), 'page' => $page, 'per_page' => $perPage, 'total_pages' => (int) ceil((int) ($total['total'] ?? 0) / $perPage), ]; } public function getTotalActiveUsers(): int { $result = $this->db->fetch("SELECT COUNT(*) as total FROM users WHERE status = 'active'"); return (int) ($result['total'] ?? 0); } }