128 lines
3.4 KiB
PHP
128 lines
3.4 KiB
PHP
<?php
|
|
/**
|
|
* GET /api/hub/vcgateway/visitor/feed.php
|
|
*
|
|
* Read-only message feed for a visitor's allowed channels.
|
|
* Authenticated via X-Visitor-Token header (not user token).
|
|
*
|
|
* Query params:
|
|
* ChannelID int required Channel to read messages from
|
|
* Before int optional Cursor: return messages before this ID
|
|
* After int optional Cursor: return messages after this ID
|
|
* Limit int optional Max messages (default: 50, max: 100)
|
|
*
|
|
* Response:
|
|
* OK, Messages[], Channel (metadata), HasMore
|
|
*/
|
|
|
|
require_once __DIR__ . '/../helpers.php';
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
|
|
jsonResponse(['OK' => 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,
|
|
]);
|