payfrit-api/api/hub/channels/create.php
Mike 629c7d2cef Add Hub Channels API — CRUD endpoints for channel management
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>
2026-03-27 01:06:14 +00:00

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