Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Redirect healthcheck #156

Closed
wants to merge 12 commits into from
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"require": {
"php": "^7.1",
"ext-json": "*",
"ext-curl": "*",
"laravel/framework": "^5.5.0|^5.6.0",
"laravelcollective/html": "^5.5.0|^5.6.0",
"doctrine/dbal": "^2.5",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class AddUrlStatusToRedirectsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('redirects', function (Blueprint $table) {
$table->boolean('status')->default(0);
});
}

/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('redirects', function (Blueprint $table) {
$table->dropColumn('status');
});
}
}
102 changes: 102 additions & 0 deletions src/Console/Commands/RedirectHealthCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php

namespace Arbory\Base\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Arbory\Base\Jobs\UpdateRedirectUrlStatus;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Symfony\Component\Console\Output\OutputInterface;

class RedirectHealthCommand extends Command
{
use DispatchesJobs;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'arbory.redirect-health';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Runs an URL healthcheck to verify the redirects table `to_url` is available and update `status` field in table';

/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$ids = $this->getIDs();
$job = new UpdateRedirectUrlStatus($ids);

$this->info('Start to check '.count($ids).' entries...');
try {
$this->dispatchNow($job);
$result = $job->getResult();
} catch (\Exception $e) {
$this->error('Command redirect-health failed with an exception');
$this->error($e->getMessage());

return 1;
}

if (! empty($result) && count($result->getInvalidUrlList())) {
$this->warn(PHP_EOL.'Invalid URLs list:');
foreach ($result->getInvalidUrlList() as $url) {
$this->warn($url);
}
}

if ($this->isSetVerboseFlag() && ! empty($result) && count($result->getErrors())) {
foreach ($result->getErrors() as $url => $err) {
$this->error('Request to '.$url.' - '.$err);
}
}

$this->warn(PHP_EOL.'Invalid entries: '.$result->getInvalidCount());
$this->info('Valid entries: '.$result->getValidCount());

return 0;
}

/**
* @return bool
*/
private function isSetVerboseFlag()
{
return $this->getOutput()->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE;
}

/**
* @return array
*/
public function selectAllRedirectIds()
{
$results = DB::table('redirects')->where('id', '>', 0)->pluck('id')->toArray();

return $results;
}

/**
* @param array $entryIds
* @param int $status
*/
public function setStatus(array $entryIds, int $status)
{
DB::table('redirects')->whereIn('id', $entryIds)->update(['status' => $status]);
}

/**
* @return array
*/
private function getIDs()
{
return $this->selectAllRedirectIds();
}
}
2 changes: 2 additions & 0 deletions src/Console/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Arbory\Base\Console;

use Arbory\Base\Console\Commands\SeedCommand;
use Arbory\Base\Console\Commands\RedirectHealthCommand;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

/**
Expand All @@ -15,5 +16,6 @@ class Kernel extends ConsoleKernel
*/
protected $commands = [
SeedCommand::class,
RedirectHealthCommand::class,
];
}
93 changes: 93 additions & 0 deletions src/Jobs/UpdateRedirectUrlStatus.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

namespace Arbory\Base\Jobs;

use Illuminate\Log\Logger;
use Illuminate\Support\Facades\DB;
use Arbory\Base\RedirectHealthChecker;
use Illuminate\Contracts\Queue\ShouldQueue;

class UpdateRedirectUrlStatus implements ShouldQueue
{
/**
* @var Logger
*/
protected $logger;

/**
* @var array
*/
protected $redirectIds;

/** @var RedirectHealthChecker */
private $redirectHealthChecker;

/**
* UpdateRedirectUrlStatus constructor.
* @param array $redirectIds
*/
public function __construct(array $redirectIds)
{
$this->redirectIds = $redirectIds;
}

/**
* Execute the job.
*
* @param Logger $logger
* @return void
*/
public function handle(Logger $logger)
{
$this->logger = $logger;

$this->checkAndUpdateRedirectStatus();
}

/**
* @return void
*/
private function checkAndUpdateRedirectStatus()
{
try {
$redirects = $this->selectRedirects($this->redirectIds);

$redirectHealthChecker = new RedirectHealthChecker($redirects);
$redirectHealthChecker->check();

$this->updateStatusBulk($redirectHealthChecker->getValidIds(), true);
$this->updateStatusBulk($redirectHealthChecker->getInvalidIds(), false);

$this->redirectHealthChecker = $redirectHealthChecker;
} catch (\Exception $e) {
$this->logger->warning($e->getMessage());
}
}

/**
* @param array $ids
* @return \Illuminate\Support\Collection
*/
public function selectRedirects(array $ids)
{
$results = DB::table('redirects')->whereIn('id', $ids)->get(['id', 'to_url']);

return $results;
}

/*
* @return void
*/
public function updateStatusBulk(array $entryIds, int $status)
{
DB::table('redirects')->whereIn('id', $entryIds)->update(['status' => $status]);
}

/**
* @return RedirectHealthChecker|null
*/
public function getResult()
{
return $this->redirectHealthChecker ?? null;
}
}
24 changes: 24 additions & 0 deletions src/Observers/RedirectObserver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace Arbory\Base\Observers;

use Arbory\Base\Pages\Redirect;
use Arbory\Base\Jobs\UpdateRedirectUrlStatus;
use Illuminate\Foundation\Bus\DispatchesJobs;

class RedirectObserver
{
use DispatchesJobs;

/**
* Handle the redirect "saved" event.
*
* @param Redirect $redirect
* @return void
*/
public function saved(Redirect $redirect)
{
$job = new UpdateRedirectUrlStatus([$redirect->id]);
$this->dispatchNow($job);
}
}
1 change: 1 addition & 0 deletions src/Pages/Redirect.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class Redirect extends Model
protected $fillable = [
'from_url',
'to_url',
'status',
];

/**
Expand Down
6 changes: 6 additions & 0 deletions src/Providers/ArboryServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@

use Arbory\Base\Menu\Menu;
use Arbory\Base\Admin\Admin;
use Arbory\Base\Pages\Redirect;
use Arbory\Base\Services\AssetPipeline;
use Illuminate\Support\ServiceProvider;
use Arbory\Base\Observers\RedirectObserver;
use Arbory\Base\Console\Commands\SeedCommand;
use Arbory\Base\Console\Commands\InstallCommand;
use Arbory\Base\Console\Commands\CreateUserCommand;
use Arbory\Base\Console\Commands\RedirectHealthCommand;
use Arbory\Base\Services\Authentication\SecurityStrategy;
use Arbory\Base\Services\Authentication\SessionSecurityService;

Expand All @@ -29,6 +32,8 @@ public function boot()
$this->registerCommands();
$this->registerLocales();

Redirect::observe(RedirectObserver::class);

$this->app->singleton(SecurityStrategy::class, function () {
return $this->app->make(SessionSecurityService::class);
});
Expand Down Expand Up @@ -73,6 +78,7 @@ private function registerCommands()
'arbory.seed' => SeedCommand::class,
'arbory.create-user' => CreateUserCommand::class,
'arbory.install' => InstallCommand::class,
'arbory.redirect-health' => RedirectHealthCommand::class,
];

foreach ($commands as $containerKey => $commandClass) {
Expand Down
Loading