Add save & update functionality to positions

Tooltips also added, as well as a general configuration file for Mojang Status URL.
Relationships were also added between forms and Vacancies.
Status verification for the dashboard was moved to a Service Provider, where it adds log entries when cache expires.
Authentication controllers were also updated to reflect the new dashboard URL.
This commit is contained in:
Miguel Nogueira 2020-05-08 00:24:56 +01:00
parent 290104eea7
commit a4e415943a
18 changed files with 441 additions and 67 deletions

View File

@ -13,4 +13,9 @@ class Form extends Model
'formStatus'
];
public function vacancy()
{
return $this->hasMany('App\Vacancy', 'vacancyFormID');
}
}

View File

@ -26,7 +26,7 @@ class ConfirmPasswordController extends Controller
*
* @var string
*/
protected $redirectTo = RouteServiceProvider::HOME;
protected $redirectTo = '/dashboard';
/**
* Create a new controller instance.

View File

@ -26,5 +26,5 @@ class ResetPasswordController extends Controller
*
* @var string
*/
protected $redirectTo = RouteServiceProvider::HOME;
protected $redirectTo = '/dashboard';
}

View File

@ -26,7 +26,7 @@ class VerificationController extends Controller
*
* @var string
*/
protected $redirectTo = RouteServiceProvider::HOME;
protected $redirectTo = '/dashboard';
/**
* Create a new controller instance.

View File

@ -5,23 +5,14 @@ namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
class DashboardController extends Controller
{
public function index()
{
// TODO: Switch status checking to provider, share with all views
// Mojang status for informational purposes
if (!Cache::has('mojang_status'))
{
$mcstatus = Http::get('https://status.mojang.com/check');
Cache::put('mojang_status', base64_encode($mcstatus->body()), now()->addMinutes(60));
}
return view('dashboard.dashboard')
->with('mcstatus', json_decode(base64_decode(Cache::get('mojang_status')), true));
return view('dashboard.dashboard');
}
}

View File

@ -2,14 +2,86 @@
namespace App\Http\Controllers;
use App\Form;
use App\Http\Requests\VacancyRequest;
use App\Vacancy;
use Illuminate\Http\Request;
class VacancyController extends Controller
{
public function index()
{
return view('dashboard.administration.positions');
return view('dashboard.administration.positions')
->with([
'forms' => Form::all(),
'vacancies' => Vacancy::all()
]);
}
public function store(VacancyRequest $request)
{
$form = Form::find($request->vacancyFormID);
if (!is_null($form))
{
Vacancy::create([
'vacancyName' => $request->vacancyName,
'vacancyDescription' => $request->vacancyDescription,
'permissionGroupName' => $request->permissionGroup,
'discordRoleID' => $request->discordRole,
'vacancyFormID' => $request->vacancyFormID,
'vacancyCount' => $request->vacancyCount
]);
$request->session()->flash('success', 'Vacancy successfully opened. It will now show in the home page.');
}
else
{
$request->session()->flash('error', 'You cannot create a vacancy without a valid form.');
}
return redirect()->back();
}
public function updatePositionAvailability(Request $request, $status, $id)
{
$vacancy = Vacancy::find($id);
if (!is_null($vacancy))
{
$type = 'success';
switch ($status)
{
case 'open':
$vacancy->open();
$message = "Position successfully opened!";
break;
case 'close':
$vacancy->close();
$message = "Position successfully closed!";
break;
default:
$message = "Please do not tamper with the button's URLs. To report a bug, please contact an administrator.";
$type = 'error';
}
}
else
{
$message = "The position you're trying to update doesn't exist!";
$type = "error";
}
$request->session()->flash($type, $message);
return redirect()->back();
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class VacancyRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'vacancyName' => 'required|string',
'vacancyDescription' => 'required|string',
'permissionGroup' => 'required|string',
'discordRole' => 'required|string',
'vacancyCount' => 'required|integer',
'vacancyFormID' => 'required|integer'
];
}
}

View File

@ -1,28 +0,0 @@
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class GuzzleServiceProvider extends ServiceProvider
{
/**
* Register services.
*
* @return void
*/
public function register()
{
//
}
/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
//
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\View;
class MojangStatusProvider extends ServiceProvider
{
/**
* Register services.
*
* @return void
*/
public function register()
{
//
}
/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
if (!Cache::has('mojang_status'))
{
Log::info("Mojang Status Provider: Mojang Status not found in the cache; Sending new request.");
$mcstatus = Http::get(config('general.urls.mojang.statuscheck'));
Cache::put('mojang_status', base64_encode($mcstatus->body()), now()->addMinutes(60));
}
View::share('mcstatus', json_decode(base64_decode(Cache::get('mojang_status')), true));
}
}

View File

@ -3,8 +3,45 @@
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
class Vacancy extends Model
{
//
public $fillable = [
'permissionGroupName',
'vacancyName',
'vacancyDescription',
'discordRoleID',
'vacancyFormID',
'vacancyCount',
'vacancyStatus'
];
public function forms()
{
return $this->belongsTo('App\Form');
}
public function open()
{
$this->update([
'vacancyStatus' => 'OPEN'
]);
Log::info("Vacancies: Vacancy " . $this->id . " (" . $this->vacancyName . ") opened by " . Auth::user()->name);
}
public function close()
{
$this->update([
'vacancyStatus' => 'CLOSED'
]);
Log::warning("Vacancies: Vacancy " . $this->id . " (" . $this->vacancyName . ") closed by " . Auth::user()->name);
}
}

View File

@ -446,6 +446,17 @@ return [
'location' => 'https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.css'
]
]
],
[
'name' => 'GlobalTooltip',
'active' => 'true',
'files' => [
[
'type' => 'js',
'asset' => false,
'location' => '/js/globaltooltip.js'
]
]
]
],
];

View File

@ -174,6 +174,7 @@ return [
// App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
\App\Providers\MojangStatusProvider::class,
],

15
config/general.php Normal file
View File

@ -0,0 +1,15 @@
<?php
return [
'urls' =>
[
'mojang' => [
'statuscheck' => env('MOJANG_STATUS_URL') ?? 'https://status.mojang.com/check'
]
]
];

View File

@ -22,6 +22,8 @@ class CreateVacanciesTable extends Migration
$table->bigInteger('vacancyFormID')->unsigned();
$table->integer('vacancyCount')->default(3);
$table->timestamps();
$table->foreign('vacancyFormID')->references('id')->on('forms');
});
}

View File

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddStatusToVacancies extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('vacancies', function (Blueprint $table) {
$table->enum('vacancyStatus', [
'OPEN',
'CLOSED'
])->after('vacancyCount');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('vacancies', function (Blueprint $table) {
$table->dropColumn('vacancyStatus');
});
}
}

3
public/js/globaltooltip.js vendored Normal file
View File

@ -0,0 +1,3 @@
$(document).ready(function() {
$('input[rel="txtTooltip"]').tooltip();
});

View File

@ -8,8 +8,114 @@
@stop
@section('js')
@if (session()->has('success'))
<script>
toastr.success("{{session('success')}}")
</script>
@elseif(session()->has('error'))
<script>
toastr.error("{{session('error')}}")
</script>
@endif
@if($errors->any())
@foreach ($errors->all() as $error)
<script>toastr.error('{{$error}}', 'Validation error!')</script>
@endforeach
@endif
@stop
@section('content')
<div class="modal fade" tabindex="-1" id="newVacancyForm" role="dialog" aria-labelledby="modalFormLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modalFormLabel">New Vacancy</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
@if(!$forms->isEmpty())
<form id="savePositionForm" action="{{route('savePosition')}}" method="POST">
@csrf
<label for="vacancyName">Vacancy name (e.g. Helper)</label>
<input type="text" id="vacancyName" name="vacancyName" class="form-control">
<label for="vacancyDescription">Vacancy Description</label>
<input type="text" id="vacancyDescription" name="vacancyDescription" class="form-control">
<div class="row mt-3">
<div class="col">
<label for="pgroup">Permission Group Name</label>
<input rel="txtTooltip" title="The permission group from your server/network's permissions manager. Compatible with Luckperms and PEX." data-toggle="tooltip" data-placement="bottom" type="text" id="pgroup" name="permissionGroup" class="form-control">
</div>
<div class="col">
<label for="discordrole">Discord Role ID (*)</label>
<input rel="txtTooltip" title="Discord Desktop: Go to your Account Settings > Appearance -> Advanced and toggle Developer Mode. On your server's roles tab, right click any role to copy it's ID." data-toggle="tooltip" data-placement="bottom" type="text" id="discordrole" name="discordRole" class="form-control">
</div>
</div>
<div class="form-group mt-4">
<label for="associatedForm">Application form</label>
<select class="custom-select" name="vacancyFormID" id="associatedForm">
<option disabled>Select a form...</option>
@foreach($forms as $form)
<option value="{{$form->id}}">{{$form->formName}}</option>
@endforeach
</select>
<label for="vacancyCount">Free slots</label>
<input rel="txtTooltip" title="How many submissions before the vacancy stops accepting new applicants?" data-toggle="tooltip" data-placement="bottom" type="text" id="vacancyCount" name="vacancyCount" class="form-control">
</div>
</form>
@else
<div class="alert alert-danger">
<p>
You cannot create a vacancy without any forms with which people would apply.
Please create a form first, then, create a vacancy.
A single form is allowed to have multiple vacancies, so you can attach future vacancies to the same form if you'd like.
</p>
</div>
@endif
</div>
<div class="modal-footer">
@if(!$forms->isEmpty())
<button type="button" class="btn btn-primary" onclick="document.getElementById('savePositionForm').submit()">Add Vacancy</button>
@endif
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-4 offset-md-4 text-center">
@ -18,7 +124,7 @@
<div class="card-body">
<button type="button" class="btn btn-primary">NEW POSITION</button>
<button type="button" class="btn btn-primary" onclick="$('#newVacancyForm').modal('show')">NEW POSITION</button>
</div>
@ -40,40 +146,79 @@
<div class="card-body">
<table class="table table-active table-borderless">
@if(!$vacancies->isEmpty())
<thead>
<table class="table table-active table-borderless" style="white-space: nowrap">
<thead>
<tr>
<th>#</th>
<th>Vacancy Name</th>
<th>Vacancy Description</th>
<th>Date Created</th>
<th>Date Updated</th>
<th>Total Applicants</th>
<th>Discord Role ID</th>
<th>Perm. Group Name</th>
<th>Open Slots</th>
<th>Status</th>
<th>Created On</th>
<th>Actions</th>
</tr>
</thead>
</thead>
<tbody>
<tbody>
<tr>
<td>1</td>
<td>Helper</td>
<td>Help manage the server</td>
<td>2020-04-03</td>
<td>2020-05-01</td>
<td>10</td>
<td>
<button type="button" class="btn btn-sm btn-danger"><i class="fa fa-ban"></i> Close Position</button>
</td>
</tr>
@foreach($vacancies as $vacancy)
</tbody>
<tr>
</table>
<td>{{$vacancy->id}}</td>
<td>{{$vacancy->vacancyName}}</td>
<td>{{$vacancy->vacancyDescription}}</td>
<td><span class="badge badge-success">{{$vacancy->discordRoleID}}</span></td>
<td><span class="badge badge-success">{{$vacancy->permissionGroupName}}</span></td>
<td>{{$vacancy->vacancyCount}}</td>
@if($vacancy->vacancyStatus == 'OPEN')
<td><span class="badge badge-success">OPEN</span></td>
@else
<td><span class="badge badge-danger">CLOSED</span></td>
@endif
<td>{{$vacancy->created_at}}</td>
<td>
@if ($vacancy->vacancyStatus == 'OPEN')
<form action="{{route('updatePositionAvailability', ['status' => 'close', 'id' => $vacancy->id])}}" method="POST" id="closePosition">
@csrf
@method('PATCH')
<button type="submit" class="btn btn-sm btn-danger"><i class="fa fa-ban"></i> Close</button>
</form>
@else
<form action="{{route('updatePositionAvailability', ['status' => 'open', 'id' => $vacancy->id])}}" method="POST" id="openPosition">
@csrf
@method('PATCH')
<button type="submit" class="btn btn-sm btn-success"><i class="fa fa-check"></i> Open</button>
</form>
@endif
</td>
</tr>
@endforeach
</tbody>
</table>
@else
<div class="alert alert-warning">
<p>Nothing to see here! Open some vacancies first. This will get applicants pouring in! (hopefully)</p>
</div>
@endif
</div>
<div class="card-footer">

View File

@ -65,7 +65,16 @@ Route::group(['middleware' => 'auth'], function(){
Route::group(['prefix' => 'admin'], function (){
Route::resource('positions', 'VacancyController');
Route::get('positions', 'VacancyController@index')
->name('showPositions');
Route::post('positions/save', 'VacancyController@store')
->name('savePosition');
Route::patch('positions/availability/{status}/{id}', 'VacancyController@updatePositionAvailability')
->name('updatePositionAvailability');
Route::get('forms/builder', 'FormController@showFormBuilder')
->name('showFormBuilder');