Beta version

This commit is too large to list all changes.
This commit is contained in:
2020-06-27 00:32:33 +01:00
parent d15c0cb12f
commit 5a8c080a31
135 changed files with 8534 additions and 12774 deletions

View File

@@ -3,13 +3,20 @@
namespace App\Http\Controllers;
use App\Application;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Log;
use App\Response;
use App\Vacancy;
use App\User;
use App\Events\ApplicationDeniedEvent;
use App\Notifications\NewApplicant;
use App\Notifications\ApplicationMoved;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Log;
class ApplicationController extends Controller
{
@@ -21,15 +28,15 @@ class ApplicationController extends Controller
{
if ($vote->userID == Auth::user()->id)
{
Log::debug('Match');
$allvotes->push($vote);
}
}
return $allvotes->count() == 1;
return ($allvotes->count() == 1) ? false : true;
}
public function showUserApps()
{
@@ -37,16 +44,22 @@ class ApplicationController extends Controller
->with('applications', Auth::user()->applications);
}
public function showUserApp(Request $request, $applicationID)
{
$application = Application::find($applicationID);
$this->authorize('view', $application);
if (!is_null($application))
{
return view('dashboard.user.viewapp')
->with(
[
'application' => $application,
'comments' => $application->comments,
'structuredResponses' => json_decode($application->response->responseData, true),
'formStructure' => $application->response->form,
'vacancy' => $application->response->vacancy,
@@ -63,6 +76,8 @@ class ApplicationController extends Controller
}
public function showAllPendingApps()
{
return view('dashboard.appmanagement.outstandingapps')
@@ -70,6 +85,9 @@ class ApplicationController extends Controller
}
public function showPendingInterview()
{
$applications = Application::with('appointment', 'user')->get();
@@ -109,6 +127,8 @@ class ApplicationController extends Controller
]);
}
public function showPeerReview()
{
return view('dashboard.appmanagement.peerreview')
@@ -116,11 +136,16 @@ class ApplicationController extends Controller
}
public function renderApplicationForm(Request $request, $vacancySlug)
{
// FIXME: Get rid of references to first(), this is a wonky query
$vacancyWithForm = Vacancy::with('forms')->where('vacancySlug', $vacancySlug)->get();
if (!$vacancyWithForm->isEmpty())
$firstVacancy = $vacancyWithForm->first();
if (!$vacancyWithForm->isEmpty() && $firstVacancy->vacancyCount !== 0 && $firstVacancy->vacancyStatus == 'OPEN')
{
return view('dashboard.application-rendering.apply')
@@ -133,15 +158,25 @@ class ApplicationController extends Controller
}
else
{
abort(404, 'We\'re ssssorry, but the application form you\'re looking for could not be found.');
abort(404, 'The application you\'re looking for could not be found or it is currently unavailable.');
}
}
public function saveApplicationAnswers(Request $request, $vacancySlug)
{
$vacancy = Vacancy::with('forms')->where('vacancySlug', $vacancySlug)->get();
if ($vacancy->first()->vacancyCount == 0 || $vacancy->first()->vacancyStatus !== 'OPEN')
{
$request->session()->flash('error', 'This application is unavailable.');
return redirect()->back();
}
Log::info('Processing new application!');
$formStructure = json_decode($vacancy->first()->forms->formStructure, true);
@@ -179,7 +214,7 @@ class ApplicationController extends Controller
Log::info('Registered form response for user ' . Auth::user()->name . ' for vacancy ' . $vacancy->first()->vacancyName);
Application::create([
$application = Application::create([
'applicantUserID' => Auth::user()->id,
'applicantFormResponseID' => $response->id,
'applicationStatus' => 'STAGE_SUBMITTED',
@@ -187,6 +222,14 @@ class ApplicationController extends Controller
Log::info('Submitted application for user ' . Auth::user()->name . ' with response ID' . $response->id);
foreach(User::all() as $user)
{
if ($user->hasRole('admin'))
{
$user->notify((new NewApplicant($application, $vacancy->first()))->delay(now()->addSeconds(10)));
}
}
$request->session()->flash('success', 'Thank you for your application! It will be reviewed as soon as possible.');
return redirect()->to(route('showUserApps'));
}
@@ -210,15 +253,15 @@ class ApplicationController extends Controller
{
case 'deny':
Log::info('User ' . Auth::user()->name . ' has denied application ID ' . $application->id);
$request->session()->flash('success', 'Application denied.');
$application->setStatus('DENIED');
event(new ApplicationDeniedEvent($application));
break;
case 'interview':
Log::info('User ' . Auth::user()->name . ' has moved application ID ' . $application->id . 'to interview stage');
$request->session()->flash('success', 'Application moved to interview stage! (:');
$application->setStatus('STAGE_INTERVIEW');
$application->user->notify(new ApplicationMoved());
break;
default:

View File

@@ -7,6 +7,8 @@ use App\Http\Requests\SaveNotesRequest;
use Carbon\Carbon;
use Illuminate\Http\Request;
use App\Appointment;
use App\Notifications\ApplicationMoved;
use App\Notifications\AppointmentScheduled;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
@@ -47,7 +49,8 @@ class AppointmentController extends Controller
'datetime' => $appointmentDate->toDateTimeString(),
'scheduled' => now()
]);
$app->user->notify(new AppointmentScheduled($appointment));
$request->session()->flash('success', 'Appointment successfully scheduled @ ' . $appointmentDate->toDateTimeString());
}
@@ -70,10 +73,12 @@ class AppointmentController extends Controller
if (!is_null($application))
{
// NOTE: This is a little confusing, refactor
$application->appointment->appointmentStatus = (in_array($status, $validStatuses)) ? strtoupper($status) : 'SCHEDULED';
$application->appointment->save();
$application->setStatus('STAGE_PEERAPPROVAL');
$application->user->notify(new ApplicationMoved());
$request->session()->flash('success', 'Interview finished! Staff members can now vote on it.');
}

View File

@@ -2,9 +2,11 @@
namespace App\Http\Controllers\Auth;
use App\User;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
class LoginController extends Controller
{
@@ -19,7 +21,9 @@ class LoginController extends Controller
|
*/
use AuthenticatesUsers;
use AuthenticatesUsers {
attemptLogin as protected originalAttemptLogin;
}
/**
* Where to redirect users after login.
@@ -37,4 +41,29 @@ class LoginController extends Controller
{
$this->middleware('guest')->except('logout');
}
// We can't customise the error message, since that would imply overriding the login method, which is large.
// Also, the user should never know that they're banned.
public function attemptLogin(Request $request)
{
$user = User::where('email', $request->email)->first();
if ($user)
{
$isBanned = $user->isBanned();
if ($isBanned)
{
return false;
}
else
{
return $this->originalAttemptLogin($request);
}
}
return $this->originalAttemptLogin($request);
}
}

View File

@@ -43,6 +43,21 @@ class RegisterController extends Controller
$this->middleware('guest');
}
public function showRegistrationForm()
{
$users = User::where('originalIP', \request()->ip())->get();
foreach($users as $user)
{
if ($user && $user->isBanned())
{
abort(403, 'You do not have permission to access this page.');
}
}
return view('auth.register');
}
/**
* Get a validator for an incoming registration request.
*
@@ -78,15 +93,10 @@ class RegisterController extends Controller
'originalIP' => request()->ip()
]);
Profile::create([
'profileShortBio' => 'Write a one-liner about you here!',
'profileAboutMe' => 'Tell us a bit about you.',
'socialLinks' => '{}',
'userID' => $user->id
]);
// It's not the registration controller's concern to create a profile for the user,
// so this code has been moved to it's respective observer, following the separation of concerns pattern.
$user->assignRole('user');
return $user;
}
}

View File

@@ -0,0 +1,91 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Ban;
use App\User;
use App\Events\UserBannedEvent;
use App\Http\Requests\BanUserRequest;
class BanController extends Controller
{
public function insert(BanUserRequest $request, User $user)
{
if ($user->is(Auth::user()))
{
$request->session()->flash('error', 'You can\'t ban yourself!');
return redirect()->back();
}
if (is_null($user->bans))
{
$reason = $request->reason;
$duration = strtolower($request->durationOperator);
$durationOperand = $request->durationOperand;
if (!empty($duration))
{
$expiryDate = now();
switch($duration)
{
case 'days':
$expiryDate->addDays($duration);
break;
case 'weeks':
$expiryDate->addWeeks($duration);
break;
case 'months':
$expiryDate->addMonths($duration);
break;
case 'years':
$expiryDate->addYears($duration);
break;
}
}
$ban = Ban::create([
'userID' => $user->id,
'reason' => $request->reason,
'bannedUntil' => $expiryDate->toDateTimeString() ?? null,
'userAgent' => "Unknown",
'authorUserID' => Auth::user()->id
]);
event(new UserBannedEvent($user, $ban));
$request->session()->flash('success', 'User banned successfully! Ban ID: #' . $ban->id);
}
else
{
$request->session()->flash('error', 'User already banned!');
}
return redirect()->back();
}
public function delete(Request $request, User $user)
{
if (!is_null($user->bans))
{
$user->bans->delete();
$request->session()->flash('success', 'User unbanned successfully!');
}
else
{
$request->session()->flash('error', 'This user isn\'t banned!');
}
return redirect()->back();
}
}

View File

@@ -0,0 +1,68 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Http\Requests\NewCommentRequest;
use App\Comment;
use App\Application;
use App\Notifications\NewComment;
use App\User;
class CommentController extends Controller
{
public function index()
{
//
}
public function insert(NewCommentRequest $request, Application $application)
{
// Type hinting makes laravel automatically validate everything
$comment = Comment::create([
'authorID' => Auth::user()->id,
'applicationID' => $application->id,
'text' => $request->comment
]);
if ($comment)
{
foreach (User::all() as $user)
{
if ($user->isStaffMember())
{
$user->notify(new NewComment($comment, $application));
}
}
$request->session()->flash('success', 'Comment posted! (:');
}
else
{
$request->session()->flash('error', 'Something went wrong while posting your comment!');
}
return redirect()->back();
}
public function delete(Request $request, Comment $comment)
{
if (Auth::user()->is($comment->user) || Auth::user()->hasRole('admin'))
{
$comment->delete();
$request->session()->flash('success', 'Comment deleted!');
}
$request->session()->flash('error', 'You do not have permission to delete this comment!');
return redirect()->back();
}
}

View File

@@ -4,6 +4,7 @@ namespace App\Http\Controllers;
use App\Vacancy;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class HomeController extends Controller
{
@@ -17,7 +18,13 @@ class HomeController extends Controller
// TODO: Relationships for Applications, Users and Responses
// Also prevent apps if user already has one in the space of 30d
// Display apps in the relevant menus
$positions = DB::table('vacancies')
->where('vacancyStatus', 'OPEN')
->where('vacancyCount', '!=', 0)
->get();
return view('home')
->with('positions', Vacancy::where('vacancyStatus', 'OPEN')->get());
->with('positions', $positions);
}
}

View File

@@ -5,15 +5,20 @@ namespace App\Http\Controllers;
use App\Http\Requests\ProfileSave;
use Illuminate\Support\Facades\Log;
use App\Profile;
use App\User;
use App\Facades\IP;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Spatie\Permission\Models\Role;
class ProfileController extends Controller
{
public function showProfile()
{
$socialMediaProfiles = json_decode(Auth::user()->profile->socialLinks, true);
$socialLinks = Auth::user()->profile->socialLinks ?? "[]";
$socialMediaProfiles = json_decode($socialLinks, true);
return view('dashboard.user.profile.userprofile')
->with([
@@ -26,11 +31,56 @@ class ProfileController extends Controller
}
// Route model binding
public function showSingleProfile(Request $request, User $user)
{
$socialMediaProfiles = json_decode($user->profile->socialLinks, true);
$createdDate = Carbon::parse($user->created_at);
$systemRoles = Role::all()->pluck('name')->all();
$userRoles = $user->roles->pluck('name')->all();
$roleList = [];
foreach($systemRoles as $role)
{
if (in_array($role, $userRoles))
{
$roleList[$role] = true;
}
else
{
$roleList[$role] = false;
}
}
if (Auth::user()->is($user) || Auth::user()->can('profiles.view.others'))
{
return view('dashboard.user.profile.displayprofile')
->with([
'profile' => $user->profile,
'github' => $socialMediaProfiles['links']['github'] ?? 'UpdateMe',
'twitter' => $socialMediaProfiles['links']['twitter'] ?? 'UpdateMe',
'insta' => $socialMediaProfiles['links']['insta'] ?? 'UpdateMe',
'discord' => $socialMediaProfiles['links']['discord'] ?? 'UpdateMe#12345',
'since' => $createdDate->englishMonth . " " . $createdDate->year,
'ipInfo' => IP::lookup($user->originalIP),
'roles' => $roleList
]);
}
else
{
abort(403, 'You cannot view someone else\'s profile.');
}
}
public function saveProfile(ProfileSave $request)
{
// TODO: Implement profile security policy for logged in users
$profile = Profile::find(Auth::user()->id);
// TODO: Switch to route model binding
$profile = User::find(Auth::user()->id)->profile;
$social = [];
if (!is_null($profile))
@@ -57,7 +107,7 @@ class ProfileController extends Controller
$profile->avatarPreference = $avatarPref;
$profile->socialLinks = json_encode($social);
$profile->save();
$newProfile = $profile->save();
$request->session()->flash('success', 'Profile settings saved successfully.');

View File

@@ -5,23 +5,117 @@ namespace App\Http\Controllers;
use App\Http\Requests\ChangeEmailRequest;
use App\Http\Requests\ChangePasswordRequest;
use App\Http\Requests\FlushSessionsRequest;
use App\Http\Requests\DeleteUserRequest;
use App\Http\Requests\SearchPlayerRequest;
use App\Http\Requests\UpdateUserRequest;
use App\User;
use App\Ban;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
use App\Facades\UUID;
use App\Notifications\EmailChanged;
use App\Notifications\ChangedPassword;
use Spatie\Permission\Models\Role;
class UserController extends Controller
{
public function showStaffMembers()
{
return view('dashboard.administration.staff-members');
$staffRoles = [
'reviewer',
'hiringManager',
'admin'
]; // TODO: Un-hardcode this, move to config/roles.php
if (Auth::user()->can('admin.stafflist'))
{
$users = User::with('roles')->get();
$staffMembers = collect([]);
foreach($users as $user)
{
if (empty($user->roles))
{
Log::debug($user->role->name);
Log::debug('Staff list: User without role detected; Ignoring');
continue;
}
foreach($user->roles as $role)
{
if (in_array($role->name, $staffRoles))
{
$staffMembers->push($user);
continue 2; // Skip directly to the next user instead of comparing more roles for the current user
}
}
}
return view('dashboard.administration.staff-members')
->with([
'users' => $staffMembers
]);
}
abort(403, 'Forbidden');
}
public function showPlayers()
{
return view('dashboard.administration.players');
$users = User::with('roles')->get();
$players = collect([]);
foreach($users as $user)
{
// TODO: Might be problematic if we don't check if the role is user
if (count($user->roles) == 1)
{
$players->push($user);
}
}
if (Auth::user()->can('admin.userlist'))
{
return view('dashboard.administration.players')
->with([
'users' => $players,
'bannedUserCount' => Ban::all()->count()
]);
}
abort(403, 'Forbidden');
}
public function showPlayersLike(SearchPlayerRequest $request)
{
$searchTerm = $request->searchTerm;
$matchingUsers = User::query()
->where('name', 'LIKE', "%{$searchTerm}%")
->orWhere('email', 'LIKE', "%{$searchTerm}%")
->get();
if (!$matchingUsers->isEmpty())
{ $request->session()->flash('success', 'There were ' . $matchingUsers->count() . ' user(s) matching your search.');
return view('dashboard.administration.players')
->with([
'users' => $matchingUsers,
'bannedUserCount' => Ban::all()->count()
]);
}
else
{
$request->session()->flash('error', 'Your search term did not return any results.');
return redirect(route('registeredPlayerList'));
}
}
public function showAccount()
@@ -62,9 +156,9 @@ class UserController extends Controller
'userID' => $user->id,
'timestamp' => now()
]);
Auth::logout();
$user->notify(new ChangedPassword());
// After logout, the user gets caught by the auth filter, and it automatically redirects back to the previous page
Auth::logout();
return redirect()->back();
}
@@ -84,6 +178,7 @@ class UserController extends Controller
'userID' => $user->id,
'timestamp' => now()
]);
$user->notify(new EmailChanged());
$request->session()->flash('success', 'Your email address has been changed!');
}
@@ -95,4 +190,88 @@ class UserController extends Controller
return redirect()->back();
}
public function delete(DeleteUserRequest $request, User $user)
{
if ($request->confirmPrompt == 'DELETE ACCOUNT')
{
$user->delete();
$request->session()->flash('success','User deleted successfully. PII has been erased.');
}
else
{
$request->session()->flash('error', 'Wrong confirmation text! Try again.');
}
return redirect()->route('registeredPlayerList');
}
public function update(UpdateUserRequest $request, User $user)
{
// Mass update would not be possible here without extra code, making route model binding useless
$user->email = $request->email;
$user->name = $request->name;
$user->uuid = $request->uuid;
$existingRoles = Role::all()
->pluck('name')
->all();
$roleDiff = array_diff($existingRoles, $request->roles);
// Adds roles that were selected. Removes roles that aren't selected if the user has them.
foreach($roleDiff as $deselectedRole)
{
if ($user->hasRole($deselectedRole) && $deselectedRole !== 'user')
{
$user->removeRole($deselectedRole);
}
}
foreach($request->roles as $role)
{
if (!$user->hasRole($role))
{
$user->assignRole($role);
}
}
$user->save();
$request->session()->flash('success', 'User updated successfully!');
return redirect()->back();
}
public function terminate(Request $request, User $user)
{
$this->authorize('terminate', Auth::user());
if (!$user->isStaffMember() || $user->is(Auth::user()))
{
$request->session()->flash('error', 'You cannot terminate this user.');
return redirect()->back();
}
foreach ($user->roles as $role)
{
if ($role->name == 'user')
{
continue;
}
$user->removeRole($role->name);
}
Log::info('User ' . $user->name . ' has just been demoted.');
$request->session()->flash('success', 'User terminated successfully.');
//TODO: Dispatch event
return redirect()->back();
}
}

View File

@@ -2,9 +2,12 @@
namespace App\Http\Controllers;
use App\Form;
use App\Http\Requests\VacancyRequest;
use App\Vacancy;
use App\User;
use App\Form;
use App\Notifications\VacancyClosed;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
@@ -68,6 +71,13 @@ class VacancyController extends Controller
$vacancy->close();
$message = "Position successfully closed!";
foreach(User::all() as $user)
{
if ($user->isStaffMember())
{
$user->notify(new VacancyClosed($vacancy));
}
}
break;
default: