diff --git a/.idea/hrm-mcserver.iml b/.idea/hrm-mcserver.iml
deleted file mode 100755
index 0fd52b5..0000000
--- a/.idea/hrm-mcserver.iml
+++ /dev/null
@@ -1,154 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
index 1a22200..7ab5d51 100755
--- a/.idea/modules.xml
+++ b/.idea/modules.xml
@@ -2,7 +2,7 @@
-
+
\ No newline at end of file
diff --git a/app/Exceptions/ApplicationNotFoundException.php b/app/Exceptions/ApplicationNotFoundException.php
new file mode 100644
index 0000000..1fc29a9
--- /dev/null
+++ b/app/Exceptions/ApplicationNotFoundException.php
@@ -0,0 +1,11 @@
+get();
if ($options->isEmpty())
{
- throw new \Exception('There are no options in category ' . $category);
+ throw new EmptyOptionsException('There are no options in category ' . $category);
}
return $options;
}
@@ -52,13 +54,13 @@ class Options
public function getOption(string $option): string
{
$value = Cache::get($option);
-
+
if (is_null($value)) {
Log::debug('Option '.$option.'not found in cache, refreshing from database');
$value = Option::where('option_name', $option)->first();
if (is_null($value)) {
- throw new \Exception('This option does not exist.');
+ throw new OptionNotFoundException('This option does not exist.');
}
Cache::put($option, $value->option_value);
Cache::put($option.'_desc', 'Undefined description');
@@ -118,7 +120,7 @@ class Options
Cache::put('option_name', $newValue, now()->addDay());
} else {
- throw new \Exception('This option does not exist.');
+ throw new OptionNotFoundException('This option does not exist.');
}
}
diff --git a/app/Http/Controllers/ApplicationController.php b/app/Http/Controllers/ApplicationController.php
index 1ec3400..e137503 100755
--- a/app/Http/Controllers/ApplicationController.php
+++ b/app/Http/Controllers/ApplicationController.php
@@ -22,35 +22,24 @@
namespace App\Http\Controllers;
use App\Application;
-use App\Events\ApplicationDeniedEvent;
-use App\Facades\JSON;
-use App\Notifications\ApplicationMoved;
-use App\Notifications\NewApplicant;
-use App\Response;
-use App\User;
-use App\Vacancy;
-use ContextAwareValidator;
+use App\Exceptions\IncompleteApplicationException;
+use App\Exceptions\UnavailableApplicationException;
+use App\Exceptions\VacancyNotFoundException;
+use App\Services\ApplicationService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
-use Illuminate\Support\Facades\Log;
class ApplicationController extends Controller
{
+ private $applicationService;
- private function canVote($votes): bool
- {
- $allvotes = collect([]);
+ public function __construct(ApplicationService $applicationService) {
- foreach ($votes as $vote) {
- if ($vote->userID == Auth::user()->id) {
- $allvotes->push($vote);
- }
- }
-
- return !(($allvotes->count() == 1));
+ $this->applicationService = $applicationService;
}
+
public function showUserApps()
{
return view('dashboard.user.applications')
@@ -70,7 +59,7 @@ class ApplicationController extends Controller
'structuredResponses' => json_decode($application->response->responseData, true),
'formStructure' => $application->response->form,
'vacancy' => $application->response->vacancy,
- 'canVote' => $this->canVote($application->votes),
+ 'canVote' => $this->applicationService->canVote($application->votes),
]
);
} else {
@@ -90,91 +79,27 @@ class ApplicationController extends Controller
}
- public function renderApplicationForm(Request $request, $vacancySlug)
+ public function renderApplicationForm($vacancySlug)
{
- $vacancyWithForm = Vacancy::with('forms')->where('vacancySlug', $vacancySlug)->get();
-
- $firstVacancy = $vacancyWithForm->first();
-
- if (!$vacancyWithForm->isEmpty() && $firstVacancy->vacancyCount !== 0 && $firstVacancy->vacancyStatus == 'OPEN') {
- return view('dashboard.application-rendering.apply')
- ->with([
- 'vacancy' => $vacancyWithForm->first(),
- 'preprocessedForm' => json_decode($vacancyWithForm->first()->forms->formStructure, true),
- ]);
- } else {
- abort(404, __('The application you\'re looking for could not be found or it is currently unavailable.'));
- }
+ return $this->applicationService->renderForm($vacancySlug);
}
public function saveApplicationAnswers(Request $request, $vacancySlug)
{
- $vacancy = Vacancy::with('forms')->where('vacancySlug', $vacancySlug)->get();
+ try {
- if ($vacancy->isEmpty()) {
+ $this->applicationService->fillForm(Auth::user(), $request->all(), $vacancySlug);
+
+ } catch (VacancyNotFoundException | IncompleteApplicationException | UnavailableApplicationException $e) {
return redirect()
->back()
- ->with('error', __('This vacancy doesn\'t exist; Please use the proper buttons to apply to one.'));
+ ->with('error', $e->getMessage());
}
- if ($vacancy->first()->vacancyCount == 0 || $vacancy->first()->vacancyStatus !== 'OPEN') {
-
- return redirect()
- ->back()
- ->with('error', __('This application is unavailable'));
- }
-
- Log::info('Processing new application!');
-
- $formStructure = json_decode($vacancy->first()->forms->formStructure, true);
- $responseValidation = ContextAwareValidator::getResponseValidator($request->all(), $formStructure);
- $applicant = Auth::user();
-
- // API users may specify ID 1 for an anonymous application, but they'll have to submit contact details for it to become active.
- // User ID 1 is exempt from application rate limits
-
- Log::info('Built response & validator structure!');
-
- if (!$responseValidation->get('validator')->fails()) {
- $response = Response::create([
- 'responseFormID' => $vacancy->first()->forms->id,
- 'associatedVacancyID' => $vacancy->first()->id, // Since a form can be used by multiple vacancies, we can only know which specific vacancy this response ties to by using a vacancy ID
- 'responseData' => $responseValidation->get('responseStructure'),
- ]);
-
- Log::info('Registered form response!', [
- 'applicant' => $applicant->name,
- 'vacancy' => $vacancy->first()->vacancyName
- ]);
-
- $application = Application::create([
- 'applicantUserID' => $applicant->id,
- 'applicantFormResponseID' => $response->id,
- 'applicationStatus' => 'STAGE_SUBMITTED',
- ]);
-
- Log::info('Submitted an application!', [
- 'responseID' => $response->id,
- 'applicant' => $applicant->name
- ]);
-
- 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(route('showUserApps'));
-
- }
-
- Log::warning('Application form for ' . $applicant->name . ' contained errors, resetting!');
-
return redirect()
->back()
- ->with('error', __('There are one or more errors in your application. Please make sure none of your fields are empty, since they are all required.'));
+ ->with('success', __('Thank you! Your application has been processed and our team will get to it shortly.'));
}
public function updateApplicationStatus(Request $request, Application $application, $newStatus)
@@ -182,36 +107,28 @@ class ApplicationController extends Controller
$messageIsError = false;
$this->authorize('update', Application::class);
-
- switch ($newStatus) {
- case 'deny':
-
- event(new ApplicationDeniedEvent($application));
- $message = __("Application denied successfully.");
-
- break;
-
- case 'interview':
- Log::info(' Moved application ID ' . $application->id . 'to interview stage!');
- $message = __('Application moved to interview stage!');
-
- $application->setStatus('STAGE_INTERVIEW');
- $application->user->notify(new ApplicationMoved());
-
- break;
-
- default:
- $message = __("There are no suitable statuses to update to.");
- $messageIsError = true;
+ try {
+ $status = $this->applicationService->updateStatus($application, $newStatus);
+ } catch (\LogicException $ex)
+ {
+ return redirect()
+ ->back()
+ ->with('error', $ex->getMessage());
}
- return redirect()->back();
+ return redirect()
+ ->back()
+ ->with('success', $status);
}
+ /**
+ * @throws \Illuminate\Auth\Access\AuthorizationException
+ * @throws \Exception
+ */
public function delete(Request $request, Application $application)
{
$this->authorize('delete', $application);
- $application->delete(); // observers will run, cleaning it up
+ $this->applicationService->delete($application);
return redirect()
->back()
diff --git a/app/Http/Controllers/AppointmentController.php b/app/Http/Controllers/AppointmentController.php
index e1d1858..18efe1d 100755
--- a/app/Http/Controllers/AppointmentController.php
+++ b/app/Http/Controllers/AppointmentController.php
@@ -23,85 +23,79 @@ namespace App\Http\Controllers;
use App\Application;
use App\Appointment;
+use App\Exceptions\InvalidAppointmentException;
+use App\Exceptions\InvalidAppointmentStatusException;
use App\Http\Requests\SaveNotesRequest;
-use App\Notifications\ApplicationMoved;
-use App\Notifications\AppointmentScheduled;
+use App\Services\AppointmentService;
+use App\Services\MeetingNoteService;
use Carbon\Carbon;
+use Illuminate\Auth\Access\AuthorizationException;
+use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Auth;
-use Illuminate\Support\Facades\Log;
class AppointmentController extends Controller
{
- private $allowedPlatforms = [
- 'ZOOM',
- 'DISCORD',
- 'SKYPE',
- 'MEET',
- 'TEAMSPEAK',
+ private $appointmentService;
+ private $meetingNoteService;
- ];
- public function saveAppointment(Request $request, Application $application)
- {
- $this->authorize('create', Appointment::class);
- $appointmentDate = Carbon::parse($request->appointmentDateTime);
+ public function __construct(AppointmentService $appointmentService, MeetingNoteService $meetingNoteService) {
- $appointment = Appointment::create([
- 'appointmentDescription' => $request->appointmentDescription,
- 'appointmentDate' => $appointmentDate->toDateTimeString(),
- 'applicationID' => $application->id,
- 'appointmentLocation' => (in_array($request->appointmentLocation, $this->allowedPlatforms)) ? $request->appointmentLocation : 'DISCORD',
- ]);
- $application->setStatus('STAGE_INTERVIEW_SCHEDULED');
-
- Log::info('User '.Auth::user()->name.' has scheduled an appointment with '.$application->user->name.' for application ID'.$application->id, [
- 'datetime' => $appointmentDate->toDateTimeString(),
- 'scheduled' => now(),
- ]);
-
- $application->user->notify(new AppointmentScheduled($appointment));
- $request->session()->flash('success', __('Appointment successfully scheduled @ :appointmentTime', ['appointmentTime', $appointmentDate->toDateTimeString()]));
-
- return redirect()->back();
+ $this->appointmentService = $appointmentService;
+ $this->meetingNoteService = $meetingNoteService;
}
- public function updateAppointment(Request $request, Application $application, $status)
+ public function saveAppointment(Request $request, Application $application): RedirectResponse
+ {
+ $this->authorize('create', Appointment::class);
+
+ $appointmentDate = Carbon::parse($request->appointmentDateTime);
+ $this->appointmentService->createAppointment($application, $appointmentDate, $request->appointmentDescription, $request->appointmentLocation);
+
+ return redirect()
+ ->back()
+ ->with('success',__('Appointment successfully scheduled @ :appointmentTime', ['appointmentTime', $appointmentDate->toDateTimeString()]));
+ }
+
+ /**
+ * @throws AuthorizationException
+ */
+ public function updateAppointment(Application $application, $status): RedirectResponse
{
$this->authorize('update', $application->appointment);
- $validStatuses = [
- 'SCHEDULED',
- 'CONCLUDED',
- ];
+ try {
+ $this->appointmentService->updateAppointment($application, $status);
- // NOTE: This is a little confusing, refactor
- $application->appointment->appointmentStatus = (in_array($status, $validStatuses)) ? strtoupper($status) : 'SCHEDULED';
- $application->appointment->save();
+ return redirect()
+ ->back()
+ ->with('success', __("Interview finished! Staff members can now vote on it."));
- $application->setStatus('STAGE_PEERAPPROVAL');
- $application->user->notify(new ApplicationMoved());
-
- $request->session()->flash('success', __('Interview finished! Staff members can now vote on it.'));
-
- return redirect()->back();
- }
-
- // also updates
- public function saveNotes(SaveNotesRequest $request, Application $application)
- {
- if (! is_null($application)) {
- $application->load('appointment');
-
- $application->appointment->meetingNotes = $request->noteText;
- $application->appointment->save();
-
- $request->session()->flash('success', __('Meeting notes have been saved.'));
- } else {
- $request->session()->flash('error', __('There\'s no appointment to save notes to!'));
+ }
+ catch (InvalidAppointmentStatusException $ex) {
+ return redirect()
+ ->back()
+ ->with('error', $ex->getMessage());
}
- return redirect()->back();
+
+ }
+
+ public function saveNotes(SaveNotesRequest $request, Application $application)
+ {
+ try {
+
+ $this->meetingNoteService->addToApplication($application, $request->noteText);
+
+ return redirect()
+ ->back()
+ ->with('success', 'Saved notes.');
+
+ } catch (InvalidAppointmentException $ex) {
+ return redirect()
+ ->back()
+ ->with('error', $ex->getMessage());
+ }
}
}
diff --git a/app/Http/Controllers/CommentController.php b/app/Http/Controllers/CommentController.php
index ca2e9c6..609c0e3 100755
--- a/app/Http/Controllers/CommentController.php
+++ b/app/Http/Controllers/CommentController.php
@@ -24,21 +24,22 @@ namespace App\Http\Controllers;
use App\Application;
use App\Comment;
use App\Http\Requests\NewCommentRequest;
+use App\Services\CommentService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class CommentController extends Controller
{
+ private $commentService;
+
+ public function __construct(CommentService $commentService) {
+ $this->commentService = $commentService;
+ }
public function insert(NewCommentRequest $request, Application $application)
{
$this->authorize('create', Comment::class);
-
- $comment = Comment::create([
- 'authorID' => Auth::user()->id,
- 'applicationID' => $application->id,
- 'text' => $request->comment,
- ]);
+ $comment = $this->commentService->addComment($application, $request->comment);
if ($comment) {
$request->session()->flash('success', __('Comment posted!'));
@@ -52,10 +53,10 @@ class CommentController extends Controller
public function delete(Request $request, Comment $comment)
{
$this->authorize('delete', $comment);
+ $this->commentService->deleteComment($comment);
- $comment->delete();
- $request->session()->flash('success', __('Comment deleted!'));
-
- return redirect()->back();
+ return redirect()
+ ->back()
+ ->with('success', __('Comment deleted!'));
}
}
diff --git a/app/Http/Controllers/ContactController.php b/app/Http/Controllers/ContactController.php
index 43c9e99..b081aee 100755
--- a/app/Http/Controllers/ContactController.php
+++ b/app/Http/Controllers/ContactController.php
@@ -21,7 +21,9 @@
namespace App\Http\Controllers;
+use App\Exceptions\FailedCaptchaException;
use App\Notifications\NewContact;
+use App\Services\ContactService;
use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
@@ -30,47 +32,32 @@ class ContactController extends Controller
{
protected $users;
- public function __construct(User $users)
+ private $contactService;
+
+ public function __construct(User $users, ContactService $contactService)
{
+ $this->contactService = $contactService;
$this->users = $users;
}
public function create(Request $request)
{
- $name = $request->name;
- $email = $request->email;
- $subject = $request->subject;
- $msg = $request->msg;
+ try {
- $challenge = $request->input('captcha');
+ $email = $request->email;
+ $msg = $request->msg;
+ $challenge = $request->input('captcha');
- // TODO: now: add middleware for this verification, move to invisible captcha
- $verifyrequest = Http::asForm()->post(config('recaptcha.verify.apiurl'), [
- 'secret' => config('recaptcha.keys.secret'),
- 'response' => $challenge,
- 'remoteip' => $request->ip(),
- ]);
+ $this->contactService->sendMessage($request->ip(), $msg, $email, $challenge);
- $response = json_decode($verifyrequest->getBody(), true);
+ return redirect()
+ ->back()
+ ->with('success',__('Message sent successfully! We usually respond within 48 hours.'));
- if (! $response['success']) {
- $request->session()->flash('error', __('Beep beep boop... Robot? Submission failed.'));
-
- return redirect()->back();
+ } catch (FailedCaptchaException $ex) {
+ return redirect()
+ ->back()
+ ->with('error', $ex->getMessage());
}
-
- foreach (User::all() as $user) {
- if ($user->hasRole('admin')) {
- $user->notify(new NewContact(collect([
- 'message' => $msg,
- 'ip' => $request->ip(),
- 'email' => $email,
- ])));
- }
- }
-
- $request->session()->flash('success', __('Message sent successfully! We usually respond within 48 hours.'));
-
- return redirect()->back();
}
}
diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php
index 573ae61..df46bd2 100755
--- a/app/Http/Controllers/DashboardController.php
+++ b/app/Http/Controllers/DashboardController.php
@@ -27,6 +27,8 @@ use App\Vacancy;
class DashboardController extends Controller
{
+ // Note: The dashboard doesn't need a service because it doesn't contain any significant business logic
+
public function index()
{
$totalPeerReview = Application::where('applicationStatus', 'STAGE_PEERAPPROVAL')->get()->count();
diff --git a/app/Http/Controllers/FormController.php b/app/Http/Controllers/FormController.php
index 7ccf9f6..82848fe 100755
--- a/app/Http/Controllers/FormController.php
+++ b/app/Http/Controllers/FormController.php
@@ -21,12 +21,20 @@
namespace App\Http\Controllers;
+use App\Exceptions\FormHasConstraintsException;
use App\Form;
+use App\Services\FormManagementService;
use ContextAwareValidator;
use Illuminate\Http\Request;
class FormController extends Controller
{
+ private $formService;
+
+ public function __construct(FormManagementService $formService) {
+ $this->formService = $formService;
+ }
+
public function index()
{
$forms = Form::all();
@@ -45,60 +53,38 @@ class FormController extends Controller
public function saveForm(Request $request)
{
- $this->authorize('create', Form::class);
- $fields = $request->all();
+ $form = $this->formService->addForm($request->all());
- if (count($fields) == 2) {
- // form is probably empty, since forms with fields will alawys have more than 2 items
-
- $request->session()->flash('error', __('Sorry, but you may not create empty forms.'));
-
- return redirect()->to(route('showForms'));
+ // Form is boolean or array
+ if ($form)
+ {
+ return redirect()
+ ->back()
+ ->with('success', __('Form created!'));
}
- $contextValidation = ContextAwareValidator::getValidator($fields, true, true);
-
- if (! $contextValidation->get('validator')->fails()) {
- $storableFormStructure = $contextValidation->get('structure');
-
- Form::create(
- [
- 'formName' => $fields['formName'],
- 'formStructure' => $storableFormStructure,
- 'formStatus' => 'ACTIVE',
- ]
- );
-
- $request->session()->flash('success', __('Form created! You can now link this form to a vacancy.'));
-
- return redirect()->to(route('showForms'));
- }
-
- $request->session()->flash('errors', $contextValidation->get('validator')->errors()->getMessages());
-
- return redirect()->back();
+ return redirect()
+ ->back()
+ ->with('errors', $form);
}
public function destroy(Request $request, Form $form)
{
$this->authorize('delete', $form);
+ try {
- $deletable = true;
+ $this->formService->deleteForm($form);
+ return redirect()
+ ->back()
+ ->with('success', __('Form deleted successfuly'));
+
+ } catch (FormHasConstraintsException $ex) {
+
+ return redirect()
+ ->back()
+ ->with('error', $ex->getMessage());
- if (! is_null($form) && ! is_null($form->vacancies) && $form->vacancies->count() !== 0 || ! is_null($form->responses)) {
- $deletable = false;
}
-
- if ($deletable) {
- $form->delete();
-
- $request->session()->flash('success', __('Form deleted successfully.'));
- } else {
- $request->session()->flash('error', __('You cannot delete this form because it\'s tied to one or more applications and ranks, or because it doesn\'t exist.'));
- }
-
- return redirect()->back();
-
}
public function preview(Request $request, Form $form)
@@ -124,22 +110,15 @@ class FormController extends Controller
public function update(Request $request, Form $form)
{
$this->authorize('update', $form);
+ $updatedForm = $this->formService->updateForm($form, $request->all());
- $contextValidation = ContextAwareValidator::getValidator($request->all(), true);
- $this->authorize('update', $form);
-
- if (! $contextValidation->get('validator')->fails()) {
- // Add the new structure into the form. New, subsquent fields will be identified by the "new" prefix
- // This prefix doesn't actually change the app's behavior when it receives applications.
- // Additionally, old applications won't of course display new and updated fields, because we can't travel into the past and get data for them
- $form->formStructure = $contextValidation->get('structure');
- $form->save();
-
- $request->session()->flash('success', __('Hooray! Your form was updated. New applications for it\'s vacancy will use it.'));
- } else {
- $request->session()->flash('errors', $contextValidation->get('validator')->errors()->getMessages());
+ if ($updatedForm instanceof Form) {
+ return redirect()->to(route('previewForm', ['form' => $updatedForm->id]));
}
- return redirect()->to(route('previewForm', ['form' => $form->id]));
+ // array of errors
+ return redirect()
+ ->back()
+ ->with('errors', $updatedForm);
}
}
diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php
index 2696b59..9293dc9 100755
--- a/app/Http/Controllers/HomeController.php
+++ b/app/Http/Controllers/HomeController.php
@@ -25,6 +25,8 @@ use App\Vacancy;
class HomeController extends Controller
{
+ // doesn't need a service, because it doesn't contain major logic.
+
/**
* Show the application dashboard.
*
diff --git a/app/Http/Controllers/OptionsController.php b/app/Http/Controllers/OptionsController.php
index 1189301..10b03e7 100755
--- a/app/Http/Controllers/OptionsController.php
+++ b/app/Http/Controllers/OptionsController.php
@@ -21,14 +21,25 @@
namespace App\Http\Controllers;
+use App\Exceptions\InvalidGamePreferenceException;
+use App\Exceptions\OptionNotFoundException;
use App\Facades\Options;
use App\Options as Option;
+use App\Services\ConfigurationService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
class OptionsController extends Controller
{
+ private $configurationService;
+
+ public function __construct(ConfigurationService $configurationService) {
+
+ $this->configurationService = $configurationService;
+
+ }
+
/**
* Display a listing of the resource.
*
@@ -36,7 +47,7 @@ class OptionsController extends Controller
*/
public function index()
{
-
+ // TODO: Replace with settings package
return view('dashboard.administration.settings')
->with([
'options' => Options::getCategory('notifications'),
@@ -51,65 +62,44 @@ class OptionsController extends Controller
]);
}
- public function saveSettings(Request $request)
+ public function saveSettings(Request $request): \Illuminate\Http\RedirectResponse
{
- if (Auth::user()->can('admin.settings.edit')) {
- Log::debug('Updating application options', [
- 'ip' => $request->ip(),
- 'ua' => $request->userAgent(),
- 'username' => Auth::user()->username,
- ]);
- foreach ($request->all() as $optionName => $option) {
- try {
- Log::debug('Going through option '.$optionName);
- if (Options::optionExists($optionName)) {
- Log::debug('Option exists, updating to new values', [
- 'opt' => $optionName,
- 'new_value' => $option,
- ]);
- Options::changeOption($optionName, $option);
- }
- } catch (\Exception $ex) {
- Log::error('Unable to update options!', [
- 'msg' => $ex->getMessage(),
- 'trace' => $ex->getTraceAsString(),
- ]);
- report($ex);
+ try {
- $errorCond = true;
- $request->session()->flash('error', __('An error occurred while trying to save settings: :message ', ['message' => $ex->getMessage()]));
- }
+ if (Auth::user()->can('admin.settings.edit')) {
+ $this->configurationService->saveConfiguration($request->all());
+
+ return redirect()
+ ->back()
+ ->with('success', __('Options updated successfully!'));
}
- if (! isset($errorCond)) {
- $request->session()->flash('success', __('Settings saved successfully!'));
- }
- } else {
- $request->session()->flash('error', __('You do not have permission to update this resource.'));
+ } catch (OptionNotFoundException | \Exception $ex) {
+
+ return redirect()
+ ->back()
+ ->with('error', $ex->getMessage());
+
}
- return redirect()->back();
+ return redirect()
+ ->back()
+ ->with('error', __('You do not have permission to update this resource.'));
}
public function saveGameIntegration(Request $request)
{
- $supportedGames = [
- 'RUST',
- 'MINECRAFT',
- 'SE',
- 'GMOD'
- ];
+ try {
- if (!is_null($request->gamePref) && in_array($request->gamePref, $supportedGames))
- {
- Options::changeOption('currentGame', $request->gamePref);
- $request->session()->flash('success', __('Updated current game.'));
+ $this->configurationService->saveGameIntegration($request->gamePref);
+ return redirect()
+ ->back()
+ ->with('success', __('Game preference updated.'));
- return redirect()->back();
+ } catch (InvalidGamePreferenceException $ex) {
+ return redirect()
+ ->back()
+ ->with('error', $ex->getMessage());
}
-
- $request->session()->flash('error', __('Unsupported game :game.', ['game' => $request->gamePref ]));
-
- return redirect()->back();
}
}
diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php
index aba3e8b..d77b028 100755
--- a/app/Http/Controllers/ProfileController.php
+++ b/app/Http/Controllers/ProfileController.php
@@ -23,6 +23,7 @@ namespace App\Http\Controllers;
use App\Facades\IP;
use App\Http\Requests\ProfileSave;
+use App\Services\ProfileService;
use App\User;
use Carbon\Carbon;
use Illuminate\Http\Request;
@@ -31,6 +32,12 @@ use Spatie\Permission\Models\Role;
class ProfileController extends Controller
{
+ private $profileService;
+
+ public function __construct(ProfileService $profileService) {
+ $this->profileService = $profileService;
+ }
+
public function index()
{
return view('dashboard.user.directory')
@@ -39,6 +46,7 @@ class ProfileController extends Controller
public function showProfile()
{
+ // TODO: Come up with cleaner social media solution, e.g. social media object
$socialLinks = Auth::user()->profile->socialLinks ?? '[]';
$socialMediaProfiles = json_decode($socialLinks, true);
@@ -52,8 +60,7 @@ class ProfileController extends Controller
]);
}
- // Route model binding
- public function showSingleProfile(Request $request, User $user)
+ public function showSingleProfile(User $user)
{
$socialMediaProfiles = json_decode($user->profile->socialLinks, true);
$createdDate = Carbon::parse($user->created_at);
@@ -102,36 +109,9 @@ class ProfileController extends Controller
public function saveProfile(ProfileSave $request)
{
- $profile = User::find(Auth::user()->id)->profile;
- $social = [];
-
- if (! is_null($profile)) {
- switch ($request->avatarPref) {
- case 'MOJANG':
- $avatarPref = 'crafatar';
-
- break;
- case 'GRAVATAR':
- $avatarPref = strtolower($request->avatarPref);
-
- break;
- }
-
- $social['links']['github'] = $request->socialGithub;
- $social['links']['twitter'] = $request->socialTwitter;
- $social['links']['insta'] = $request->socialInsta;
- $social['links']['discord'] = $request->socialDiscord;
-
- $profile->profileShortBio = $request->shortBio;
- $profile->profileAboutMe = $request->aboutMe;
- $profile->avatarPreference = $avatarPref;
- $profile->socialLinks = json_encode($social);
-
- $newProfile = $profile->save();
-
- $request->session()->flash('success', __('Profile settings saved successfully.'));
- }
-
- return redirect()->back();
+ $this->profileService->updateProfile(Auth::user()->id, $request);
+ return redirect()
+ ->back()
+ ->with('success', __('Profile updated.'));
}
}
diff --git a/app/Http/Controllers/SecuritySettingsController.php b/app/Http/Controllers/SecuritySettingsController.php
index febe168..fbe8e7d 100644
--- a/app/Http/Controllers/SecuritySettingsController.php
+++ b/app/Http/Controllers/SecuritySettingsController.php
@@ -4,6 +4,7 @@ namespace App\Http\Controllers;
use App\Facades\Options;
use App\Http\Requests\SaveSecuritySettings;
+use App\Services\SecuritySettingsService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
@@ -11,38 +12,24 @@ use function PHPSTORM_META\map;
class SecuritySettingsController extends Controller
{
+ private $securityService;
+
+ public function __construct(SecuritySettingsService $securityService) {
+ $this->securityService = $securityService;
+ }
+
public function save(SaveSecuritySettings $request)
{
- $validPolicies = [
- 'off',
- 'low',
- 'medium',
- 'high'
- ];
+ $this->securityService->save($request->secPolicy, [
+ 'graceperiod' => $request->graceperiod,
+ 'pwExpiry' => $request->pwExpiry,
+ 'enforce2fa' => $request->enforce2fa,
+ 'requirePMC' => $request->requirePMC
+ ]);
- if (in_array($request->secPolicy, $validPolicies))
- {
- Options::changeOption('pw_security_policy', $request->secPolicy);
-
- Log::debug('[Options] Changing option pw_security_policy', [
- 'new_value' => $request->secPolicy
- ]);
- }
- else
- {
- Log::debug('[WARN] Ignoring bogus policy', [
- 'avaliable' => $validPolicies,
- 'given' >= $request->secPolicy
- ]);
- }
-
- Options::changeOption('graceperiod', $request->graceperiod);
- Options::changeOption('password_expiry', $request->pwExpiry);
- Options::changeOption('force2fa', $request->enforce2fa);
- Options::changeOption('requireGameLicense', $request->requirePMC);
-
- $request->session()->flash('success', __('Settings saved successfully.'));
- return redirect()->back();
+ return redirect()
+ ->back()
+ ->with('success', __('Settings saved.'));
}
}
diff --git a/app/Http/Controllers/TeamController.php b/app/Http/Controllers/TeamController.php
index bbcb1f6..91565f7 100755
--- a/app/Http/Controllers/TeamController.php
+++ b/app/Http/Controllers/TeamController.php
@@ -21,13 +21,18 @@
namespace App\Http\Controllers;
+use App\Exceptions\InvalidInviteException;
+use App\Exceptions\PublicTeamInviteException;
+use App\Exceptions\UserAlreadyInvitedException;
use App\Http\Requests\EditTeamRequest;
use App\Http\Requests\NewTeamRequest;
use App\Http\Requests\SendInviteRequest;
use App\Mail\InviteToTeam;
+use App\Services\TeamService;
use App\Team;
use App\User;
use App\Vacancy;
+use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Mail;
@@ -37,10 +42,15 @@ use Mpociot\Teamwork\TeamInvite;
class TeamController extends Controller
{
+ private $teamService;
+
+ public function __construct(TeamService $teamService) {
+ $this->teamService = $teamService;
+ }
+
/**
* Display a listing of the resource.
*
- * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Http\Response
*/
public function index()
{
@@ -56,23 +66,17 @@ class TeamController extends Controller
* Store a newly created resource in storage.
*
* @param NewTeamRequest $request
- * @return \Illuminate\Http\RedirectResponse
+ * @return RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function store(NewTeamRequest $request)
{
$this->authorize('create', Team::class);
+ $this->teamService->createTeam($request->teamName, Auth::user()->id);
- $team = Team::create([
- 'name' => $request->teamName,
- 'owner_id' => Auth::user()->id,
- ]);
-
- Auth::user()->teams()->attach($team->id);
-
- $request->session()->flash('success', __('Team successfully created.'));
-
- return redirect()->back();
+ return redirect()
+ ->back()
+ ->with('success', __('Team successfully created.'));
}
/**
@@ -98,21 +102,24 @@ class TeamController extends Controller
*
* @param EditTeamRequest $request
* @param Team $team
- * @return \Illuminate\Http\Response
+ * @return RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
- public function update(EditTeamRequest $request, Team $team): \Illuminate\Http\Response
+ public function update(EditTeamRequest $request, Team $team): RedirectResponse
{
$this->authorize('update', $team);
+ $team = $this->teamService->updateTeam($team, $request->teamDescription, $team->joinType);
- $team->description = $request->teamDescription;
- $team->openJoin = $request->joinType;
+ if ($team) {
+ return redirect()
+ ->to(route('teams.index'))
+ ->with('success', __('Team updated.'));
+ }
- $team->save();
- $request->session()->flash('success', __('Team edited successfully.'));
-
- return redirect()->to(route('teams.index'));
+ return redirect()
+ ->back()
+ ->with('error', __('An error ocurred while trying to update this team.'));
}
/**
@@ -126,68 +133,45 @@ class TeamController extends Controller
// wip
}
- public function invite(SendInviteRequest $request, Team $team): \Illuminate\Http\RedirectResponse
+ public function invite(SendInviteRequest $request, Team $team): RedirectResponse
{
$this->authorize('invite', $team);
- $user = User::findOrFail($request->user);
+ try {
- if (! $team->openJoin) {
- if (! Teamwork::hasPendingInvite($user->email, $team)) {
- Teamwork::inviteToTeam($user, $team, function (TeamInvite $invite) use ($user) {
- Mail::to($user)->send(new InviteToTeam($invite));
- });
+ $this->teamService->inviteUser($team, $request->user);
- $request->session()->flash('success', __('Invite sent! They can now accept or deny it.'));
- } else {
- $request->session()->flash('error', __('This user has already been invited.'));
- }
- } else {
- $request->session()->flash('error', __('You can\'t invite users to public teams.'));
+ return redirect()
+ ->back()
+ ->with('success', __('User invited successfully!'));
+
+ } catch (UserAlreadyInvitedException | PublicTeamInviteException $ex) {
+ return redirect()
+ ->back()
+ ->with('error', $ex->getMessage());
}
-
- return redirect()->back();
}
- public function processInviteAction(Request $request, $action, $token): \Illuminate\Http\RedirectResponse
+ public function processInviteAction(Request $request, $action, $token): RedirectResponse
{
- switch ($action) {
- case 'accept':
+ try {
- $invite = Teamwork::getInviteFromAcceptToken($token);
+ $this->teamService->processInvite(Auth::user(), $action, $token);
- if ($invite && $invite->user->is(Auth::user())) {
- Teamwork::acceptInvite($invite);
- $request->session()->flash('success', __('Invite accepted! You have now joined :teamName.', ['teamName' => $invite->team->name]));
- } else {
- $request->session()->flash('error', __('Invalid or expired invite URL.'));
- }
+ return redirect()
+ ->to(route('teams.index'))
+ ->with('success', __('Invite processed successfully!'));
- break;
+ } catch (InvalidInviteException $e) {
- case 'deny':
-
- $invite = Teamwork::getInviteFromDenyToken($token);
-
- if ($invite && $invite->user->is(Auth::user())) {
- Teamwork::denyInvite($invite);
- $request->session()->flash('success', __('Invite denied! Ask for another invite if this isn\'t what you meant.'));
- } else {
- $request->session()->flash('error', __('Invalid or expired invite URL.'));
- }
-
- break;
-
- default:
- $request->session()->flash('error', 'Sorry, but the invite URL you followed was malformed. Try asking for another invite, or submit a bug report.');
+ return redirect()
+ ->back()
+ ->with('error', $e->getMessage());
}
-
- // This page will show the user's current teams
- return redirect()->to(route('teams.index'));
}
- public function switchTeam(Request $request, Team $team): \Illuminate\Http\RedirectResponse
+ public function switchTeam(Request $request, Team $team): RedirectResponse
{
$this->authorize('switchTeam', $team);
@@ -203,44 +187,13 @@ class TeamController extends Controller
}
// Since it's a separate form, we shouldn't use the same update method
- public function assignVacancies(Request $request, Team $team): \Illuminate\Http\RedirectResponse
+ public function assignVacancies(Request $request, Team $team): RedirectResponse
{
$this->authorize('update', $team);
+ $message = $this->teamService->updateVacancies($team, $request->assocVacancies);
- // P.S. To future developers
- // This method gave me a lot of trouble lol. It's hard to write code when you're half asleep.
- // There may be an n+1 query in the view and I don't think there's a way to avoid that without writing a lot of extra code.
-
- $requestVacancies = $request->assocVacancies;
- $currentVacancies = $team->vacancies->pluck('id')->all();
-
- if (is_null($requestVacancies)) {
- foreach ($team->vacancies as $vacancy) {
- $team->vacancies()->detach($vacancy->id);
- }
-
- $request->session()->flash('success', __('Removed all vacancy associations.'));
-
- return redirect()->back();
- }
-
- $vacancyDiff = array_diff($requestVacancies, $currentVacancies);
- $deselectedDiff = array_diff($currentVacancies, $requestVacancies);
-
- if (! empty($vacancyDiff) || ! empty($deselectedDiff)) {
- foreach ($vacancyDiff as $selectedVacancy) {
- $team->vacancies()->attach($selectedVacancy);
- }
-
- foreach ($deselectedDiff as $deselectedVacancy) {
- $team->vacancies()->detach($deselectedVacancy);
- }
- } else {
- $team->vacancies()->attach($requestVacancies);
- }
-
- $request->session()->flash('success', __('Assignments changed successfully.'));
-
- return redirect()->back();
+ return redirect()
+ ->back()
+ ->with('success', $message);
}
}
diff --git a/app/Http/Controllers/TeamFileController.php b/app/Http/Controllers/TeamFileController.php
index 56b6786..b6fbdc6 100755
--- a/app/Http/Controllers/TeamFileController.php
+++ b/app/Http/Controllers/TeamFileController.php
@@ -5,6 +5,8 @@ namespace App\Http\Controllers;
// Most of these namespaces have no effect on the code, however, they're used by IDEs so they can resolve return types and for PHPDocumentor as well
+use App\Exceptions\FileUploadException;
+use App\Services\TeamFileService;
use App\TeamFile;
use App\Http\Requests\UploadFileRequest;
@@ -24,11 +26,16 @@ use Illuminate\Http\Response;
class TeamFileController extends Controller
{
+ private $fileService;
+
+ public function __construct(TeamFileService $fileService) {
+ $this->fileService = $fileService;
+ }
+
/**
* Display a listing of the resource.
*
* @param Request $request
- * @return Application|Factory|View|Response
*/
public function index(Request $request)
{
@@ -55,33 +62,24 @@ class TeamFileController extends Controller
{
$this->authorize('store', TeamFile::class);
- $upload = $request->file('file');
+ try {
+ $caption = $request->caption;
+ $description = $request->description;
- $file = $upload->store('uploads');
- $originalFileName = $upload->getClientOriginalName();
- $originalFileExtension = $upload->extension();
- $originalFileSize = $upload->getSize();
+ $this->fileService->addFile($request->file('file'), Auth::user()->id, Auth::user()->currentTeam->id, $caption, $description);
- $fileEntry = TeamFile::create([
- 'uploaded_by' => Auth::user()->id,
- 'team_id' => Auth::user()->currentTeam->id,
- 'name' => $originalFileName,
- 'caption' => $request->caption,
- 'description' => $request->description,
- 'fs_location' => $file,
- 'extension' => $originalFileExtension,
- 'size' => $originalFileSize
- ]);
+ return redirect()
+ ->back()
+ ->with('success', __('File uploaded successfully.'));
+
+ } catch (FileUploadException $uploadException) {
+
+ return redirect()
+ ->back()
+ ->with('error', $uploadException->getMessage());
- if ($fileEntry && !is_bool($file))
- {
- $request->session()->flash('success', 'File uploaded successfully!');
- return redirect()->back();
}
- $request->session()->flash('error', 'There was an unknown error whilst trying to upload your file.');
- return redirect()->back();
-
}
@@ -101,29 +99,6 @@ class TeamFileController extends Controller
}
}
- /**
- * Show the form for editing the specified resource.
- *
- * @param \App\TeamFile $teamFile
- * @return Response
- */
- public function edit(TeamFile $teamFile)
- {
- //
- }
-
- /**
- * Update the specified resource in storage.
- *
- * @param \Illuminate\Http\Request $request
- * @param \App\TeamFile $teamFile
- * @return Response
- */
- public function update(Request $request, TeamFile $teamFile)
- {
- //
- }
-
/**
* Remove the specified resource from storage.
*
diff --git a/app/Services/ApplicationService.php b/app/Services/ApplicationService.php
new file mode 100644
index 0000000..f7defa6
--- /dev/null
+++ b/app/Services/ApplicationService.php
@@ -0,0 +1,158 @@
+where('vacancySlug', $vacancySlug)->get();
+
+ $firstVacancy = $vacancyWithForm->first();
+
+ if (!$vacancyWithForm->isEmpty() && $firstVacancy->vacancyCount !== 0 && $firstVacancy->vacancyStatus == 'OPEN') {
+ return view('dashboard.application-rendering.apply')
+ ->with([
+ 'vacancy' => $vacancyWithForm->first(),
+ 'preprocessedForm' => json_decode($vacancyWithForm->first()->forms->formStructure, true),
+ ]);
+ } else {
+
+ throw new ApplicationNotFoundException('The application you\'re looking for could not be found or it is currently unavailable.', 404);
+
+ }
+ }
+
+ /**
+ * Fills a vacancy's form with submitted data.
+ *
+ * @throws UnavailableApplicationException Thrown when the application has no vacancies or is closed
+ * @throws VacancyNotFoundException Thrown when the associated vacancy is not found
+ * @throws IncompleteApplicationException Thrown when there are missing fields
+ */
+ public function fillForm(Authenticatable $applicant, array $formData, $vacancySlug): bool
+ {
+ $vacancy = Vacancy::with('forms')->where('vacancySlug', $vacancySlug)->get();
+
+ if ($vacancy->isEmpty()) {
+
+ throw new VacancyNotFoundException('This vacancy doesn\'t exist; Please use the proper buttons to apply to one.', 404);
+
+ }
+
+ if ($vacancy->first()->vacancyCount == 0 || $vacancy->first()->vacancyStatus !== 'OPEN') {
+
+ throw new UnavailableApplicationException("This application is unavailable.");
+ }
+
+ Log::info('Processing new application!');
+
+ $formStructure = json_decode($vacancy->first()->forms->formStructure, true);
+ $responseValidation = ContextAwareValidator::getResponseValidator($formData, $formStructure);
+
+
+ Log::info('Built response & validator structure!');
+
+ if (!$responseValidation->get('validator')->fails()) {
+ $response = Response::create([
+ 'responseFormID' => $vacancy->first()->forms->id,
+ 'associatedVacancyID' => $vacancy->first()->id, // Since a form can be used by multiple vacancies, we can only know which specific vacancy this response ties to by using a vacancy ID
+ 'responseData' => $responseValidation->get('responseStructure'),
+ ]);
+
+ Log::info('Registered form response!', [
+ 'applicant' => $applicant->name,
+ 'vacancy' => $vacancy->first()->vacancyName
+ ]);
+
+ $application = Application::create([
+ 'applicantUserID' => $applicant->id,
+ 'applicantFormResponseID' => $response->id,
+ 'applicationStatus' => 'STAGE_SUBMITTED',
+ ]);
+
+ Log::info('Submitted an application!', [
+ 'responseID' => $response->id,
+ 'applicant' => $applicant->name
+ ]);
+
+ foreach (User::all() as $user) {
+ if ($user->hasRole('admin')) {
+ $user->notify((new NewApplicant($application, $vacancy->first()))->delay(now()->addSeconds(10)));
+ }
+ }
+
+ return true;
+
+ }
+
+ Log::warning('Application form for ' . $applicant->name . ' contained errors, resetting!');
+
+ throw new IncompleteApplicationException('There are one or more errors in your application. Please make sure none of your fields are empty, since they are all required.');
+ }
+
+ public function updateStatus(Application $application, $newStatus)
+ {
+ switch ($newStatus) {
+ case 'deny':
+
+ event(new ApplicationDeniedEvent($application));
+ $message = __("Application denied successfully.");
+
+ break;
+
+ case 'interview':
+ Log::info(' Moved application ID ' . $application->id . 'to interview stage!');
+ $message = __('Application moved to interview stage!');
+
+ $application->setStatus('STAGE_INTERVIEW');
+ $application->user->notify(new ApplicationMoved());
+
+ break;
+
+ default:
+ throw new \LogicException("Wrong status parameter. Please notify a developer.");
+ }
+
+ return $message;
+ }
+
+ /**
+ * @throws \Exception
+ */
+ public function delete(Application $application): ?bool
+ {
+ return $application->delete();
+ }
+
+
+ public function canVote($votes): bool
+ {
+ $allvotes = collect([]);
+
+ foreach ($votes as $vote) {
+ if ($vote->userID == Auth::user()->id) {
+ $allvotes->push($vote);
+ }
+ }
+
+ return !(($allvotes->count() == 1));
+ }
+}
diff --git a/app/Services/AppointmentService.php b/app/Services/AppointmentService.php
new file mode 100644
index 0000000..35f6de3
--- /dev/null
+++ b/app/Services/AppointmentService.php
@@ -0,0 +1,90 @@
+ $appointmentDescription,
+ 'appointmentDate' => $appointmentDate->toDateTimeString(),
+ 'applicationID' => $application->id,
+ 'appointmentLocation' => (in_array($appointmentLocation, $this->allowedPlatforms)) ? $appointmentLocation : 'DISCORD',
+ ]);
+ $application->setStatus('STAGE_INTERVIEW_SCHEDULED');
+
+ Log::info('User '.Auth::user()->name.' has scheduled an appointment with '.$application->user->name.' for application ID'.$application->id, [
+ 'datetime' => $appointmentDate->toDateTimeString(),
+ 'scheduled' => now(),
+ ]);
+
+ $application->user->notify(new AppointmentScheduled($appointment));
+
+
+ return true;
+ }
+
+ /**
+ * Updates the appointment with the new $status.
+ * It also sets the application's status to peer approval.
+ *
+ * Set $updateApplication to false to only update its status
+ *
+ * @throws InvalidAppointmentStatusException
+ */
+ public function updateAppointment(Application $application, $status, $updateApplication = true)
+ {
+ $validStatuses = [
+ 'SCHEDULED',
+ 'CONCLUDED',
+ ];
+
+ if ($status == 'SCHEDULED' || $status == 'CONCLUDED')
+ {
+ $application->appointment->appointmentStatus = strtoupper($status);
+ $application->appointment->save();
+
+ if ($updateApplication)
+ {
+ $application->setStatus('STAGE_PEERAPPROVAL');
+ $application->user->notify(new ApplicationMoved());
+ }
+ }
+ else
+ {
+ throw new InvalidAppointmentStatusException("Invalid appointment status!");
+ }
+
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getAllowedPlatforms(): array
+ {
+ return $this->allowedPlatforms;
+ }
+
+}
diff --git a/app/Services/CommentService.php b/app/Services/CommentService.php
new file mode 100644
index 0000000..545d3d5
--- /dev/null
+++ b/app/Services/CommentService.php
@@ -0,0 +1,27 @@
+ Auth::user()->id,
+ 'applicationID' => $application->id,
+ 'text' => $comment,
+ ]);
+ }
+
+ public function deleteComment(Comment $comment): ?bool
+ {
+ return $comment->delete();
+ }
+
+}
diff --git a/app/Services/ConfigurationService.php b/app/Services/ConfigurationService.php
new file mode 100644
index 0000000..249a24d
--- /dev/null
+++ b/app/Services/ConfigurationService.php
@@ -0,0 +1,75 @@
+ $option) {
+ try {
+
+ Log::debug('Going through option '.$optionName);
+ if (Options::optionExists($optionName)) {
+ Log::debug('Option exists, updating to new values', [
+ 'opt' => $optionName,
+ 'new_value' => $option,
+ ]);
+ Options::changeOption($optionName, $option);
+ }
+
+ } catch (\Exception $ex) {
+
+ Log::error('Unable to update options!', [
+ 'msg' => $ex->getMessage(),
+ 'trace' => $ex->getTraceAsString(),
+ ]);
+
+ // Let service caller handle this without failing here
+ throw $ex;
+ }
+ }
+ }
+
+ /**
+ * Saves the chosen game integration
+ *
+ * @throws InvalidGamePreferenceException
+ * @returns bool
+ */
+ public function saveGameIntegration($gamePreference): bool
+ {
+
+ // TODO: Find solution to dynamically support games
+
+ $supportedGames = [
+ 'RUST',
+ 'MINECRAFT',
+ 'SE',
+ 'GMOD'
+ ];
+
+ if (!is_null($gamePreference) && in_array($gamePreference, $supportedGames))
+ {
+ Options::changeOption('currentGame', $gamePreference);
+ return true;
+ }
+
+ throw new InvalidGamePreferenceException("Unsupported game " . $gamePreference);
+ }
+
+}
diff --git a/app/Services/ContactService.php b/app/Services/ContactService.php
new file mode 100644
index 0000000..86c7938
--- /dev/null
+++ b/app/Services/ContactService.php
@@ -0,0 +1,47 @@
+post(config('recaptcha.verify.apiurl'), [
+ 'secret' => config('recaptcha.keys.secret'),
+ 'response' => $challenge,
+ 'remoteip' => $ipAddress,
+ ]);
+
+ $response = json_decode($verifyrequest->getBody(), true);
+
+ if (! $response['success']) {
+ throw new FailedCaptchaException('Beep beep boop... Robot? Submission failed.');
+ }
+
+ foreach (User::all() as $user) {
+ if ($user->hasRole('admin')) {
+ $user->notify(new NewContact(collect([
+ 'message' => $message,
+ 'ip' => $ipAddress,
+ 'email' => $email,
+ ])));
+ }
+ }
+ }
+
+
+}
diff --git a/app/Services/FormManagementService.php b/app/Services/FormManagementService.php
new file mode 100644
index 0000000..a7e9ce0
--- /dev/null
+++ b/app/Services/FormManagementService.php
@@ -0,0 +1,76 @@
+get('validator')->fails()) {
+ $storableFormStructure = $contextValidation->get('structure');
+
+ Form::create(
+ [
+ 'formName' => $fields['formName'],
+ 'formStructure' => $storableFormStructure,
+ 'formStatus' => 'ACTIVE',
+ ]
+ );
+ return true;
+ }
+ return $contextValidation->get('validator')->errors()->getMessages();
+ }
+
+ public function deleteForm(Form $form) {
+
+ $deletable = true;
+
+ if (! is_null($form) && ! is_null($form->vacancies) && $form->vacancies->count() !== 0 || ! is_null($form->responses)) {
+ $deletable = false;
+ }
+
+ if ($deletable) {
+
+ $form->delete();
+ return true;
+
+ } else {
+
+ throw new FormHasConstraintsException(__('You cannot delete this form because it\'s tied to one or more applications and ranks, or because it doesn\'t exist.'));
+
+ }
+ }
+
+ public function updateForm(Form $form, $fields) {
+
+ $contextValidation = ContextAwareValidator::getValidator($fields, true);
+
+ if (! $contextValidation->get('validator')->fails()) {
+ // Add the new structure into the form. New, subsquent fields will be identified by the "new" prefix
+ // This prefix doesn't actually change the app's behavior when it receives applications.
+ // Additionally, old applications won't of course display new and updated fields, because we can't travel into the past and get data for them
+ $form->formStructure = $contextValidation->get('structure');
+ $form->save();
+
+ return $form;
+
+ } else {
+ return $contextValidation->get('validator')->errors()->getMessages();
+ }
+ }
+
+}
diff --git a/app/Services/MeetingNoteService.php b/app/Services/MeetingNoteService.php
new file mode 100644
index 0000000..e1136e9
--- /dev/null
+++ b/app/Services/MeetingNoteService.php
@@ -0,0 +1,36 @@
+load('appointment');
+
+ $application->appointment->meetingNotes = $noteText;
+ $application->appointment->save();
+
+ return true;
+
+ } else {
+ throw new InvalidAppointmentException('There\'s no appointment to save notes to!');
+ }
+
+ }
+
+}
diff --git a/app/Services/ProfileService.php b/app/Services/ProfileService.php
new file mode 100644
index 0000000..438527b
--- /dev/null
+++ b/app/Services/ProfileService.php
@@ -0,0 +1,50 @@
+profile;
+ $social = [];
+
+ if (! is_null($profile)) {
+ switch ($request->avatarPref) {
+ case 'MOJANG':
+ $avatarPref = 'crafatar';
+
+ break;
+ case 'GRAVATAR':
+ $avatarPref = strtolower($request->avatarPref);
+
+ break;
+ }
+
+ $social['links']['github'] = $request->socialGithub;
+ $social['links']['twitter'] = $request->socialTwitter;
+ $social['links']['insta'] = $request->socialInsta;
+ $social['links']['discord'] = $request->socialDiscord;
+
+ $profile->profileShortBio = $request->shortBio;
+ $profile->profileAboutMe = $request->aboutMe;
+ $profile->avatarPreference = $avatarPref;
+ $profile->socialLinks = json_encode($social);
+
+ return $profile->save();
+ }
+
+ throw new ProfileNotFoundException("This profile does not exist.");
+ }
+
+}
diff --git a/app/Services/SecuritySettingsService.php b/app/Services/SecuritySettingsService.php
new file mode 100644
index 0000000..43329b4
--- /dev/null
+++ b/app/Services/SecuritySettingsService.php
@@ -0,0 +1,54 @@
+ $policy
+ ]);
+ }
+ else
+ {
+ Log::debug('[WARN] Ignoring bogus policy', [
+ 'avaliable' => $validPolicies,
+ 'given' => $policy
+ ]);
+ }
+
+ Options::changeOption('graceperiod', $options['graceperiod']);
+ Options::changeOption('password_expiry', $options['pwexpiry']);
+ Options::changeOption('force2fa', $options['enforce2fa']);
+ Options::changeOption('requireGameLicense', $options['requirePMC']);
+
+ return true;
+
+ }
+
+}
diff --git a/app/Services/TeamFileService.php b/app/Services/TeamFileService.php
new file mode 100644
index 0000000..c94719c
--- /dev/null
+++ b/app/Services/TeamFileService.php
@@ -0,0 +1,42 @@
+store('uploads');
+ $originalFileName = $upload->getClientOriginalName();
+ $originalFileExtension = $upload->extension();
+ $originalFileSize = $upload->getSize();
+
+ $fileEntry = TeamFile::create([
+ 'uploaded_by' => $uploader,
+ 'team_id' => $team,
+ 'name' => $originalFileName,
+ 'caption' => $caption,
+ 'description' => $description,
+ 'fs_location' => $file,
+ 'extension' => $originalFileExtension,
+ 'size' => $originalFileSize
+ ]);
+
+ if ($fileEntry && !is_bool($file))
+ {
+ return $fileEntry;
+ }
+
+ throw new FileUploadException("There was an unknown error whilst trying to upload your file.");
+
+ }
+
+}
diff --git a/app/Services/TeamService.php b/app/Services/TeamService.php
new file mode 100644
index 0000000..9e7c8c0
--- /dev/null
+++ b/app/Services/TeamService.php
@@ -0,0 +1,165 @@
+ $teamName,
+ 'owner_id' => $ownerID,
+ ]);
+
+ Auth::user()->teams()->attach($team->id);
+
+ return $team;
+ }
+
+ public function updateTeam(Team $team, $teamDescription, $joinType): bool
+ {
+
+ $team->description = $teamDescription;
+ $team->openJoin = $joinType;
+
+ return $team->save();
+ }
+
+ /**
+ * Invites a user to a $team.
+ *
+ * @throws PublicTeamInviteException Thrown when trying to invite a user to a public team
+ * @throws UserAlreadyInvitedException Thrown when a user is already invited
+ */
+ public function inviteUser(Team $team, $userID): bool
+ {
+
+ $user = User::findOrFail($userID);
+
+ if (! $team->openJoin) {
+ if (! Teamwork::hasPendingInvite($user->email, $team)) {
+ Teamwork::inviteToTeam($user, $team, function (TeamInvite $invite) use ($user) {
+ Mail::to($user)->send(new InviteToTeam($invite));
+ });
+ return true;
+ } else {
+ throw new UserAlreadyInvitedException('This user has already been invited.');
+ }
+ } else {
+ throw new PublicTeamInviteException('You can\'t invite users to public teams.');
+ }
+
+ }
+
+ /**
+ * Accepts or denies a user invite
+ *
+ * @param Authenticatable $user
+ * @param $action
+ * @param $token
+ * @return bool True on success or exception on failure
+ * @throws InvalidInviteException Thrown when the invite code / url is invalid
+ */
+ public function processInvite(Authenticatable $user, $action, $token): bool {
+
+ switch ($action) {
+ case 'accept':
+
+ $invite = Teamwork::getInviteFromAcceptToken($token);
+
+ if ($invite && $invite->user->is($user)) {
+ Teamwork::acceptInvite($invite);
+
+ } else {
+
+ throw new InvalidInviteException('Invalid or expired invite URL.');
+ }
+
+ break;
+
+ case 'deny':
+
+ $invite = Teamwork::getInviteFromDenyToken($token);
+
+ if ($invite && $invite->user->is($user)) {
+
+ Teamwork::denyInvite($invite);
+
+ } else {
+
+ throw new InvalidInviteException('Invalid or expired invite URL.');
+ }
+
+ break;
+
+ default:
+ throw new InvalidInviteException('Sorry, but the invite URL you followed was malformed.');
+ }
+
+ return true;
+
+ }
+
+
+ /**
+ * @param Team $team
+ * @param $associatedVacancies
+ * @return string The success message, exception/bool if error
+ */
+ public function updateVacancies(Team $team, $associatedVacancies): string
+ {
+
+ // P.S. To future developers
+ // This method gave me a lot of trouble lol. It's hard to write code when you're half asleep.
+ // There may be an n+1 query in the view and I don't think there's a way to avoid that without writing a lot of extra code.
+
+ $requestVacancies = $associatedVacancies;
+ $currentVacancies = $team->vacancies->pluck('id')->all();
+
+ if (is_null($requestVacancies)) {
+ foreach ($team->vacancies as $vacancy) {
+ $team->vacancies()->detach($vacancy->id);
+ }
+
+ return 'Removed all vacancy associations.';
+ }
+
+ $vacancyDiff = array_diff($requestVacancies, $currentVacancies);
+ $deselectedDiff = array_diff($currentVacancies, $requestVacancies);
+
+ if (! empty($vacancyDiff) || ! empty($deselectedDiff)) {
+ foreach ($vacancyDiff as $selectedVacancy) {
+ $team->vacancies()->attach($selectedVacancy);
+ }
+
+ foreach ($deselectedDiff as $deselectedVacancy) {
+ $team->vacancies()->detach($deselectedVacancy);
+ }
+ } else {
+ $team->vacancies()->attach($requestVacancies);
+ }
+ return 'Assignments changed successfully.';
+ }
+}