From 71f5de8726ce644d3c628c230bb2bfe7a5931764 Mon Sep 17 00:00:00 2001 From: James Brooks Date: Tue, 14 Jul 2015 14:09:32 +0100 Subject: [PATCH] Introduce the Subscriber API. Closes #787 --- .../Controllers/Api/SubscriberController.php | 77 ++++++++++++ app/Http/Routes/ApiRoutes.php | 4 + app/Models/Subscriber.php | 14 ++- app/Presenters/SubscriberPresenter.php | 33 +++++ app/Presenters/Traits/TimestampsTrait.php | 11 ++ composer.json | 3 +- composer.lock | 114 +++++++++++++++++- database/factories/ModelFactory.php | 10 ++ phpunit.xml.dist | 2 +- tests/Api/SubscriberTest.php | 75 ++++++++++++ 10 files changed, 338 insertions(+), 5 deletions(-) create mode 100644 app/Http/Controllers/Api/SubscriberController.php create mode 100644 app/Presenters/SubscriberPresenter.php create mode 100644 tests/Api/SubscriberTest.php diff --git a/app/Http/Controllers/Api/SubscriberController.php b/app/Http/Controllers/Api/SubscriberController.php new file mode 100644 index 000000000..203fe534f --- /dev/null +++ b/app/Http/Controllers/Api/SubscriberController.php @@ -0,0 +1,77 @@ +paginator($subscribers, $request); + } + + /** + * Create a new subscriber. + * + * @return \CachetHQ\Cachet\Models\Subscriber + */ + public function postSubscribers() + { + $subscriberData = Binput::except('verify'); + + try { + $subscriber = Subscriber::create($subscriberData); + } catch (Exception $e) { + throw new BadRequestHttpException(); + } + + if ($subscriber->isValid()) { + // If we're auto-verifying the subscriber, don't bother with this event. + if (!(Binput::get('verify'))) { + event(new CustomerHasSubscribedEvent($subscriber)); + } + + return $this->item($subscriber); + } + + throw new BadRequestHttpException(); + } + + /** + * Delete a subscriber. + * + * @param \CachetHQ\Cachet\Models\Subscriber $subscriber + * + * @return \Illuminate\Http\JsonResponse + */ + public function deleteSubscriber(Subscriber $subscriber) + { + $subscriber->delete(); + + return $this->noContent(); + } +} diff --git a/app/Http/Routes/ApiRoutes.php b/app/Http/Routes/ApiRoutes.php index 14625e186..d46ed4808 100644 --- a/app/Http/Routes/ApiRoutes.php +++ b/app/Http/Routes/ApiRoutes.php @@ -45,10 +45,13 @@ class ApiRoutes // Api protected $router->group(['middleware' => 'auth.api'], function ($router) { + $router->get('subscribers', 'SubscriberController@getSubscribers'); + $router->post('components', 'ComponentController@postComponents'); $router->post('incidents', 'IncidentController@postIncidents'); $router->post('metrics', 'MetricController@postMetrics'); $router->post('metrics/{metric}/points', 'MetricPointController@postMetricPoints'); + $router->post('subscribers', 'SubscriberController@postSubscribers'); $router->put('components/{component}', 'ComponentController@putComponent'); $router->put('incidents/{incident}', 'IncidentController@putIncident'); @@ -59,6 +62,7 @@ class ApiRoutes $router->delete('incidents/{incident}', 'IncidentController@deleteIncident'); $router->delete('metrics/{metric}', 'MetricController@deleteMetric'); $router->delete('metrics/{metric}/points/{metric_point}', 'MetricPointController@deleteMetricPoint'); + $router->delete('subscribers/{subscriber}', 'SubscriberController@deleteSubscriber'); }); }); } diff --git a/app/Models/Subscriber.php b/app/Models/Subscriber.php index b17721e93..91472f66b 100644 --- a/app/Models/Subscriber.php +++ b/app/Models/Subscriber.php @@ -11,11 +11,13 @@ namespace CachetHQ\Cachet\Models; +use CachetHQ\Cachet\Presenters\SubscriberPresenter; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; +use McCool\LaravelAutoPresenter\HasPresenter; use Watson\Validating\ValidatingTrait; -class Subscriber extends Model +class Subscriber extends Model implements HasPresenter { use SoftDeletes, ValidatingTrait; @@ -75,4 +77,14 @@ class Subscriber extends Model { return str_random(42); } + + /** + * Get the presenter class. + * + * @return string + */ + public function getPresenterClass() + { + return SubscriberPresenter::class; + } } diff --git a/app/Presenters/SubscriberPresenter.php b/app/Presenters/SubscriberPresenter.php new file mode 100644 index 000000000..cc9e4a364 --- /dev/null +++ b/app/Presenters/SubscriberPresenter.php @@ -0,0 +1,33 @@ +wrappedObject->toArray(), [ + 'created_at' => $this->created_at(), + 'updated_at' => $this->updated_at(), + 'verified_at' => $this->verified_at(), + ]); + } +} diff --git a/app/Presenters/Traits/TimestampsTrait.php b/app/Presenters/Traits/TimestampsTrait.php index 1ec048d41..673d062aa 100644 --- a/app/Presenters/Traits/TimestampsTrait.php +++ b/app/Presenters/Traits/TimestampsTrait.php @@ -47,4 +47,15 @@ trait TimestampsTrait return (new Date($this->wrappedObject->deleted_at)) ->setTimezone($this->setting->get('app_timezone'))->toDateTimeString(); } + + /** + * Present formatted date time. + * + * @return string + */ + public function verified_at() + { + return (new Date($this->wrappedObject->verified_at)) + ->setTimezone($this->setting->get('app_timezone'))->toDateTimeString(); + } } diff --git a/composer.json b/composer.json index 70b82a92e..9339a32bd 100644 --- a/composer.json +++ b/composer.json @@ -39,7 +39,8 @@ "require-dev": { "fzaninotto/faker": "^1.5", "phpunit/phpunit": "^4.7.6", - "laravel/homestead": "^2.1" + "laravel/homestead": "^2.1", + "mockery/mockery": "~0.9.4" }, "autoload": { "classmap": [ diff --git a/composer.lock b/composer.lock index 6590554d1..390c7eaa4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "94c9c02a89e02e83d24580aa8ac901bf", + "hash": "8535c80a786ed3bcac0a4d7d77421697", "packages": [ { "name": "alt-three/segment", @@ -1546,7 +1546,7 @@ "name": "Jeremy Lindblom", "email": "jeremeamia@gmail.com", "homepage": "https://github.com/jeremeamia", - "role": "developer" + "role": "Developer" } ], "description": "Serialize Closure objects, including their context and binding", @@ -3376,6 +3376,51 @@ ], "time": "2015-05-29 06:29:14" }, + { + "name": "hamcrest/hamcrest-php", + "version": "v1.2.2", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "b37020aa976fa52d3de9aa904aa2522dc518f79c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/b37020aa976fa52d3de9aa904aa2522dc518f79c", + "reference": "b37020aa976fa52d3de9aa904aa2522dc518f79c", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "1.3.3", + "satooshi/php-coveralls": "dev-master" + }, + "type": "library", + "autoload": { + "classmap": [ + "hamcrest" + ], + "files": [ + "hamcrest/Hamcrest.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD" + ], + "description": "This is the PHP port of Hamcrest Matchers", + "keywords": [ + "test" + ], + "time": "2015-05-11 14:41:42" + }, { "name": "laravel/homestead", "version": "v2.1.5", @@ -3417,6 +3462,71 @@ "description": "A virtual machine for web artisans.", "time": "2015-07-23 14:53:43" }, + { + "name": "mockery/mockery", + "version": "0.9.4", + "source": { + "type": "git", + "url": "https://github.com/padraic/mockery.git", + "reference": "70bba85e4aabc9449626651f48b9018ede04f86b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/padraic/mockery/zipball/70bba85e4aabc9449626651f48b9018ede04f86b", + "reference": "70bba85e4aabc9449626651f48b9018ede04f86b", + "shasum": "" + }, + "require": { + "hamcrest/hamcrest-php": "~1.1", + "lib-pcre": ">=7.0", + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.9.x-dev" + } + }, + "autoload": { + "psr-0": { + "Mockery": "library/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "http://blog.astrumfutura.com" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "http://davedevelopment.co.uk" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework for use in unit testing with PHPUnit, PHPSpec or any other testing framework. Its core goal is to offer a test double framework with a succinct API capable of clearly defining all possible object operations and interactions using a human readable Domain Specific Language (DSL). Designed as a drop in alternative to PHPUnit's phpunit-mock-objects library, Mockery is easy to integrate with PHPUnit and can operate alongside phpunit-mock-objects without the World ending.", + "homepage": "http://github.com/padraic/mockery", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "time": "2015-04-02 19:54:00" + }, { "name": "phpdocumentor/reflection-docblock", "version": "2.0.4", diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index f2b166fb1..e963a0f9e 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -9,6 +9,8 @@ * file that was distributed with this source code. */ +use Carbon\Carbon; + $factory->define('CachetHQ\Cachet\Models\User', function ($faker) { return [ 'username' => $faker->userName, @@ -57,3 +59,11 @@ $factory->define('CachetHQ\Cachet\Models\MetricPoint', function ($faker) { 'value' => rand(1, 100), ]; }); + +$factory->define('CachetHQ\Cachet\Models\Subscriber', function ($faker) { + return [ + 'email' => $faker->email, + 'verify_code' => 'Mqr80r2wJtxHCW5Ep4azkldFfIwHhw98M9HF04dn0z', + 'verified_at' => Carbon::now(), + ]; +}); diff --git a/phpunit.xml.dist b/phpunit.xml.dist index edbcbade9..80d59bba6 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -26,7 +26,7 @@ - + diff --git a/tests/Api/SubscriberTest.php b/tests/Api/SubscriberTest.php new file mode 100644 index 000000000..7d85ceaf4 --- /dev/null +++ b/tests/Api/SubscriberTest.php @@ -0,0 +1,75 @@ +get('/api/v1/subscribers'); + $this->assertResponseStatus(401); + $this->seeHeader('Content-Type', 'application/json'); + } + + public function testGetSubscribers() + { + $this->beUser(); + + $subscriber = factory('CachetHQ\Cachet\Models\Subscriber')->create(); + + $this->get('/api/v1/subscribers'); + $this->seeHeader('Content-Type', 'application/json'); + $this->assertResponseOk(); + } + + public function testCreateSubscriber() + { + $this->beUser(); + + $this->expectsEvents('CachetHQ\Cachet\Events\CustomerHasSubscribedEvent'); + + $this->post('/api/v1/subscribers', [ + 'email' => 'james@cachethq.io', + ]); + $this->assertResponseOk(); + $this->seeHeader('Content-Type', 'application/json'); + $this->seeJson(['email' => 'james@cachethq.io']); + } + + public function testCreateSubscriberAutoVerified() + { + $this->beUser(); + + $this->post('/api/v1/subscribers', [ + 'email' => 'james@cachethq.io', + 'verify' => true, + ]); + $this->assertResponseOk(); + $this->seeHeader('Content-Type', 'application/json'); + $this->seeJson(['email' => 'james@cachethq.io']); + } + + public function testDeleteSubscriber() + { + $this->beUser(); + + $subscriber = factory('CachetHQ\Cachet\Models\Subscriber')->create(); + $this->delete("/api/v1/subscribers/{$subscriber->id}"); + $this->assertResponseStatus(204); + } +}