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, ], ]);