Compare commits

...
Author SHA1 Message Date
db2c7b19ed Merge pull request 'MTM Improve app by leveraging more Laravel code' (#111) from develop into main
Reviewed-on: #111
2026-08-01 20:37:15 +02:00
31c49ac3fc
Adopt Laravel's Image facade for media processing, upgrade Intervention to v4
Laravel 13's Image facade wraps Intervention Image v4 internally, so
switching our upload width probe and resize job/command to it required
bumping intervention/image ^3 -> ^4 (and its intervention/gif ^5
dependency). Removes our own ImageManager container binding and
config/image.php in favour of Laravel's built-in driver resolution.

Also fixes a latent filename mismatch in ProcessMediaJobTest that Pint's
stricter typing on the new Image API turned into a hard TypeError, and
tidies config/flare.php to use imported class names instead of FQCNs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Sorsgn85nw7uQyRMNvzyD
2026-08-01 17:07:38 +01:00
287520ad7b
Force HTTPS URL generation in production
Uses Laravel's built-in URL::forceHttps() so route(), url(), and asset()
always generate https:// links in production, without affecting local dev.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RP5TeZbk9KS754xuJMobjE
2026-07-28 19:20:18 +01:00
2eab5749ea
Update dependencies 2026-07-26 10:57:20 +01:00
e08763c526
Migrate outbound HTTP calls from Guzzle to the Http facade
Replaces every raw GuzzleHttp\Client usage (Nominatim, web.archive.org,
webmention fetch/discover/send, Bridgy syndication, IndieAuth client_id
lookup, contact avatar/h-card fetch, profile image download, and the
CloudConvert screenshot pipeline) with Illuminate\Support\Facades\Http,
and enables Http::preventStrayRequests() globally in tests so any
un-faked outbound call now fails loudly instead of silently hitting the
network.

The CloudConvert retry-until-finished middleware in AppServiceProvider
is replaced by a plain polling loop in SaveScreenshot, which also fixes
a latent bug where the old middleware decoded a response object instead
of its body. Bridgy syndication jobs keep their tries=1/no-retry
semantics unchanged to avoid duplicate publishes. Guzzle's PSR-7 helpers
(Header, UriResolver, Utils) stay in SendWebMentions since Http has no
equivalent for them.

Every affected test file's Guzzle MockHandler/HandlerStack boilerplate
is replaced with Http::fake()/Http::sequence().

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8j7cJhCiYDsGUgB7obKNQ
2026-07-26 09:12:01 +01:00
36 changed files with 1331 additions and 1431 deletions

View file

@ -6,9 +6,9 @@ namespace App\Console\Commands;
use App\Models\Media; use App\Models\Media;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Image\ImageException;
use Illuminate\Support\Facades\Image;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Intervention\Image\Exceptions\DecoderException;
use Intervention\Image\ImageManager;
class ReprocessMediaImages extends Command class ReprocessMediaImages extends Command
{ {
@ -16,7 +16,7 @@ class ReprocessMediaImages extends Command
protected $description = 'Regenerate medium and small image variants using correct aspect-ratio scaling'; protected $description = 'Regenerate medium and small image variants using correct aspect-ratio scaling';
public function handle(ImageManager $manager): void public function handle(): void
{ {
$media = Media::where('type', 'image') $media = Media::where('type', 'image')
->whereNotNull('image_widths') ->whereNotNull('image_widths')
@ -44,10 +44,10 @@ class ReprocessMediaImages extends Command
$this->info("Processing: {$path}"); $this->info("Processing: {$path}");
$image = Image::fromStorage($path, 'public');
try { try {
$file = Storage::disk('public')->get($path); $image->width();
$image = $manager->read($file); } catch (ImageException) {
} catch (DecoderException) {
$this->warn(' Could not decode image, skipping.'); $this->warn(' Could not decode image, skipping.');
continue; continue;
@ -57,11 +57,8 @@ class ReprocessMediaImages extends Command
$extension = array_pop($filenameParts); $extension = array_pop($filenameParts);
$basename = trim(implode('.', $filenameParts), '.'); $basename = trim(implode('.', $filenameParts), '.');
$medium = $image->scale(width: 1000); Storage::disk('public')->put($basename.'-medium.'.$extension, $image->scale(width: 1000)->toBytes());
Storage::disk('public')->put($basename.'-medium.'.$extension, (string) $medium->encode()); Storage::disk('public')->put($basename.'-small.'.$extension, $image->scale(width: 500)->toBytes());
$small = $image->scale(width: 500);
Storage::disk('public')->put($basename.'-small.'.$extension, (string) $small->encode());
$this->info(' Done.'); $this->info(' Done.');
} }

View file

@ -6,11 +6,11 @@ namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Contact; use App\Models\Contact;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\BadResponseException;
use Illuminate\Filesystem\Filesystem; use Illuminate\Filesystem\Filesystem;
use Illuminate\Http\Client\RequestException;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Http;
use Illuminate\View\View; use Illuminate\View\View;
class ContactsController extends Controller class ContactsController extends Controller
@ -113,14 +113,13 @@ class ContactsController extends Controller
$avatar = null; $avatar = null;
$contact = Contact::findOrFail($contactId); $contact = Contact::findOrFail($contactId);
if ($contact->homepage !== null && mb_strlen($contact->homepage) !== 0) { if ($contact->homepage !== null && mb_strlen($contact->homepage) !== 0) {
$client = resolve(Client::class);
try { try {
$response = $client->get($contact->homepage); $response = Http::throw()->get($contact->homepage);
} catch (BadResponseException $e) { } catch (RequestException $e) {
return redirect('/admin/contacts/'.$contactId.'/edit') return redirect('/admin/contacts/'.$contactId.'/edit')
->with('error', 'Bad resposne from contact’s homepage'); ->with('error', 'Bad resposne from contact’s homepage');
} }
$mf2 = \Mf2\parse((string) $response->getBody(), $contact->homepage); $mf2 = \Mf2\parse($response->body(), $contact->homepage);
foreach ($mf2['items'] as $microformat) { foreach ($mf2['items'] as $microformat) {
if (Arr::get($microformat, 'type.0') === 'h-card') { if (Arr::get($microformat, 'type.0') === 'h-card') {
$avatarURL = Arr::get($microformat, 'properties.photo.0.value'); $avatarURL = Arr::get($microformat, 'properties.photo.0.value');
@ -129,8 +128,8 @@ class ContactsController extends Controller
} }
if ($avatarURL !== null) { if ($avatarURL !== null) {
try { try {
$avatar = $client->get($avatarURL); $avatar = Http::throw()->get($avatarURL);
} catch (BadResponseException $e) { } catch (RequestException $e) {
return redirect('/admin/contacts/'.$contactId.'/edit') return redirect('/admin/contacts/'.$contactId.'/edit')
->with('error', 'Unable to download avatar'); ->with('error', 'Unable to download avatar');
} }
@ -141,7 +140,7 @@ class ContactsController extends Controller
if ($filesystem->isDirectory($directory) === false) { if ($filesystem->isDirectory($directory) === false) {
$filesystem->makeDirectory($directory); $filesystem->makeDirectory($directory);
} }
$filesystem->put($directory.'/image', $avatar->getBody()); $filesystem->put($directory.'/image', $avatar->body());
return view('admin.contacts.getavatarsuccess', [ return view('admin.contacts.getavatarsuccess', [
'homepage' => parse_url($contact->homepage, PHP_URL_HOST), 'homepage' => parse_url($contact->homepage, PHP_URL_HOST),

View file

@ -5,13 +5,12 @@ declare(strict_types=1);
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Services\TokenService; use App\Services\TokenService;
use Exception;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Uri; use GuzzleHttp\Psr7\Uri;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use Illuminate\View\View; use Illuminate\View\View;
use Random\RandomException; use Random\RandomException;
@ -199,15 +198,13 @@ class IndieAuthController extends Controller
} }
// Otherwise we need to check the redirect_uri is in the client_id's redirect_uris // Otherwise we need to check the redirect_uri is in the client_id's redirect_uris
$guzzle = resolve(Client::class);
try { try {
$clientInfo = $guzzle->get($clientId); $clientInfo = Http::throw()->get($clientId);
} catch (Exception) { } catch (\Throwable) {
return false; return false;
} }
$clientInfoParsed = \Mf2\parse($clientInfo->getBody()->getContents(), $clientId); $clientInfoParsed = \Mf2\parse($clientInfo->body(), $clientId);
$redirectUris = $clientInfoParsed['rels']['redirect_uri'] ?? []; $redirectUris = $clientInfoParsed['rels']['redirect_uri'] ?? [];

View file

@ -13,9 +13,10 @@ use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Response; use Illuminate\Http\Response;
use Illuminate\Http\UploadedFile; use Illuminate\Http\UploadedFile;
use Illuminate\Image\ImageException;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Image;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Intervention\Image\ImageManager;
use Ramsey\Uuid\Uuid; use Ramsey\Uuid\Uuid;
class MicropubMediaController extends Controller class MicropubMediaController extends Controller
@ -111,12 +112,9 @@ class MicropubMediaController extends Controller
$filename = Storage::disk('local')->putFile('media', $file); $filename = Storage::disk('local')->putFile('media', $file);
/** @var ImageManager $manager */
$manager = resolve(ImageManager::class);
try { try {
$image = $manager->read($request->file('file')); $width = Image::fromUpload($request->file('file'))->width();
$width = $image->width(); } catch (ImageException) {
} catch (Exception) {
// not an image // not an image
$width = null; $width = null;
} }

View file

@ -4,14 +4,14 @@ declare(strict_types=1);
namespace App\Jobs; namespace App\Jobs;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Filesystem\FileNotFoundException; use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\FileSystem\FileSystem; use Illuminate\FileSystem\FileSystem;
use Illuminate\Http\Client\RequestException;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Http;
class DownloadWebMention implements ShouldQueue class DownloadWebMention implements ShouldQueue
{ {
@ -29,15 +29,15 @@ class DownloadWebMention implements ShouldQueue
/** /**
* Execute the job. * Execute the job.
* *
* @throws GuzzleException * @throws RequestException
* @throws FileNotFoundException * @throws FileNotFoundException
*/ */
public function handle(Client $guzzle): void public function handle(): void
{ {
$response = $guzzle->request('GET', $this->source); // 4XX and 5XX responses should throw so Laravel can catch and
// 4XX and 5XX responses should get Guzzle to throw an exception, // retry these automatically.
// Laravel should catch and retry these automatically. $response = Http::throw()->get($this->source);
if ($response->getStatusCode() === 200) { if ($response->status() === 200) {
$filesystem = new FileSystem; $filesystem = new FileSystem;
$filename = storage_path('HTML').'/'.$this->createFilenameFromURL($this->source); $filename = storage_path('HTML').'/'.$this->createFilenameFromURL($this->source);
// backup file first // backup file first
@ -56,7 +56,7 @@ class DownloadWebMention implements ShouldQueue
// save new HTML // save new HTML
$filesystem->put( $filesystem->put(
$filename, $filename,
(string) $response->getBody() $response->body()
); );
// remove backup if the same // remove backup if the same
if ($filesystem->exists($filenameBackup)) { if ($filesystem->exists($filenameBackup)) {

View file

@ -5,14 +5,13 @@ declare(strict_types=1);
namespace App\Jobs; namespace App\Jobs;
use App\Models\Like; use App\Models\Like;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Http;
use Jonnybarnes\WebmentionsParser\Authorship; use Jonnybarnes\WebmentionsParser\Authorship;
use Jonnybarnes\WebmentionsParser\Exceptions\AuthorshipParserException; use Jonnybarnes\WebmentionsParser\Exceptions\AuthorshipParserException;
@ -32,13 +31,11 @@ class ProcessLike implements ShouldQueue
/** /**
* Execute the job. * Execute the job.
*
* @throws GuzzleException
*/ */
public function handle(Client $client, Authorship $authorship): int public function handle(Authorship $authorship): int
{ {
$response = $client->request('GET', $this->like->url); $response = Http::throw()->get($this->like->url);
$mf2 = \Mf2\parse((string) $response->getBody(), $this->like->url); $mf2 = \Mf2\parse($response->body(), $this->like->url);
if (Arr::has($mf2, 'items.0.properties.content')) { if (Arr::has($mf2, 'items.0.properties.content')) {
$this->like->content = $mf2['items'][0]['properties']['content'][0]['html']; $this->like->content = $mf2['items'][0]['properties']['content'][0]['html'];
} }

View file

@ -7,11 +7,11 @@ namespace App\Jobs;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Image\ImageException;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Image;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Intervention\Image\Exceptions\DecoderException;
use Intervention\Image\ImageManager;
class ProcessMedia implements ShouldQueue class ProcessMedia implements ShouldQueue
{ {
@ -30,15 +30,16 @@ class ProcessMedia implements ShouldQueue
/** /**
* Execute the job. * Execute the job.
*/ */
public function handle(ImageManager $manager): void public function handle(): void
{ {
// Load file // Load file
$file = Storage::disk('local')->get($this->filename); $file = Storage::disk('local')->get($this->filename);
// Open file // Open file
$image = Image::fromStorage($this->filename, 'local');
try { try {
$image = $manager->read($file); $width = $image->width();
} catch (DecoderException) { } catch (ImageException) {
// not an image; delete file and end job // not an image; delete file and end job
Storage::disk('local')->delete($this->filename); Storage::disk('local')->delete($this->filename);
@ -49,18 +50,15 @@ class ProcessMedia implements ShouldQueue
Storage::disk('public')->put($this->filename, $file); Storage::disk('public')->put($this->filename, $file);
// Create smaller versions if necessary // Create smaller versions if necessary
if ($image->width() > 1000) { if ($width > 1000) {
$filenameParts = explode('.', $this->filename); $filenameParts = explode('.', $this->filename);
$extension = array_pop($filenameParts); $extension = array_pop($filenameParts);
// the following achieves this data flow // the following achieves this data flow
// foo.bar.png => ['foo', 'bar', 'png'] => ['foo', 'bar'] => foo.bar // foo.bar.png => ['foo', 'bar', 'png'] => ['foo', 'bar'] => foo.bar
$basename = trim(implode('.', $filenameParts), '.'); $basename = trim(implode('.', $filenameParts), '.');
$medium = $image->scale(width: 1000); Storage::disk('public')->put($basename.'-medium.'.$extension, $image->scale(width: 1000)->toBytes());
Storage::disk('public')->put($basename.'-medium.'.$extension, (string) $medium->encode()); Storage::disk('public')->put($basename.'-small.'.$extension, $image->scale(width: 500)->toBytes());
$small = $image->scale(width: 500);
Storage::disk('public')->put($basename.'-small.'.$extension, (string) $small->encode());
} }
// Now we can delete the locally saved image // Now we can delete the locally saved image

View file

@ -7,13 +7,12 @@ namespace App\Jobs;
use App\Exceptions\RemoteContentNotFoundException; use App\Exceptions\RemoteContentNotFoundException;
use App\Models\Note; use App\Models\Note;
use App\Models\WebMention; use App\Models\WebMention;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\RequestException;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Http;
use Jonnybarnes\WebmentionsParser\Exceptions\InvalidMentionException; use Jonnybarnes\WebmentionsParser\Exceptions\InvalidMentionException;
use Jonnybarnes\WebmentionsParser\Parser; use Jonnybarnes\WebmentionsParser\Parser;
use Mf2; use Mf2;
@ -36,18 +35,20 @@ class ProcessWebMention implements ShouldQueue
* Execute the job. * Execute the job.
* *
* @throws RemoteContentNotFoundException * @throws RemoteContentNotFoundException
* @throws GuzzleException
* @throws InvalidMentionException * @throws InvalidMentionException
*/ */
public function handle(Parser $parser, Client $guzzle): void public function handle(Parser $parser): void
{ {
try { try {
$response = $guzzle->request('GET', $this->source); $response = Http::get($this->source);
} catch (RequestException $e) { } catch (ConnectionException) {
throw new RemoteContentNotFoundException; throw new RemoteContentNotFoundException;
} }
$this->saveRemoteContent((string) $response->getBody(), $this->source); if ($response->failed()) {
$microformats = Mf2\parse((string) $response->getBody(), $this->source); throw new RemoteContentNotFoundException;
}
$this->saveRemoteContent($response->body(), $this->source);
$microformats = Mf2\parse($response->body(), $this->source);
$webmentions = WebMention::where('source', $this->source)->get(); $webmentions = WebMention::where('source', $this->source)->get();
foreach ($webmentions as $webmention) { foreach ($webmentions as $webmention) {
// check webmention still references target // check webmention still references target

View file

@ -4,13 +4,14 @@ declare(strict_types=1);
namespace App\Jobs; namespace App\Jobs;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Client\RequestException;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Http;
use Jonnybarnes\WebmentionsParser\Authorship; use Jonnybarnes\WebmentionsParser\Authorship;
use Jonnybarnes\WebmentionsParser\Exceptions\AuthorshipParserException; use Jonnybarnes\WebmentionsParser\Exceptions\AuthorshipParserException;
@ -55,12 +56,10 @@ class SaveProfileImage implements ShouldQueue
&& parse_url($photo, PHP_URL_HOST) !== 'pbs.twimg.com' && parse_url($photo, PHP_URL_HOST) !== 'pbs.twimg.com'
&& parse_url($photo, PHP_URL_HOST) !== 'twitter.com' && parse_url($photo, PHP_URL_HOST) !== 'twitter.com'
) { ) {
$client = resolve(Client::class);
try { try {
$response = $client->get($photo); $response = Http::throw()->get($photo);
$image = $response->getBody(); $image = $response->body();
} catch (RequestException) { } catch (ConnectionException|RequestException) {
// we are opening and reading the default image so that // we are opening and reading the default image so that
$default = public_path().'/assets/profile-images/default-image'; $default = public_path().'/assets/profile-images/default-image';
$handle = fopen($default, 'rb'); $handle = fopen($default, 'rb');

View file

@ -5,14 +5,15 @@ declare(strict_types=1);
namespace App\Jobs; namespace App\Jobs;
use App\Models\Bookmark; use App\Models\Bookmark;
use GuzzleHttp\Client;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Http\Client\Response;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use JsonException;
class SaveScreenshot implements ShouldQueue class SaveScreenshot implements ShouldQueue
{ {
@ -27,77 +28,68 @@ class SaveScreenshot implements ShouldQueue
/** /**
* Execute the job. * Execute the job.
*
*
* @throws JsonException
*/ */
public function handle(): void public function handle(): void
{ {
// A normal Guzzle client $cloudConvert = Http::baseUrl('https://api.cloudconvert.com/v2')
$client = resolve(Client::class); ->withToken(config('services.cloudconvert.token'))
// A Guzzle client with a custom Middleware to retry the CloudConvert API requests ->throw();
$retryClient = resolve('RetryGuzzle');
// First request that CloudConvert takes a screenshot of the URL // First request that CloudConvert takes a screenshot of the URL
$takeScreenshotJobResponse = $client->request('POST', 'https://api.cloudconvert.com/v2/capture-website', [ $takeScreenshotJobResponse = $cloudConvert->post('/capture-website', [
'headers' => [ 'url' => $this->bookmark->url,
'Authorization' => 'Bearer '.config('services.cloudconvert.token'), 'output_format' => 'png',
], 'screen_width' => 1440,
'json' => [ 'screen_height' => 900,
'url' => $this->bookmark->url, 'wait_until' => 'networkidle0',
'output_format' => 'png', 'wait_time' => 100,
'screen_width' => 1440,
'screen_height' => 900,
'wait_until' => 'networkidle0',
'wait_time' => 100,
],
]); ]);
$taskId = json_decode($takeScreenshotJobResponse->getBody()->getContents(), false, 512, JSON_THROW_ON_ERROR)->data->id; $taskId = $takeScreenshotJobResponse->json('data.id');
// Now wait till the status job is finished // Now wait till the status job is finished
$screenshotJobStatusResponse = $retryClient->request('GET', 'https://api.cloudconvert.com/v2/tasks/'.$taskId, [ $screenshotJobStatusResponse = $this->pollUntilFinished($cloudConvert, $taskId);
'headers' => [
'Authorization' => 'Bearer '.config('services.cloudconvert.token'),
],
'query' => [
'include' => 'payload',
],
]);
$finishedCaptureId = json_decode($screenshotJobStatusResponse->getBody()->getContents(), false, 512, JSON_THROW_ON_ERROR)->data->id; $finishedCaptureId = $screenshotJobStatusResponse->json('data.id');
// Now we can create a new job to request thst the screenshot is exported to a temporary URL we can download the screenshot from // Now we can create a new job to request thst the screenshot is exported to a temporary URL we can download the screenshot from
$exportImageJob = $client->request('POST', 'https://api.cloudconvert.com/v2/export/url', [ $exportImageJob = $cloudConvert->post('/export/url', [
'headers' => [ 'input' => $finishedCaptureId,
'Authorization' => 'Bearer '.config('services.cloudconvert.token'), 'archive_multiple_files' => false,
],
'json' => [
'input' => $finishedCaptureId,
'archive_multiple_files' => false,
],
]); ]);
$exportImageJobId = json_decode($exportImageJob->getBody()->getContents(), false, 512, JSON_THROW_ON_ERROR)->data->id; $exportImageJobId = $exportImageJob->json('data.id');
// Again, wait till the status of this export job is finished // Again, wait till the status of this export job is finished
$finalImageUrlResponse = $retryClient->request('GET', 'https://api.cloudconvert.com/v2/tasks/'.$exportImageJobId, [ $finalImageUrlResponse = $this->pollUntilFinished($cloudConvert, $exportImageJobId);
'headers' => [
'Authorization' => 'Bearer '.config('services.cloudconvert.token'),
],
'query' => [
'include' => 'payload',
],
]);
// Now we can download the screenshot and save it to the storage // Now we can download the screenshot and save it to the storage
$finalImageUrl = json_decode($finalImageUrlResponse->getBody()->getContents(), false, 512, JSON_THROW_ON_ERROR)->data->result->files[0]->url; $finalImageUrl = $finalImageUrlResponse->json('data.result.files.0.url');
$finalImageUrlContent = $client->request('GET', $finalImageUrl); $finalImageUrlContent = Http::throw()->get($finalImageUrl);
Storage::disk('public')->put('/assets/img/bookmarks/'.$taskId.'.png', $finalImageUrlContent->getBody()->getContents()); Storage::disk('public')->put('/assets/img/bookmarks/'.$taskId.'.png', $finalImageUrlContent->body());
$this->bookmark->screenshot = $taskId; $this->bookmark->screenshot = $taskId;
$this->bookmark->save(); $this->bookmark->save();
} }
/**
* Poll a CloudConvert task until it reports a "finished" status.
*/
private function pollUntilFinished(PendingRequest $client, string $taskId): Response
{
$attempts = 0;
do {
$response = $client->get('/tasks/'.$taskId, ['include' => 'payload']);
$finished = $response->json('data.status') === 'finished';
if (! $finished) {
$attempts++;
usleep(1_000_000); // 1 second, matches CloudConvert's own polling guidance
}
} while (! $finished && $attempts < 5);
return $response;
}
} }

View file

@ -5,8 +5,6 @@ declare(strict_types=1);
namespace App\Jobs; namespace App\Jobs;
use App\Models\Note; use App\Models\Note;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Psr7\Header; use GuzzleHttp\Psr7\Header;
use GuzzleHttp\Psr7\UriResolver; use GuzzleHttp\Psr7\UriResolver;
use GuzzleHttp\Psr7\Utils; use GuzzleHttp\Psr7\Utils;
@ -14,6 +12,7 @@ use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Mf2\Parser; use Mf2\Parser;
@ -32,8 +31,6 @@ class SendWebMentions implements ShouldQueue
/** /**
* Execute the job. * Execute the job.
*
* @throws GuzzleException
*/ */
public function handle(): void public function handle(): void
{ {
@ -43,12 +40,9 @@ class SendWebMentions implements ShouldQueue
foreach ($urls as $url) { foreach ($urls as $url) {
$endpoint = $this->discoverWebmentionEndpoint($url); $endpoint = $this->discoverWebmentionEndpoint($url);
if ($endpoint !== null) { if ($endpoint !== null) {
$guzzle = resolve(Client::class); Http::asForm()->post($endpoint, [
$guzzle->post($endpoint, [ 'source' => $this->note->uri,
'form_params' => [ 'target' => $url,
'source' => $this->note->uri,
'target' => $url,
],
]); ]);
} }
} }
@ -56,8 +50,6 @@ class SendWebMentions implements ShouldQueue
/** /**
* Discover if a URL has a webmention endpoint. * Discover if a URL has a webmention endpoint.
*
* @throws GuzzleException
*/ */
public function discoverWebmentionEndpoint(string $url): ?string public function discoverWebmentionEndpoint(string $url): ?string
{ {
@ -71,10 +63,9 @@ class SendWebMentions implements ShouldQueue
$endpoint = null; $endpoint = null;
$guzzle = resolve(Client::class); $response = Http::get($url);
$response = $guzzle->get($url);
// check HTTP Headers for webmention endpoint // check HTTP Headers for webmention endpoint
$links = Header::parse($response->getHeader('Link')); $links = Header::parse($response->header('Link'));
foreach ($links as $link) { foreach ($links as $link) {
if (array_key_exists('rel', $link) && mb_stristr($link['rel'], 'webmention')) { if (array_key_exists('rel', $link) && mb_stristr($link['rel'], 'webmention')) {
return $this->resolveUri(trim($link[0], '<>'), $url); return $this->resolveUri(trim($link[0], '<>'), $url);
@ -82,7 +73,7 @@ class SendWebMentions implements ShouldQueue
} }
// failed to find a header so parse HTML // failed to find a header so parse HTML
$html = (string) $response->getBody(); $html = $response->body();
if ($html === '') { if ($html === '') {
return null; return null;

View file

@ -5,13 +5,12 @@ declare(strict_types=1);
namespace App\Jobs; namespace App\Jobs;
use App\Models\Note; use App\Models\Note;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Http;
class SyndicateNoteToBluesky implements ShouldQueue class SyndicateNoteToBluesky implements ShouldQueue
{ {
@ -31,29 +30,18 @@ class SyndicateNoteToBluesky implements ShouldQueue
/** /**
* Execute the job. * Execute the job.
*
* @throws GuzzleException
*/ */
public function handle(Client $guzzle): void public function handle(): void
{ {
$response = $guzzle->request( // no ->throw()/->retry() here — Bridgy would duplicate-publish on retry, see $tries above
'POST', $response = Http::acceptJson()->asForm()->post('https://brid.gy/publish/webmention', [
'https://brid.gy/publish/webmention', 'source' => $this->note->uri,
[ 'target' => 'https://brid.gy/publish/bluesky',
'headers' => [ ]);
'Accept' => 'application/json',
],
'form_params' => [
'source' => $this->note->uri,
'target' => 'https://brid.gy/publish/bluesky',
],
'http_errors' => false,
]
);
$body = json_decode((string) $response->getBody(), true); $body = $response->json();
if ($response->getStatusCode() === 201) { if ($response->status() === 201) {
$this->note->bluesky_url = $body['url']; $this->note->bluesky_url = $body['url'];
$this->note->save(); $this->note->save();
@ -61,7 +49,7 @@ class SyndicateNoteToBluesky implements ShouldQueue
} }
throw new \RuntimeException( throw new \RuntimeException(
'Bridgy publish to Bluesky failed: '.($body['error'] ?? (string) $response->getBody()) 'Bridgy publish to Bluesky failed: '.($body['error'] ?? $response->body())
); );
} }
} }

View file

@ -5,13 +5,12 @@ declare(strict_types=1);
namespace App\Jobs; namespace App\Jobs;
use App\Models\Note; use App\Models\Note;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Http;
class SyndicateNoteToMastodon implements ShouldQueue class SyndicateNoteToMastodon implements ShouldQueue
{ {
@ -31,29 +30,18 @@ class SyndicateNoteToMastodon implements ShouldQueue
/** /**
* Execute the job. * Execute the job.
*
* @throws GuzzleException
*/ */
public function handle(Client $guzzle): void public function handle(): void
{ {
$response = $guzzle->request( // no ->throw()/->retry() here — Bridgy would duplicate-publish on retry, see $tries above
'POST', $response = Http::acceptJson()->asForm()->post('https://brid.gy/publish/webmention', [
'https://brid.gy/publish/webmention', 'source' => $this->note->uri,
[ 'target' => 'https://brid.gy/publish/mastodon',
'headers' => [ ]);
'Accept' => 'application/json',
],
'form_params' => [
'source' => $this->note->uri,
'target' => 'https://brid.gy/publish/mastodon',
],
'http_errors' => false,
]
);
$body = json_decode((string) $response->getBody(), true); $body = $response->json();
if ($response->getStatusCode() === 201) { if ($response->status() === 201) {
$this->note->mastodon_url = $body['url']; $this->note->mastodon_url = $body['url'];
$this->note->save(); $this->note->save();
@ -61,7 +49,7 @@ class SyndicateNoteToMastodon implements ShouldQueue
} }
throw new \RuntimeException( throw new \RuntimeException(
'Bridgy publish to Mastodon failed: '.($body['error'] ?? (string) $response->getBody()) 'Bridgy publish to Mastodon failed: '.($body['error'] ?? $response->body())
); );
} }
} }

View file

@ -7,7 +7,6 @@ namespace App\Models;
use App\CommonMark\Generators\MentionGenerator; use App\CommonMark\Generators\MentionGenerator;
use App\CommonMark\Renderers\MentionRenderer; use App\CommonMark\Renderers\MentionRenderer;
use App\Observers\NoteObserver; use App\Observers\NoteObserver;
use GuzzleHttp\Client;
use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\Hidden; use Illuminate\Database\Eloquent\Attributes\Hidden;
use Illuminate\Database\Eloquent\Attributes\ObservedBy; use Illuminate\Database\Eloquent\Attributes\ObservedBy;
@ -20,6 +19,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\MorphMany; use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Jonnybarnes\IndieWeb\Numbers; use Jonnybarnes\IndieWeb\Numbers;
use Laravel\Scout\Searchable; use Laravel\Scout\Searchable;
use League\CommonMark\Environment\Environment; use League\CommonMark\Environment\Environment;
@ -363,18 +363,15 @@ class Note extends Model
$latLng = $latitude.','.$longitude; $latLng = $latitude.','.$longitude;
return Cache::get($latLng, function () use ($latLng, $latitude, $longitude) { return Cache::get($latLng, function () use ($latLng, $latitude, $longitude) {
$guzzle = resolve(Client::class); $response = Http::withHeaders(['User-Agent' => 'jonnybarnes.uk, email jonny@jonnybarnes.uk'])
$response = $guzzle->request('GET', 'https://nominatim.openstreetmap.org/reverse', [ ->get('https://nominatim.openstreetmap.org/reverse', [
'query' => [
'format' => 'json', 'format' => 'json',
'lat' => $latitude, 'lat' => $latitude,
'lon' => $longitude, 'lon' => $longitude,
'zoom' => 18, 'zoom' => 18,
'addressdetails' => 1, 'addressdetails' => 1,
], ]);
'headers' => ['User-Agent' => 'jonnybarnes.uk via Guzzle, email jonny@jonnybarnes.uk'], $json = $response->object();
]);
$json = json_decode((string) $response->getBody());
if (isset($json->address->suburb)) { if (isset($json->address->suburb)) {
$locality = $json->address->suburb; $locality = $json->address->suburb;
if (isset($json->address->city)) { if (isset($json->address->city)) {

View file

@ -2,15 +2,11 @@
namespace App\Providers; namespace App\Providers;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Intervention\Image\ImageManager;
use Lcobucci\JWT\Configuration; use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Hmac\Sha256; use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory; use Lcobucci\JWT\Signer\Key\InMemory;
@ -33,11 +29,6 @@ class AppServiceProvider extends ServiceProvider
*/ */
public function boot(): void public function boot(): void
{ {
// configure Intervention/Image
$this->app->bind('Intervention\Image\ImageManager', function () {
return ImageManager::withDriver(config('image.driver'));
});
/** /**
* Paginate a standard Laravel Collection. * Paginate a standard Laravel Collection.
* *
@ -82,39 +73,10 @@ class AppServiceProvider extends ServiceProvider
); );
}); });
// Configure Guzzle
$this->app->bind('RetryGuzzle', function () {
$handlerStack = HandlerStack::create();
$handlerStack->push(Middleware::retry(
function ($retries, $request, $response, $exception) {
// Limit the number of retries to 5
if ($retries >= 5) {
return false;
}
// Retry connection exceptions
if ($exception instanceof ConnectException) {
return true;
}
// Retry on server errors
if ($response && $response->getStatusCode() >= 500) {
return true;
}
// Finally for CloudConvert, retry if status is not final
return json_decode($response, false, 512, JSON_THROW_ON_ERROR)->data->status !== 'finished';
},
function () {
// Retry after 1 second
return 1000;
}
));
return new Client(['handler' => $handlerStack]);
});
// Turn on Eloquent strict mode when developing // Turn on Eloquent strict mode when developing
Model::shouldBeStrict(! $this->app->isProduction()); Model::shouldBeStrict(! $this->app->isProduction());
// Force HTTPS URL generation in production
URL::forceHttps($this->app->isProduction());
} }
} }

View file

@ -8,10 +8,8 @@ use App\Exceptions\InternetArchiveException;
use App\Jobs\ProcessBookmark; use App\Jobs\ProcessBookmark;
use App\Models\Bookmark; use App\Models\Bookmark;
use App\Models\Tag; use App\Models\Tag;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str; use Illuminate\Support\Str;
class BookmarkService class BookmarkService
@ -55,21 +53,19 @@ class BookmarkService
* Given a URL, attempt to save it to the Internet Archive. * Given a URL, attempt to save it to the Internet Archive.
* *
* @throws InternetArchiveException * @throws InternetArchiveException
* @throws GuzzleException
*/ */
public function getArchiveLink(string $url): string public function getArchiveLink(string $url): string
{ {
$client = resolve(Client::class); $response = Http::get('https://web.archive.org/save/'.$url);
try {
$response = $client->request('GET', 'https://web.archive.org/save/'.$url); if ($response->clientError()) {
} catch (ClientException $e) {
// throw an exception to be caught // throw an exception to be caught
throw new InternetArchiveException; throw new InternetArchiveException;
} }
if ($response->hasHeader('Content-Location')) {
if (Str::startsWith(Arr::get($response->getHeader('Content-Location'), 0), '/web')) { $contentLocation = $response->header('Content-Location');
return $response->getHeader('Content-Location')[0]; if ($contentLocation !== '' && Str::startsWith($contentLocation, '/web')) {
} return $contentLocation;
} }
// throw an exception to be caught // throw an exception to be caught

View file

@ -14,9 +14,8 @@
"ext-pgsql": "*", "ext-pgsql": "*",
"ext-sodium": "*", "ext-sodium": "*",
"cviebrock/eloquent-sluggable": "^13.0", "cviebrock/eloquent-sluggable": "^13.0",
"guzzlehttp/guzzle": "^7.2",
"indieauth/client": "^1.1", "indieauth/client": "^1.1",
"intervention/image": "^3", "intervention/image": "^4.0",
"jonnybarnes/indieweb": "~0.2", "jonnybarnes/indieweb": "~0.2",
"jonnybarnes/webmentions-parser": "~0.5", "jonnybarnes/webmentions-parser": "~0.5",
"laravel/framework": "^13.0", "laravel/framework": "^13.0",
@ -27,7 +26,7 @@
"league/commonmark": "^2.0", "league/commonmark": "^2.0",
"league/flysystem-aws-s3-v3": "^3.0", "league/flysystem-aws-s3-v3": "^3.0",
"mf2/mf2": "~0.3", "mf2/mf2": "~0.3",
"spatie/laravel-flare": "^2.2", "spatie/laravel-flare": "^3.0",
"symfony/html-sanitizer": "^8.0", "symfony/html-sanitizer": "^8.0",
"tempest/highlight": "^2.27", "tempest/highlight": "^2.27",
"web-auth/webauthn-lib": "^5.0" "web-auth/webauthn-lib": "^5.0"
@ -42,8 +41,8 @@
"laravel/sail": "^1.41", "laravel/sail": "^1.41",
"mockery/mockery": "^1.6", "mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.6", "nunomaduro/collision": "^8.6",
"phpunit/php-code-coverage": "^12.0", "phpunit/php-code-coverage": "^14.0",
"phpunit/phpunit": "^12.5.12", "phpunit/phpunit": "^13.0",
"spatie/laravel-ray": "^1.12", "spatie/laravel-ray": "^1.12",
"spatie/x-ray": "^1.2" "spatie/x-ray": "^1.2"
}, },

1276
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,6 @@
<?php <?php
use Spatie\FlareClient\Api;
use Spatie\FlareClient\Sampling\RateSampler; use Spatie\FlareClient\Sampling\RateSampler;
use Spatie\LaravelFlare\AttributesProviders\LaravelUserAttributesProvider;
use Spatie\LaravelFlare\FlareConfig; use Spatie\LaravelFlare\FlareConfig;
use Spatie\LaravelFlare\Senders\LaravelHttpSender; use Spatie\LaravelFlare\Senders\LaravelHttpSender;
@ -21,17 +19,6 @@ return [
'key' => env('FLARE_KEY'), 'key' => env('FLARE_KEY'),
/*
|
|--------------------------------------------------------------------------
| Flare Base URL
|--------------------------------------------------------------------------
|
| Which server should be used to send the reports/traces to.
|
*/
'base_url' => env('FLARE_BASE_URL', Api::BASE_URL),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Collects | Collects
@ -47,22 +34,6 @@ return [
extra: [] extra: []
), ),
/*
|--------------------------------------------------------------------------
| Attribute providers
|--------------------------------------------------------------------------
|
| When sending an error report or trace to Flare attributes can be added to
| the report or trace for common entries. An example of such an entry is
| the currently authenticated user. In an attribute provider you can
| specify which attributes should be sent.
|
*/
'attribute_providers' => [
'user' => LaravelUserAttributesProvider::class,
],
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Censor data | Censor data
@ -88,19 +59,49 @@ return [
'X-XSRF-TOKEN', 'X-XSRF-TOKEN',
], ],
'client_ips' => false, 'client_ips' => false,
'cookies' => false,
'session' => false,
], ],
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Reporting log statements | Sender
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| If this setting is `false` log statements won't be sent as events to Flare, | The sender is responsible for sending the error reports and traces to
| no matter which error level you specified in the Flare log channel. | Flare. By default, Laravel Flare sends them over HTTP. To use the local
| Flare daemon, switch the sender class to
| `Spatie\FlareClient\Senders\DaemonSender::class` and set `daemon_url`.
| The daemon sender defaults to localhost on port 8787 and uses its own
| default timeouts and fallback sender config unless you override them.
| |
*/ */
'send_logs_as_events' => true, 'sender' => [
'class' => LaravelHttpSender::class,
'config' => [
'timeout' => 10,
],
],
// Daemon sender example
// 'sender' => [
// 'class' => \Spatie\FlareClient\Senders\DaemonSender::class,
// 'config' => [
// 'daemon_url' => env('FLARE_DAEMON_URL', 'http://127.0.0.1:8787'),
// ],
// ],
/*
|--------------------------------------------------------------------------
| Report
|--------------------------------------------------------------------------
|
| Flare reports errors and exceptions happening within your application.
|
*/
'report' => env('FLARE_REPORT', true),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -109,10 +110,27 @@ return [
| When reporting errors, you can specify which error levels should be | When reporting errors, you can specify which error levels should be
| reported. By default, all error levels are reported by setting | reported. By default, all error levels are reported by setting
| this value to `null`. | this value to `null`.
*/ */
'report_error_levels' => null, 'report_error_levels' => null,
/*
|--------------------------------------------------------------------------
| Override grouping
|--------------------------------------------------------------------------
|
| Flare will try to group errors and exceptions as best as possible, that
| being said, sometimes you might want to override the grouping. You can
| do this by adding exception classes to this array which should always
| be grouped by exception class, exception message or exception class
| and message.
|
*/
'overridden_groupings' => [
// Illuminate\Http\Client\ConnectionException::class => Spatie\FlareClient\Enums\OverriddenGrouping::ExceptionMessageAndClass,
],
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Share button | Share button
@ -126,40 +144,6 @@ return [
'enable_share_button' => true, 'enable_share_button' => true,
/*
|--------------------------------------------------------------------------
| Override grouping
|--------------------------------------------------------------------------
|
| Flare will try to group errors and exceptions as best as possible, that
| being said, sometimes you might want to override the grouping. You can
| do this by adding exception classes to this array which should always
| be grouped by exception class, exception message or exception class
| and message.
|
*/
'overridden_groupings' => [
// Illuminate\Http\Client\ConnectionException::class => Spatie\FlareClient\Enums\OverriddenGrouping::ExceptionMessageAndClass,
],
/*
|--------------------------------------------------------------------------
| Sender
|--------------------------------------------------------------------------
|
| The sender is responsible for sending the error reports and traces to
| Flare it can be configured if needed.
|
*/
'sender' => [
'class' => LaravelHttpSender::class,
'config' => [
'timeout' => 10,
],
],
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Trace | Trace
@ -170,7 +154,7 @@ return [
| |
*/ */
'trace' => env('FLARE_TRACE', false), 'trace' => env('FLARE_TRACE', true),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -183,26 +167,35 @@ return [
| which means that 10% of the traces will be recorded. | which means that 10% of the traces will be recorded.
| |
*/ */
'sampler' => [ 'sampler' => [
'class' => RateSampler::class, 'class' => RateSampler::class,
'config' => [ 'config' => [
'rate' => 0.1, 'rate' => env('FLARE_SAMPLER_RATE', 0.1),
], ],
], ],
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Trace limits | Log
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| |
| Limits for the tracing data. These limits are used to prevent | Logging show you an overview of log entries within your application.
| the tracing data from growing too large.
| |
*/ */
'trace_limits' => [
'max_spans' => 512, 'log' => env('FLARE_LOG', false),
'max_attributes_per_span' => 128,
'max_span_events_per_span' => 128, /*
'max_attributes_per_span_event' => 128, |--------------------------------------------------------------------------
], | Minimal log level
|--------------------------------------------------------------------------
|
| You can specify the minimal (Monolog) log level that should be sent to Flare.
| Log levels lower than the specified level will be ignored.
| If null all log levels will be sent to Flare.
|
*/
'minimal_log_level' => null,
]; ];

View file

@ -1,22 +0,0 @@
<?php
use Intervention\Image\Drivers\Gd\Driver;
return [
/*
|--------------------------------------------------------------------------
| Image Driver
|--------------------------------------------------------------------------
|
| Intervention Image supports "GD Library" and "Imagick" to process images
| internally. You may choose one of them according to your PHP
| configuration. By default PHP's "GD Library" implementation is used.
|
| Supported: "gd", "imagick"
|
*/
'driver' => Driver::class,
];

Some files were not shown because too many files have changed in this diff Show more