From 0b1fee1cdc0cd65c8da80e2f8dedea88e817cc13 Mon Sep 17 00:00:00 2001 From: James Collins Date: Tue, 24 Jan 2023 16:30:49 +1000 Subject: [PATCH] added subscriptions --- app/Filters/SubscriptionFilter.php | 30 +++++ .../Api/SubscriptionController.php | 118 +++++++++--------- app/Http/Requests/BaseRequest.php | 2 + app/Http/Requests/SubscriptionRequest.php | 34 +++++ app/Mail/SubscriptionConfirm.php | 61 +++++++++ app/Mail/SubscriptionUnsubscribed.php | 61 +++++++++ resources/css/utils.scss | 18 +-- resources/js/router/index.js | 8 ++ resources/js/views/Home.vue | 82 ++++++++++-- resources/js/views/Unsubscribe.vue | 118 ++++++++++++++++++ .../user/subscription_confirm.blade.php | 103 +++++++++++++++ .../user/subscription_confirm_plain.php | 14 +++ .../user/subscription_unsubscribed.blade.php | 102 +++++++++++++++ .../user/subscription_unsubscribed_plain.php | 10 ++ routes/api.php | 2 + 15 files changed, 690 insertions(+), 73 deletions(-) create mode 100644 app/Filters/SubscriptionFilter.php create mode 100644 app/Http/Requests/SubscriptionRequest.php create mode 100644 app/Mail/SubscriptionConfirm.php create mode 100644 app/Mail/SubscriptionUnsubscribed.php create mode 100644 resources/js/views/Unsubscribe.vue create mode 100644 resources/views/emails/user/subscription_confirm.blade.php create mode 100644 resources/views/emails/user/subscription_confirm_plain.php create mode 100644 resources/views/emails/user/subscription_unsubscribed.blade.php create mode 100644 resources/views/emails/user/subscription_unsubscribed_plain.php diff --git a/app/Filters/SubscriptionFilter.php b/app/Filters/SubscriptionFilter.php new file mode 100644 index 0000000..04d0d8d --- /dev/null +++ b/app/Filters/SubscriptionFilter.php @@ -0,0 +1,30 @@ +hasPermission('admin/users') !== true) { + return ['id', 'email', 'confirmed_at']; + } + } +} diff --git a/app/Http/Controllers/Api/SubscriptionController.php b/app/Http/Controllers/Api/SubscriptionController.php index fc4d90b..45f138f 100644 --- a/app/Http/Controllers/Api/SubscriptionController.php +++ b/app/Http/Controllers/Api/SubscriptionController.php @@ -2,27 +2,12 @@ namespace App\Http\Controllers\Api; -use App\Enum\HttpResponseCodes; -use App\Filters\UserFilter; -use App\Http\Requests\UserUpdateRequest; -use App\Http\Requests\UserStoreRequest; -use App\Http\Requests\UserForgotPasswordRequest; -use App\Http\Requests\UserForgotUsernameRequest; -use App\Http\Requests\UserRegisterRequest; -use App\Http\Requests\UserResendVerifyEmailRequest; -use App\Http\Requests\UserResetPasswordRequest; -use App\Http\Requests\UserVerifyEmailRequest; +use App\Models\Subscription; +use App\Filters\SubscriptionFilter; +use App\Http\Requests\SubscriptionRequest; use App\Jobs\SendEmailJob; -use App\Mail\ChangedEmail; -use App\Mail\ChangedPassword; -use App\Mail\ChangeEmailVerify; -use App\Mail\ForgotUsername; -use App\Mail\ForgotPassword; -use App\Mail\EmailVerify; -use App\Models\User; -use App\Models\UserCode; -use Illuminate\Http\Request; -use Illuminate\Support\Facades\Hash; +use App\Mail\SubscriptionConfirm; +use App\Mail\SubscriptionUnsubscribed; class SubscriptionController extends ApiController { @@ -32,16 +17,16 @@ class SubscriptionController extends ApiController public function __construct() { $this->middleware('auth:sanctum') - ->except([]); + ->except(['store', 'destroyByEmail']); } /** - * Display a listing of the resource. + * Display a listing of subscribers. * - * @param \App\Filters\UserFilter $filter Filter object. + * @param \App\Filters\SubscriptionFilter $filter Filter object. * @return \Illuminate\Http\Response */ - public function index(UserFilter $filter) + public function index(SubscriptionFilter $filter) { $collection = $filter->filter(); return $this->respondAsResource( @@ -51,76 +36,97 @@ class SubscriptionController extends ApiController } /** - * Store a newly created user in the database. + * Store a subscriber email in the database. * - * @param UserStoreRequest $request The user update request. + * @param SubscriptionRequest $request The subscriber update request. * @return \Illuminate\Http\Response */ - public function store(UserStoreRequest $request) + public function store(SubscriptionRequest $request) { - if ($request->user()->hasPermission('admin/user') !== true) { - return $this->respondForbidden(); + if (Subscription::where('email', $request->email)->first() !== null) { + return $this->respondWithErrors(['email' => 'This email address has already subscribed']); } - $user = User::create($request->all()); - return $this->respondAsResource((new UserFilter($request))->filter($user), [], HttpResponseCodes::HTTP_CREATED); + Subscription::create($request->all()); + dispatch((new SendEmailJob($request->email, new SubscriptionConfirm($request->email))))->onQueue('mail'); + + return $this->respondCreated(); } /** * Display the specified user. * - * @param UserFilter $filter The user filter. - * @param User $user The user model. + * @param SubscriptionFilter $filter The subscription filter. + * @param Subscription $subscription The subscription model. * @return \Illuminate\Http\Response */ - public function show(UserFilter $filter, User $user) + public function show(SubscriptionFilter $filter, Subscription $subscription) { - return $this->respondAsResource($filter->filter($user)); + return $this->respondAsResource($filter->filter($subscription)); } /** * Update the specified resource in storage. * - * @param UserUpdateRequest $request The user update request. - * @param User $user The specified user. + * @param SubscriptionRequest $request The subscription update request. + * @param Subscription $subscription The specified subscription. * @return \Illuminate\Http\Response */ - public function update(UserUpdateRequest $request, User $user) + public function update(SubscriptionRequest $request, Subscription $subscription) { - $input = []; - $updatable = ['username', 'first_name', 'last_name', 'email', 'phone', 'password']; + // $input = []; + // $updatable = ['username', 'first_name', 'last_name', 'email', 'phone', 'password']; - if ($request->user()->hasPermission('admin/user') === true) { - $updatable = array_merge($updatable, ['email_verified_at']); - } elseif ($request->user()->is($user) !== true) { - return $this->respondForbidden(); - } + // if ($request->user()->hasPermission('admin/user') === true) { + // $updatable = array_merge($updatable, ['email_verified_at']); + // } elseif ($request->user()->is($user) !== true) { + // return $this->respondForbidden(); + // } - $input = $request->only($updatable); - if (array_key_exists('password', $input) === true) { - $input['password'] = Hash::make($request->input('password')); - } + // $input = $request->only($updatable); + // if (array_key_exists('password', $input) === true) { + // $input['password'] = Hash::make($request->input('password')); + // } - $user->update($input); + // $user->update($input); - return $this->respondAsResource((new UserFilter($request))->filter($user)); + // return $this->respondAsResource((new UserFilter($request))->filter($user)); } /** * Remove the user from the database. * - * @param User $user The specified user. + * @param Subscription $subscription The specified subscription. * @return \Illuminate\Http\Response */ - public function destroy(User $user) + public function destroy(Subscription $subscription) { - if ($user->hasPermission('admin/user') === false) { - return $this->respondForbidden(); + // if ($user->hasPermission('admin/user') === false) { + // return $this->respondForbidden(); + // } + + $email = $subscription->email; + + $subscription->delete(); + return $this->respondNoContent(); + } + + /** + * Remove the user from the database. + * + * @param SubscriptionRequest $request The specified subscription. + * @return \Illuminate\Http\Response + */ + public function destroyByEmail(SubscriptionRequest $request) + { + $subscription = Subscription::where('email', $request->email)->first(); + if ($subscription !== null) { + $subscription->delete(); + dispatch((new SendEmailJob($request->email, new SubscriptionUnsubscribed($request->email))))->onQueue('mail'); } - $user->delete(); return $this->respondNoContent(); } } diff --git a/app/Http/Requests/BaseRequest.php b/app/Http/Requests/BaseRequest.php index f99f46c..4d41794 100644 --- a/app/Http/Requests/BaseRequest.php +++ b/app/Http/Requests/BaseRequest.php @@ -40,6 +40,8 @@ class BaseRequest extends FormRequest $rules = $this->mergeRules($rules, $this->postRules()); } elseif (method_exists($this, 'putRules') === true && request()->isMethod('put') === true) { $rules = $this->mergeRules($rules, $this->postRules()); + } elseif (method_exists($this, 'destroyRules') === true && request()->isMethod('delete') === true) { + $rules = $this->mergeRules($rules, $this->destroyRules()); } return $rules; diff --git a/app/Http/Requests/SubscriptionRequest.php b/app/Http/Requests/SubscriptionRequest.php new file mode 100644 index 0000000..b34374e --- /dev/null +++ b/app/Http/Requests/SubscriptionRequest.php @@ -0,0 +1,34 @@ + + */ + public function postRules() + { + return [ + 'email' => 'required|email', + 'captcha_token' => [new Recaptcha()], + ]; + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function destroyRules() + { + return [ + 'email' => 'required|email', + 'captcha_token' => [new Recaptcha()], + ]; + } +} diff --git a/app/Mail/SubscriptionConfirm.php b/app/Mail/SubscriptionConfirm.php new file mode 100644 index 0000000..c896049 --- /dev/null +++ b/app/Mail/SubscriptionConfirm.php @@ -0,0 +1,61 @@ +email = $email; + } + + /** + * Get the message envelope. + * + * @return \Illuminate\Mail\Mailables\Envelope + */ + public function envelope() + { + return new Envelope( + subject: '🗞️ You\'re on the mailing list!', + ); + } + + /** + * Get the message content definition. + * + * @return \Illuminate\Mail\Mailables\Content + */ + public function content() + { + return new Content( + view: 'emails.user.subscription_confirm', + text: 'emails.user.subscription_confirm_plain', + ); + } +} diff --git a/app/Mail/SubscriptionUnsubscribed.php b/app/Mail/SubscriptionUnsubscribed.php new file mode 100644 index 0000000..ce8fdaf --- /dev/null +++ b/app/Mail/SubscriptionUnsubscribed.php @@ -0,0 +1,61 @@ +email = $email; + } + + /** + * Get the message envelope. + * + * @return \Illuminate\Mail\Mailables\Envelope + */ + public function envelope() + { + return new Envelope( + subject: 'You have been unsubscribed', + ); + } + + /** + * Get the message content definition. + * + * @return \Illuminate\Mail\Mailables\Content + */ + public function content() + { + return new Content( + view: 'emails.user.subscription_unsubscribed', + text: 'emails.user.subscription_unsubscribed_plain', + ); + } +} diff --git a/resources/css/utils.scss b/resources/css/utils.scss index 1ef8450..536672c 100644 --- a/resources/css/utils.scss +++ b/resources/css/utils.scss @@ -92,33 +92,33 @@ /* Padding */ @each $index, $size in $spacer { .p-#{$index} { - padding: #{$size}; + padding: #{$size} !important; } .pt-#{$index} { - padding-top: #{$size}; + padding-top: #{$size} !important; } .pb-#{$index} { - padding-bottom: #{$size}; + padding-bottom: #{$size} !important; } .pl-#{$index} { - padding-left: #{$size}; + padding-left: #{$size} !important; } .pr-#{$index} { - padding-right: #{$size}; + padding-right: #{$size} !important; } .px-#{$index} { - padding-left: #{$size}; - padding-right: #{$size}; + padding-left: #{$size} !important; + padding-right: #{$size} !important; } .py-#{$index} { - padding-top: #{$size}; - padding-bottom: #{$size}; + padding-top: #{$size} !important; + padding-bottom: #{$size} !important; } } diff --git a/resources/js/router/index.js b/resources/js/router/index.js index 3950d93..ae4dcee 100644 --- a/resources/js/router/index.js +++ b/resources/js/router/index.js @@ -65,6 +65,14 @@ export const routes = [ }, component: () => import("@/views/Rules.vue"), }, + { + path: "/unsubscribe", + name: "unsubscribe", + meta: { + title: "Unsubscribe", + }, + component: () => import("@/views/Unsubscribe.vue"), + }, { path: "/terms", name: "terms", diff --git a/resources/js/views/Home.vue b/resources/js/views/Home.vue index c509c47..06cf894 100644 --- a/resources/js/views/Home.vue +++ b/resources/js/views/Home.vue @@ -110,25 +110,67 @@ Join our mailing list to receive tips, tricks and be notified of upcoming workshops.

-
- - -
+ +
+
+ + + + +
+
+
diff --git a/resources/js/views/Unsubscribe.vue b/resources/js/views/Unsubscribe.vue new file mode 100644 index 0000000..6c8ff31 --- /dev/null +++ b/resources/js/views/Unsubscribe.vue @@ -0,0 +1,118 @@ + + + diff --git a/resources/views/emails/user/subscription_confirm.blade.php b/resources/views/emails/user/subscription_confirm.blade.php new file mode 100644 index 0000000..6f11ea3 --- /dev/null +++ b/resources/views/emails/user/subscription_confirm.blade.php @@ -0,0 +1,103 @@ + + + + + + + STEMMechanics - Subscription + + + +
+ + STEMMechanics Logo + +

Howdy there,

+

At your request, you are now subscribed to our newsletter giving you tips, tricks and letting you know when new workshops are scheduled.

+

If this wasn't you, you can unsubscribe by visiting stemmechanics.com.au/unsubscribe

+
+ +
+ + + diff --git a/resources/views/emails/user/subscription_confirm_plain.php b/resources/views/emails/user/subscription_confirm_plain.php new file mode 100644 index 0000000..0f9ed81 --- /dev/null +++ b/resources/views/emails/user/subscription_confirm_plain.php @@ -0,0 +1,14 @@ +Howdy there, + +At your request, you are now subscribed to our newsletter giving you tips, tricks and letting you know when new workshops are scheduled. + +If this wasn't you, you can unsubscribe by visiting the following URL in your browser: + +https://www.stemmechanics.com.au/unsubscribe + +Need help or got feedback? Contact us at https://www.stemmechanics.com.au/contact or touch base on twitter at @stemmechanics + +-- +Sent by STEMMechanics +https://www.stemmechanics.com.au/ +PO Box 36, Edmonton, QLD 4869, Australia diff --git a/resources/views/emails/user/subscription_unsubscribed.blade.php b/resources/views/emails/user/subscription_unsubscribed.blade.php new file mode 100644 index 0000000..2d50528 --- /dev/null +++ b/resources/views/emails/user/subscription_unsubscribed.blade.php @@ -0,0 +1,102 @@ + + + + + + + STEMMechanics - Subscription + + + +
+ + STEMMechanics Logo + +

Howdy there,

+

At your request, you are now unsubscribed from our newsletter.

+
+ +
+ + + diff --git a/resources/views/emails/user/subscription_unsubscribed_plain.php b/resources/views/emails/user/subscription_unsubscribed_plain.php new file mode 100644 index 0000000..225100c --- /dev/null +++ b/resources/views/emails/user/subscription_unsubscribed_plain.php @@ -0,0 +1,10 @@ +Howdy there, + +At your request, you have been unsubscribed from our newsletter. + +Need help or got feedback? Contact us at https://www.stemmechanics.com.au/contact or touch base on twitter at @stemmechanics + +-- +Sent by STEMMechanics +https://www.stemmechanics.com.au/ +PO Box 36, Edmonton, QLD 4869, Australia diff --git a/routes/api.php b/routes/api.php index 754ea46..164a20f 100644 --- a/routes/api.php +++ b/routes/api.php @@ -7,6 +7,7 @@ use App\Http\Controllers\Api\PostController; use App\Http\Controllers\Api\EventController; use App\Http\Controllers\Api\MediaController; use App\Http\Controllers\Api\ContactController; +use App\Http\Controllers\Api\SubscriptionController; /* |-------------------------------------------------------------------------- @@ -37,6 +38,7 @@ Route::apiResource('posts', PostController::class); Route::apiResource('events', EventController::class); Route::apiResource('subscriptions', SubscriptionController::class); +Route::delete('subscriptions', [SubscriptionController::class, 'destroyByEmail']); Route::post('/contact', [ContactController::class, 'send']);