Skip to content

Commit

Permalink
feat: gallery dynamic meta image (#188)
Browse files Browse the repository at this point in the history
  • Loading branch information
alfonsobries authored Oct 24, 2023
1 parent 071c493 commit 393c45e
Show file tree
Hide file tree
Showing 20 changed files with 953 additions and 316 deletions.
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,7 @@ DISCORD_INVITE_API_URL=https://discord.com/api/v9/invites/MJyWKkCJ5k?with_counts
TELESCOPE_ENABLED=false

2FA_ENABLED=true

BROWSERSHOT_NODE_BINARY="/usr/local/bin/node"
BROWSERSHOT_NPM_BINARY="/usr/local/bin/npm"
BROWSERSHOT_TIMEOUT=60
13 changes: 13 additions & 0 deletions SETUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,19 @@ When you run `composer db:dev` command, you'll notice there are several users se

You can always create a new admin user by running `php artisan make:admin` CLI command.

## Dynamic Meta Images

Dashbrd utilizes [`spatie/browsershot`](https://spatie.be/docs/browsershot/v2) to generate dynamic meta images for the galleries section. For this package to function properly, Node 7.6.0 or higher, along with the Puppeteer Node library, are required.

You need to adjust the following .env variables according to your server configuration:

```plaintext
BROWSERSHOT_NODE_BINARY="/usr/local/bin/node"
BROWSERSHOT_NPM_BINARY="/usr/local/bin/npm"
```

For details on driver requirements and installation, [visit here](https://spatie.be/docs/browsershot/v2/requirements).

## Reports

When a model gets reported (Gallery, Collection, NFT), this creates an entry in the Admin panel to review. Depending on your needs, you may want to be notified as well to quickly respond to new reports. For this reason, it's possible to add a webhook to get reports pushed to Slack as well. You can adjust the `.env` as follows to achieve this:
Expand Down
56 changes: 56 additions & 0 deletions app/Console/Commands/PruneMetaImages.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

declare(strict_types=1);

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;

class PruneMetaImages extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'prune-meta-images';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Removes unused meta images';

/**
* Execute the console command.
*/
public function handle(): int
{
$validImages = array_map(fn ($item) => $item->image_name, DB::select(get_query('gallery.get_meta_images')));
// Convert to associative array so we can use `isset`
$validImages = array_combine($validImages, $validImages);

$directory = storage_path(sprintf('meta/galleries/'));

if (is_dir($directory)) {
// Open directory
if ($handle = opendir($directory)) {
// Loop through each file in the directory
while (false !== ($file = readdir($handle))) {
// Check if file is a PNG and not in validImages
if (pathinfo($file, PATHINFO_EXTENSION) === 'png' && ! isset($validImages[$file])) {
// Delete the file
unlink($directory.$file);
}
}

// Close directory handle
closedir($handle);
}
}

return Command::SUCCESS;
}
}
5 changes: 5 additions & 0 deletions app/Console/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use App\Console\Commands\MarketData\FetchPriceHistory;
use App\Console\Commands\MarketData\UpdateTokenDetails;
use App\Console\Commands\MarketData\VerifySupportedCurrencies;
use App\Console\Commands\PruneMetaImages;
use App\Console\Commands\SyncSpamContracts;
use App\Console\Commands\UpdateCollectionsFiatValue;
use App\Console\Commands\UpdateDiscordMembers;
Expand Down Expand Up @@ -122,6 +123,10 @@ private function scheduleJobsForGalleries(Schedule $schedule): void
->withoutOverlapping()
->hourlyAt(2);

$schedule
->command(PruneMetaImages::class)
->daily();

$schedule
->command(UpdateGalleriesValue::class)
->withoutOverlapping()
Expand Down
2 changes: 1 addition & 1 deletion app/Http/Controllers/GalleryController.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public function view(Request $request, Gallery $gallery): Response
])->withViewData([
'title' => trans('metatags.galleries.view.title', ['name' => $gallery->name]),
'description' => trans('metatags.galleries.view.description', ['name' => $gallery->name]),
'image' => trans('metatags.galleries.view.image'),
'image' => route('galleries.meta-image', ['gallery' => $gallery->slug]),
]);
}

Expand Down
100 changes: 100 additions & 0 deletions app/Http/Controllers/MetaImageController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?php

declare(strict_types=1);

namespace App\Http\Controllers;

use App\Models\Gallery;
use Illuminate\Support\Facades\Cache;
use Spatie\Browsershot\Browsershot;
use Spatie\Image\Image;
use Spatie\Image\Manipulations;
use Symfony\Component\HttpFoundation\BinaryFileResponse;

class MetaImageController extends Controller
{
public function __invoke(Gallery $gallery): BinaryFileResponse
{
$imagePath = $this->getImagePath($gallery);

$this->generateMetaImage($gallery, $imagePath);

return response()->file($imagePath);
}

private function generateMetaImage(Gallery $gallery, string $imagePath): void
{
Cache::lock($gallery->slug.'_meta_image', config('dashbrd.browsershot.timeout') + 15)->get(function () use ($gallery, $imagePath) {
if ($this->shouldGenerateMetaImage($imagePath)) {
$screenshotPath = $this->takeScreenshot($gallery);

$this->storeMetaImage($imagePath, $screenshotPath);
}
});
}

private function shouldGenerateMetaImage(string $imagePath): bool
{
return ! file_exists($imagePath);
}

private function getImagePath(Gallery $gallery): string
{
return storage_path(sprintf('meta/galleries/%s.png', $this->getImageName($gallery)));
}

/**
* IMPORTANT: Ensure this method is in sync with the query in `queries/gallery.get_meta_images.sql`
*/
private function getImageName(Gallery $gallery): string
{
$parts[] = $gallery->nfts()->orderByPivot('order_index', 'asc')->limit(4)->pluck('id')->join('.');

$parts[] = $gallery->nfts()->count();

$parts[] = $gallery->name;

return $gallery->slug.'_'.md5(implode('_', $parts));
}

private function takeScreenshot(Gallery $gallery): string
{
$tempPath = storage_path('tmp/'.$gallery->slug.'_screenshot.png');

app(Browsershot::class)->url(route('galleries.view', ['gallery' => $gallery->slug]))
->windowSize(1480, 768)
->timeout(config('dashbrd.browsershot.timeout'))
->waitForFunction("document.querySelectorAll('[data-testid=GalleryNfts__nft]').length > 0 && Array.from(document.querySelectorAll('[data-testid=GalleryNfts__nft]')).slice(0, 4).every(el => ! el.querySelector('[data-testid=Skeleton]'))")
->setNodeBinary(config('dashbrd.browsershot.node_binary'))
->setNpmBinary(config('dashbrd.browsershot.npm_binary'))
->save($tempPath);

$this->resizeScreenshot($tempPath);

return $tempPath;
}

private function resizeScreenshot(string $screenshotPath): void
{
$image = Image::load($screenshotPath);

$image->width(1006)->crop(Manipulations::CROP_TOP, 1006, 373);

$image->save($screenshotPath);
}

private function storeMetaImage(string $imagePath, string $screenshotPath): string
{
$template = Image::load(resource_path('images/gallery/gallery_template.png'));

$template
->watermark($screenshotPath)
->watermarkPosition(Manipulations::POSITION_BOTTOM);

$template->save($imagePath);

unlink($screenshotPath);

return $imagePath;
}
}
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"monolog/monolog": "^3.4",
"sentry/sentry-laravel": "^3.8",
"simplito/elliptic-php": "^1.0",
"spatie/browsershot": "^3.59",
"spatie/laravel-data": "^3.9",
"spatie/laravel-medialibrary": "^10.13",
"spatie/laravel-permission": "^5.11",
Expand Down
70 changes: 68 additions & 2 deletions composer.lock

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

5 changes: 5 additions & 0 deletions config/dashbrd.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
use App\Enums\Period;

return [
'browsershot' => [
'timeout' => (int) env('BROWSERSHOT_TIMEOUT', 60),
'node_binary' => env('BROWSERSHOT_NODE_BINARY', '/usr/local/bin/node'),
'npm_binary' => env('BROWSERSHOT_NPM_BINARY', '/usr/local/bin/npm'),
],
'contact_email' => env('CONTACT_EMAIL', 'support@ardenthq.com'),

'features' => [
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@
"file-saver": "^2.0.5",
"i18next": "^22.5.1",
"lru-cache": "^10.0.1",
"puppeteer": "^21.4.0",
"react-chartjs-2": "^5.2.0",
"react-i18next": "^12.3.1",
"react-in-viewport": "1.0.0-alpha.30",
Expand Down
Loading

0 comments on commit 393c45e

Please sign in to comment.