Skip to content

Commit

Permalink
User can unsubscribe
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcyLina committed Aug 9, 2024
1 parent c8ba5ea commit 8b9363f
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 157 deletions.
65 changes: 33 additions & 32 deletions app/Console/Commands/SendResourceDigestEmail.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,36 +13,37 @@

class SendResourceDigestEmail extends Command
{
protected $signature = 'send:send-resource-digest-email';
protected $description = 'Send the monthly resource digest email';

public function __construct()
{
parent::__construct();
}

public function handle()
{
$resources = Resource::where('created_at', '>=', Carbon::now()->subDays(30))->get();

if ($resources->isEmpty()) {
$this->info('No resources created in the last 30 days. Email not sent.');

return;
}

$data = $resources->toArray();

User::where('is_subscriber', true)->chunk(100, function ($subscribedUsers) use ($data) {
foreach ($subscribedUsers as $user) {
try {
Mail::to($user->email)->queue(new ResourceDigestEmail($data));
} catch (Exception $e) {
Log::error('Failed to send email to ' . $user->email . ': ' . $e->getMessage());
}
}
});

$this->info('Monthly resource digest sent successfully to all subscribed users.');
}
protected $signature = 'send:send-resource-digest-email';
protected $description = 'Send the monthly resource digest email';

public function __construct()
{
parent::__construct();
}

public function handle()
{
$resources = Resource::where('created_at', '>=', Carbon::now()->subDays(30))->get();

if ($resources->isEmpty()) {
$this->info('No resources created in the last 30 days. Email not sent.');
return;
}

User::where('is_subscriber', true)->chunk(100, function ($subscribedUsers) use ($resources) {
foreach ($subscribedUsers as $user) {
try {
// Compute the unsubscribe URL with locale
$locale = $user->locale ?? 'en'; // Use a default locale if necessary
$unsubscribeUrl = route('unsubscribe', ['token' => $user->unsubscribe_token, 'locale' => $locale]);

Mail::to($user->email)->queue(new ResourceDigestEmail($resources, $user, $unsubscribeUrl));
} catch (Exception $e) {
Log::error('Failed to send email to ' . $user->email . ': ' . $e->getMessage());
}
}
});

$this->info('Monthly resource digest sent successfully to all subscribed users.');
}
}
131 changes: 70 additions & 61 deletions app/Http/Controllers/PreferenceController.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,71 +3,80 @@
namespace App\Http\Controllers;

use App\Facades\Preferences;
use App\Models\User;
use App\Preferences\ResourceLanguagePreference;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\View\View;

class PreferenceController extends Controller
{
public function index(): View
{
return view('preferences', [
'currentResourceLanguagePreference' => Preferences::get('resource-language'),
'resourceLanguagePreferences' => (new ResourceLanguagePreference)->options(),
'preferredLocale' => Preferences::get('locale'),
]);
}

public function update(Request $request)
{
Preferences::set(
collect($request->only(Preferences::getValidKeys()))->filter()
);

$user = auth()->user();

if ($user) {
if ($request->filled('track')) {
$user->track_id = $request->input('track');
}

$user->is_subscriber = $request->has('digest-subscriber');

$user->save();
}

session()->flash('toast', 'Your preferences were saved.');

if ($request->wantsJson()) {
return response()->json(['status' => 'success']);
}

if ($request->input('locale') !== locale()) {
return redirect(
str_replace('/' . locale() . '/', '/' . $request->input('locale') . '/', back()->getTargetUrl())
);
}

return back();
}

public function unsubscribe(Request $request)
{
$user = auth()->user();

if ($user) {
$user->is_subscriber = false;
$user->save();

session()->flash('toast', 'You have been unsubscribed.');

if ($request->wantsJson()) {
return response()->json(['status' => 'success']);
}

return redirect()->route('welcome');
}

return redirect()->route('home')->with('error', 'Invalid unsubscribe link.');
}
public function index(): View
{
return view('preferences', [
'currentResourceLanguagePreference' => Preferences::get('resource-language'),
'resourceLanguagePreferences' => (new ResourceLanguagePreference)->options(),
'preferredLocale' => Preferences::get('locale'),
]);
}

public function update(Request $request)
{
Preferences::set(
collect($request->only(Preferences::getValidKeys()))->filter()
);

$user = auth()->user();

if ($user) {
if ($request->filled('track')) {
$user->track_id = $request->input('track');
}

$request->validate([
'is_subscriber' => 'nullable|boolean',
]);

$user->is_subscriber = $request->has('digest-subscriber');

if ($user->is_subscriber) {
$user->unsubscribe_token = Str::random(60);
} else {
$user->unsubscribe_token = null;
}

$user->save();
}

session()->flash('toast', 'Your preferences were saved.');

if ($request->wantsJson()) {
return response()->json(['status' => 'success']);
}

if ($request->input('locale') !== locale()) {
return redirect(
str_replace('/' . locale() . '/', '/' . $request->input('locale') . '/', back()->getTargetUrl())
);
}

return back();
}

public function unsubscribe(Request $request, $locale, $token)
{
$user = User::where('unsubscribe_token', $token)->first();

if ($user) {
$user->is_subscriber = false;
$user->unsubscribe_token = null;
$user->save();

session()->flash('toast', 'You have been unsubscribed.');

return redirect()->route('welcome', ['locale' => $locale]);
}

return redirect()->route('welcome', ['locale' => $locale])->with('error', 'Invalid unsubscribe link.');
}
}
66 changes: 37 additions & 29 deletions app/Mail/ResourceDigestEmail.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,33 +11,41 @@

class ResourceDigestEmail extends Mailable implements ShouldQueue
{
use Queueable, SerializesModels;

public $resources;

public function __construct($resources)
{
$this->resources = $resources;
}

public function envelope()
{
return new Envelope(
subject: 'New Onramp Resources!',
from: '[email protected]',
);
}

public function content()
{
return new Content(
markdown: 'emails.resource-digest',
with: ['resources' => $this->resources],
);
}

public function attachments(): array
{
return [];
}
use Queueable, SerializesModels;

public $resources;
public $user;
public $unsubscribeUrl;

public function __construct($resources, $user, $unsubscribeUrl)
{
$this->resources = $resources;
$this->user = $user;
$this->unsubscribeUrl = $unsubscribeUrl;
}

public function envelope()
{
return new Envelope(
subject: 'New Onramp Resources!',
from: '[email protected]',
);
}

public function content()
{
return new Content(
markdown: 'emails.resource-digest',
with: [
'resources' => $this->resources,
'user' => $this->user,
'unsubscribeUrl' => $this->unsubscribeUrl,
],
);
}

public function attachments(): array
{
return [];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('unsubscribe_token', 60)->unique()->nullable();
});
}

public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('unsubscribe_token');
});
}
};
9 changes: 6 additions & 3 deletions resources/views/emails/resource-digest.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@

@component('mail::panel')
@foreach ($resources as $resource)
- [{{ $resource['name'] }}]({{ $resource['url'] }})<br>
Added on {{ \Carbon\Carbon::parse($resource['created_at'])->format('F j, Y') }}<br><br>
- [{{ $resource['name'] }}]({{ $resource['url'] }})
Added on {{ \Carbon\Carbon::parse($resource['created_at'])->format('F j, Y') }}

@endforeach
@endcomponent

### Happy Coding!

### Your friends at {{ config('app.name') }}

<p>If you no longer wish to receive these emails, you can <a href="{{ route('unsubscribe') }}">unsubscribe here</a>.</p>
<x-mail::button :url="$unsubscribeUrl">
Unsubscribe
</x-mail::button>
@endcomponent
65 changes: 33 additions & 32 deletions routes/web.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,36 +15,37 @@
Route::get('/', RootRedirectController::class);

Route::prefix('{locale}')->group(function () {
Route::view('/', 'welcome')->name('welcome');

Route::view('use-of-data', 'use-of-data')->name('use-of-data');
Route::get('glossary', [GlossaryController::class, 'index'])->name('glossary');
Route::get('tracks', [TrackController::class, 'index'])->name('tracks');

Route::prefix('modules')->name('modules.')->group(function () {
Route::get('/', [ModuleController::class, 'index'])->name('index');
Route::get('{module}/{resourceType}', [ModuleController::class, 'show'])
->name('show')
->where('resourceType', 'free-resources|paid-resources|quizzes|exercises');
});

Route::middleware('auth')->group(function () {
Route::get('wizard', [WizardController::class, 'index'])->name('wizard.index');
Route::post('wizard', [WizardController::class, 'store'])->name('wizard.store');
Route::get('profile', [ProfileController::class, 'show'])->name('user.profile.show');
Route::put('profile', [ProfileController::class, 'update'])->name('user.profile.update');
Route::get('preferences', [PreferenceController::class, 'index'])->name('user.preferences.index');
Route::post('completions', [CompletionsController::class, 'store'])->name('user.completions.store');
Route::delete('completions', [CompletionsController::class, 'destroy'])->name('user.completions.destroy');
});

Route::patch('preferences', [PreferenceController::class, 'update'])->name('user.preferences.update');
Route::get('unsubscribe', [PreferenceController::class, 'unsubscribe'])->name('unsubscribe');

Auth::routes(['register' => false]);

Route::prefix('login')->group(function () {
Route::get('github', [LoginController::class, 'redirectToProvider'])->name('login.github');
Route::get('github/callback', [LoginController::class, 'handleProviderCallback']);
});
Route::view('/', 'welcome')->name('welcome');

Route::view('use-of-data', 'use-of-data')->name('use-of-data');
Route::get('glossary', [GlossaryController::class, 'index'])->name('glossary');
Route::get('tracks', [TrackController::class, 'index'])->name('tracks');

Route::prefix('modules')->name('modules.')->group(function () {
Route::get('/', [ModuleController::class, 'index'])->name('index');
Route::get('{module}/{resourceType}', [ModuleController::class, 'show'])
->name('show')
->where('resourceType', 'free-resources|paid-resources|quizzes|exercises');
});

Route::middleware('auth')->group(function () {
Route::get('wizard', [WizardController::class, 'index'])->name('wizard.index');
Route::post('wizard', [WizardController::class, 'store'])->name('wizard.store');
Route::get('profile', [ProfileController::class, 'show'])->name('user.profile.show');
Route::put('profile', [ProfileController::class, 'update'])->name('user.profile.update');
Route::get('preferences', [PreferenceController::class, 'index'])->name('user.preferences.index');
Route::post('completions', [CompletionsController::class, 'store'])->name('user.completions.store');
Route::delete('completions', [CompletionsController::class, 'destroy'])->name('user.completions.destroy');
});

Route::patch('preferences', [PreferenceController::class, 'update'])->name('user.preferences.update');
Route::get('unsubscribe/{token}', [PreferenceController::class, 'unsubscribe'])->name('unsubscribe');


Auth::routes(['register' => false]);

Route::prefix('login')->group(function () {
Route::get('github', [LoginController::class, 'redirectToProvider'])->name('login.github');
Route::get('github/callback', [LoginController::class, 'handleProviderCallback']);
});
});

0 comments on commit 8b9363f

Please sign in to comment.