forked from miguel456/rbrecruiter
Added services
This commit moves most controller logic onto Services. Services are part of the Service-Repository pattern. The models act as repositories. Services are easily testable and are needed for the upcoming API, in order to avoid duplicated code and to maintain a single source of "truth". The User, Vacancy and Vote controllers still need their logic moved onto services.
This commit is contained in:
158
app/Services/ApplicationService.php
Normal file
158
app/Services/ApplicationService.php
Normal file
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use ContextAwareValidator;
|
||||
use App\Application;
|
||||
use App\Events\ApplicationDeniedEvent;
|
||||
use App\Exceptions\ApplicationNotFoundException;
|
||||
use App\Exceptions\IncompleteApplicationException;
|
||||
use App\Exceptions\UnavailableApplicationException;
|
||||
use App\Exceptions\VacancyNotFoundException;
|
||||
use App\Notifications\ApplicationMoved;
|
||||
use App\Notifications\NewApplicant;
|
||||
use App\Response;
|
||||
use App\User;
|
||||
use App\Vacancy;
|
||||
use Illuminate\Auth\Authenticatable;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class ApplicationService
|
||||
{
|
||||
public function renderForm($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 {
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
90
app/Services/AppointmentService.php
Normal file
90
app/Services/AppointmentService.php
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
|
||||
use App\Application;
|
||||
use App\Appointment;
|
||||
use App\Exceptions\InvalidAppointmentStatusException;
|
||||
use App\Notifications\ApplicationMoved;
|
||||
use App\Notifications\AppointmentScheduled;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class AppointmentService
|
||||
{
|
||||
private $allowedPlatforms = [
|
||||
|
||||
'ZOOM',
|
||||
'DISCORD',
|
||||
'SKYPE',
|
||||
'MEET',
|
||||
'TEAMSPEAK',
|
||||
|
||||
];
|
||||
|
||||
public function createAppointment(Application $application, Carbon $appointmentDate, $appointmentDescription, $appointmentLocation)
|
||||
{
|
||||
$appointment = Appointment::create([
|
||||
'appointmentDescription' => $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;
|
||||
}
|
||||
|
||||
}
|
27
app/Services/CommentService.php
Normal file
27
app/Services/CommentService.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
|
||||
use App\Application;
|
||||
use App\Comment;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class CommentService
|
||||
{
|
||||
|
||||
public function addComment(Application $application, $comment): Comment {
|
||||
return Comment::create([
|
||||
'authorID' => Auth::user()->id,
|
||||
'applicationID' => $application->id,
|
||||
'text' => $comment,
|
||||
]);
|
||||
}
|
||||
|
||||
public function deleteComment(Comment $comment): ?bool
|
||||
{
|
||||
return $comment->delete();
|
||||
}
|
||||
|
||||
}
|
75
app/Services/ConfigurationService.php
Normal file
75
app/Services/ConfigurationService.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
|
||||
use App\Exceptions\InvalidGamePreferenceException;
|
||||
use App\Exceptions\OptionNotFoundException;
|
||||
use App\Facades\Options;
|
||||
use Illuminate\Auth\Authenticatable;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class ConfigurationService
|
||||
{
|
||||
|
||||
/**
|
||||
* @throws OptionNotFoundException|\Exception
|
||||
*
|
||||
*/
|
||||
public function saveConfiguration($configuration) {
|
||||
|
||||
foreach ($configuration 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(),
|
||||
]);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
}
|
47
app/Services/ContactService.php
Normal file
47
app/Services/ContactService.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
|
||||
use App\Exceptions\FailedCaptchaException;
|
||||
use App\Notifications\NewContact;
|
||||
use App\User;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class ContactService
|
||||
{
|
||||
|
||||
/**
|
||||
* Sends a message to all admins.
|
||||
*
|
||||
* @throws FailedCaptchaException
|
||||
*/
|
||||
public function sendMessage($ipAddress, $message, $email, $challenge)
|
||||
{
|
||||
// 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' => $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,
|
||||
])));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
76
app/Services/FormManagementService.php
Normal file
76
app/Services/FormManagementService.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Exceptions\EmptyFormException;
|
||||
use App\Exceptions\FormHasConstraintsException;
|
||||
use App\Form;
|
||||
use ContextAwareValidator;
|
||||
|
||||
class FormManagementService
|
||||
{
|
||||
|
||||
public function addForm($fields) {
|
||||
|
||||
if (count($fields) == 2) {
|
||||
// form is probably empty, since forms with fields will always have more than 2 items
|
||||
throw new EmptyFormException('Sorry, but you may not create empty forms.');
|
||||
}
|
||||
|
||||
$contextValidation = ContextAwareValidator::getValidator($fields, true, true);
|
||||
|
||||
if (! $contextValidation->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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
36
app/Services/MeetingNoteService.php
Normal file
36
app/Services/MeetingNoteService.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
|
||||
use App\Application;
|
||||
use App\Exceptions\InvalidAppointmentException;
|
||||
|
||||
class MeetingNoteService
|
||||
{
|
||||
/**
|
||||
* Adds meeting notes to an application.
|
||||
*
|
||||
* @param Application $application
|
||||
* @param $noteText
|
||||
* @return bool
|
||||
* @throws InvalidAppointmentException Thrown when an application doesn't have an appointment to save notes to
|
||||
*/
|
||||
public function addToApplication(Application $application, $noteText): bool {
|
||||
|
||||
if (! is_null($application)) {
|
||||
$application->load('appointment');
|
||||
|
||||
$application->appointment->meetingNotes = $noteText;
|
||||
$application->appointment->save();
|
||||
|
||||
return true;
|
||||
|
||||
} else {
|
||||
throw new InvalidAppointmentException('There\'s no appointment to save notes to!');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
50
app/Services/ProfileService.php
Normal file
50
app/Services/ProfileService.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
|
||||
use App\Exceptions\ProfileNotFoundException;
|
||||
use App\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class ProfileService
|
||||
{
|
||||
|
||||
/**
|
||||
* @throws ProfileNotFoundException
|
||||
*/
|
||||
public function updateProfile($userID, Request $request) {
|
||||
$profile = User::find($userID)->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.");
|
||||
}
|
||||
|
||||
}
|
54
app/Services/SecuritySettingsService.php
Normal file
54
app/Services/SecuritySettingsService.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
|
||||
use App\Facades\Options;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class SecuritySettingsService
|
||||
{
|
||||
|
||||
/**
|
||||
* Saves the app security settings.
|
||||
*
|
||||
* @param $policy
|
||||
* @param array $options
|
||||
* @return bool
|
||||
*/
|
||||
public function save($policy, $options = []) {
|
||||
|
||||
$validPolicies = [
|
||||
'off',
|
||||
'low',
|
||||
'medium',
|
||||
'high'
|
||||
];
|
||||
|
||||
if (in_array($policy, $validPolicies))
|
||||
{
|
||||
Options::changeOption('pw_security_policy', $policy);
|
||||
|
||||
Log::debug('[Options] Changing option pw_security_policy', [
|
||||
'new_value' => $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;
|
||||
|
||||
}
|
||||
|
||||
}
|
42
app/Services/TeamFileService.php
Normal file
42
app/Services/TeamFileService.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
|
||||
use App\Exceptions\FileUploadException;
|
||||
use App\TeamFile;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class TeamFileService
|
||||
{
|
||||
|
||||
public function addFile(UploadedFile $upload, $uploader, $team, $caption, $description) {
|
||||
|
||||
$file = $upload->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.");
|
||||
|
||||
}
|
||||
|
||||
}
|
165
app/Services/TeamService.php
Normal file
165
app/Services/TeamService.php
Normal file
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
|
||||
use App\Exceptions\InvalidInviteException;
|
||||
use App\Exceptions\PublicTeamInviteException;
|
||||
use App\Exceptions\UserAlreadyInvitedException;
|
||||
use App\Mail\InviteToTeam;
|
||||
use App\Team;
|
||||
use App\User;
|
||||
use Illuminate\Auth\Authenticatable;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Mpociot\Teamwork\Facades\Teamwork;
|
||||
use Mpociot\Teamwork\TeamInvite;
|
||||
|
||||
class TeamService
|
||||
{
|
||||
|
||||
/**
|
||||
* Create a team
|
||||
*
|
||||
* @param $teamName
|
||||
* @param $ownerID
|
||||
* @return Team
|
||||
*/
|
||||
public function createTeam($teamName, $ownerID): Team {
|
||||
|
||||
$team = Team::create([
|
||||
'name' => $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.';
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user