diff --git a/composer.json b/composer.json index b221f9d..b24cf97 100644 --- a/composer.json +++ b/composer.json @@ -19,9 +19,7 @@ "php": "^8.4", "ext-curl": "*", "ext-json": "*", - "guzzlehttp/guzzle": "7.x", "illuminate/contracts": "^10.0||^11.0||^12.0", - "league/oauth2-client": "^2", "spatie/laravel-data": "^4.15", "spatie/laravel-package-tools": "^1.16" }, diff --git a/src/Concerns/InteractsWithHttpRequests.php b/src/Concerns/InteractsWithHttpRequests.php new file mode 100644 index 0000000..8469568 --- /dev/null +++ b/src/Concerns/InteractsWithHttpRequests.php @@ -0,0 +1,82 @@ + $apiKey, + ]); + + $url = rtrim($apiUrl, '/').'/'.ltrim($request, '/'); + + $client = Http::withHeaders(array_merge([ + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + ], $headers ?? [])); + + return $async + ? $client->async()->{$type}($url, $payload) + : $client->{$type}($url, $payload); + + } catch (InvalidApiKeyException $th) { + throw new InvalidApiKeyException('Error: '.$th->getMessage()); + } catch (InvalidApiUrlException $th) { + throw new InvalidApiUrlException('Error: '.$th->getMessage()); + } catch (Exception $th) { + throw new Exception('Error: '.$th->getMessage()); + } + } +} diff --git a/src/DTOs/CompaignDTO.php b/src/DTOs/Campaigns/CampaignDTO.php similarity index 75% rename from src/DTOs/CompaignDTO.php rename to src/DTOs/Campaigns/CampaignDTO.php index 9ec47e1..a2cf8cd 100644 --- a/src/DTOs/CompaignDTO.php +++ b/src/DTOs/Campaigns/CampaignDTO.php @@ -1,11 +1,11 @@ ['required', 'string'], 'plain_text' => ['string', 'nullable'], 'html_text' => ['required', 'string'], - 'list_ids' => ['required', 'string'], - 'segment_ids' => ['required', 'string'], + 'list_ids' => ['required_if:send_campaign,1', 'string'], + 'segment_ids' => ['required_if:send_campaign,1', 'string'], 'exclude_list_ids' => ['string', 'nullable'], 'exclude_segment_ids' => ['string', 'nullable'], - 'brand_id' => ['required', 'string'], + 'brand_id' => ['required_if:send_campaign,0', 'string'], 'query_string' => ['string', 'nullable'], 'track_opens' => ['integer', 'nullable', 'in:0,1,2'], 'track_clicks' => ['integer', 'nullable', 'in:0,1,2'], - 'send_compaign' => ['integer', 'nullable', 'in:0,1'], + 'send_campaign' => ['integer', 'nullable', 'in:0,1'], 'schedule_date_time' => ['date', 'nullable'], 'schedule_timezone' => ['date', 'nullable'], ]; diff --git a/src/DTOs/Lists/ListsDTO.php b/src/DTOs/Lists/ListsDTO.php new file mode 100644 index 0000000..0c99256 --- /dev/null +++ b/src/DTOs/Lists/ListsDTO.php @@ -0,0 +1,21 @@ + ['in:yes,no'], + ]; + } +} diff --git a/src/DTOs/Subscribers/DeleteSubscriberDTO.php b/src/DTOs/Subscribers/DeleteSubscriberDTO.php new file mode 100644 index 0000000..a679be0 --- /dev/null +++ b/src/DTOs/Subscribers/DeleteSubscriberDTO.php @@ -0,0 +1,23 @@ + ['email'], + ]; + } +} diff --git a/src/DTOs/SubscribersDTO.php b/src/DTOs/Subscribers/SubscribeDTO.php similarity index 50% rename from src/DTOs/SubscribersDTO.php rename to src/DTOs/Subscribers/SubscribeDTO.php index 0ba3734..ed32fdf 100644 --- a/src/DTOs/SubscribersDTO.php +++ b/src/DTOs/Subscribers/SubscribeDTO.php @@ -1,11 +1,13 @@ ['string', 'nullable'], - 'email' => ['required', 'string', 'email'], - 'list' => ['required', 'string'], - 'country' => ['string', 'nullable'], - 'ipaddress' => ['string', 'nullable', 'ip'], - 'referrer' => ['string', 'nullable'], - 'gdpr' => ['boolean', 'nullable'], - 'silent' => ['boolean', 'nullable'], - 'boolean' => ['boolean', 'nullable'], + 'email' => ['email'], + 'ipaddress' => ['ip'], ]; } } diff --git a/src/DTOs/Subscribers/SubscriberStatusDTO.php b/src/DTOs/Subscribers/SubscriberStatusDTO.php new file mode 100644 index 0000000..4c23a2b --- /dev/null +++ b/src/DTOs/Subscribers/SubscriberStatusDTO.php @@ -0,0 +1,23 @@ + ['email'], + ]; + } +} diff --git a/src/DTOs/Subscribers/UnsubscribeDTO.php b/src/DTOs/Subscribers/UnsubscribeDTO.php new file mode 100644 index 0000000..cb69897 --- /dev/null +++ b/src/DTOs/Subscribers/UnsubscribeDTO.php @@ -0,0 +1,24 @@ + ['email'], + ]; + } +} diff --git a/src/Exceptions/CompaingException.php b/src/Exceptions/CompaingException.php deleted file mode 100644 index f2aa4df..0000000 --- a/src/Exceptions/CompaingException.php +++ /dev/null @@ -1,8 +0,0 @@ -apiKey = config('laravel-sendy.api_key'); - $this->apiUrl = config('laravel-sendy.api_url'); - } - - public function subscribers(): Subscribers - { - return new Subscribers; - } - - public function lists(): Lists - { - return new Lists; - } - - public function brands(): Brands + public function subscribers(): Resources\Subscribers { - return new Brands; + return new Resources\Subscribers; } - public function campaigns(): Campaigns + public function lists(): Resources\Lists { - return new Campaigns; + return new Resources\Lists; } - public function __call(string $function, array $args) + public function brands(): Resources\Brands { - $options = ['get', 'post', 'put', 'delete', 'patch']; - $path = (isset($args[0])) ? $args[0] : null; - $data = (isset($args[1])) ? $args[1] : []; - $headers = (isset($args[2])) ? $args[2] : []; - - if (! in_array($function, $options)) { - throw new Exception("Method {$function} not found."); - } - - return self::guzzle( - type: $function, - request: $path, - data: $data, - headers: $headers - ); - } - - /** - * @throws \Exception - */ - protected function guzzle(string $type, string $request, array $data = [], array $headers = []): mixed - { - try { - $client = new Client; - - $mainHeaders = [ - 'Content-Type' => 'application/json', - 'Accept' => 'application/json', - ]; - - $headers = is_array($headers) && count($headers) > 0 - ? array_merge($mainHeaders, $headers) - : $mainHeaders; - - $response = $client->{$type}($this->apiUrl.$request, [ - 'headers' => $headers, - 'body' => json_encode(array_merge($data, [ - 'api_key' => $this->apiKey, - ])), - ]); - - $responseObject = $response->getBody()->getContents(); - - return $this->isJson($responseObject) - ? json_decode($responseObject, true) - : $responseObject; - - } catch (ClientException $th) { - throw new Exception('Error: '.$th->getMessage()); - } catch (Exception $th) { - throw new Exception('Error: '.$th->getMessage()); - } + return new Resources\Brands; } - protected function isJson(string $string): bool + public function campaigns(): Resources\Campaigns { - return is_array(json_decode($string)) && - (json_last_error() === JSON_ERROR_NONE); + return new Resources\Campaigns; } } diff --git a/src/Resources/Brands.php b/src/Resources/Brands.php index 03da1af..ba94cf1 100644 --- a/src/Resources/Brands.php +++ b/src/Resources/Brands.php @@ -1,6 +1,6 @@ toArray(); + $data = CampaignDTO::validate($data); return LaravelSendy::post('/api/campaigns/create.php', $data); } diff --git a/src/Resources/Lists.php b/src/Resources/Lists.php index 82a5f78..dde78e3 100644 --- a/src/Resources/Lists.php +++ b/src/Resources/Lists.php @@ -1,7 +1,8 @@ $brandId, - 'include_hidden' => $includeHidden, - ]); + $data = ListsDTO::validate($data); - return LaravelSendy::get('/api/lists/get-lists.php', $params); + return LaravelSendy::post('/api/lists/get-lists.php', $data, $async); } } diff --git a/src/Resources/Subscribers.php b/src/Resources/Subscribers.php index 10879de..61500b1 100644 --- a/src/Resources/Subscribers.php +++ b/src/Resources/Subscribers.php @@ -1,56 +1,49 @@ toArray(); + $data = SubscribeDTO::validate($data); - return LaravelSendy::post('subscribe', $data); + return LaravelSendy::post('subscribe', $data, $async); } - public function unsubscribe(int $listId, string $email, bool $plainTextResponse) + public function unsubscribe(array $data, bool $async = false) { - $data = http_build_query([ - 'list' => $listId, - 'email' => $email, - 'boolean' => $plainTextResponse, - ]); + $data = UnsubscribeDTO::validate($data); - return LaravelSendy::post('/api/subscribers/unsubscribe.php', $data); + return LaravelSendy::post('api/subscribers/unsubscribe.php', $data, $async); } - public function delete(int $listId, string $email) + public function delete(array $data, bool $async = false) { - $data = http_build_query([ - 'list_id' => $listId, - 'email' => $email, - ]); + $data = DeleteSubscriberDTO::validate($data); - return LaravelSendy::post('/api/subscribers/delete.php', $data); + return LaravelSendy::post('api/subscribers/delete.php', $data, $async); } - public function status(int $listId, string $email) + public function status(array $data, bool $async = false) { - $data = http_build_query([ - 'list_id' => $listId, - 'email' => $email, - ]); + $data = SubscriberStatusDTO::validate($data); - return LaravelSendy::post('/api/subscribers/subscription-status.php', $data); + return LaravelSendy::post('api/subscribers/subscription-status.php', $data, $async); } - public function count(int $listId) + public function count(int $listId, bool $async = false) { - $data = http_build_query([ + $data = [ 'list_id' => $listId, - ]); + ]; - return LaravelSendy::post('/api/subscribers/active-subscriber-count.php', $data); + return LaravelSendy::post('api/subscribers/subscriber-count.php', $data, $async); } } diff --git a/tests/ExampleTest.php b/tests/ExampleTest.php deleted file mode 100644 index 5d36321..0000000 --- a/tests/ExampleTest.php +++ /dev/null @@ -1,5 +0,0 @@ -toBeTrue(); -}); diff --git a/tests/Resources/BrandsTest.php b/tests/Resources/BrandsTest.php new file mode 100644 index 0000000..3e98357 --- /dev/null +++ b/tests/Resources/BrandsTest.php @@ -0,0 +1,26 @@ + 'test_api_key', + 'laravel-sendy.api_url' => 'https://sendy.test/', + ]); +}); + +it('can get subscriber brands', function () { + Http::fake([ + 'https://sendy.test/api/brands/get-brands.php' => Http::response([123 => 'Brand Name'], 200), + ]); + + $response = LaravelSendy::brands()->get(); + + expect($response->json())->toBe([123 => 'Brand Name']); + + Http::assertSent(function ($request) { + return $request->url() === 'https://sendy.test/api/brands/get-brands.php' && + $request['api_key'] === 'test_api_key'; + }); +}); diff --git a/tests/Resources/CompaignsTest.php b/tests/Resources/CompaignsTest.php new file mode 100644 index 0000000..cd83292 --- /dev/null +++ b/tests/Resources/CompaignsTest.php @@ -0,0 +1,47 @@ + 'test_api_key', + 'laravel-sendy.api_url' => 'https://sendy.test/', + ]); +}); + +it('can create and send a campaigns', function () { + Http::fake([ + 'https://sendy.test/api/campaigns/create.php' => Http::response(['status' => 'Campaign created and now sending'], 200), + ]); + + $response = LaravelSendy::campaigns()->create([ + 'subject' => 'Test Subject', + 'from_name' => 'John Doe', + 'from_email' => 'john@example.com', + 'reply_to' => 'alex@example.com', + 'title' => 'Test Title', + 'plain_text' => 'This is a plain text version of the email.', + 'html_text' => '