Skip to content

Commit cc3d262

Browse files
authored
Feat/horizon (#74)
* fix: Styling issues * feat: Implement Horizon * feat: Send package approved notification email via background process * feat: Delegate Og image generation to background job * feat: Restrict horizon access to admin users * feat: Schedule horizon snapshots * feat: Start the implementation of failed jobs notifications
1 parent 4f2fcd6 commit cc3d262

File tree

16 files changed

+836
-237
lines changed

16 files changed

+836
-237
lines changed

app/Actions/GeneratePackageOgImageAction.php

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,20 @@ public function handle(Package $package): void
2020
$storagePath = storage_path('app/public/package-og-images');
2121
$this->ensureDir($storagePath);
2222

23-
$filename = Str::slug($package->name) . '-' . Str::random(8) . '.jpg';
24-
$fullPath = $storagePath . '/' . $filename;
23+
$filename = Str::slug($package->name).'-'.Str::random(8).'.jpg';
24+
$fullPath = $storagePath.'/'.$filename;
2525

2626
// Binaries
2727
$nodePath = $this->which('node') ?? '/usr/bin/node';
28-
$npmPath = $this->which('npm') ?? '/usr/bin/npm';
28+
$npmPath = $this->which('npm') ?? '/usr/bin/npm';
2929

3030
// Writable Chrome profile/cache dir
3131
$userDataDir = rtrim((string) env('BROWSERSHOT_USER_DATA_DIR', '/var/www/browsershot-cache'), '/');
3232
$this->ensureDir($userDataDir);
3333

3434
// Ensure the env seen by the child process points to our writable dir
3535
foreach (['HOME', 'XDG_CONFIG_HOME', 'XDG_CACHE_HOME'] as $var) {
36-
putenv($var . '=' . $userDataDir);
36+
putenv($var.'='.$userDataDir);
3737
$_ENV[$var] = $userDataDir;
3838
$_SERVER[$var] = $userDataDir;
3939
}
@@ -79,12 +79,12 @@ public function handle(Package $package): void
7979
->usingFileName($filename)
8080
->toMediaCollection('og-images', 'package-og-images');
8181

82-
Log::info('OG Image Generated: ' . $package->getFirstMediaUrl('og-images'));
82+
Log::info('OG Image Generated: '.$package->getFirstMediaUrl('og-images'));
8383
} else {
8484
Log::error('OG image file missing after save', ['path' => $fullPath]);
8585
}
8686
} catch (\Throwable $e) {
87-
Log::error('OG Image Generation Error: ' . $e->getMessage(), ['exception' => $e]);
87+
Log::error('OG Image Generation Error: '.$e->getMessage(), ['exception' => $e]);
8888
throw $e;
8989
}
9090
}
@@ -99,6 +99,7 @@ private function ensureDir(string $dir): void
9999
private function which(string $bin): ?string
100100
{
101101
$path = @exec(sprintf('command -v %s', escapeshellarg($bin)));
102+
102103
return $path ?: null;
103104
}
104105

app/Http/Controllers/PackageController.php

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,14 @@
22

33
namespace App\Http\Controllers;
44

5-
use App\Actions\GeneratePackageOgImageAction;
65
use App\Http\Resources\Admin\CategoryResource;
76
use App\Http\Resources\PackageResource;
7+
use App\Jobs\GenerateOgImageForPackageJob;
88
use App\Models\Category;
99
use App\Models\Package;
1010
use App\Queries\PackageQuery;
1111
use App\Services\GitHubService;
1212
use Illuminate\Http\Request;
13-
use Illuminate\Support\Facades\Log;
1413
use Inertia\Inertia;
1514

1615
class PackageController extends Controller
@@ -50,16 +49,7 @@ public function show(string $slug)
5049
->firstOrFail();
5150

5251
if (! $package->hasMedia('og-images')) {
53-
try {
54-
app(GeneratePackageOgImageAction::class)->handle($package);
55-
56-
$package->load('media');
57-
} catch (\Exception $e) {
58-
Log::error('Failed to generate OG image in controller', [
59-
'package_id' => $package->id,
60-
'error' => $e->getMessage(),
61-
]);
62-
}
52+
GenerateOgImageForPackageJob::dispatch($package);
6353
}
6454

6555
return Inertia::render('Package', [
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
namespace App\Jobs;
4+
5+
use App\Actions\GeneratePackageOgImageAction;
6+
use App\Models\Package;
7+
use Illuminate\Contracts\Queue\ShouldQueue;
8+
use Illuminate\Foundation\Queue\Queueable;
9+
use Illuminate\Support\Facades\Log;
10+
11+
class GenerateOgImageForPackageJob implements ShouldQueue
12+
{
13+
use Queueable;
14+
15+
/**
16+
* Create a new job instance.
17+
*/
18+
public function __construct(public readonly Package $package) {}
19+
20+
/**
21+
* Execute the job.
22+
*/
23+
public function handle(): void
24+
{
25+
try {
26+
app(GeneratePackageOgImageAction::class)->handle($this->package);
27+
28+
$this->package->load('media');
29+
} catch (\Exception $e) {
30+
Log::error('Failed to generate OG image in controller', [
31+
'package_id' => $this->package->id,
32+
'error' => $e->getMessage(),
33+
]);
34+
}
35+
}
36+
37+
public function tags()
38+
{
39+
return [
40+
'OgImageGeneration',
41+
'Package:'.$this->package->id,
42+
];
43+
}
44+
}

app/Models/Package.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
class Package extends Model implements HasMedia
3333
{
3434
use Filterable;
35+
3536
// TODO: Fix admin detector in laravel model status package
3637
use HasActiveScope;
3738
use HasSlug;

app/Notifications/PackageApprovedNotification.php

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44

55
use App\Models\PackageSubmission;
66
use Illuminate\Bus\Queueable;
7+
use Illuminate\Contracts\Queue\ShouldQueue;
78
use Illuminate\Notifications\Messages\MailMessage;
89
use Illuminate\Notifications\Notification;
910

10-
class PackageApprovedNotification extends Notification
11+
class PackageApprovedNotification extends Notification implements ShouldQueue
1112
{
1213
use Queueable;
1314

@@ -42,16 +43,4 @@ public function toMail(object $notifiable): MailMessage
4243
->line('Thank you for contributing to the Laravel community!')
4344
->salutation('The Laravel Hub Team');
4445
}
45-
46-
/**
47-
* Get the array representation of the notification.
48-
*
49-
* @return array<string, mixed>
50-
*/
51-
public function toArray(object $notifiable): array
52-
{
53-
return [
54-
//
55-
];
56-
}
5746
}

app/Providers/AppServiceProvider.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
use Illuminate\Cache\RateLimiting\Limit;
88
use Illuminate\Database\Eloquent\Model;
99
use Illuminate\Http\Resources\Json\JsonResource;
10+
use Illuminate\Queue\Events\JobFailed;
11+
use Illuminate\Support\Facades\Queue;
1012
use Illuminate\Support\Facades\RateLimiter;
1113
use Illuminate\Support\Facades\Request;
1214
use Illuminate\Support\Facades\URL;
@@ -40,6 +42,10 @@ public function boot(): void
4042

4143
$this->configurePasswordRateLimiting();
4244

45+
Queue::failing(function (JobFailed $job) {
46+
// TODO: Send email
47+
});
48+
4349
}
4450

4551
protected function configurePasswordRateLimiting(): void
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace App\Providers;
4+
5+
use Illuminate\Support\Facades\Auth;
6+
use Illuminate\Support\Facades\Gate;
7+
use Laravel\Horizon\Horizon;
8+
use Laravel\Horizon\HorizonApplicationServiceProvider;
9+
10+
class HorizonServiceProvider extends HorizonApplicationServiceProvider
11+
{
12+
/**
13+
* Bootstrap any application services.
14+
*/
15+
public function boot(): void
16+
{
17+
parent::boot();
18+
19+
// Horizon::routeSmsNotificationsTo('15556667777');
20+
Horizon::routeMailNotificationsTo('[email protected]');
21+
// Horizon::routeSlackNotificationsTo('slack-webhook-url', '#channel');
22+
}
23+
24+
/**
25+
* Register the Horizon gate.
26+
*
27+
* This gate determines who can access Horizon in non-local environments.
28+
*/
29+
protected function gate(): void
30+
{
31+
Gate::define('viewHorizon', function ($user = null) {
32+
return Auth::check() && Auth::user()->is_admin;
33+
});
34+
}
35+
}

bootstrap/app.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
->withSchedule(function (Schedule $schedule) {
3131
$schedule->command('app:publish-scheduled-posts')->hourly();
3232
$schedule->command('disposable:update')->weekly();
33+
$schedule->command('horizon:snapshot')->everyFiveMinutes();
3334
})
3435
->withExceptions(function (Exceptions $exceptions) {
3536
Integration::handles($exceptions);

bootstrap/providers.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
return [
44
App\Providers\AppServiceProvider::class,
55
App\Providers\Filament\AdminPanelProvider::class,
6+
App\Providers\HorizonServiceProvider::class,
67
];

composer.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"filament/spatie-laravel-settings-plugin": "^3.2",
1919
"inertiajs/inertia-laravel": "^2.0",
2020
"laravel/framework": "^12.0",
21+
"laravel/horizon": "^5.33",
2122
"laravel/octane": "^2.8",
2223
"laravel/sanctum": "^4.0",
2324
"laravel/scout": "^10.13",
@@ -79,12 +80,12 @@
7980
],
8081
"dev": [
8182
"Composer\\Config::disableProcessTimeout",
82-
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite"
83+
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan horizon\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,horizon,logs,vite"
8384
],
8485
"dev:ssr": [
8586
"npm run build:ssr",
8687
"Composer\\Config::disableProcessTimeout",
87-
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"php artisan inertia:start-ssr\" --names=server,queue,logs,ssr"
88+
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan horizon\" \"php artisan pail --timeout=0\" \"php artisan inertia:start-ssr\" --names=server,horizon,logs,ssr"
8889
],
8990
"format": [
9091
"@pint",

0 commit comments

Comments
 (0)