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:
2021-07-25 22:54:15 +01:00
parent c739933668
commit 8942623bde
44 changed files with 1308 additions and 691 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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.
*

View File

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

View File

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

View File

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

View File

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

View File

@@ -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.
*