Browse Source

Beta version

This commit is too large to list all changes.
l10n_master
Miguel N. 11 months ago
parent
commit
5a8c080a31
  1. 3
      .env.example
  2. 6
      .idea/hrm-mcserver.iml
  3. 6
      .idea/php.xml
  4. 22
      .vscode/launch.json
  5. 8
      app/Application.php
  6. 1
      app/Appointment.php
  7. 25
      app/Ban.php
  8. 26
      app/Comment.php
  9. 3
      app/Console/Commands/CountVotes.php
  10. 7
      app/Console/Kernel.php
  11. 42
      app/CustomFacades/IP.php
  12. 36
      app/Events/NewApplicationEvent.php
  13. 36
      app/Events/UserBannedEvent.php
  14. 13
      app/Facades/IP.php
  15. 13
      app/Facades/UUID.php
  16. 65
      app/Http/Controllers/ApplicationController.php
  17. 7
      app/Http/Controllers/AppointmentController.php
  18. 31
      app/Http/Controllers/Auth/LoginController.php
  19. 26
      app/Http/Controllers/Auth/RegisterController.php
  20. 91
      app/Http/Controllers/BanController.php
  21. 68
      app/Http/Controllers/CommentController.php
  22. 9
      app/Http/Controllers/HomeController.php
  23. 60
      app/Http/Controllers/ProfileController.php
  24. 187
      app/Http/Controllers/UserController.php
  25. 12
      app/Http/Controllers/VacancyController.php
  26. 3
      app/Http/Kernel.php
  27. 39
      app/Http/Middleware/Bancheck.php
  28. 29
      app/Http/Middleware/ForceLogoutMiddleware.php
  29. 34
      app/Http/Requests/BanUserRequest.php
  30. 31
      app/Http/Requests/DeleteUserRequest.php
  31. 33
      app/Http/Requests/NewCommentRequest.php
  32. 31
      app/Http/Requests/SearchPlayerRequest.php
  33. 34
      app/Http/Requests/UpdateUserRequest.php
  34. 56
      app/Jobs/CleanBans.php
  35. 4
      app/Listeners/DenyUser.php
  36. 46
      app/Listeners/OnUserBanned.php
  37. 11
      app/Listeners/OnUserRegistration.php
  38. 7
      app/Listeners/PromoteUser.php
  39. 93
      app/Notifications/ApplicationApproved.php
  40. 85
      app/Notifications/ApplicationDenied.php
  41. 64
      app/Notifications/ApplicationMoved.php
  42. 64
      app/Notifications/AppointmentFinished.php
  43. 71
      app/Notifications/AppointmentScheduled.php
  44. 64
      app/Notifications/ChangedPassword.php
  45. 63
      app/Notifications/EmailChanged.php
  46. 98
      app/Notifications/NewApplicant.php
  47. 69
      app/Notifications/NewComment.php
  48. 100
      app/Notifications/NewUser.php
  49. 71
      app/Notifications/UserBanned.php
  50. 68
      app/Notifications/VacancyClosed.php
  51. 116
      app/Observers/UserObserver.php
  52. 33
      app/Policies/ApplicationPolicy.php
  53. 8
      app/Policies/ProfilePolicy.php
  54. 41
      app/Policies/UserPolicy.php
  55. 3
      app/Providers/AppServiceProvider.php
  56. 7
      app/Providers/AuthServiceProvider.php
  57. 3
      app/Providers/EventServiceProvider.php
  58. 33
      app/Providers/IPInfoProvider.php
  59. 2
      app/Providers/RouteServiceProvider.php
  60. 33
      app/Providers/UUIDConversionProvider.php
  61. 1
      app/Response.php
  62. 53
      app/UUID/UUID.php
  63. 34
      app/User.php
  64. 5
      composer.json
  65. 1205
      composer.lock
  66. 60
      config/adminlte.php
  67. 4
      config/app.php
  68. 13
      config/general.php
  69. 141
      config/log-viewer.php
  70. 12
      config/notification.php
  71. 143
      config/permission.php
  72. 12
      config/slack.php
  73. 3
      database/migrations/2020_04_29_022245_create_profiles_table.php
  74. 4
      database/migrations/2020_04_29_022402_create_applications_table.php
  75. 4
      database/migrations/2020_04_29_022421_create_votes_table.php
  76. 4
      database/migrations/2020_04_29_022442_create_appointments_table.php
  77. 5
      database/migrations/2020_04_29_022542_create_vacancies_table.php
  78. 4
      database/migrations/2020_04_29_023647_create_staff_profiles_table.php
  79. 6
      database/migrations/2020_04_29_030107_create_responses_table.php
  80. 3
      database/migrations/2020_05_14_004542_change_form_structure_length.php
  81. 110
      database/migrations/2020_05_29_234431_create_permission_tables.php
  82. 41
      database/migrations/2020_06_08_153602_create_bans_table.php
  83. 44
      database/migrations/2020_06_20_210255_create_comments_table.php
  84. 36
      database/migrations/2020_06_25_093708_create_jobs_table.php
  85. 3
      database/seeds/DatabaseSeeder.php
  86. 98
      database/seeds/PermissionSeeder.php
  87. 146
      database/seeds/UserSeeder.php
  88. 38
      public/css/comments.css
  89. 3
      public/css/picker.css
  90. 12223
      public/js/app.js
  91. 30
      resources/js/app.js
  92. 13
      resources/lang/vendor/log-viewer/ar.json
  93. 13
      resources/lang/vendor/log-viewer/bg.json
  94. 13
      resources/lang/vendor/log-viewer/de.json
  95. 13
      resources/lang/vendor/log-viewer/es.json
  96. 13
      resources/lang/vendor/log-viewer/et.json
  97. 13
      resources/lang/vendor/log-viewer/fa.json
  98. 13
      resources/lang/vendor/log-viewer/fr.json
  99. 13
      resources/lang/vendor/log-viewer/hu.json
  100. 13
      resources/lang/vendor/log-viewer/hy.json

3
.env.example

@ -18,6 +18,9 @@ RECAPTCHA_PRIVATE_KEY=
RECAPTCHA_VERIFY_URL="https://www.google.com/recaptcha/api/siteverify"
# WARNING: Your contact form will be useless if you change this value. Only change this URL if Google updates it.
IPGEO_API_KEY=""
IPGEO_API_URL=""
BROADCAST_DRIVER=log
CACHE_DRIVER=file
QUEUE_CONNECTION=sync

6
.idea/hrm-mcserver.iml

@ -94,10 +94,12 @@
<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/spatie/laravel-permission" />
<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" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/debug" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/deprecation-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/error-handler" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/event-dispatcher" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/event-dispatcher-contracts" />
@ -108,14 +110,18 @@
<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-grapheme" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-idn" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-normalizer" />
<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-php80" />
<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" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/string" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/translation" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/translation-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/var-dumper" />

6
.idea/php.xml

@ -121,6 +121,12 @@
<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" />
<path value="$PROJECT_DIR$/vendor/spatie/laravel-permission" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-normalizer" />
<path value="$PROJECT_DIR$/vendor/symfony/deprecation-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/string" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-grapheme" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php80" />
</include_path>
</component>
<component name="PhpProjectSharedConfiguration" php_language_level="7.2" />

22
.vscode/launch.json

@ -0,0 +1,22 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Listen for XDebug",
"type": "php",
"request": "launch",
"port": 9000
},
{
"name": "Launch currently open script",
"type": "php",
"request": "launch",
"program": "${file}",
"cwd": "${fileDirname}",
"port": 9000
}
]
}

8
app/Application.php

@ -15,6 +15,8 @@ class Application extends Model
];
public function user()
{
return $this->belongsTo('App\User', 'applicantUserID', 'id');
@ -35,6 +37,12 @@ class Application extends Model
return $this->belongsToMany('App\Vote', 'votes_has_application');
}
public function comments()
{
return $this->hasMany('App\Comment', 'applicationID', 'id');
}
public function setStatus($status)
{
return $this->update([

1
app/Appointment.php

@ -16,6 +16,7 @@ class Appointment extends Model
public function application()
{
// FIXME: Possible bug here, where laravel looks for the wrong column in the applications table.
return $this->belongsTo('App\Application', 'id', 'applicationID');
}

25
app/Ban.php

@ -0,0 +1,25 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Ban extends Model
{
public $fillable = [
'userID',
'reason',
'bannedUntil',
'userAgent',
'authorUserID'
];
public function user()
{
return $this->belongsTo('App\User', 'userID', 'id');
}
}

26
app/Comment.php

@ -0,0 +1,26 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
protected $fillable = [
'authorID',
'applicationID',
'text'
];
public function application()
{
return $this->belongsTo('App\Application', 'applicationID', 'id');
}
public function user()
{
return $this->belongsTo('App\User', 'authorID', 'id');
}
}

3
app/Console/Commands/CountVotes.php

@ -96,6 +96,9 @@ class CountVotes extends Command
$this->info('✓ Dispatched promotion event for applicant ' . $application->user->name);
if (!$this->option('dryrun'))
{
$application->response->vacancy->vacancyCount -= 1;
$application->response->vacancy->save();
event(new ApplicationApprovedEvent(Application::find($application->id)));
}
else

7
app/Console/Kernel.php

@ -4,6 +4,7 @@ namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
use App\Jobs\CleanBans;
class Kernel extends ConsoleKernel
{
@ -27,7 +28,11 @@ class Kernel extends ConsoleKernel
// $schedule->command('inspire')->hourly();
$schedule->command('vote:evaluate')
->everyFiveMinutes();
->daily();
// Production value: Every day
$schedule->job(new CleanBans)
->daily();
// Production value: Every day
}

42
app/CustomFacades/IP.php

@ -0,0 +1,42 @@
<?php
namespace App\CustomFacades;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
class IP
{
/**
* Looks up information on a specified IP address. Caches results automatically.
* @param string $IP IP address to lookup
* @return object
*/
public function lookup(string $IP): object
{
if (empty($IP))
{
throw new LogicException(__METHOD__ . 'is missing parameter IP!');
}
$params = [
'apiKey' => config('general.keys.ipapi.apikey'),
'ip' => $IP
];
// TODO: Maybe unwrap this? Methods are chained here
return json_decode(Cache::remember($IP, 3600, function() use ($IP)
{
return Http::get(config('general.urls.ipapi.ipcheck'), [
'apiKey' => config('general.keys.ipapi.apikey'),
'ip' => $IP
])->body();
}));
}
}

36
app/Events/NewApplicationEvent.php

@ -0,0 +1,36 @@
<?php
namespace App\Events;
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 NewApplicationEvent
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('channel-name');
}
}

36
app/Events/UserBannedEvent.php

@ -0,0 +1,36 @@
<?php
namespace App\Events;
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;
use App\User;
use App\Ban;
class UserBannedEvent
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $user;
public $ban;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(User $user, Ban $ban)
{
$this->user = $user;
$this->ban = $ban;
}
}

13
app/Facades/IP.php

@ -0,0 +1,13 @@
<?php
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
class IP extends Facade
{
protected static function getFacadeAccessor()
{
return 'ipInformationFacade';
}
}

13
app/Facades/UUID.php

@ -0,0 +1,13 @@
<?php
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
class UUID extends Facade
{
protected static function getFacadeAccessor()
{
return 'uuidConversionFacade';
}
}

65
app/Http/Controllers/ApplicationController.php

@ -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:

7
app/Http/Controllers/AppointmentController.php

@ -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.');
}

31
app/Http/Controllers/Auth/LoginController.php

@ -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);
}
}

26
app/Http/Controllers/Auth/RegisterController.php

@ -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;
}
}

91
app/Http/Controllers/BanController.php

@ -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();
}
}

68
app/Http/Controllers/CommentController.php

@ -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();
}
}

9
app/Http/Controllers/HomeController.php

@ -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);
}
}

60
app/Http/Controllers/ProfileController.php

@ -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
}
public function saveProfile(ProfileSave $request)
// Route model binding
public function showSingleProfile(Request $request, User $user)
{
// TODO: Implement profile security policy for logged in users
$profile = Profile::find(Auth::user()->id);
$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: 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.');

187
app/Http/Controllers/UserController.php

@ -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();
}
}

12
app/Http/Controllers/VacancyController.php

@ -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:

3
app/Http/Kernel.php

@ -63,6 +63,7 @@ class Kernel extends HttpKernel
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'eligibility' => \App\Http\Middleware\ApplicationEligibility::class,
'usernameUUID' => \App\Http\Middleware\UsernameUUID::class
'usernameUUID' => \App\Http\Middleware\UsernameUUID::class,
'forcelogout' => \App\Http\Middleware\ForceLogoutMiddleware::class
];
}

39
app/Http/Middleware/Bancheck.php

@ -0,0 +1,39 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\View;
class Bancheck
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$userIP = $request->ip();
$anonymousUser = User::where('ipAddress', $userIP)->get();
if (Auth::check() && Auth::user()->isBanned())
{
View::share('isBanned', true);
}
elseif(!$anonymousUser->isEmpty() && User::find($anonymousUser->id)->isBanned())
{
View::share('isBanned', true);
}
else
{
View::share('isBanned', false);
}
return $next($request);
}
}

29
app/Http/Middleware/ForceLogoutMiddleware.php

@ -0,0 +1,29 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
class ForceLogoutMiddleware
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if (Auth::user()->isBanned())
{
Auth::logout();
$request->session()->flash('error', 'Error: Your session has been forcefully terminated. Please try again in a few days.');
return redirect('/');
}
return $next($request);
}
}

34
app/Http/Requests/BanUserRequest.php

@ -0,0 +1,34 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
class BanUserRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return Auth::user()->hasRole('admin');
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'reason' => 'required|string',
'durationOperand' => 'nullable|integer',
'durationOperator' => 'nullable|string'
];
}
}

31
app/Http/Requests/DeleteUserRequest.php

@ -0,0 +1,31 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
class DeleteUserRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return Auth::user()->hasRole('admin');
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'confirmPrompt' => 'required|string'
];
}
}

33
app/Http/Requests/NewCommentRequest.php

@ -0,0 +1,33 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
class NewCommentRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
// TODO: Switch to permission checking when there are comment permission nodes
return Auth::user()->hasAnyRole('reviewer', 'hiringManager', 'admin');
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'comment' => 'required|string|max:600|min:20'
];
}
}

31
app/Http/Requests/SearchPlayerRequest.php

@ -0,0 +1,31 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
class SearchPlayerRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return Auth::user()->can('admin.userlist');
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'searchTerm' => 'required|string|max:17' // max user char limit set by Mojang
];
}
}

34
app/Http/Requests/UpdateUserRequest.php

@ -0,0 +1,34 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
class UpdateUserRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return Auth::user()->hasRole('admin');
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'email' => 'required|email',
'name' => 'required|string',
'uuid' => 'required|max:32|min:32',
'roles' => 'required_without_all'
];
}
}

56
app/Jobs/CleanBans.php

@ -0,0 +1,56 @@
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use App\Ban;
use Carbon\Carbon;
class CleanBans implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $bans;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct()
{
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{