forked from miguel456/rbrecruiter
Major changes - Vote system now finished
This commit is contained in:
parent
cc8c293cc6
commit
d15c0cb12f
@ -49,3 +49,5 @@ PUSHER_APP_CLUSTER=mt1
|
||||
|
||||
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
|
||||
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
|
||||
|
||||
SENTRY_LARAVEL_DSN=
|
||||
|
@ -9,6 +9,7 @@
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/asm89/stack-cors" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/barryvdh/laravel-debugbar" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/brick/math" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/clue/stream-filter" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/composer" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/dnoegel/php-xdg-base-dir" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/cache" />
|
||||
@ -30,6 +31,8 @@
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/promises" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/psr7" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/hamcrest/hamcrest-php" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/http-interop/http-factory-guzzle" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/jean85/pretty-package-versions" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/jeroennoten/laravel-adminlte" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/laravel/framework" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/laravel/tinker" />
|
||||
@ -44,8 +47,16 @@
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/nikic/php-parser" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/nunomaduro/collision" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/opis/closure" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/paragonie/random_compat" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phar-io/manifest" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phar-io/version" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/php-http/client-common" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/php-http/discovery" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/php-http/guzzle6-adapter" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/php-http/httplug" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/php-http/message" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/php-http/message-factory" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/php-http/promise" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phpdocumentor/reflection-common" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phpdocumentor/reflection-docblock" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phpdocumentor/type-resolver" />
|
||||
@ -59,6 +70,8 @@
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/phpunit" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/container" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/event-dispatcher" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-client" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-factory" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-message" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/log" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/simple-cache" />
|
||||
@ -79,6 +92,8 @@
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/resource-operations" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/type" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/version" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sentry/sentry" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sentry/sentry-laravel" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/swiftmailer/swiftmailer" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/console" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/css-selector" />
|
||||
@ -90,12 +105,14 @@
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/http-foundation" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/http-kernel" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/mime" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/options-resolver" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-ctype" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-iconv" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-idn" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-mbstring" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php72" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php73" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-uuid" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/process" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/routing" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/service-contracts" />
|
||||
|
@ -104,6 +104,23 @@
|
||||
<path value="$PROJECT_DIR$/vendor/barryvdh/laravel-debugbar" />
|
||||
<path value="$PROJECT_DIR$/vendor/maximebf/debugbar" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/debug" />
|
||||
<path value="$PROJECT_DIR$/vendor/sentry/sentry-laravel" />
|
||||
<path value="$PROJECT_DIR$/vendor/sentry/sentry" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-uuid" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/options-resolver" />
|
||||
<path value="$PROJECT_DIR$/vendor/psr/http-client" />
|
||||
<path value="$PROJECT_DIR$/vendor/psr/http-factory" />
|
||||
<path value="$PROJECT_DIR$/vendor/php-http/message-factory" />
|
||||
<path value="$PROJECT_DIR$/vendor/php-http/httplug" />
|
||||
<path value="$PROJECT_DIR$/vendor/php-http/client-common" />
|
||||
<path value="$PROJECT_DIR$/vendor/php-http/discovery" />
|
||||
<path value="$PROJECT_DIR$/vendor/php-http/message" />
|
||||
<path value="$PROJECT_DIR$/vendor/php-http/promise" />
|
||||
<path value="$PROJECT_DIR$/vendor/php-http/guzzle6-adapter" />
|
||||
<path value="$PROJECT_DIR$/vendor/paragonie/random_compat" />
|
||||
<path value="$PROJECT_DIR$/vendor/clue/stream-filter" />
|
||||
<path value="$PROJECT_DIR$/vendor/jean85/pretty-package-versions" />
|
||||
<path value="$PROJECT_DIR$/vendor/http-interop/http-factory-guzzle" />
|
||||
</include_path>
|
||||
</component>
|
||||
<component name="PhpProjectSharedConfiguration" php_language_level="7.2" />
|
||||
|
@ -14,6 +14,7 @@ class Application extends Model
|
||||
|
||||
];
|
||||
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo('App\User', 'applicantUserID', 'id');
|
||||
@ -29,10 +30,16 @@ class Application extends Model
|
||||
return $this->hasOne('App\Appointment', 'applicationID', 'id');
|
||||
}
|
||||
|
||||
public function votes()
|
||||
{
|
||||
return $this->belongsToMany('App\Vote', 'votes_has_application');
|
||||
}
|
||||
|
||||
public function setStatus($status)
|
||||
{
|
||||
return $this->update([
|
||||
'applicationStatus' => $status
|
||||
]);
|
||||
|
||||
}
|
||||
}
|
||||
|
133
app/Console/Commands/CountVotes.php
Normal file
133
app/Console/Commands/CountVotes.php
Normal file
@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Application;
|
||||
use App\Events\ApplicationApprovedEvent;
|
||||
use App\Events\ApplicationDeniedEvent;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class CountVotes extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'votes:evaluate {--d|dryrun : Controls whether passing applicants should be promoted (e.g. only show results)}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Iterates through eligible applications and determines if they should be approved based on the number of votes';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$eligibleApps = Application::where('applicationStatus', 'STAGE_PEERAPPROVAL')->get();
|
||||
$pbar = $this->output->createProgressBar($eligibleApps->count());
|
||||
|
||||
if($eligibleApps->isEmpty())
|
||||
{
|
||||
$this->error('𐄂 There are no applications that need to be processed.');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($eligibleApps as $application)
|
||||
{
|
||||
$votes = $application->votes;
|
||||
$voteCount = $application->votes->count();
|
||||
|
||||
$positiveVotes = 0;
|
||||
$negativeVotes = 0;
|
||||
|
||||
if ($voteCount > 5)
|
||||
{
|
||||
$this->info('Counting votes for application ID ' . $application->id);
|
||||
foreach ($votes as $vote)
|
||||
{
|
||||
switch ($vote->allowedVoteType)
|
||||
{
|
||||
case 'VOTE_APPROVE':
|
||||
$positiveVotes++;
|
||||
break;
|
||||
case 'VOTE_DENY':
|
||||
$negativeVotes++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->info('Total votes for application ID ' . $application->id . ': ' . $voteCount);
|
||||
$this->info('Calculating criteria...');
|
||||
$negativeVotePercent = floor(($negativeVotes / $voteCount) * 100);
|
||||
$positiveVotePercent = floor(($positiveVotes / $voteCount) * 100);
|
||||
|
||||
$pollResult = $positiveVotePercent > $negativeVotePercent;
|
||||
|
||||
$this->table([
|
||||
'% of approval votes',
|
||||
'% of denial votes'
|
||||
], [ // array of arrays, e.g. rows
|
||||
[
|
||||
$positiveVotePercent . "%",
|
||||
$negativeVotePercent . "%"
|
||||
]
|
||||
]);
|
||||
|
||||
if ($pollResult)
|
||||
{
|
||||
$this->info('✓ Dispatched promotion event for applicant ' . $application->user->name);
|
||||
if (!$this->option('dryrun'))
|
||||
{
|
||||
event(new ApplicationApprovedEvent(Application::find($application->id)));
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->warn('Dry run: Event won\'t be dispatched');
|
||||
}
|
||||
|
||||
$pbar->advance();
|
||||
|
||||
}
|
||||
else {
|
||||
|
||||
if (!$this->option('dryrun'))
|
||||
{
|
||||
event(new ApplicationDeniedEvent(Application::find($application->id)));
|
||||
}
|
||||
else {
|
||||
$this->warn('Dry run: Event won\'t be dispatched');
|
||||
}
|
||||
|
||||
$pbar->advance();
|
||||
$this->error('𐄂 Applicant ' . $application->user->name . ' does not meet vote criteria (Majority)');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->warn("Application ID" . $application->id . " did not have enough votes for processing (min 5)");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$pbar->finish();
|
||||
return true;
|
||||
}
|
||||
}
|
@ -25,6 +25,10 @@ class Kernel extends ConsoleKernel
|
||||
protected function schedule(Schedule $schedule)
|
||||
{
|
||||
// $schedule->command('inspire')->hourly();
|
||||
|
||||
$schedule->command('vote:evaluate')
|
||||
->everyFiveMinutes();
|
||||
// Production value: Every day
|
||||
}
|
||||
|
||||
/**
|
||||
|
31
app/Events/ApplicationApprovedEvent.php
Normal file
31
app/Events/ApplicationApprovedEvent.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use App\Application;
|
||||
use Illuminate\Broadcasting\Channel;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Broadcasting\PresenceChannel;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ApplicationApprovedEvent
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
public $application;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param Application $application
|
||||
*/
|
||||
public function __construct(Application $application)
|
||||
{
|
||||
$this->application = $application;
|
||||
}
|
||||
|
||||
|
||||
}
|
30
app/Events/ApplicationDeniedEvent.php
Normal file
30
app/Events/ApplicationDeniedEvent.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use App\Application;
|
||||
use Illuminate\Broadcasting\Channel;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Broadcasting\PresenceChannel;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ApplicationDeniedEvent
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
public $application;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Application $application)
|
||||
{
|
||||
$this->application = $application;
|
||||
}
|
||||
|
||||
}
|
@ -36,6 +36,10 @@ class Handler extends ExceptionHandler
|
||||
*/
|
||||
public function report(Throwable $exception)
|
||||
{
|
||||
if (app()->bound('sentry') && $this->shouldReport($exception)) {
|
||||
app('sentry')->captureException($exception);
|
||||
}
|
||||
|
||||
parent::report($exception);
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,22 @@ use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class ApplicationController extends Controller
|
||||
{
|
||||
private function canVote($votes)
|
||||
{
|
||||
$allvotes = collect([]);
|
||||
|
||||
foreach ($votes as $vote)
|
||||
{
|
||||
if ($vote->userID == Auth::user()->id)
|
||||
{
|
||||
Log::debug('Match');
|
||||
$allvotes->push($vote);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return $allvotes->count() == 1;
|
||||
}
|
||||
|
||||
public function showUserApps()
|
||||
{
|
||||
@ -33,7 +49,8 @@ class ApplicationController extends Controller
|
||||
'application' => $application,
|
||||
'structuredResponses' => json_decode($application->response->responseData, true),
|
||||
'formStructure' => $application->response->form,
|
||||
'vacancy' => $application->response->vacancy
|
||||
'vacancy' => $application->response->vacancy,
|
||||
'canVote' => $this->canVote($application->votes)
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -96,6 +113,7 @@ class ApplicationController extends Controller
|
||||
{
|
||||
return view('dashboard.appmanagement.peerreview')
|
||||
->with('applications', Application::where('applicationStatus', 'STAGE_PEERAPPROVAL')->get());
|
||||
|
||||
}
|
||||
|
||||
public function renderApplicationForm(Request $request, $vacancySlug)
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Application;
|
||||
use App\Http\Requests\SaveNotesRequest;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Appointment;
|
||||
@ -84,5 +85,24 @@ class AppointmentController extends Controller
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
// also updates
|
||||
public function saveNotes(SaveNotesRequest $request, $applicationID)
|
||||
{
|
||||
$application = Application::find($applicationID);
|
||||
|
||||
if (!is_null($application))
|
||||
{
|
||||
$application->appointment->meetingNotes = $request->noteText;
|
||||
$application->appointment->save();
|
||||
|
||||
$request->session()->flash('success', 'Meeting notes have been saved.');
|
||||
}
|
||||
else
|
||||
{
|
||||
$request->session()->flash('error', 'Sanity check failed: There\'s no appointment to save notes to!');
|
||||
}
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
}
|
||||
|
34
app/Http/Controllers/DevToolsController.php
Normal file
34
app/Http/Controllers/DevToolsController.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Application;
|
||||
use App\Events\ApplicationApprovedEvent;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class DevToolsController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return view('dashboard.administration.devtools')
|
||||
->with('applications', Application::where('applicationStatus', 'STAGE_PEERAPPROVAL')->get());
|
||||
}
|
||||
|
||||
public function forceVoteCount(Request $request)
|
||||
{
|
||||
$application = Application::find($request->application);
|
||||
|
||||
if (!is_null($application))
|
||||
{
|
||||
event(new ApplicationApprovedEvent($application));
|
||||
|
||||
$request->session()->flash('success', 'Event dispatched! Please check the debug logs for more info');
|
||||
}
|
||||
else
|
||||
{
|
||||
$request->session()->flash('error', 'Application doesn\'t exist!');
|
||||
}
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
}
|
@ -2,9 +2,43 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Application;
|
||||
use App\Http\Requests\VoteRequest;
|
||||
use App\Jobs\ProcessVoteList;
|
||||
use App\Vote;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class VoteController extends Controller
|
||||
{
|
||||
//
|
||||
|
||||
public function vote(VoteRequest $voteRequest, $applicationID)
|
||||
{
|
||||
$application = Application::find($applicationID);
|
||||
|
||||
if (!is_null($application))
|
||||
{
|
||||
$vote = Vote::create([
|
||||
'userID' => Auth::user()->id,
|
||||
'allowedVoteType' => $voteRequest->voteType,
|
||||
]);
|
||||
|
||||
$vote->application()->attach($applicationID);
|
||||
|
||||
Log::info('User ' . Auth::user()->name . ' has voted in applicant ' . $application->user->name . '\'s application', [
|
||||
'voteType' => $voteRequest->voteType
|
||||
]);
|
||||
|
||||
$voteRequest->session()->flash('success', 'Your vote has been registered! You will now be notified about the outcome of this application.');
|
||||
}
|
||||
else
|
||||
{
|
||||
$voteRequest->session()->flash('error', 'Can\t vote a non existant application!');
|
||||
}
|
||||
|
||||
// Cron job will run command that processes votes
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
}
|
||||
|
30
app/Http/Requests/SaveNotesRequest.php
Normal file
30
app/Http/Requests/SaveNotesRequest.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class SaveNotesRequest 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 [
|
||||
'noteText' => 'required|string'
|
||||
];
|
||||
}
|
||||
}
|
30
app/Http/Requests/VoteRequest.php
Normal file
30
app/Http/Requests/VoteRequest.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class VoteRequest 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 [
|
||||
'voteType' => 'required|string|in:VOTE_DENY,VOTE_APPROVE'
|
||||
];
|
||||
}
|
||||
}
|
35
app/Listeners/DenyUser.php
Normal file
35
app/Listeners/DenyUser.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Events\ApplicationDeniedEvent;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class DenyUser
|
||||
{
|
||||
/**
|
||||
* Create the event listener.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param ApplicationDeniedEvent $event
|
||||
* @return void
|
||||
*/
|
||||
public function handle(ApplicationDeniedEvent $event)
|
||||
{
|
||||
$event->application->setStatus('DENIED');
|
||||
Log::info('User ' . $event->application->user->name . ' just had their application denied.');
|
||||
|
||||
// Also dispatch other notifications
|
||||
}
|
||||
}
|
48
app/Listeners/PromoteUser.php
Normal file
48
app/Listeners/PromoteUser.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Events\ApplicationApprovedEvent;
|
||||
use App\StaffProfile;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class PromoteUser
|
||||
{
|
||||
/**
|
||||
* Create the event listener.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param ApplicationApprovedEvent $event
|
||||
* @return void
|
||||
*/
|
||||
public function handle(ApplicationApprovedEvent $event)
|
||||
{
|
||||
$event->application->setStatus('APPROVED');
|
||||
|
||||
$staffProfile = StaffProfile::create([
|
||||
'userID' => $event->application->user->id,
|
||||
'approvalDate' => now()->toDateTimeString(),
|
||||
'memberNotes' => 'Approved by staff members. Welcome them to the team!'
|
||||
]);
|
||||
|
||||
Log::info('User ' . $event->application->user->name . ' has just been promoted!', [
|
||||
'newRank' => $event->application->response->vacancy->permissionGroupName,
|
||||
'staffProfileID' => $staffProfile->id
|
||||
]);
|
||||
// TODO: Dispatch alert email and notifications for the user and staff members
|
||||
// TODO: Also assign new app role based on the permission group name
|
||||
|
||||
}
|
||||
}
|
@ -20,6 +20,12 @@ class EventServiceProvider extends ServiceProvider
|
||||
SendEmailVerificationNotification::class,
|
||||
OnUserRegistration::class
|
||||
],
|
||||
'App\Events\ApplicationApprovedEvent' => [
|
||||
'App\Listeners\PromoteUser'
|
||||
],
|
||||
'App\Events\ApplicationDeniedEvent' => [
|
||||
'App\Listeners\DenyUser'
|
||||
]
|
||||
];
|
||||
|
||||
/**
|
||||
@ -29,6 +35,7 @@ class EventServiceProvider extends ServiceProvider
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
|
||||
parent::boot();
|
||||
|
||||
//
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use GuzzleHttp\Exception\ConnectException;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
@ -32,8 +33,17 @@ class MojangStatusProvider extends ServiceProvider
|
||||
{
|
||||
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));
|
||||
try
|
||||
{
|
||||
$mcstatus = Http::get(config('general.urls.mojang.statuscheck'));
|
||||
Cache::put('mojang_status', base64_encode($mcstatus->body()), now()->addDays(3));
|
||||
}
|
||||
catch(ConnectException $connectException)
|
||||
{
|
||||
Log::critical('Could not connect to Mojang servers: Cannot check/refresh status', [
|
||||
'message' => $connectException->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
View::share('mcstatus', json_decode(base64_decode(Cache::get('mojang_status')), true));
|
||||
|
@ -6,5 +6,13 @@ use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class StaffProfile extends Model
|
||||
{
|
||||
//
|
||||
public $fillable = [
|
||||
|
||||
'userID',
|
||||
'approvalDate',
|
||||
'terminationDate',
|
||||
'resignationDate',
|
||||
'memberNotes'
|
||||
|
||||
];
|
||||
}
|
||||
|
@ -42,8 +42,14 @@ class User extends Authenticatable
|
||||
return $this->hasMany('App\Application', 'applicantUserID', 'id');
|
||||
}
|
||||
|
||||
public function votes()
|
||||
{
|
||||
return $this->hasMany('App\Vote', 'userID', 'id');
|
||||
}
|
||||
|
||||
public function profile()
|
||||
{
|
||||
return $this->hasOne('App\Profile', 'userID', 'id');
|
||||
}
|
||||
|
||||
}
|
||||
|
21
app/Vote.php
21
app/Vote.php
@ -6,5 +6,24 @@ use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Vote extends Model
|
||||
{
|
||||
//
|
||||
public $fillable = [
|
||||
|
||||
'userID',
|
||||
'allowedVoteType',
|
||||
|
||||
];
|
||||
|
||||
public $touches = [
|
||||
'application'
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo('App\User', 'id', 'userID');
|
||||
}
|
||||
|
||||
public function application()
|
||||
{
|
||||
return $this->belongsToMany('App\Application', 'votes_has_application');
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,8 @@
|
||||
"jeroennoten/laravel-adminlte": "^3.2",
|
||||
"laravel/framework": "^7.0",
|
||||
"laravel/tinker": "^2.0",
|
||||
"laravel/ui": "^2.0"
|
||||
"laravel/ui": "^2.0",
|
||||
"sentry/sentry-laravel": "1.7.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"barryvdh/laravel-debugbar": "^3.3",
|
||||
|
1081
composer.lock
generated
1081
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -294,6 +294,11 @@ return [
|
||||
'text' => 'Global Notification Settings',
|
||||
'icon' => 'far fa-bell',
|
||||
'url' => '/admin/notifications'
|
||||
],
|
||||
[
|
||||
'text' => 'Developer Tools',
|
||||
'icon' => 'fas fa-code',
|
||||
'url' => '/admin/devtools'
|
||||
]
|
||||
]
|
||||
],
|
||||
|
24
config/sentry.php
Normal file
24
config/sentry.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'dsn' => env('SENTRY_LARAVEL_DSN', env('SENTRY_DSN')),
|
||||
|
||||
// capture release as git sha
|
||||
// 'release' => trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')),
|
||||
|
||||
'breadcrumbs' => [
|
||||
// Capture Laravel logs in breadcrumbs
|
||||
'logs' => true,
|
||||
|
||||
// Capture SQL queries in breadcrumbs
|
||||
'sql_queries' => true,
|
||||
|
||||
// Capture bindings on SQL queries logged in breadcrumbs
|
||||
'sql_bindings' => true,
|
||||
|
||||
// Capture queue job information in breadcrumbs
|
||||
'queue_info' => true,
|
||||
],
|
||||
|
||||
];
|
3
public/css/viewapplication.css
vendored
3
public/css/viewapplication.css
vendored
@ -1,6 +1,5 @@
|
||||
|
||||
.footer-button {
|
||||
.footer-buttons {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
75
resources/views/dashboard/administration/devtools.blade.php
Normal file
75
resources/views/dashboard/administration/devtools.blade.php
Normal file
@ -0,0 +1,75 @@
|
||||
@extends('adminlte::page')
|
||||
|
||||
@section('title', 'Raspberry Network | Developer Options')
|
||||
|
||||
@section('content_header')
|
||||
|
||||
<h4>Administration / Developer Tools</h4>
|
||||
|
||||
@stop
|
||||
|
||||
@section('js')
|
||||
<x-global-errors></x-global-errors>
|
||||
@stop
|
||||
|
||||
@section('content')
|
||||
|
||||
<x-modal id="confirmForceEventDispatch" modal-label="confirmForceEventDispatch" modal-title="Choose an application" include-close-button="true">
|
||||
|
||||
<p>Please choose an application to force re-evaluation</p>
|
||||
<form method="POST" id="forceEval" action="{{route('devToolsForceVoteCount')}}">
|
||||
@csrf
|
||||
<select name="application" class="custom-select">
|
||||
@if(!$applications->isEmpty())
|
||||
@foreach($applications as $application)
|
||||
|
||||
<option value="{{$application->id}}">Application ID {{$application->id}} ({{$application->user->name}})</option>
|
||||
|
||||
@endforeach
|
||||
@else
|
||||
<option value="null" disabled>There are no valid applications</option>
|
||||
@endif
|
||||
</select>
|
||||
|
||||
</form>
|
||||
|
||||
<x-slot name="modalFooter">
|
||||
<button type="button" class="btn btn-danger" onclick="document.getElementById('forceEval').submit()">Dispatch event now</button>
|
||||
</x-slot>
|
||||
|
||||
</x-modal>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
|
||||
<div class="alert alert-warning">
|
||||
|
||||
<i class="fa fa-exclamation-triangle"></i> <b>Warning: Do not use these options if you don't know what you're doing, even if you have access to this page.</b>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col">
|
||||
|
||||
<x-card id="tools" card-title="Event Management" footer-style="text-center">
|
||||
|
||||
<x-slot name="cardHeader">
|
||||
|
||||
</x-slot>
|
||||
|
||||
<button type="button" class="btn btn-danger" onclick="$('#confirmForceEventDispatch').modal('show')">Force Vote Evaluation</button>
|
||||
|
||||
<x-slot name="cardFooter">
|
||||
|
||||
</x-slot>
|
||||
</x-card>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@stop
|
@ -67,8 +67,6 @@
|
||||
<td><span class="badge badge-warning">{{($application->applicationStatus == 'STAGE_PEERAPPROVAL') ? 'Peer Review' : 'Unknown'}}</span></td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-info btn-sm" onclick="window.location.href='{{route('showUserApp', ['id' => $application->id])}}'"><i class="far fa-clipboard"></i> Review</button>
|
||||
<button type="button" class="btn btn-success btn-sm"><i class="fas fa-user-check"></i> Vote: Approve</button>
|
||||
<button type="button" class="btn btn-danger btn-sm"><i class="fas fa-user-times"></i> Vote: Deny</button>
|
||||
</td>
|
||||
|
||||
@endforeach
|
||||
|
@ -22,10 +22,30 @@
|
||||
<script type="text/javascript" src="/js/app.js"></script>
|
||||
<x-global-errors></x-global-errors>
|
||||
|
||||
@if (!$canVote)
|
||||
<script>
|
||||
toastr.info('You cannot vote on this application anymore.', 'Warning')
|
||||
</script>
|
||||
@endif
|
||||
|
||||
@stop
|
||||
|
||||
@section('content')
|
||||
|
||||
<x-modal id="notes" modal-label="notes" modal-title="Shared Notepad" include-close-button="true">
|
||||
|
||||
<form id="meetingNotes" method="POST" action="{{route('saveNotes', ['applicationID' => $application->id])}}">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
<textarea name="noteText" rows="5" class="form-control">{{$application->appointment->meetingNotes ?? 'There are no notes yet. Add some!'}}</textarea>
|
||||
</form>
|
||||
<p class="text-muted text-sm">Last updated @ {{$application->appointment->updated_at}}</p>
|
||||
|
||||
<x-slot name="modalFooter">
|
||||
<button type="button" class="btn btn-success" onclick="document.getElementById('meetingNotes').submit()"><i class="far fa-paper-plane"></i> Save & Close</button>
|
||||
</x-slot>
|
||||
</x-modal>
|
||||
|
||||
<x-modal id="denyApplication" modal-label="denyApplicationLabel" modal-title="Please confirm" include-close-button="true">
|
||||
|
||||
<p>Are you sure you want to deny this application? Please keep in mind that this user will only be allowed to apply 30 days after their first application.</p>
|
||||
@ -246,7 +266,8 @@
|
||||
</form>
|
||||
<button class="btn btn-warning mr-3">View Meeting Notes</button>
|
||||
|
||||
<button class="btn btn-danger mr-3">Cancel Interview</button>
|
||||
<!-- Show to users only -->
|
||||
<button class="btn btn-success mr-3">Accept Meeting</button>
|
||||
</x-slot>
|
||||
|
||||
</x-card>
|
||||
@ -270,10 +291,22 @@
|
||||
|
||||
<x-slot name="cardFooter">
|
||||
|
||||
<button type="button" class="btn btn-sm btn-warning">Vote: Approve Applicant</button>
|
||||
<button type="button" class="btn btn-sm btn-warning">Vote: Deny Applicant</button>
|
||||
@if($canVote)
|
||||
|
||||
<button type="button" class="btn btn-sm btn-warning ml-5">Meeting Notes</button>
|
||||
<form class="d-inline-block" method="POST" action="{{route('voteApplication', ['id' => $application->id])}}">
|
||||
@csrf
|
||||
<input type="hidden" name="voteType" value="VOTE_APPROVE">
|
||||
<button type="submit" class="btn btn-sm btn-warning">Vote: Approve Applicant</button>
|
||||
</form>
|
||||
<form class="d-inline-block" method="POST" action="{{route('voteApplication', ['id' => $application->id])}}">
|
||||
@csrf
|
||||
<input type="hidden" name="voteType" value="VOTE_DENY">
|
||||
<button type="submit" class="btn btn-sm btn-warning">Vote: Deny Applicant</button>
|
||||
</form>
|
||||
|
||||
@endif
|
||||
|
||||
<button type="button" class="btn btn-sm btn-warning {{($canVote) ? 'ml-5' : ''}}" onclick="$('#notes').modal('show')">Meeting Notes</button>
|
||||
</x-slot>
|
||||
|
||||
</x-card>
|
||||
|
@ -12,7 +12,6 @@ use Illuminate\Support\Facades\Route;
|
||||
| contains the "web" middleware group. Now create something great!
|
||||
|
|
||||
*/
|
||||
|
||||
Route::group(['prefix' => 'auth', 'middleware' => ['usernameUUID']], function (){
|
||||
|
||||
Auth::routes();
|
||||
@ -38,9 +37,14 @@ Route::group(['middleware' => 'auth'], function(){
|
||||
->name('showUserApps')
|
||||
->middleware('eligibility');
|
||||
|
||||
|
||||
Route::get('/view/{id}', 'ApplicationController@showUserApp')
|
||||
->name('showUserApp');
|
||||
|
||||
Route::patch('/notes/save/{applicationID}', 'AppointmentController@saveNotes')
|
||||
->name('saveNotes');
|
||||
|
||||
|
||||
Route::patch('/update/{id}/{newStatus}', 'ApplicationController@updateApplicationStatus')
|
||||
->name('updateApplicationStatus');
|
||||
|
||||
@ -54,6 +58,10 @@ Route::group(['middleware' => 'auth'], function(){
|
||||
->name('pendingInterview');
|
||||
|
||||
|
||||
Route::post('{id}/staff/vote', 'VoteController@vote')
|
||||
->name('voteApplication');
|
||||
|
||||
|
||||
});
|
||||
|
||||
Route::group(['prefix' => 'appointments'], function (){
|
||||
@ -137,6 +145,14 @@ Route::group(['middleware' => 'auth'], function(){
|
||||
Route::get('forms', 'FormController@index')
|
||||
->name('showForms');
|
||||
|
||||
|
||||
Route::get('devtools', 'DevToolsController@index')
|
||||
->name('devTools');
|
||||
|
||||
// we could use route model binding
|
||||
Route::post('devtools/vote-evaluation/force', 'DevToolsController@forceVoteCount')
|
||||
->name('devToolsForceVoteCount');
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
0
storage/debugbar/.gitignore
vendored
Normal file → Executable file
0
storage/debugbar/.gitignore
vendored
Normal file → Executable file
Loading…
Reference in New Issue
Block a user