├── .github └── dependabot.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── config └── laravel-subscribers.php ├── database └── migrations │ └── 2018_01_01_000000_create_subscribers_table.php ├── package.json ├── phpunit.xml ├── resources └── js │ └── components │ └── SubscriberForm.vue ├── routes ├── api.php └── web.php ├── scrutinizer.yml ├── src ├── Events │ ├── SubscriberCreated.php │ ├── SubscriberDeleted.php │ └── SubscriberVerified.php ├── Exceptions │ ├── Handler.php │ └── SubscriberVerificationException.php ├── Http │ ├── Controllers │ │ ├── Api │ │ │ └── SubscriberController.php │ │ └── SubscriberController.php │ └── Requests │ │ ├── DeleteSubscriberRequest.php │ │ ├── StoreSubscriberRequest.php │ │ └── VerifySubscriberRequest.php ├── Notifications │ └── SubscriberVerifyEmail.php ├── Nova │ ├── Metrics │ │ └── NewSubscribers.php │ └── Resources │ │ └── Subscriber.php ├── Subscriber.php ├── SubscribersServiceProvider.php └── Traits │ └── CanSubscribe.php ├── tests ├── SubscriberTest.php └── TestCase.php └── webpack.mix.js /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | registries: 3 | git-github-com: 4 | type: git 5 | url: https://github.com 6 | username: x-access-token 7 | password: "${{secrets.GIT_GITHUB_COM_PASSWORD}}" 8 | 9 | updates: 10 | - package-ecosystem: composer 11 | directory: "/" 12 | schedule: 13 | interval: daily 14 | time: "04:00" 15 | open-pull-requests-limit: 10 16 | versioning-strategy: increase 17 | registries: 18 | - git-github-com 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /vendor 3 | npm-debug.log 4 | yarn-error.log 5 | composer.lock 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 7.1 5 | - 7.2 6 | - 7.3 7 | 8 | before_script: 9 | - travis_retry composer self-update 10 | - travis_retry composer update ${COMPOSER_FLAGS} --no-interaction --prefer-source 11 | 12 | script: 13 | - vendor/bin/phpunit tests/ 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Clément Rigo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Manage Internal Newsletter Subscribers With Laravel 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/mydnic/laravel-subscribers.svg)](https://packagist.org/packages/mydnic/laravel-subscribers) 4 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE) 5 | [![Build Status](https://img.shields.io/travis/com/mydnic/laravel-subscribers.svg)](https://travis-ci.com/mydnic/laravel-subscribers) 6 | [![Code Quality](https://img.shields.io/scrutinizer/g/mydnic/laravel-subscribers.svg)](https://scrutinizer-ci.com/g/mydnic/laravel-subscribers/) 7 | 8 | 9 | ## Installation 10 | 11 | You may use Composer to Install Laravel Subscribers: 12 | 13 | ```bash 14 | composer require mydnic/laravel-subscribers 15 | ``` 16 | 17 | The package will automatically register itself 18 | 19 | You then must publish the migration with: 20 | 21 | ```bash 22 | php artisan vendor:publish --provider="Mydnic\Subscribers\SubscribersServiceProvider" --tag="subscribers-migrations" 23 | ``` 24 | 25 | ## Usage 26 | 27 | In your view, you simply need to add a form that you can customize the way you want 28 | 29 | ```blade 30 |
31 | @csrf 32 | 33 | 34 |
35 | 36 | @if (session('subscribed')) 37 |
38 | {{ session('subscribed') }} 39 |
40 | @endif 41 | ``` 42 | 43 | 44 | ### Delete 45 | Simply provide this link to your subscribers: 46 | 47 | ```blade 48 | unsubscribe 49 | ``` 50 | 51 | This will generate a link like `/subscribers/delete?email=email@example.com` 52 | 53 | ### Subscribe manually from the user model 54 | 55 | Alternatively, you can manage the subscription of a user from the user model. 56 | 57 | In order to do that you will need to add the `CanSubscribe` trait 58 | 59 | ```php 60 | use Mydnic\Subscribers\Traits\CanSubscribe; 61 | 62 | class User extends Model 63 | { 64 | use CanSubscribe; 65 | } 66 | ``` 67 | 68 | ```php 69 | // subscribe user 70 | $user->subscribe(); 71 | 72 | // unsubscribe user 73 | $user->unsubscribe(); 74 | ``` 75 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mydnic/laravel-subscribers", 3 | "description": "Easily Manage Internal Newsletter Subscribers in Laravel", 4 | "keywords": [ 5 | "laravel", 6 | "customer", 7 | "feedback" 8 | ], 9 | "license": "MIT", 10 | "homepage": "https://github.com/mydnic/laravel-subscribers", 11 | "authors": [ 12 | { 13 | "name": "Clément Rigo", 14 | "email": "rigoclement@mydnic.be" 15 | } 16 | ], 17 | "require": { 18 | "php": "^7.1|8.*", 19 | "laravel/framework": ">=8.40.0" 20 | }, 21 | "require-dev": { 22 | "phpunit/phpunit": "^9.5", 23 | "orchestra/testbench": "^6.17.0", 24 | "mockery/mockery": "^1.3" 25 | }, 26 | "autoload": { 27 | "psr-4": { 28 | "Mydnic\\Subscribers\\": "src/" 29 | } 30 | }, 31 | "autoload-dev": { 32 | "psr-4": { 33 | "Mydnic\\Subscribers\\Test\\": "tests" 34 | } 35 | }, 36 | "extra": { 37 | "laravel": { 38 | "providers": [ 39 | "Mydnic\\Subscribers\\SubscribersServiceProvider" 40 | ] 41 | } 42 | }, 43 | "scripts": { 44 | "test": "vendor/bin/phpunit" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /config/laravel-subscribers.php: -------------------------------------------------------------------------------- 1 | env('LARAVEL_SUBSCRIBERS_VERIFY', false), 4 | 'redirect_url' => 'home', 5 | /* 6 | |-------------------------------------------------------------------------- 7 | | Notifications Mail Messages 8 | |-------------------------------------------------------------------------- 9 | | 10 | */ 11 | 'mail' => [ 12 | 'verify' => [ 13 | 'expiration' => 60, // in minutes 14 | 'subject' => 'Verify Email Address', 15 | 'greeting' => 'Hello!', 16 | 'content' => [ 17 | 'Please click the button below to verify your email address.' 18 | ], 19 | 'action' => 'Verify Email Address', 20 | 'footer' => [ 21 | 'If you did not sign up for our newsletter, no further action is required.' 22 | ], 23 | ] 24 | ] 25 | ]; 26 | -------------------------------------------------------------------------------- /database/migrations/2018_01_01_000000_create_subscribers_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->string('email')->unique(); 19 | $table->timestamps(); 20 | $table->softDeletes(); 21 | $table->timestamp('email_verified_at')->nullable(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down() 31 | { 32 | Schema::dropIfExists('subscribers'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "npm run development", 5 | "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", 6 | "watch": "npm run development -- --watch", 7 | "watch-poll": "npm run watch -- --watch-poll", 8 | "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js", 9 | "prod": "npm run production", 10 | "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js" 11 | }, 12 | "devDependencies": { 13 | "axios": "^0.18", 14 | "cross-env": "^5.1", 15 | "laravel-mix": "^2.0", 16 | "lodash": "^4.17.4", 17 | "vue": "^2.5.7" 18 | }, 19 | "dependencies": { 20 | "html2canvas": "^1.0.0-alpha.12" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | tests 15 | 16 | 17 | 18 | 19 | src/ 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /resources/js/components/SubscriberForm.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 59 | 60 | 65 | -------------------------------------------------------------------------------- /routes/api.php: -------------------------------------------------------------------------------- 1 | name('store'); 6 | -------------------------------------------------------------------------------- /routes/web.php: -------------------------------------------------------------------------------- 1 | name('store'); 6 | Route::get('delete', 'SubscriberController@delete')->name('delete'); 7 | Route::get('verify/{id}/{hash}', 'SubscriberController@verify')->name('verify'); 8 | -------------------------------------------------------------------------------- /scrutinizer.yml: -------------------------------------------------------------------------------- 1 | filter: 2 | excluded_paths: [tests/*] 3 | 4 | checks: 5 | php: 6 | remove_extra_empty_lines: true 7 | remove_php_closing_tag: true 8 | remove_trailing_whitespace: true 9 | fix_use_statements: 10 | remove_unused: true 11 | preserve_multiple: false 12 | preserve_blanklines: true 13 | order_alphabetically: true 14 | fix_php_opening_tag: true 15 | fix_linefeed: true 16 | fix_line_ending: true 17 | fix_identation_4spaces: true 18 | -------------------------------------------------------------------------------- /src/Events/SubscriberCreated.php: -------------------------------------------------------------------------------- 1 | subscriber = $subscriber; 28 | } 29 | 30 | /** 31 | * Get the channels the event should broadcast on. 32 | * 33 | * @return \Illuminate\Broadcasting\Channel|array 34 | */ 35 | public function broadcastOn() 36 | { 37 | // return new PrivateChannel('channel-name'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Events/SubscriberDeleted.php: -------------------------------------------------------------------------------- 1 | subscriber = $subscriber; 28 | } 29 | 30 | /** 31 | * Get the channels the event should broadcast on. 32 | * 33 | * @return \Illuminate\Broadcasting\Channel|array 34 | */ 35 | public function broadcastOn() 36 | { 37 | // return new PrivateChannel('channel-name'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Events/SubscriberVerified.php: -------------------------------------------------------------------------------- 1 | subscriber = $subscriber; 28 | } 29 | 30 | /** 31 | * Get the channels the event should broadcast on. 32 | * 33 | * @return \Illuminate\Broadcasting\Channel|array 34 | */ 35 | public function broadcastOn() 36 | { 37 | // return new PrivateChannel('channel-name'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Exceptions/Handler.php: -------------------------------------------------------------------------------- 1 | all()); 19 | 20 | return response()->json([ 21 | 'created' => true, 22 | ], 201); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Http/Controllers/SubscriberController.php: -------------------------------------------------------------------------------- 1 | all()); 23 | 24 | if (config('laravel-subscribers.verify')) { 25 | $subscriber->sendEmailVerificationNotification(); 26 | return redirect()->route(config('laravel-subscribers.redirect_url')) 27 | ->with('subscribed', __('Please verify your email address!')); 28 | } 29 | 30 | return redirect()->route(config('laravel-subscribers.redirect_url')) 31 | ->with('subscribed', __('You are successfully subscribed to our list!')); 32 | } 33 | 34 | public function delete(DeleteSubscriberRequest $request) 35 | { 36 | $request->subscriber()->delete(); 37 | return view('subscribe.deleted'); 38 | } 39 | 40 | public function verify(VerifySubscriberRequest $request) 41 | { 42 | $subscriber = Subscriber::find($request->id); 43 | if (!hash_equals((string) $request->route('id'), (string) $subscriber->getKey())) { 44 | throw new SubscriberVerificationException; 45 | } 46 | 47 | if (!hash_equals((string) $request->route('hash'), sha1($subscriber->getEmailForVerification()))) { 48 | throw new SubscriberVerificationException; 49 | } 50 | 51 | if ($subscriber->hasVerifiedEmail()) { 52 | return $request->wantsJson() 53 | ? new Response('', 204) 54 | : redirect($this->redirectPath()); 55 | } 56 | 57 | if ($subscriber->markEmailAsVerified()) { 58 | event(new SubscriberVerified($subscriber)); 59 | } 60 | 61 | return $request->wantsJson() 62 | ? new Response('', 204) 63 | : redirect()->route(config('laravel-subscribers.redirect_url'))->with('verified', __('You are successfully subscribed to our list!')); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Http/Requests/DeleteSubscriberRequest.php: -------------------------------------------------------------------------------- 1 | 'required|email|exists:subscribers,email', 19 | ]; 20 | } 21 | 22 | public function subscriber() 23 | { 24 | return Subscriber::where('email', $this->input('email'))->firstOrFail(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Http/Requests/StoreSubscriberRequest.php: -------------------------------------------------------------------------------- 1 | 'required|email|unique:subscribers,email', 21 | ]; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Http/Requests/VerifySubscriberRequest.php: -------------------------------------------------------------------------------- 1 | 'required|email|exists:subscribers,email', 19 | ]; 20 | } 21 | 22 | public function subscriber() 23 | { 24 | return Subscriber::where('email', $this->input('email'))->firstOrFail(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Notifications/SubscriberVerifyEmail.php: -------------------------------------------------------------------------------- 1 | verificationUrl($notifiable); 49 | 50 | $mail = new MailMessage(); 51 | 52 | $mail->subject(Lang::get(config('laravel-subscribers.mail.verify.subject', 'Verify Email Address'))); 53 | $mail->greeting(Lang::get(config('laravel-subscribers.mail.verify.greeting', 'Hello!'))); 54 | 55 | if (!empty(config('laravel-subscribers.mail.verify.content'))) { 56 | foreach (config('laravel-subscribers.mail.verify.content') as $value) { 57 | $mail->line(Lang::get($value)); 58 | } 59 | } else { 60 | $mail->line(Lang::get('Please click the button below to verify your email address.')); 61 | } 62 | 63 | $mail->action(Lang::get(config('laravel-subscribers.mail.verify.action', 'Verify Email Address')), $verificationUrl); 64 | 65 | if (!empty(config('laravel-subscribers.mail.verify.footer'))) { 66 | foreach (config('laravel-subscribers.mail.verify.footer') as $value) { 67 | $mail->line(Lang::get($value)); 68 | } 69 | } else { 70 | $mail->line(Lang::get('If you did not sign up for our newsletter, no further action is required.')); 71 | } 72 | 73 | return $mail; 74 | } 75 | 76 | /** 77 | * Get the array representation of the notification. 78 | * 79 | * @param mixed $notifiable 80 | * @return array 81 | */ 82 | public function toArray($notifiable) 83 | { 84 | return [ 85 | // 86 | ]; 87 | } 88 | 89 | /** 90 | * Get the verification URL for the given notifiable. 91 | * 92 | * @param mixed $notifiable 93 | * @return string 94 | */ 95 | protected function verificationUrl($notifiable) 96 | { 97 | return URL::temporarySignedRoute( 98 | 'subscribers.verify', 99 | Carbon::now()->addMinutes(config('laravel-subscribers.mail.verify.expiration')), 100 | [ 101 | 'id' => $notifiable->getKey(), 102 | 'hash' => sha1($notifiable->getEmailForVerification()), 103 | ] 104 | ); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/Nova/Metrics/NewSubscribers.php: -------------------------------------------------------------------------------- 1 | count($request, Subscriber::class); 20 | } 21 | 22 | /** 23 | * Get the ranges available for the metric. 24 | * 25 | * @return array 26 | */ 27 | public function ranges() 28 | { 29 | return [ 30 | 30 => '30 Days', 31 | 60 => '60 Days', 32 | 365 => '365 Days', 33 | 'MTD' => 'Month To Date', 34 | 'QTD' => 'Quarter To Date', 35 | 'YTD' => 'Year To Date', 36 | ]; 37 | } 38 | 39 | /** 40 | * Determine for how many minutes the metric should be cached. 41 | * 42 | * @return \DateTimeInterface|\DateInterval|float|int 43 | */ 44 | public function cacheFor() 45 | { 46 | // return now()->addMinutes(5); 47 | } 48 | 49 | /** 50 | * Get the URI key for the metric. 51 | * 52 | * @return string 53 | */ 54 | public function uriKey() 55 | { 56 | return 'new-subscribers'; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Nova/Resources/Subscriber.php: -------------------------------------------------------------------------------- 1 | sortable(), 47 | 48 | Text::make('Email') 49 | ->sortable() 50 | ->rules('required', 'email', 'max:255') 51 | ->creationRules('unique:subscribers,email') 52 | ->updateRules('unique:subscribers,email,{{resourceId}}'), 53 | ]; 54 | } 55 | 56 | /** 57 | * Get the cards available for the request. 58 | * 59 | * @param \Illuminate\Http\Request $request 60 | * @return array 61 | */ 62 | public function cards(Request $request) 63 | { 64 | return [ 65 | new NewSubscribers, 66 | ]; 67 | } 68 | 69 | /** 70 | * Get the filters available for the resource. 71 | * 72 | * @param \Illuminate\Http\Request $request 73 | * @return array 74 | */ 75 | public function filters(Request $request) 76 | { 77 | return []; 78 | } 79 | 80 | /** 81 | * Get the lenses available for the resource. 82 | * 83 | * @param \Illuminate\Http\Request $request 84 | * @return array 85 | */ 86 | public function lenses(Request $request) 87 | { 88 | return []; 89 | } 90 | 91 | /** 92 | * Get the actions available for the resource. 93 | * 94 | * @param \Illuminate\Http\Request $request 95 | * @return array 96 | */ 97 | public function actions(Request $request) 98 | { 99 | return []; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/Subscriber.php: -------------------------------------------------------------------------------- 1 | SubscriberCreated::class, 26 | 'deleted' => SubscriberDeleted::class, 27 | ]; 28 | 29 | 30 | /** 31 | * Determine if the user has verified their email address. 32 | * 33 | * @return bool 34 | */ 35 | public function hasVerifiedEmail() 36 | { 37 | return ! is_null($this->email_verified_at); 38 | } 39 | 40 | /** 41 | * Mark the given user's email as verified. 42 | * 43 | * @return bool 44 | */ 45 | public function markEmailAsVerified() 46 | { 47 | return $this->forceFill([ 48 | 'email_verified_at' => $this->freshTimestamp(), 49 | ])->save(); 50 | } 51 | 52 | /** 53 | * Send the email verification notification. 54 | * 55 | * @return void 56 | */ 57 | public function sendEmailVerificationNotification() 58 | { 59 | $this->notify(new SubscriberVerifyEmail); 60 | } 61 | 62 | /** 63 | * Get the email address that should be used for verification. 64 | * 65 | * @return string 66 | */ 67 | public function getEmailForVerification() 68 | { 69 | return $this->email; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/SubscribersServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->runningInConsole()) { 18 | $this->registerPublishing(); 19 | } 20 | 21 | $this->registerRoutes(); 22 | } 23 | 24 | /** 25 | * Register the package's publishable resources. 26 | * 27 | * @return void 28 | */ 29 | protected function registerPublishing() 30 | { 31 | $this->publishes([ 32 | __DIR__ . '/../resources/js/components' => resource_path('js/components/Subscribers'), 33 | ], 'subscribers-vue-component'); 34 | 35 | $this->publishes([ 36 | __DIR__ . '/../database/migrations' => database_path('migrations'), 37 | ], 'subscribers-migrations'); 38 | 39 | $this->publishes([ 40 | __DIR__.'/../config/laravel-subscribers.php' => config_path('laravel-subscribers.php'), 41 | ], 'subscribers-config'); 42 | } 43 | 44 | /** 45 | * Register the package routes. 46 | * 47 | * @return void 48 | */ 49 | protected function registerRoutes() 50 | { 51 | Route::group($this->apiRouteConfiguration(), function () { 52 | $this->loadRoutesFrom(__DIR__ . '/../routes/api.php'); 53 | }); 54 | Route::group($this->webRouteConfiguration(), function () { 55 | $this->loadRoutesFrom(__DIR__ . '/../routes/web.php'); 56 | }); 57 | } 58 | 59 | /** 60 | * Get the Subscribers route group configuration array for web middleware. 61 | * 62 | * @return array 63 | */ 64 | protected function webRouteConfiguration() 65 | { 66 | return [ 67 | 'namespace' => 'Mydnic\Subscribers\Http\Controllers', 68 | 'as' => 'subscribers.', 69 | 'prefix' => 'subscribers', 70 | 'middleware' => 'web', 71 | ]; 72 | } 73 | 74 | /** 75 | * Get the Subscribers route group configuration array for api middleware. 76 | * 77 | * @return array 78 | */ 79 | protected function apiRouteConfiguration() 80 | { 81 | return [ 82 | 'namespace' => 'Mydnic\Subscribers\Http\Controllers\Api', 83 | 'as' => 'subscribers.api.', 84 | 'prefix' => 'subscribers-api', 85 | 'middleware' => 'api', 86 | ]; 87 | } 88 | 89 | /** 90 | * Register any package services. 91 | * 92 | * @return void 93 | */ 94 | public function register() 95 | { 96 | if (! $this->app->configurationIsCached()) { 97 | $this->mergeConfigFrom(__DIR__.'/../config/laravel-subscribers.php', 'laravel-subscribers'); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Traits/CanSubscribe.php: -------------------------------------------------------------------------------- 1 | $this->email]); 12 | 13 | if (config('laravel-subscribers.verify')) { 14 | $subscriber->sendEmailVerificationNotification(); 15 | } 16 | } 17 | 18 | public function unsubscribe() 19 | { 20 | Subscriber::where('email', $this->email)->delete(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/SubscriberTest.php: -------------------------------------------------------------------------------- 1 | post('/subscribers-api/subscriber', [ 20 | 'email' => 'some@email.com', 21 | ]); 22 | 23 | $request->assertStatus(201); 24 | 25 | $subscriber = Subscriber::first(); 26 | $this->assertEquals('some@email.com', $subscriber->email); 27 | 28 | Event::assertDispatched(SubscriberCreated::class, function ($e) use ($subscriber) { 29 | return $e->subscriber->id === $subscriber->id; 30 | }); 31 | } 32 | 33 | /** @test */ 34 | public function it_saves_the_subscriber_via_web() 35 | { 36 | Event::fake(); 37 | 38 | $request = $this->post('/subscribers/subscriber', [ 39 | 'email' => 'someweb@email.com', 40 | ]); 41 | 42 | $request->assertStatus(302); 43 | 44 | $subscriber = Subscriber::first(); 45 | $this->assertEquals('someweb@email.com', $subscriber->email); 46 | 47 | Event::assertDispatched(SubscriberCreated::class, function ($e) use ($subscriber) { 48 | return $e->subscriber->id === $subscriber->id; 49 | }); 50 | } 51 | 52 | /** @test */ 53 | public function it_refuses_existing_subscribers() 54 | { 55 | Subscriber::create(['email' => 'some@email.com']); 56 | 57 | $request = $this->post('/subscribers-api/subscriber', [ 58 | 'email' => 'some@email.com', 59 | ]); 60 | 61 | $this->assertEquals(1, Subscriber::count()); 62 | } 63 | 64 | /** @test */ 65 | public function it_deletes_existing_subscribers() 66 | { 67 | Event::fake(); 68 | 69 | Subscriber::create(['email' => 'some@email.com']); 70 | 71 | $request = $this->get('/subscribers/delete?email=some@email.com'); 72 | 73 | $this->assertEquals(0, Subscriber::count()); 74 | 75 | Event::assertDispatched(SubscriberDeleted::class); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | setUpDatabase(); 17 | } 18 | 19 | protected function getPackageProviders($app) 20 | { 21 | return [SubscribersServiceProvider::class]; 22 | } 23 | 24 | protected function getEnvironmentSetUp($app) 25 | { 26 | $config = $app->get('config'); 27 | $config->set('logging.default', 'errorlog'); 28 | $config->set('database.default', 'testbench'); 29 | $config->set('database.connections.testbench', [ 30 | 'driver' => 'sqlite', 31 | 'database' => ':memory:', 32 | 'prefix' => '', 33 | ]); 34 | $app->when(DatabaseEntriesRepository::class) 35 | ->needs('$connection') 36 | ->give('testbench'); 37 | } 38 | 39 | protected function setUpDatabase() 40 | { 41 | Schema::dropIfExists('subscribers'); 42 | 43 | Schema::create('subscribers', function (Blueprint $table) { 44 | $table->increments('id'); 45 | $table->string('email')->unique(); 46 | $table->timestamps(); 47 | }); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /webpack.mix.js: -------------------------------------------------------------------------------- 1 | let mix = require('laravel-mix'); 2 | 3 | /* 4 | |-------------------------------------------------------------------------- 5 | | Mix Asset Management 6 | |-------------------------------------------------------------------------- 7 | | 8 | | Mix provides a clean, fluent API for defining some Webpack build steps 9 | | for your Laravel application. By default, we are compiling the Sass 10 | | file for the application as well as bundling up all the JS files. 11 | | 12 | */ 13 | 14 | mix 15 | .setPublicPath('public') 16 | 17 | .js('resources/js/kustomer.js', 'public/js') 18 | 19 | .sass('resources/sass/kustomer.scss', 'public/css') 20 | 21 | .version() 22 | --------------------------------------------------------------------------------