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

Add web cron ability #821 #1073

Merged
merged 5 commits into from
Mar 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions app/Database/migrations/2021_03_05_044305_add_kvp_table.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

use App\Contracts\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

/**
* Add a hub to the subfleet is
*/
class AddKvpTable extends Migration
{
public function up()
{
Schema::create('kvp', function (Blueprint $table) {
$table->string('key')->index();
$table->string('value');
});
}
}
6 changes: 6 additions & 0 deletions app/Database/seeds/settings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -361,3 +361,9 @@
options: ''
type: 'text'
description: 'Discord public channel ID for broadcasat notifications'
- key: 'cron.random_id'
name: 'Cron Randomized ID'
group: 'cron'
value: ''
type: 'hidden'
description: ''
37 changes: 37 additions & 0 deletions app/Exceptions/CronInvalid.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace App\Exceptions;

class CronInvalid extends AbstractHttpException
{
public const MESSAGE = 'Cron ID is disabled or invalid';

public function __construct()
{
parent::__construct(400, static::MESSAGE);
}

/**
* Return the RFC 7807 error type (without the URL root)
*/
public function getErrorType(): string
{
return 'cron-invalid';
}

/**
* Get the detailed error string
*/
public function getErrorDetails(): string
{
return $this->getMessage();
}

/**
* Return an array with the error details, merged with the RFC7807 response
*/
public function getErrorMetadata(): array
{
return [];
}
}
35 changes: 35 additions & 0 deletions app/Http/Controllers/Admin/MaintenanceController.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use App\Repositories\KvpRepository;
use App\Services\CronService;
use App\Services\VersionService;
use App\Support\Utils;
use Codedge\Updater\UpdaterManager;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
Expand Down Expand Up @@ -34,7 +35,12 @@ public function __construct(

public function index()
{
// Get the cron URL
$cron_id = setting('cron.random_id');
$cron_url = empty($cron_id) ? 'Not enabled' : url(route('api.maintenance.cron', $cron_id));

return view('admin.maintenance.index', [
'cron_url' => $cron_url,
'cron_path' => $this->cronSvc->getCronExecString(),
'cron_problem_exists' => $this->cronSvc->cronProblemExists(),
'new_version' => $this->kvpRepo->get('new_version_available', false),
Expand Down Expand Up @@ -117,4 +123,33 @@ public function update(Request $request)

return redirect('/update/downloader');
}

/**
* Enable the cron, or if it's enabled, change the ID that is used
*
* @param Request $request
*/
public function cron_enable(Request $request)
{
$id = Utils::generateNewId(24);
setting_save('cron.random_id', $id);

Flash::success('Web cron refreshed!');
return redirect(route('admin.maintenance.index'));
}

/**
* Disable the web cron
*
* @param Request $request
*
* @return mixed
*/
public function cron_disable(Request $request)
{
setting_save('cron.random_id', '');

Flash::success('Web cron disabled!');
return redirect(route('admin.maintenance.index'));
}
}
2 changes: 1 addition & 1 deletion app/Http/Controllers/Admin/SettingsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ private function getCurrencyList(): array
*/
public function index()
{
$settings = Setting::orderBy('order', 'asc')->get();
$settings = Setting::where('type', '!=', 'hidden')->orderBy('order')->get();
$settings = $settings->groupBy('group');

return view('admin.settings.index', [
Expand Down
1 change: 0 additions & 1 deletion app/Http/Controllers/Api/FlightController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

use App\Contracts\Controller;
use App\Exceptions\AssetNotFound;
use App\Exceptions\Unauthorized;
use App\Http\Resources\Flight as FlightResource;
use App\Http\Resources\Navdata as NavdataResource;
use App\Models\SimBrief;
Expand Down
35 changes: 35 additions & 0 deletions app/Http/Controllers/Api/MaintenanceController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace App\Http\Controllers\Api;

use App\Contracts\Controller;
use App\Exceptions\CronInvalid;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;

class MaintenanceController extends Controller
{
/**
* Run the cron job from the web
*
* @param Request $request
* @param string $id The ID passed in for the cron
*
* @return mixed
*/
public function cron(Request $request, string $id)
{
$cron_id = setting('cron.random_id');
if (empty($cron_id) || $id !== $cron_id) {
throw new CronInvalid();
}

$output = '';
Artisan::call('schedule:run');
$output .= trim(Artisan::output());

return response([
'content' => $output,
]);
}
}
23 changes: 23 additions & 0 deletions app/Models/Kvp.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace App\Models;

use App\Contracts\Model;

/**
* @property string key
* @property string value
*/
class Kvp extends Model
{
public $table = 'kvp';
public $timestamps = false;
public $incrementing = false;

protected $keyType = 'string';

public $fillable = [
'key',
'value',
];
}
1 change: 1 addition & 0 deletions app/Models/SimBrief.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* @property int $user_id The user that generated this
* @property string $flight_id Optional, if attached to a flight, removed if attached to PIREP
* @property string $pirep_id Optional, if attached to a PIREP, removed if attached to flight
* @property string $aircraft_id The aircraft this is for
* @property string $acars_xml
* @property string $ofp_xml
* @property string $ofp_html
Expand Down
8 changes: 8 additions & 0 deletions app/Providers/RouteServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,12 @@ private function mapAdminRoutes()
Route::match(['post'], 'maintenance/forcecheck', 'MaintenanceController@forcecheck')
->name('maintenance.forcecheck')->middleware('ability:admin,maintenance');

Route::match(['post'], 'maintenance/cron_enable', 'MaintenanceController@cron_enable')
->name('maintenance.cron_enable')->middleware('ability:admin,maintenance');

Route::match(['post'], 'maintenance/cron_disable', 'MaintenanceController@cron_disable')
->name('maintenance.cron_disable')->middleware('ability:admin,maintenance');

// subfleet
Route::get('subfleets/export', 'SubfleetController@export')
->name('subfleets.export')->middleware('ability:admin,fleet');
Expand Down Expand Up @@ -508,6 +514,8 @@ private function mapApiRoutes()
Route::get('pireps/{pirep_id}', 'PirepController@get');
Route::get('pireps/{pirep_id}/acars/geojson', 'AcarsController@acars_geojson');

Route::get('cron/{id}', 'MaintenanceController@cron')->name('maintenance.cron');

Route::get('news', 'NewsController@index');
Route::get('status', 'StatusController@status');
Route::get('version', 'StatusController@status');
Expand Down
42 changes: 41 additions & 1 deletion resources/views/admin/maintenance/cron.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
</h6>
<div class="row" style="padding-top: 5px">
<div class="col-sm-12">
<p>A cron must be created that runs every minute calling artisan. Example:</p>
<p>A cron must be created that runs every minute calling artisan. An example is below.
<strong><a href="{{ docs_link('cron') }}" target="_blank">See the docs</a></strong></p>
<label style="width: 100%">
<input type="text" value="{{ $cron_path }}" class="form-control" style="width: 100%"/>
</label>
Expand All @@ -20,6 +21,45 @@
@endif
</div>
</div>

<hr>

<div class="row" style="padding-top: 5px">
<div class="col-sm-12">
<h5>Web Cron</h5>
</div>
<div class="col-sm-6">
<p>
If you don't have cron access on your server, you can use a web-cron service to
access this URL every minute. Keep it disabled if you're not using it. It's a
unique ID that can be reset/changed if needed for security.
</p>
</div>
<div class="col-sm-6 pull-right">
<table class="table-condensed">
<tr class="text-right">
<td style="padding-right: 10px;" class="text-right">
{{ Form::open(['url' => route('admin.maintenance.cron_enable'),
'method' => 'post']) }}
{{ Form::button('Enable/Change ID', ['type' => 'submit', 'class' => 'btn btn-success']) }}
{{ Form::close() }}
</td>
<td class="text-right">
{{ Form::open(['url' => route('admin.maintenance.cron_disable'),
'method' => 'post']) }}
{{ Form::button('Disable', ['type' => 'submit', 'class' => 'btn btn-warning']) }}
{{ Form::close() }}
</td>
</tr>
</table>
</div>
<div class="col-sm-12">

<label style="width: 100%">
<input type="text" value="{{ $cron_url }}" class="form-control" style="width: 100%"/>
</label>
</div>
</div>
</div>
</div>
</div>
18 changes: 18 additions & 0 deletions tests/ApiTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use App\Models\Subfleet;
use App\Models\User;
use App\Services\FareService;
use App\Support\Utils;
use Exception;
use function random_int;

Expand Down Expand Up @@ -312,4 +313,21 @@ public function testGetUser()
$this->assertNotNull($user);
$this->assertTrue(strpos($user['avatar'], 'gravatar') !== -1);
}

/**
* Test that the web cron runs
*/
public function testWebCron()
{
$this->updateSetting('cron.random_id', '');
$this->get('/api/cron/sdf')->assertStatus(400);

$id = Utils::generateNewId(24);
$this->updateSetting('cron.random_id', $id);

$this->get('/api/cron/sdf')->assertStatus(400);

$res = $this->get('/api/cron/'.$id);
$res->assertStatus(200);
}
}
19 changes: 19 additions & 0 deletions tests/UtilsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Tests;

use App\Repositories\KvpRepository;
use App\Support\ICAO;
use App\Support\Units\Time;
use App\Support\Utils;
Expand All @@ -15,6 +16,24 @@ public function testDates()
$this->assertNotNull($carbon);
}

/**
* Simple test for KVP
*/
public function testKvp()
{
/** @var KvpRepository $kvpRepo */
$kvpRepo = app(KvpRepository::class);
$kvpRepo->save('testkey', 'some value');
$this->assertEquals('some value', $kvpRepo->get('testkey'));

// test that default value is working
$this->assertEquals('default value', $kvpRepo->get('unknownkey', 'default value'));

// try saving an integer
$kvpRepo->save('intval', 1);
$this->assertEquals(1, $kvpRepo->get('intval'));
}

/**
* @throws \Exception
*/
Expand Down