false, 'ERROR' => 'method_not_allowed'], 405); } $visitor = requireVisitorAuth(); $channelId = (int)($_GET['ChannelID'] ?? 0); if ($channelId <= 0) { jsonResponse(['OK' => false, 'ERROR' => 'channel_id_required'], 400); } // Verify visitor has access to this channel if (!visitorCanReadChannel($visitor, $channelId)) { jsonResponse(['OK' => false, 'ERROR' => 'channel_not_allowed'], 403); } // Verify channel exists and is not archived $channel = queryOne( "SELECT ID, Name, DisplayName, Purpose, ChannelType FROM Hub_Channels WHERE ID = ? AND IsArchived = 0", [$channelId] ); if (!$channel) { jsonResponse(['OK' => false, 'ERROR' => 'channel_not_found'], 404); } // Pagination $before = (int)($_GET['Before'] ?? 0); $after = (int)($_GET['After'] ?? 0); $limit = min(100, max(1, (int)($_GET['Limit'] ?? 50))); // Build query - read only non-deleted messages $where = ['m.ChannelID = ?', 'm.IsDeleted = 0']; $params = [$channelId]; if ($before > 0) { $where[] = 'm.ID < ?'; $params[] = $before; } if ($after > 0) { $where[] = 'm.ID > ?'; $params[] = $after; } $whereClause = implode(' AND ', $where); // Fetch one extra to check HasMore $fetchLimit = $limit + 1; // Order: newest first for Before cursor, oldest first for After cursor $order = ($after > 0) ? 'ASC' : 'DESC'; $rows = queryTimed( "SELECT m.ID, m.SenderAddress, m.Content, m.ParentID, m.IsEdited, m.CreatedAt, m.UpdatedAt FROM Hub_Messages m WHERE $whereClause ORDER BY m.ID $order LIMIT $fetchLimit", $params ); $hasMore = count($rows) > $limit; if ($hasMore) { array_pop($rows); } // If we fetched in ASC order, reverse for consistent newest-first if ($after > 0) { $rows = array_reverse($rows); } // Format messages (read-only: no edit/delete capabilities for visitors) $messages = []; foreach ($rows as $row) { // Resolve sender display name $senderName = $row['SenderAddress']; // Try to get agent name $agent = queryOne( "SELECT AgentName FROM Sprinter_Agents WHERE FullAddress = ? LIMIT 1", [$row['SenderAddress']] ); if ($agent) { $senderName = $agent['AgentName']; } $messages[] = [ 'ID' => (int)$row['ID'], 'SenderAddress' => $row['SenderAddress'], 'SenderName' => $senderName, 'Content' => $row['Content'], 'ParentID' => $row['ParentID'] ? (int)$row['ParentID'] : null, 'IsEdited' => (bool)$row['IsEdited'], 'CreatedAt' => toISO8601($row['CreatedAt']), ]; } jsonResponse([ 'OK' => true, 'Channel' => [ 'ID' => (int)$channel['ID'], 'Name' => $channel['Name'], 'DisplayName' => $channel['DisplayName'], 'Purpose' => $channel['Purpose'], ], 'Messages' => $messages, 'HasMore' => $hasMore, ]);