Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
37 changes: 37 additions & 0 deletions app/Actions/Newsletters/CreateNewsletter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

namespace App\Actions\Newsletters;

use App\Actions\Models\LoadPosts\LoadPublishedPosts;
use App\Contracts\Actions\Newsletters\CreatesNewsletter;
use Spatie\Mailcoach\Domain\Audience\Models\EmailList;
use Spatie\Mailcoach\Domain\Campaign\Models\Campaign;

final class CreateNewsletter implements CreatesNewsletter
{
public function __construct(
private EmailList $emailList,
private string $fromEmail,
) {
}

public function handle(): ?Campaign
{
$postsFromLastWeek = (new LoadPublishedPosts())->handle()->where('publish_date', '>', now()->subWeek())->get();

if ($postsFromLastWeek->isEmpty()) {
return null;
}

$campaign = Campaign::query()->create([
'from_email' => $this->fromEmail,
'subject' => 'Pest Newsletter',
'html' => view('newsletter.content', ['posts' => $postsFromLastWeek])->render(),
'email_list_id' => $this->emailList->getKey(),
]);

return $campaign;
}
}
16 changes: 16 additions & 0 deletions app/Actions/Newsletters/SendNewsletter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace App\Actions\Newsletters;

use App\Contracts\Actions\Newsletters\SendsNewsletter;
use Spatie\Mailcoach\Domain\Campaign\Models\Campaign;

final class SendNewsletter implements SendsNewsletter
{
public function handle(Campaign $campaign): void
{
$campaign->send();
}
}
17 changes: 17 additions & 0 deletions app/Actions/Newsletters/SendTestNewsletter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace App\Actions\Newsletters;

use App\Contracts\Actions\Newsletters\SendsTestNewsletter;
use Spatie\Mailcoach\Domain\Campaign\Models\Campaign;
use Wink\WinkAuthor;

final class SendTestNewsletter implements SendsTestNewsletter
{
public function handle(Campaign $campaign): void
{
$campaign->sendTestMail(WinkAuthor::query()->pluck('email')->all());
}
}
37 changes: 37 additions & 0 deletions app/Console/Commands/BuildNewsletterCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

namespace App\Console\Commands;

use App\Contracts\Actions\Newsletters\CreatesNewsletter;
use App\Contracts\Actions\Newsletters\SendsTestNewsletter;
use App\Jobs\SendNewsletter;
use Illuminate\Console\Command;

final class BuildNewsletterCommand extends Command
{
protected $signature = 'site:newsletter {--force}';

protected $description = 'Build, test and prepare to send this week\'s newsletter';

public function handle(CreatesNewsletter $creator, SendsTestNewsletter $tester): int
{
if (!$campaign = $creator->handle()) {
$this->warn('There was no news to send!');

return Command::SUCCESS;
}

$this->info("View the newsletter in your browser: {$campaign->webviewUrl()}");
$tester->handle($campaign);

if ($this->option('force') || $this->confirm('Are you sure you want to send this newsletter?')) {
$delay = now()->addHours(3);
SendNewsletter::dispatch($campaign)->delay($delay);
$this->info("The newsletter will be sent to all subscribers in {$delay->diffForHumans()}.");
}

return Command::SUCCESS;
}
}
3 changes: 3 additions & 0 deletions app/Console/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace App\Console;

use App\Console\Commands\BuildNewsletterCommand;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
use Spatie\Health\Commands\RunHealthChecksCommand;
Expand All @@ -17,6 +18,8 @@ final class Kernel extends ConsoleKernel
*/
protected function schedule(Schedule $schedule)
{
$schedule->command(BuildNewsletterCommand::class, ['--force' => true])->fridays()->at('17:00');

$schedule->command('mailcoach:calculate-statistics')->everyMinute();
$schedule->command('mailcoach:send-scheduled-campaigns')->everyMinute();
$schedule->command('mailcoach:send-campaign-summary-mail')->hourly();
Expand Down
12 changes: 12 additions & 0 deletions app/Contracts/Actions/Newsletters/CreatesNewsletter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace App\Contracts\Actions\Newsletters;

use Spatie\Mailcoach\Domain\Campaign\Models\Campaign;

interface CreatesNewsletter
{
public function handle(): ?Campaign;
}
12 changes: 12 additions & 0 deletions app/Contracts/Actions/Newsletters/SendsNewsletter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace App\Contracts\Actions\Newsletters;

use Spatie\Mailcoach\Domain\Campaign\Models\Campaign;

interface SendsNewsletter
{
public function handle(Campaign $campaign): void;
}
12 changes: 12 additions & 0 deletions app/Contracts/Actions/Newsletters/SendsTestNewsletter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace App\Contracts\Actions\Newsletters;

use Spatie\Mailcoach\Domain\Campaign\Models\Campaign;

interface SendsTestNewsletter
{
public function handle(Campaign $campaign): void;
}
30 changes: 30 additions & 0 deletions app/Jobs/SendNewsletter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace App\Jobs;

use App\Contracts\Actions\Newsletters\SendsNewsletter;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Spatie\Mailcoach\Domain\Campaign\Models\Campaign;

final class SendNewsletter implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
use SerializesModels;

public function __construct(private Campaign $campaign)
{
}

public function handle(SendsNewsletter $sender): void
{
$sender->handle($this->campaign);
}
}
21 changes: 21 additions & 0 deletions app/Providers/ActionServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,21 @@

namespace App\Providers;

use App\Actions\Newsletters\CreateNewsletter;
use App\Actions\Newsletters\SendNewsletter;
use App\Actions\Newsletters\SendTestNewsletter;
use App\Actions\Resources\ProvidePostResource;
use App\Actions\Subscriptions\CreateSubscription;
use App\Actions\Subscriptions\DeleteSubscription;
use App\Contracts\Actions\Newsletters\CreatesNewsletter;
use App\Contracts\Actions\Newsletters\SendsNewsletter;
use App\Contracts\Actions\Newsletters\SendsTestNewsletter;
use App\Contracts\Actions\Resources\ProvidesPostResource;
use App\Contracts\Actions\Subscriptions\CreatesSubscription;
use App\Contracts\Actions\Subscriptions\DeletesSubscription;
use Illuminate\Foundation\Application;
use Illuminate\Support\ServiceProvider;
use Spatie\Mailcoach\Domain\Audience\Models\EmailList;

final class ActionServiceProvider extends ServiceProvider
{
Expand All @@ -21,5 +29,18 @@ final class ActionServiceProvider extends ServiceProvider
ProvidesPostResource::class => ProvidePostResource::class,
CreatesSubscription::class => CreateSubscription::class,
DeletesSubscription::class => DeleteSubscription::class,
CreatesNewsletter::class => CreateNewsletter::class,
SendsTestNewsletter::class => SendTestNewsletter::class,
SendsNewsletter::class => SendNewsletter::class,
];

public function register(): void
{
$this->app->bind(CreateNewsletter::class, function (Application $app) {
return new CreateNewsletter(
$app->make(EmailList::class),
$app->make('config')->get('mail.from.address'),
);
});
}
}
14 changes: 7 additions & 7 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 7 additions & 6 deletions database/seeders/DatabaseSeeder.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,19 @@ public function run(Application $app): void
{
WinkAuthor::query()->delete();

if ($app->environment('local')) {
AuthorFactory::new()->create(['email' => '[email protected]']);
}

// @phpstan-ignore-next-line
$this->resolve(EmailListSeeder::class)->run();
// @phpstan-ignore-next-line
$this->resolve(TagSeeder::class)->run();

if (app()->environment('local')) {
if ($app->environment('local')) {
$author = AuthorFactory::new()->create([
'email' => '[email protected]',
'name' => 'Luke Downing',
]);

// @phpstan-ignore-next-line
$this->resolve(PostSeeder::class)->run();
$this->resolve(PostSeeder::class)->forAuthor($author)->run();
}
}
}
20 changes: 17 additions & 3 deletions database/seeders/PostSeeder.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,32 @@

namespace Database\Seeders;

use Database\Factories\AuthorFactory;
use Database\Factories\PostFactory;
use Illuminate\Database\Seeder;
use Illuminate\Support\Str;
use Wink\WinkAuthor;
use Wink\WinkTag;

class PostSeeder extends Seeder
{
private ?WinkAuthor $author = null;

public function forAuthor(WinkAuthor $author): self
{
$this->author = $author;

return $this;
}

public function run(): void
{
PostFactory::new()->count(10)->create();
PostFactory::new()->count(30)->hasTags($this->tag('blog'))->create();
PostFactory::new()->count(5)->hasTags($this->tag('blog'))->unpublished()->create();
/** @var WinkAuthor $author */
$author = $this->author ?? AuthorFactory::new()->create();

PostFactory::new()->count(10)->for($author, 'author')->create();
PostFactory::new()->count(30)->for($author, 'author')->hasTags($this->tag('blog'))->create();
PostFactory::new()->count(5)->for($author, 'author')->hasTags($this->tag('blog'))->unpublished()->create();
}

private function tag(string $slug): WinkTag
Expand Down
4 changes: 2 additions & 2 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
<server name="APP_ENV" value="testing"/>
<server name="BCRYPT_ROUNDS" value="4"/>
<server name="CACHE_DRIVER" value="array"/>
<server name="DB_CONNECTION" value="sqlite"/>
<server name="DB_DATABASE" value=":memory:"/>
<server name="DB_CONNECTION" value="sqlite"/>
<server name="DB_DATABASE" value=":memory:"/>
<server name="MAIL_MAILER" value="array"/>
<server name="QUEUE_CONNECTION" value="sync"/>
<server name="SESSION_DRIVER" value="array"/>
Expand Down
13 changes: 13 additions & 0 deletions resources/views/newsletter/content.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<h1>Hey there!</h1>
<p>Here's what's new with Pest this week:</p>
<ul>
@foreach($posts as $post)
<li>
<a href="{{ route('posts.show', $post->slug) }}">
{{ $post->title }}
</a>
</li>
@endforeach
</ul>
<p>Thanks for supporting Pest PHP. Keep testing!</p>
<span>Regards, The Pest Team</span>
Loading