diff --git a/README.md b/README.md index 88b8c40..2ab0617 100644 --- a/README.md +++ b/README.md @@ -41,4 +41,65 @@ try { } catch (Exception $e) { echo "Error: " . $e->getMessage(); } +``` + +### Managing Channels + +All channel methods return an array with the decoded JSON response from the API. On failure, a `RuntimeException` is thrown. + +#### List channels + +```php +$channels = $client->listChannels(); +// $channels['data'] — array of channel objects +``` + +#### Create a channel + +```php +$channel = $client->createChannel( + name: 'My Channel', + description: 'Optional description', // optional +); +// $channel['data'] — created channel object +``` + +#### Get a channel + +```php +$channel = $client->getChannel('PUSHINATOR_CHANNEL_ID'); +// $channel['data'] — channel object +``` + +#### Update a channel + +```php +$channel = $client->updateChannel( + channelId: 'PUSHINATOR_CHANNEL_ID', + name: 'Updated Name', + description: 'Updated description', // optional +); +// $channel['data'] — updated channel object +``` + +#### Delete a channel + +```php +$channel = $client->deleteChannel('PUSHINATOR_CHANNEL_ID'); +// $channel['data'] — deleted channel object +``` + +#### Channel object structure + +```json +{ + "data": { + "id": "channel-id", + "name": "My Channel", + "description": "Optional description", + "acknowledgment_enabled": false, + "created_at": "2024-01-01T00:00:00.000000Z", + "updated_at": "2024-01-01T00:00:00.000000Z" + } +} ``` \ No newline at end of file diff --git a/src/PushinatorClient.php b/src/PushinatorClient.php index f228970..6adc5cc 100644 --- a/src/PushinatorClient.php +++ b/src/PushinatorClient.php @@ -64,4 +64,114 @@ public function sendNotification(string $channelId, string $notification): void ); } } + + /** + * List all channels. + * + * @return array + */ + public function listChannels(): array + { + return $this->request('GET', '/api/v2/channels'); + } + + /** + * Create a new channel. + * + * @param string $name + * @param string|null $description + * @param bool|null $acknowledgmentEnabled + * @return array + */ + public function createChannel(string $name, ?string $description = null, ?bool $acknowledgmentEnabled = null): array + { + $body = ['name' => $name]; + + if ($description !== null) { + $body['description'] = $description; + } + + return $this->request('POST', '/api/v2/channels', $body); + } + + /** + * Get a channel by ID. + * + * @param string $channelId + * @return array + */ + public function getChannel(string $channelId): array + { + return $this->request('GET', "/api/v2/channels/{$channelId}"); + } + + /** + * Update a channel by ID. + * + * @param string $channelId + * @param string $name + * @param string|null $description + * @param bool|null $acknowledgmentEnabled + * @return array + */ + public function updateChannel(string $channelId, string $name, ?string $description = null, ?bool $acknowledgmentEnabled = null): array + { + $body = ['name' => $name]; + + if ($description !== null) { + $body['description'] = $description; + } + + return $this->request('PUT', "/api/v2/channels/{$channelId}", $body); + } + + /** + * Delete a channel by ID. + * + * @param string $channelId + * @return array + */ + public function deleteChannel(string $channelId): array + { + return $this->request('DELETE', "/api/v2/channels/{$channelId}"); + } + + /** + * @param string $method + * @param string $path + * @param array $body + * @return array + */ + private function request(string $method, string $path, array $body = []): array + { + try { + $options = [ + 'headers' => [ + 'Authorization' => "Bearer {$this->apiToken}", + 'Content-Type' => 'application/json', + 'User-Agent' => self::$userAgent, + ], + ]; + + if (!empty($body)) { + $options['json'] = $body; + } + + $response = $this->client->request($method, self::$baseUrl . $path, $options); + + if ($response->getStatusCode() !== 200) { + throw new \RuntimeException( + "Request failed. Status: {$response->getStatusCode()}, Body: {$response->getBody()}" + ); + } + + return json_decode($response->getBody()->getContents(), true); + } catch (GuzzleException $e) { + throw new \RuntimeException( + "Request failed: {$e->getMessage()}", + $e->getCode(), + $e + ); + } + } } diff --git a/tests/ChannelsTest.php b/tests/ChannelsTest.php new file mode 100644 index 0000000..9841abb --- /dev/null +++ b/tests/ChannelsTest.php @@ -0,0 +1,251 @@ +push($history); + + return new PushinatorClient('test-token', new Client(['handler' => $handlerStack])); + } + + private function channelResponse(array $overrides = []): array + { + return array_merge([ + 'id' => 'channel-123', + 'name' => 'Test Channel', + 'description' => 'A test channel', + 'acknowledgment_enabled' => false, + 'created_at' => '2024-01-01T00:00:00.000000Z', + 'updated_at' => '2024-01-01T00:00:00.000000Z', + ], $overrides); + } + + // ------------------------------------------------------------------------- + // listChannels + // ------------------------------------------------------------------------- + + #[Test] + public function testListChannelsRequest() + { + $container = []; + $responseData = ['data' => [$this->channelResponse()]]; + $pushinator = $this->makeClient( + [new Response(200, [], json_encode($responseData))], + $container + ); + + $result = $pushinator->listChannels(); + + $request = $container[0]['request']; + $this->assertEquals('GET', $request->getMethod()); + $this->assertEquals('https://api.pushinator.com/api/v2/channels', $request->getUri()); + $this->assertEquals('Bearer test-token', $request->getHeaderLine('Authorization')); + $this->assertEquals('application/json', $request->getHeaderLine('Content-Type')); + $this->assertEquals('pushinator-php/1.0', $request->getHeaderLine('User-Agent')); + $this->assertEquals($responseData, $result); + } + + #[Test] + public function testListChannelsFailure() + { + $pushinator = $this->makeClient([new Response(500)]); + + $this->expectException(\RuntimeException::class); + $pushinator->listChannels(); + } + + // ------------------------------------------------------------------------- + // createChannel + // ------------------------------------------------------------------------- + + #[Test] + public function testCreateChannelRequest() + { + $container = []; + $responseData = ['data' => $this->channelResponse()]; + $pushinator = $this->makeClient( + [new Response(200, [], json_encode($responseData))], + $container + ); + + $result = $pushinator->createChannel('Test Channel', 'A test channel', true); + + $request = $container[0]['request']; + $this->assertEquals('POST', $request->getMethod()); + $this->assertEquals('https://api.pushinator.com/api/v2/channels', $request->getUri()); + $this->assertEquals('Bearer test-token', $request->getHeaderLine('Authorization')); + $this->assertEquals('application/json', $request->getHeaderLine('Content-Type')); + $this->assertEquals('pushinator-php/1.0', $request->getHeaderLine('User-Agent')); + $this->assertEquals( + '{"name":"Test Channel","description":"A test channel"}', + $request->getBody()->getContents() + ); + $this->assertEquals($responseData, $result); + } + + #[Test] + public function testCreateChannelWithNameOnly() + { + $container = []; + $pushinator = $this->makeClient( + [new Response(200, [], json_encode(['data' => $this->channelResponse()]))], + $container + ); + + $pushinator->createChannel('Test Channel'); + + $this->assertEquals( + '{"name":"Test Channel"}', + $container[0]['request']->getBody()->getContents() + ); + } + + #[Test] + public function testCreateChannelFailure() + { + $pushinator = $this->makeClient([new Response(500)]); + + $this->expectException(\RuntimeException::class); + $pushinator->createChannel('Test Channel'); + } + + // ------------------------------------------------------------------------- + // getChannel + // ------------------------------------------------------------------------- + + #[Test] + public function testGetChannelRequest() + { + $container = []; + $responseData = ['data' => $this->channelResponse()]; + $pushinator = $this->makeClient( + [new Response(200, [], json_encode($responseData))], + $container + ); + + $result = $pushinator->getChannel('channel-123'); + + $request = $container[0]['request']; + $this->assertEquals('GET', $request->getMethod()); + $this->assertEquals('https://api.pushinator.com/api/v2/channels/channel-123', $request->getUri()); + $this->assertEquals('Bearer test-token', $request->getHeaderLine('Authorization')); + $this->assertEquals('application/json', $request->getHeaderLine('Content-Type')); + $this->assertEquals('pushinator-php/1.0', $request->getHeaderLine('User-Agent')); + $this->assertEquals($responseData, $result); + } + + #[Test] + public function testGetChannelNotFound() + { + $pushinator = $this->makeClient([new Response(404)]); + + $this->expectException(\RuntimeException::class); + $pushinator->getChannel('channel-123'); + } + + // ------------------------------------------------------------------------- + // updateChannel + // ------------------------------------------------------------------------- + + #[Test] + public function testUpdateChannelRequest() + { + $container = []; + $responseData = ['data' => $this->channelResponse(['name' => 'Updated Channel'])]; + $pushinator = $this->makeClient( + [new Response(200, [], json_encode($responseData))], + $container + ); + + $result = $pushinator->updateChannel('channel-123', 'Updated Channel', 'New description', false); + + $request = $container[0]['request']; + $this->assertEquals('PUT', $request->getMethod()); + $this->assertEquals('https://api.pushinator.com/api/v2/channels/channel-123', $request->getUri()); + $this->assertEquals('Bearer test-token', $request->getHeaderLine('Authorization')); + $this->assertEquals('application/json', $request->getHeaderLine('Content-Type')); + $this->assertEquals('pushinator-php/1.0', $request->getHeaderLine('User-Agent')); + $this->assertEquals( + '{"name":"Updated Channel","description":"New description"}', + $request->getBody()->getContents() + ); + $this->assertEquals($responseData, $result); + } + + #[Test] + public function testUpdateChannelWithNameOnly() + { + $container = []; + $pushinator = $this->makeClient( + [new Response(200, [], json_encode(['data' => $this->channelResponse()]))], + $container + ); + + $pushinator->updateChannel('channel-123', 'Updated Channel'); + + $this->assertEquals( + '{"name":"Updated Channel"}', + $container[0]['request']->getBody()->getContents() + ); + } + + #[Test] + public function testUpdateChannelNotFound() + { + $pushinator = $this->makeClient([new Response(404)]); + + $this->expectException(\RuntimeException::class); + $pushinator->updateChannel('channel-123', 'Updated Channel'); + } + + // ------------------------------------------------------------------------- + // deleteChannel + // ------------------------------------------------------------------------- + + #[Test] + public function testDeleteChannelRequest() + { + $container = []; + $responseData = ['data' => $this->channelResponse()]; + $pushinator = $this->makeClient( + [new Response(200, [], json_encode($responseData))], + $container + ); + + $result = $pushinator->deleteChannel('channel-123'); + + $request = $container[0]['request']; + $this->assertEquals('DELETE', $request->getMethod()); + $this->assertEquals('https://api.pushinator.com/api/v2/channels/channel-123', $request->getUri()); + $this->assertEquals('Bearer test-token', $request->getHeaderLine('Authorization')); + $this->assertEquals('application/json', $request->getHeaderLine('Content-Type')); + $this->assertEquals('pushinator-php/1.0', $request->getHeaderLine('User-Agent')); + $this->assertEquals($responseData, $result); + } + + #[Test] + public function testDeleteChannelNotFound() + { + $pushinator = $this->makeClient([new Response(404)]); + + $this->expectException(\RuntimeException::class); + $pushinator->deleteChannel('channel-123'); + } +}