New endpoints under /api/hub/channels/: - create.php: Create channel with type (public/private/direct), auto-add creator as owner - list.php: List channels with filters (type, agent membership, archived, pagination) - get.php: Get channel by ID or Name, includes member list - update.php: Update display name, purpose, archive status (admin/owner only) - delete.php: Hard-delete channel (owner only), FK cascade removes members - members.php: List channel members with agent info - join.php: Join public channels (private requires invite) - leave.php: Leave channel (owners blocked from leaving) Database: Hub_Channels + Hub_ChannelMembers tables with FK cascade. Task #59 (T51-Sub1) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
91 lines
2.9 KiB
PHP
91 lines
2.9 KiB
PHP
<?php
|
|
/**
|
|
* POST /api/hub/channels/create.php
|
|
*
|
|
* Create a new channel.
|
|
*
|
|
* Body:
|
|
* Name string REQUIRED unique slug (lowercase, hyphens, no spaces)
|
|
* DisplayName string optional human-readable name (defaults to Name)
|
|
* Purpose string optional channel description
|
|
* ChannelType string optional public|private|direct (default: public)
|
|
* CreatedBy string REQUIRED agent address (e.g. sprinter.payfrit.mike)
|
|
*
|
|
* Response: { OK: true, Channel: { ... } }
|
|
*/
|
|
|
|
require_once __DIR__ . '/../../helpers.php';
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
jsonResponse(['OK' => false, 'ERROR' => 'method_not_allowed'], 405);
|
|
}
|
|
|
|
$body = readJsonBody();
|
|
|
|
// --- Validate required fields ---
|
|
$name = trim($body['Name'] ?? '');
|
|
$createdBy = trim($body['CreatedBy'] ?? '');
|
|
|
|
if ($name === '') {
|
|
jsonResponse(['OK' => false, 'ERROR' => 'name_required']);
|
|
}
|
|
if ($createdBy === '') {
|
|
jsonResponse(['OK' => false, 'ERROR' => 'created_by_required']);
|
|
}
|
|
|
|
// Sanitize name: lowercase, alphanumeric + hyphens only
|
|
$name = strtolower(preg_replace('/[^a-zA-Z0-9\-]/', '', $name));
|
|
if ($name === '' || strlen($name) > 100) {
|
|
jsonResponse(['OK' => false, 'ERROR' => 'invalid_name']);
|
|
}
|
|
|
|
$displayName = trim($body['DisplayName'] ?? '') ?: $name;
|
|
$purpose = trim($body['Purpose'] ?? '');
|
|
$channelType = strtolower(trim($body['ChannelType'] ?? 'public'));
|
|
|
|
if (!in_array($channelType, ['public', 'private', 'direct'], true)) {
|
|
jsonResponse(['OK' => false, 'ERROR' => 'invalid_channel_type']);
|
|
}
|
|
|
|
// Enforce length limits
|
|
if (strlen($displayName) > 200) $displayName = substr($displayName, 0, 200);
|
|
if (strlen($purpose) > 500) $purpose = substr($purpose, 0, 500);
|
|
|
|
// --- Check uniqueness ---
|
|
$existing = queryOne("SELECT ID FROM Hub_Channels WHERE Name = ?", [$name]);
|
|
if ($existing) {
|
|
jsonResponse(['OK' => false, 'ERROR' => 'name_already_exists']);
|
|
}
|
|
|
|
// --- Insert channel ---
|
|
queryTimed(
|
|
"INSERT INTO Hub_Channels (Name, DisplayName, Purpose, ChannelType, CreatedBy)
|
|
VALUES (?, ?, ?, ?, ?)",
|
|
[$name, $displayName, $purpose, $channelType, $createdBy]
|
|
);
|
|
$channelId = (int) lastInsertId();
|
|
|
|
// --- Auto-add creator as owner ---
|
|
queryTimed(
|
|
"INSERT INTO Hub_ChannelMembers (ChannelID, AgentAddress, Role) VALUES (?, ?, 'owner')",
|
|
[$channelId, $createdBy]
|
|
);
|
|
|
|
// --- Fetch and return ---
|
|
$channel = queryOne("SELECT * FROM Hub_Channels WHERE ID = ?", [$channelId]);
|
|
|
|
jsonResponse([
|
|
'OK' => true,
|
|
'Channel' => [
|
|
'ID' => (int) $channel['ID'],
|
|
'Name' => $channel['Name'],
|
|
'DisplayName' => $channel['DisplayName'],
|
|
'Purpose' => $channel['Purpose'],
|
|
'ChannelType' => $channel['ChannelType'],
|
|
'CreatedBy' => $channel['CreatedBy'],
|
|
'IsArchived' => (bool) $channel['IsArchived'],
|
|
'CreatedAt' => toISO8601($channel['CreatedAt']),
|
|
'UpdatedAt' => toISO8601($channel['UpdatedAt']),
|
|
'MemberCount' => 1,
|
|
],
|
|
]);
|