Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Monthly Resource Digest #594

Merged
merged 40 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
4ef35eb
Add migration, email files. Update as necessary
MarcyLina Aug 9, 2024
b26e83d
create jobs table
MarcyLina Aug 9, 2024
5239217
Add input
MarcyLina Aug 9, 2024
a2d59b9
Cleanup
MarcyLina Aug 9, 2024
d834360
Wire up the input
MarcyLina Aug 9, 2024
c3cf138
Format email template
MarcyLina Aug 9, 2024
3fcb351
WIP unsubscribing
MarcyLina Aug 9, 2024
4d348fe
Dusting
MarcyLina Aug 9, 2024
762a4f7
Ignore Dusting commit in git blame
MarcyLina Aug 9, 2024
90e24da
fix routes
MarcyLina Aug 9, 2024
f726790
Dusting
MarcyLina Aug 9, 2024
c8ba5ea
Ignore Dusting commit in git blame
MarcyLina Aug 9, 2024
8b9363f
User can unsubscribe
MarcyLina Aug 9, 2024
3d09d3a
Dusting
MarcyLina Aug 9, 2024
94c620e
Ignore Dusting commit in git blame
MarcyLina Aug 9, 2024
ea0aa76
Cleanup
MarcyLina Aug 16, 2024
4bf7978
Dusting
MarcyLina Aug 16, 2024
5f06e88
Ignore Dusting commit in git blame
MarcyLina Aug 16, 2024
4d65a80
Add dpace between routes
MarcyLina Aug 16, 2024
5cf85c7
Dusting
MarcyLina Aug 16, 2024
1383464
Ignore Dusting commit in git blame
MarcyLina Aug 16, 2024
e802d72
Change directive to tag
MarcyLina Aug 16, 2024
488c87a
More directives to tags
MarcyLina Aug 16, 2024
47ae386
Add testing
MarcyLina Sep 20, 2024
084a471
Merge branch 'main' into mla/monthly-resource-digest
MarcyLina Sep 20, 2024
7ab5d6e
Update app/Http/Controllers/PreferenceController.php
MarcyLina Sep 27, 2024
f2174ac
Change to ternary
MarcyLina Sep 27, 2024
39ee2d6
Dusting
MarcyLina Sep 27, 2024
4076886
Ignore Dusting commit in git blame
MarcyLina Sep 27, 2024
267f972
move route. remove conditional.
MarcyLina Sep 27, 2024
08ae1ae
Add track to validation
MarcyLina Sep 27, 2024
23cc791
Add update call
MarcyLina Sep 27, 2024
0fae228
Dusting
MarcyLina Sep 27, 2024
6a2d9f7
Ignore Dusting commit in git blame
MarcyLina Sep 27, 2024
431eb19
Shorten construct method
MarcyLina Sep 27, 2024
bf119c7
Dusting
MarcyLina Sep 27, 2024
d1c8139
Ignore Dusting commit in git blame
MarcyLina Sep 27, 2024
5a6c1a6
Move column
MarcyLina Sep 27, 2024
9a00c3e
Dusting
MarcyLina Sep 27, 2024
0740f18
Ignore Dusting commit in git blame
MarcyLina Sep 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .git-blame-ignore-revs
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# Dusting
c380d69ba5f0813410af5eba98c3d3b83e4c288f
b7707e028a9838565a51d617382b0f2690bcd5cb
4d348fe5b2d842f7ec602331da5da10fc926fd61
f726790a2e2c09018532c8e7397359d443b63e70
3d09d3acf18dc8e5cf70cc30a7e42650e98575f9
4bf79783e522ac781fcb7b12cde179c306c85b60
5cf85c749558b2871c1011ef177e37f59f4421be
44 changes: 44 additions & 0 deletions app/Console/Commands/SendResourceDigestEmail.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

namespace App\Console\Commands;

use App\Mail\ResourceDigestEmail;
use App\Models\Resource;
use App\Models\User;
use Carbon\Carbon;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;

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

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 {
$locale = $user->locale ?? 'en';
$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.');
}
}
5 changes: 4 additions & 1 deletion app/Console/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

namespace App\Console;

use App\Console\Commands\SendResourceDigestEmail;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
protected $commands = [
//
SendResourceDigestEmail::class,
];

/**
Expand All @@ -18,6 +19,8 @@ protected function schedule(Schedule $schedule): void
{
$schedule->command('resource:expired -N')
->weeklyOn(Schedule::FRIDAY, '06:00');

$schedule->command('mail:send-resource-digest-email')->monthly();
}

/**
Expand Down
23 changes: 20 additions & 3 deletions app/Http/Controllers/PreferenceController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use App\Facades\Preferences;
use App\Preferences\ResourceLanguagePreference;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\View\View;

class PreferenceController extends Controller
Expand All @@ -24,10 +25,26 @@ public function update(Request $request)
collect($request->only(Preferences::getValidKeys()))->filter()
);

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

auth()->user()->save();
if ($user) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the user.preferences.update route be moved up into the auth middleware group? If so that would avoid the need for this if condition.

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

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

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think track_id and digest-subscriber can be added to the validate check before updating.


if ($user->is_subscriber) {
$user->unsubscribe_token = Str::random(60);
} else {
$user->unsubscribe_token = null;
}
MarcyLina marked this conversation as resolved.
Show resolved Hide resolved

$user->save();
}

session()->flash('toast', 'Your preferences were saved.');
Expand Down
21 changes: 21 additions & 0 deletions app/Http/Controllers/SubscriptionController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace App\Http\Controllers;

use App\Models\User;

class SubscriptionController extends Controller
{
public function destroy($locale, $token)
{
$user = User::where('unsubscribe_token', $token)->first();

$user->is_subscriber = false;
$user->unsubscribe_token = null;
$user->save();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$user->is_subscriber = false;
$user->unsubscribe_token = null;
$user->save();
$user->update([
'is_subscriber' => false,
'unsubscribe_token' => null,
]);

When I can, I prefer using update because it feels more concise (despite being one more line) and tidy.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd do this in a single $user->update() call.


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

return redirect()->route('welcome', ['locale' => $locale]);
}
}
51 changes: 51 additions & 0 deletions app/Mail/ResourceDigestEmail.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;

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

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

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be rewritten like the following and then the properties on lines 16-18 can be removed.

Suggested change
}
public function __construct(
public $resources,
public $user,
public $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 [];
}
}
5 changes: 4 additions & 1 deletion app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@ class User extends Authenticatable
];

protected $hidden = [
'password', 'remember_token', 'github_token',
'password',
'remember_token',
'github_token',
];

protected $casts = [
'email_verified_at' => 'datetime',
'preferences' => 'object',
'is_subscriber' => 'boolean',
];

public function track()
Expand Down
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(): void
{
Schema::table('users', function (Blueprint $table) {
$table->boolean('is_subscriber')->default(false);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like to add ->after() and specify a column that I want the new column to appear after so the timestamps (created_at, etc.) are always last.

This option is only available in MySQL and MariaDB, so we couldn't use it for a Postgres project.

});
}

public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('is_subscriber');
});
}
};
26 changes: 26 additions & 0 deletions database/migrations/2024_08_09_164523_create_jobs_table.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

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

return new class extends Migration
{
public function up(): void
{
Schema::create('jobs', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('queue')->index();
$table->longText('payload');
$table->unsignedTinyInteger('attempts');
$table->unsignedInteger('reserved_at')->nullable();
$table->unsignedInteger('available_at');
$table->unsignedInteger('created_at');
});
}

public function down(): void
{
Schema::dropIfExists('jobs');
}
};
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');
});
}
};
8 changes: 7 additions & 1 deletion database/seeders/ContentSeeder.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,17 @@ class ContentSeeder extends Seeder
public function run(): void
{
User::factory()->create([
'email' => 'testuser@tighten.co',
'email' => 'testadmin@tighten.co',
'password' => bcrypt('password'),
'role' => 'admin',
]);

User::factory()->create([
'email' => '[email protected]',
'password' => bcrypt('password'),
'role' => 'user',
]);

$seedsDirectory = config('seeder.directory', 'database/json');

$seeds = $this->getSeedFiles($seedsDirectory);
Expand Down
14 changes: 6 additions & 8 deletions resources/views/components/button/primary.blade.php
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
@props([
'href' => null,
'href' => null,
])

<x-button.base
:href="$href"
{{ $attributes->merge([
'class' => 'focus:bg-white focus:text-purple bg-purple hover:bg-white hover:text-purple hover:no-underline focus:outline-none border-purple active:bg-white active:text-purple'
]) }}
>
{{ $slot }}
<x-button.base :href="$href" {{ $attributes->merge([
'class' => 'focus:bg-white focus:text-purple bg-purple hover:bg-white hover:text-purple hover:no-underline focus:outline-none hover:border-purple focus:border-purple active:bg-white active:text-purple'
]) }}
>
{{ $slot }}
</x-button.base>
23 changes: 11 additions & 12 deletions resources/views/emails/rejected-resource.blade.php
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
@component('mail::message')
Hello {{ $user->name }},
<x-mail::message>
Hello {{ $user->name }},

The resource you suggested on [Onramp]({{ route('welcome', ['locale' => 'en']) }}) has been rejected by one of our reviewers:
The resource you suggested on [Onramp]({{ route('welcome', ['locale' => 'en']) }}) has been rejected by one of our reviewers:

@component('mail::panel')
{{ $resource->name }}<br>
[Resource URL]({{ $resource->url }})
###### *Submitted on {{ $resource->created_at->format('d-m-Y') }}*
<x-mail::panel>
{{ $resource->name }}<br>
[Resource URL]({{ $resource->url }})
###### *Submitted on {{ $resource->created_at->format('d-m-Y') }}*

**{{ $resource->rejected_reason }}**
@endcomponent
**{{ $resource->rejected_reason }}**
</x-mail::panel>

We thank you for your contribution and encourage you to submit new resources that may be useful to the community.

@endcomponent
We thank you for your contribution and encourage you to submit new resources that may be useful to the community.
</x-mail::message>
19 changes: 19 additions & 0 deletions resources/views/emails/resource-digest.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<x-mail::message>
# Here are the latest resources:

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

@endforeach
</x-mail::panel>

### Happy Coding!

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

<x-mail::button :url="$unsubscribeUrl">
Unsubscribe
</x-mail::button>
</x-mail::message>
Loading
Loading