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
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
"roboc/laravel-glide": "^1.1.0",
"spatie/laravel-sluggable": "^2.1.0",
"maatwebsite/excel": "^3.0.5",
"unisharp/laravel-filemanager": "^1.8.5"
"unisharp/laravel-filemanager": "^1.8.5",
"ext-curl": "*"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move this up, group with other extensions

},
"require-dev" : {
"satooshi/php-coveralls": "^1.0",
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');
});
}
}
109 changes: 109 additions & 0 deletions src/Console/Commands/RedirectHealthCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?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;

class RedirectHealthCommand extends Command
{
use DispatchesJobs;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'arbory.redirect-health
{ids=[] : The array of IDs from redirects table to check (if not provided then would be selected all redirects table entries)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we pass "array" via CLI? Please specify that command expects comma separated values.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this really necessary? Who would ever know ID for specific redirect entry.

{--errors : Show curl request errors}';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-v --verbose are standart flags to increase console output. Could we use those?


/**
* 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()
{
try {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't put all your code in Try block, this is bad error suppression

$ids = $this->getIDs();
$job = new UpdateRedirectUrlStatus($ids);

$this->info('Start to check '.count($ids).' entries...');

$this->dispatchNow($job);
$result = $job->getResult();

if (! empty($result) && count($result->getInvalidUrlList())) {
$this->warn("\nInvalid URLs list:");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use PHP_EOL for new line

foreach ($result->getInvalidUrlList() as $url) {
$this->warn($url);
}
}

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

$this->warn("\nInvalid entries: {$result->getInvalidCount()}");
$this->info("Valid entries: {$result->getValidCount()}");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use sprintf or concat for string concatination

} catch (\Exception $e) {
$this->error('Redirects healthcheck failed with an exception');
$this->error($e->getMessage());

return 2;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why 2? Magic nu,ber

}

return 0;
}

/**
* @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()
{
$param = $this->argument('ids');

$final_ids = [];
foreach (explode(',', $param) as $id) {
if (is_numeric($id)) {
$final_ids[] = $id;
}
}

if (count($final_ids)) {
return $final_ids;
}

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