Complete backend for SprintChat Hub migration: - Messages: send, edit, delete, list (paginated cursor), thread, search - Files: upload (multipart), download, thumbnail, info, list - Users: get, getByIds, search, status (online detection) - Reactions: add, remove, list (grouped by emoji) - Pins: pin, unpin, list (with message content) - Channel stats: member/message/pinned/unread counts 4 new DB tables: Hub_Messages, Hub_Files, Hub_Reactions, Hub_PinnedPosts 21 new endpoints added to PUBLIC_ROUTES Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
82 lines
2.9 KiB
PHP
82 lines
2.9 KiB
PHP
<?php
|
|
/**
|
|
* POST /api/hub/messages/send.php
|
|
*
|
|
* Send a message to a channel.
|
|
*
|
|
* Body:
|
|
* ChannelID int REQUIRED
|
|
* SenderAddress string REQUIRED agent address
|
|
* Content string REQUIRED message text
|
|
* ParentID int optional for threaded replies
|
|
*
|
|
* Response: { OK: true, Message: { ... } }
|
|
*/
|
|
|
|
require_once __DIR__ . '/../../helpers.php';
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
jsonResponse(['OK' => false, 'ERROR' => 'method_not_allowed'], 405);
|
|
}
|
|
|
|
$body = readJsonBody();
|
|
|
|
$channelId = (int) ($body['ChannelID'] ?? 0);
|
|
$senderAddress = trim($body['SenderAddress'] ?? '');
|
|
$content = trim($body['Content'] ?? '');
|
|
$parentId = isset($body['ParentID']) ? (int) $body['ParentID'] : null;
|
|
|
|
if ($channelId <= 0) jsonResponse(['OK' => false, 'ERROR' => 'channel_id_required']);
|
|
if ($senderAddress === '') jsonResponse(['OK' => false, 'ERROR' => 'sender_address_required']);
|
|
if ($content === '') jsonResponse(['OK' => false, 'ERROR' => 'content_required']);
|
|
|
|
// Verify channel exists and is not archived
|
|
$channel = queryOne("SELECT ID, IsArchived FROM Hub_Channels WHERE ID = ?", [$channelId]);
|
|
if (!$channel) jsonResponse(['OK' => false, 'ERROR' => 'channel_not_found']);
|
|
if ((bool) $channel['IsArchived']) jsonResponse(['OK' => false, 'ERROR' => 'channel_archived']);
|
|
|
|
// Verify sender is a member of the channel
|
|
$member = queryOne(
|
|
"SELECT ID FROM Hub_ChannelMembers WHERE ChannelID = ? AND AgentAddress = ?",
|
|
[$channelId, $senderAddress]
|
|
);
|
|
if (!$member) jsonResponse(['OK' => false, 'ERROR' => 'not_a_member']);
|
|
|
|
// Verify parent message exists and belongs to same channel (if threaded)
|
|
if ($parentId !== null) {
|
|
$parent = queryOne(
|
|
"SELECT ID FROM Hub_Messages WHERE ID = ? AND ChannelID = ? AND IsDeleted = 0",
|
|
[$parentId, $channelId]
|
|
);
|
|
if (!$parent) jsonResponse(['OK' => false, 'ERROR' => 'parent_message_not_found']);
|
|
}
|
|
|
|
// Insert message
|
|
queryTimed(
|
|
"INSERT INTO Hub_Messages (ChannelID, SenderAddress, Content, ParentID) VALUES (?, ?, ?, ?)",
|
|
[$channelId, $senderAddress, $content, $parentId]
|
|
);
|
|
$messageId = (int) lastInsertId();
|
|
|
|
// Update member's last viewed
|
|
queryTimed(
|
|
"UPDATE Hub_ChannelMembers SET LastViewedAt = NOW() WHERE ChannelID = ? AND AgentAddress = ?",
|
|
[$channelId, $senderAddress]
|
|
);
|
|
|
|
$msg = queryOne("SELECT * FROM Hub_Messages WHERE ID = ?", [$messageId]);
|
|
|
|
jsonResponse([
|
|
'OK' => true,
|
|
'Message' => [
|
|
'ID' => (int) $msg['ID'],
|
|
'ChannelID' => (int) $msg['ChannelID'],
|
|
'SenderAddress' => $msg['SenderAddress'],
|
|
'Content' => $msg['Content'],
|
|
'ParentID' => $msg['ParentID'] ? (int) $msg['ParentID'] : null,
|
|
'IsEdited' => (bool) $msg['IsEdited'],
|
|
'IsDeleted' => false,
|
|
'CreatedAt' => toISO8601($msg['CreatedAt']),
|
|
'UpdatedAt' => toISO8601($msg['UpdatedAt']),
|
|
],
|
|
]);
|