mirror of
https://github.com/Kovah/LinkAce.git
synced 2025-02-24 11:13:02 +01:00
Implement first version for automated link checks (#20)
This commit is contained in:
parent
5abc8f6584
commit
719b82d230
189
app/Console/CheckLinksCommand.php
Normal file
189
app/Console/CheckLinksCommand.php
Normal file
@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Link;
|
||||
use GuzzleHttp\Client;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
/**
|
||||
* Class CheckLinksCommand
|
||||
*
|
||||
* @package App\Console\Commands
|
||||
*/
|
||||
class CheckLinksCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'links:check';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Checks links for availablility';
|
||||
|
||||
/**
|
||||
* Check a maximum of 100 links at once
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $limit = 100;
|
||||
|
||||
/** @var int */
|
||||
protected $offset;
|
||||
|
||||
/** @var int */
|
||||
protected $total;
|
||||
|
||||
/** @var int */
|
||||
protected $checked_link_count;
|
||||
|
||||
/** @var Client */
|
||||
protected $client;
|
||||
|
||||
/** @var string */
|
||||
protected $cache_key_offset = 'command_links:check_offset';
|
||||
|
||||
/** @var string */
|
||||
protected $cache_key_skip_timestamp = 'command_links:check_skip_timestamp';
|
||||
|
||||
/** @var string */
|
||||
protected $cache_key_checked_count = 'command_links:check_checked_count';
|
||||
|
||||
/**
|
||||
* RegisterUser constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->client = new Client();
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
// Check if the command should skip the execution
|
||||
$skip_timestamp = Cache::get($this->cache_key_skip_timestamp);
|
||||
$this->offset = Cache::get($this->cache_key_offset, 0);
|
||||
$this->checked_link_count = Cache::get($this->cache_key_checked_count, 0);
|
||||
|
||||
if (now()->timestamp < $skip_timestamp) {
|
||||
return;
|
||||
}
|
||||
|
||||
$links = $this->getLinks();
|
||||
|
||||
// Cancel if there are no links to check
|
||||
if ($links->isEmpty()) {
|
||||
Cache::forget($this->cache_key_offset);
|
||||
Cache::forget($this->cache_key_skip_timestamp);
|
||||
|
||||
$this->comment('No links found, aborting...');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check all provided links
|
||||
$this->comment('Checking ' . $links->count() . ' links now.');
|
||||
|
||||
$links->each(function ($link) {
|
||||
$this->checkLink($link);
|
||||
|
||||
// Prevent spam-ish behaviour by limiting outgoing HTTP requests
|
||||
sleep(1);
|
||||
});
|
||||
|
||||
// Check if there are more links to check
|
||||
$checked_count = $this->checked_link_count + $links->count();
|
||||
Cache::forever($this->cache_key_checked_count, $checked_count);
|
||||
|
||||
if ($this->total > $checked_count) {
|
||||
|
||||
// If yes, simply save the offset to the cache.
|
||||
// The next link check will pick it up and continue the check
|
||||
$next_offset = $this->offset + $this->limit;
|
||||
Cache::forever($this->cache_key_offset, $next_offset);
|
||||
|
||||
$this->comment('Saving offset for next link check.');
|
||||
|
||||
} else {
|
||||
|
||||
// If not, all links have been successfully checked.
|
||||
// Save a cache flag that prevents link checks for the next days.
|
||||
$next_check = now()->addDays(5)->timestamp;
|
||||
Cache::forever($this->cache_key_skip_timestamp, $next_check);
|
||||
|
||||
$this->comment('All existing links checked, next link check scheduled for ' . now()->addDays(5)->toDateTimeString());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get links but limit the results to a fixed number of links
|
||||
* If there is an offset saved, use this instead of beginning from the first entry
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getLinks()
|
||||
{
|
||||
// Get the total amount of remaining links
|
||||
$this->total = Link::count();
|
||||
|
||||
// Get a porton of the remaining links based on the limit
|
||||
return Link::orderBy('id', 'ASC')->offset($this->offset)->limit($this->limit)->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the URL of an link and set the status accordingly
|
||||
*
|
||||
* @param Link $link
|
||||
* @return void
|
||||
* @throws \GuzzleHttp\Exception\GuzzleException
|
||||
*/
|
||||
protected function checkLink(Link $link): void
|
||||
{
|
||||
$this->comment('Checking link ' . $link->url);
|
||||
|
||||
$options = [
|
||||
'http_errors' => false, // Do not throw exceptions for 4xx and 5xx errors
|
||||
'timeout' => 5, // wait a maximum of 5 seconds for the request to finish
|
||||
];
|
||||
|
||||
try {
|
||||
$res = $this->client->request('GET', $link->url, $options);
|
||||
$status_code = $res->getStatusCode();
|
||||
} catch (\Exception $e) {
|
||||
// Just abort now, may be a temporary issue
|
||||
$this->warn('› Unknown error while trying to check the URL, trying again later.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the status code is not 200
|
||||
if ($status_code !== 200) {
|
||||
// If the link target is a redirect, set the status 2 (moved)
|
||||
// else set the status to 3 (broken)
|
||||
if ($status_code === 301 || $status_code === 302) {
|
||||
$link->status = 2;
|
||||
$this->warn('› Link moved to another URL!');
|
||||
} else {
|
||||
$link->status = 3;
|
||||
$this->error('› Link seems to be broken!');
|
||||
}
|
||||
|
||||
$link->save();
|
||||
|
||||
} else {
|
||||
$this->info('› Link looks okay.');
|
||||
}
|
||||
}
|
||||
}
|
@ -2,10 +2,16 @@
|
||||
|
||||
namespace App\Console;
|
||||
|
||||
use App\Console\Commands\CheckLinksCommand;
|
||||
use App\Console\Commands\RegisterUserCommand;
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
|
||||
/**
|
||||
* Class Kernel
|
||||
*
|
||||
* @package App\Console
|
||||
*/
|
||||
class Kernel extends ConsoleKernel
|
||||
{
|
||||
/**
|
||||
@ -14,7 +20,8 @@ class Kernel extends ConsoleKernel
|
||||
* @var array
|
||||
*/
|
||||
protected $commands = [
|
||||
RegisterUserCommand::class
|
||||
RegisterUserCommand::class,
|
||||
CheckLinksCommand::class,
|
||||
];
|
||||
|
||||
/**
|
||||
@ -25,8 +32,7 @@ class Kernel extends ConsoleKernel
|
||||
*/
|
||||
protected function schedule(Schedule $schedule)
|
||||
{
|
||||
// $schedule->command('inspire')
|
||||
// ->hourly();
|
||||
$schedule->command('links:check')->hourly();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -4,6 +4,7 @@ namespace App\Http\Controllers\API;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
|
||||
/**
|
||||
* Class CronController
|
||||
@ -13,11 +14,18 @@ use Illuminate\Http\Request;
|
||||
class CronController extends Controller
|
||||
{
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Request $request
|
||||
* @param null|string $cron_token
|
||||
* @return void
|
||||
*/
|
||||
public function run(Request $request)
|
||||
public function run(Request $request, $cron_token)
|
||||
{
|
||||
//
|
||||
// Verify the cron token
|
||||
if (!$cron_token || $cron_token !== systemsettings('cron_token')) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
// Run all cron tasks
|
||||
Artisan::call('schedule:run');
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user