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:
parent
c739933668
commit
8942623bde
|
@ -1,154 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module type="WEB_MODULE" version="4">
|
|
||||||
<component name="NewModuleRootManager">
|
|
||||||
<content url="file://$MODULE_DIR$">
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/database/factories" isTestSource="false" packagePrefix="Database\Factories\" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" packagePrefix="Tests\" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/database/seeders" isTestSource="false" packagePrefix="Database\Seeders\" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/spec" isTestSource="true" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/app" isTestSource="false" packagePrefix="App\" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/almasaeed2010/adminlte" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/asm89/stack-cors" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/awssat/discord-notification-channel" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/barryvdh/laravel-debugbar" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/berkayk/onesignal-laravel" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/brick/math" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/clue/stream-filter" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/composer" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/dnoegel/php-xdg-base-dir" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/cache" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/dbal" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/event-manager" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/inflector" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/instantiator" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/lexer" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/dragonmantank/cron-expression" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/egulias/email-validator" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/facade/flare-client-php" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/facade/ignition" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/facade/ignition-contracts" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/fideloper/proxy" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/filp/whoops" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/fruitcake/laravel-cors" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/fzaninotto/faker" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/graham-campbell/result-type" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/guzzle" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/promises" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/psr7" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/hamcrest/hamcrest-php" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/http-interop/http-factory-guzzle" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/jean85/pretty-package-versions" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/jeroennoten/laravel-adminlte" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/laravel/framework" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/laravel/tinker" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/laravel/ui" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/league/commonmark" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/league/flysystem" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/league/mime-type-detection" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/maximebf/debugbar" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/mcamara/laravel-localization" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/mockery/mockery" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/monolog/monolog" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/mpociot/teamwork" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/myclabs/deep-copy" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/nesbot/carbon" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/nikic/php-parser" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/nunomaduro/collision" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/opis/closure" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/paragonie/random_compat" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phar-io/manifest" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phar-io/version" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/php-http/client-common" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/php-http/discovery" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/php-http/guzzle6-adapter" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/php-http/httplug" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/php-http/message" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/php-http/message-factory" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/php-http/promise" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phpdocumentor/reflection-common" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phpdocumentor/reflection-docblock" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phpdocumentor/type-resolver" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phpoption/phpoption" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phpspec/prophecy" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-code-coverage" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-file-iterator" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-invoker" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-text-template" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-timer" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-token-stream" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/phpunit" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/container" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/event-dispatcher" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-client" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-factory" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-message" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/log" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/simple-cache" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psy/psysh" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/ralouphie/getallheaders" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/ramsey/collection" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/ramsey/uuid" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/scrivo/highlight.php" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/cli-parser" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/code-unit" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/code-unit-reverse-lookup" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/comparator" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/complexity" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/diff" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/environment" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/exporter" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/global-state" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/lines-of-code" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/object-enumerator" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/object-reflector" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/recursion-context" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/resource-operations" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/type" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/version" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sentry/sentry" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sentry/sentry-laravel" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/spatie/laravel-permission" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/swiftmailer/swiftmailer" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/console" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/css-selector" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/debug" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/deprecation-contracts" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/error-handler" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/event-dispatcher" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/event-dispatcher-contracts" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/finder" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/http-client" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/http-client-contracts" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/http-foundation" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/http-kernel" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/mime" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/options-resolver" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-ctype" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-iconv" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-grapheme" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-idn" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-normalizer" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-mbstring" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php72" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php73" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php80" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-uuid" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/process" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/psr-http-message-bridge" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/routing" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/service-contracts" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/string" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/translation" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/translation-contracts" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/var-dumper" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/theseer/tokenizer" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/tijsverkoyen/css-to-inline-styles" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/vlucas/phpdotenv" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/voku/portable-ascii" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/webmozart/assert" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/vendor/laravel/sanctum" />
|
|
||||||
</content>
|
|
||||||
<orderEntry type="inheritedJdk" />
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
|
@ -2,7 +2,7 @@
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectModuleManager">
|
<component name="ProjectModuleManager">
|
||||||
<modules>
|
<modules>
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/hrm-mcserver.iml" filepath="$PROJECT_DIR$/.idea/hrm-mcserver.iml" />
|
<module fileurl="file://$PROJECT_DIR$/../rbrecruiter/.idea/rbrecruiter.iml" filepath="$PROJECT_DIR$/../rbrecruiter/.idea/rbrecruiter.iml" />
|
||||||
</modules>
|
</modules>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||||
|
|
||||||
|
class ApplicationNotFoundException extends ModelNotFoundException
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class EmptyFormException extends Exception
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class EmptyOptionsException extends Exception
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class FailedCaptchaException extends Exception
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class FileUploadException extends Exception
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class FormHasConstraintsException extends Exception
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class IncompleteApplicationException extends Exception
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class InvalidAppointmentException extends Exception
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class InvalidAppointmentStatusException extends Exception
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class InvalidGamePreferenceException extends Exception
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class InvalidInviteException extends Exception
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class OptionCategoryNotFoundException extends Exception
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class OptionNotFoundException extends Exception
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class ProfileNotFoundException extends Exception
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class PublicTeamInviteException extends Exception
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class UnavailableApplicationException extends Exception
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class UserAlreadyInvitedException extends Exception
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||||
|
|
||||||
|
class VacancyNotFoundException extends ModelNotFoundException
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
|
@ -21,6 +21,8 @@
|
||||||
|
|
||||||
namespace App\Helpers;
|
namespace App\Helpers;
|
||||||
|
|
||||||
|
use App\Exceptions\EmptyOptionsException;
|
||||||
|
use App\Exceptions\OptionNotFoundException;
|
||||||
use App\Options as Option;
|
use App\Options as Option;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
@ -34,7 +36,7 @@ class Options
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an assortment of settings found in the mentioned category
|
* Returns an assortment of settings found in the mentioned category
|
||||||
*
|
*
|
||||||
* @param $category The category
|
* @param $category The category
|
||||||
* @return Collection The settings in this category
|
* @return Collection The settings in this category
|
||||||
*/
|
*/
|
||||||
|
@ -43,7 +45,7 @@ class Options
|
||||||
$options = Option::where('option_category', $category)->get();
|
$options = Option::where('option_category', $category)->get();
|
||||||
if ($options->isEmpty())
|
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;
|
return $options;
|
||||||
}
|
}
|
||||||
|
@ -52,13 +54,13 @@ class Options
|
||||||
public function getOption(string $option): string
|
public function getOption(string $option): string
|
||||||
{
|
{
|
||||||
$value = Cache::get($option);
|
$value = Cache::get($option);
|
||||||
|
|
||||||
|
|
||||||
if (is_null($value)) {
|
if (is_null($value)) {
|
||||||
Log::debug('Option '.$option.'not found in cache, refreshing from database');
|
Log::debug('Option '.$option.'not found in cache, refreshing from database');
|
||||||
$value = Option::where('option_name', $option)->first();
|
$value = Option::where('option_name', $option)->first();
|
||||||
if (is_null($value)) {
|
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, $value->option_value);
|
||||||
Cache::put($option.'_desc', 'Undefined description');
|
Cache::put($option.'_desc', 'Undefined description');
|
||||||
|
@ -118,7 +120,7 @@ class Options
|
||||||
|
|
||||||
Cache::put('option_name', $newValue, now()->addDay());
|
Cache::put('option_name', $newValue, now()->addDay());
|
||||||
} else {
|
} else {
|
||||||
throw new \Exception('This option does not exist.');
|
throw new OptionNotFoundException('This option does not exist.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,35 +22,24 @@
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Application;
|
use App\Application;
|
||||||
use App\Events\ApplicationDeniedEvent;
|
use App\Exceptions\IncompleteApplicationException;
|
||||||
use App\Facades\JSON;
|
use App\Exceptions\UnavailableApplicationException;
|
||||||
use App\Notifications\ApplicationMoved;
|
use App\Exceptions\VacancyNotFoundException;
|
||||||
use App\Notifications\NewApplicant;
|
use App\Services\ApplicationService;
|
||||||
use App\Response;
|
|
||||||
use App\User;
|
|
||||||
use App\Vacancy;
|
|
||||||
use ContextAwareValidator;
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
|
|
||||||
class ApplicationController extends Controller
|
class ApplicationController extends Controller
|
||||||
{
|
{
|
||||||
|
|
||||||
|
private $applicationService;
|
||||||
|
|
||||||
private function canVote($votes): bool
|
public function __construct(ApplicationService $applicationService) {
|
||||||
{
|
|
||||||
$allvotes = collect([]);
|
|
||||||
|
|
||||||
foreach ($votes as $vote) {
|
$this->applicationService = $applicationService;
|
||||||
if ($vote->userID == Auth::user()->id) {
|
|
||||||
$allvotes->push($vote);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return !(($allvotes->count() == 1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function showUserApps()
|
public function showUserApps()
|
||||||
{
|
{
|
||||||
return view('dashboard.user.applications')
|
return view('dashboard.user.applications')
|
||||||
|
@ -70,7 +59,7 @@ class ApplicationController extends Controller
|
||||||
'structuredResponses' => json_decode($application->response->responseData, true),
|
'structuredResponses' => json_decode($application->response->responseData, true),
|
||||||
'formStructure' => $application->response->form,
|
'formStructure' => $application->response->form,
|
||||||
'vacancy' => $application->response->vacancy,
|
'vacancy' => $application->response->vacancy,
|
||||||
'canVote' => $this->canVote($application->votes),
|
'canVote' => $this->applicationService->canVote($application->votes),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
} else {
|
} 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();
|
return $this->applicationService->renderForm($vacancySlug);
|
||||||
|
|
||||||
$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.'));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function saveApplicationAnswers(Request $request, $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()
|
return redirect()
|
||||||
->back()
|
->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()
|
return redirect()
|
||||||
->back()
|
->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)
|
public function updateApplicationStatus(Request $request, Application $application, $newStatus)
|
||||||
|
@ -182,36 +107,28 @@ class ApplicationController extends Controller
|
||||||
$messageIsError = false;
|
$messageIsError = false;
|
||||||
$this->authorize('update', Application::class);
|
$this->authorize('update', Application::class);
|
||||||
|
|
||||||
|
try {
|
||||||
switch ($newStatus) {
|
$status = $this->applicationService->updateStatus($application, $newStatus);
|
||||||
case 'deny':
|
} catch (\LogicException $ex)
|
||||||
|
{
|
||||||
event(new ApplicationDeniedEvent($application));
|
return redirect()
|
||||||
$message = __("Application denied successfully.");
|
->back()
|
||||||
|
->with('error', $ex->getMessage());
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->back();
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->with('success', $status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
public function delete(Request $request, Application $application)
|
public function delete(Request $request, Application $application)
|
||||||
{
|
{
|
||||||
$this->authorize('delete', $application);
|
$this->authorize('delete', $application);
|
||||||
$application->delete(); // observers will run, cleaning it up
|
$this->applicationService->delete($application);
|
||||||
|
|
||||||
return redirect()
|
return redirect()
|
||||||
->back()
|
->back()
|
||||||
|
|
|
@ -23,85 +23,79 @@ namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Application;
|
use App\Application;
|
||||||
use App\Appointment;
|
use App\Appointment;
|
||||||
|
use App\Exceptions\InvalidAppointmentException;
|
||||||
|
use App\Exceptions\InvalidAppointmentStatusException;
|
||||||
use App\Http\Requests\SaveNotesRequest;
|
use App\Http\Requests\SaveNotesRequest;
|
||||||
use App\Notifications\ApplicationMoved;
|
use App\Services\AppointmentService;
|
||||||
use App\Notifications\AppointmentScheduled;
|
use App\Services\MeetingNoteService;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Auth\Access\AuthorizationException;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
|
|
||||||
class AppointmentController extends Controller
|
class AppointmentController extends Controller
|
||||||
{
|
{
|
||||||
private $allowedPlatforms = [
|
|
||||||
|
|
||||||
'ZOOM',
|
private $appointmentService;
|
||||||
'DISCORD',
|
private $meetingNoteService;
|
||||||
'SKYPE',
|
|
||||||
'MEET',
|
|
||||||
'TEAMSPEAK',
|
|
||||||
|
|
||||||
];
|
|
||||||
|
|
||||||
public function saveAppointment(Request $request, Application $application)
|
public function __construct(AppointmentService $appointmentService, MeetingNoteService $meetingNoteService) {
|
||||||
{
|
|
||||||
$this->authorize('create', Appointment::class);
|
|
||||||
$appointmentDate = Carbon::parse($request->appointmentDateTime);
|
|
||||||
|
|
||||||
$appointment = Appointment::create([
|
$this->appointmentService = $appointmentService;
|
||||||
'appointmentDescription' => $request->appointmentDescription,
|
$this->meetingNoteService = $meetingNoteService;
|
||||||
'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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
$this->authorize('update', $application->appointment);
|
||||||
|
|
||||||
$validStatuses = [
|
try {
|
||||||
'SCHEDULED',
|
$this->appointmentService->updateAppointment($application, $status);
|
||||||
'CONCLUDED',
|
|
||||||
];
|
|
||||||
|
|
||||||
// NOTE: This is a little confusing, refactor
|
return redirect()
|
||||||
$application->appointment->appointmentStatus = (in_array($status, $validStatuses)) ? strtoupper($status) : 'SCHEDULED';
|
->back()
|
||||||
$application->appointment->save();
|
->with('success', __("Interview finished! Staff members can now vote on it."));
|
||||||
|
|
||||||
$application->setStatus('STAGE_PEERAPPROVAL');
|
}
|
||||||
$application->user->notify(new ApplicationMoved());
|
catch (InvalidAppointmentStatusException $ex) {
|
||||||
|
return redirect()
|
||||||
$request->session()->flash('success', __('Interview finished! Staff members can now vote on it.'));
|
->back()
|
||||||
|
->with('error', $ex->getMessage());
|
||||||
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!'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,21 +24,22 @@ namespace App\Http\Controllers;
|
||||||
use App\Application;
|
use App\Application;
|
||||||
use App\Comment;
|
use App\Comment;
|
||||||
use App\Http\Requests\NewCommentRequest;
|
use App\Http\Requests\NewCommentRequest;
|
||||||
|
use App\Services\CommentService;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
class CommentController extends Controller
|
class CommentController extends Controller
|
||||||
{
|
{
|
||||||
|
private $commentService;
|
||||||
|
|
||||||
|
public function __construct(CommentService $commentService) {
|
||||||
|
$this->commentService = $commentService;
|
||||||
|
}
|
||||||
|
|
||||||
public function insert(NewCommentRequest $request, Application $application)
|
public function insert(NewCommentRequest $request, Application $application)
|
||||||
{
|
{
|
||||||
$this->authorize('create', Comment::class);
|
$this->authorize('create', Comment::class);
|
||||||
|
$comment = $this->commentService->addComment($application, $request->comment);
|
||||||
$comment = Comment::create([
|
|
||||||
'authorID' => Auth::user()->id,
|
|
||||||
'applicationID' => $application->id,
|
|
||||||
'text' => $request->comment,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if ($comment) {
|
if ($comment) {
|
||||||
$request->session()->flash('success', __('Comment posted!'));
|
$request->session()->flash('success', __('Comment posted!'));
|
||||||
|
@ -52,10 +53,10 @@ class CommentController extends Controller
|
||||||
public function delete(Request $request, Comment $comment)
|
public function delete(Request $request, Comment $comment)
|
||||||
{
|
{
|
||||||
$this->authorize('delete', $comment);
|
$this->authorize('delete', $comment);
|
||||||
|
$this->commentService->deleteComment($comment);
|
||||||
|
|
||||||
$comment->delete();
|
return redirect()
|
||||||
$request->session()->flash('success', __('Comment deleted!'));
|
->back()
|
||||||
|
->with('success', __('Comment deleted!'));
|
||||||
return redirect()->back();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,9 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Exceptions\FailedCaptchaException;
|
||||||
use App\Notifications\NewContact;
|
use App\Notifications\NewContact;
|
||||||
|
use App\Services\ContactService;
|
||||||
use App\User;
|
use App\User;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
|
@ -30,47 +32,32 @@ class ContactController extends Controller
|
||||||
{
|
{
|
||||||
protected $users;
|
protected $users;
|
||||||
|
|
||||||
public function __construct(User $users)
|
private $contactService;
|
||||||
|
|
||||||
|
public function __construct(User $users, ContactService $contactService)
|
||||||
{
|
{
|
||||||
|
$this->contactService = $contactService;
|
||||||
$this->users = $users;
|
$this->users = $users;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function create(Request $request)
|
public function create(Request $request)
|
||||||
{
|
{
|
||||||
$name = $request->name;
|
try {
|
||||||
$email = $request->email;
|
|
||||||
$subject = $request->subject;
|
|
||||||
$msg = $request->msg;
|
|
||||||
|
|
||||||
$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
|
$this->contactService->sendMessage($request->ip(), $msg, $email, $challenge);
|
||||||
$verifyrequest = Http::asForm()->post(config('recaptcha.verify.apiurl'), [
|
|
||||||
'secret' => config('recaptcha.keys.secret'),
|
|
||||||
'response' => $challenge,
|
|
||||||
'remoteip' => $request->ip(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
$response = json_decode($verifyrequest->getBody(), true);
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->with('success',__('Message sent successfully! We usually respond within 48 hours.'));
|
||||||
|
|
||||||
if (! $response['success']) {
|
} catch (FailedCaptchaException $ex) {
|
||||||
$request->session()->flash('error', __('Beep beep boop... Robot? Submission failed.'));
|
return redirect()
|
||||||
|
->back()
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,8 @@ use App\Vacancy;
|
||||||
|
|
||||||
class DashboardController extends Controller
|
class DashboardController extends Controller
|
||||||
{
|
{
|
||||||
|
// Note: The dashboard doesn't need a service because it doesn't contain any significant business logic
|
||||||
|
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
$totalPeerReview = Application::where('applicationStatus', 'STAGE_PEERAPPROVAL')->get()->count();
|
$totalPeerReview = Application::where('applicationStatus', 'STAGE_PEERAPPROVAL')->get()->count();
|
||||||
|
|
|
@ -21,12 +21,20 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Exceptions\FormHasConstraintsException;
|
||||||
use App\Form;
|
use App\Form;
|
||||||
|
use App\Services\FormManagementService;
|
||||||
use ContextAwareValidator;
|
use ContextAwareValidator;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class FormController extends Controller
|
class FormController extends Controller
|
||||||
{
|
{
|
||||||
|
private $formService;
|
||||||
|
|
||||||
|
public function __construct(FormManagementService $formService) {
|
||||||
|
$this->formService = $formService;
|
||||||
|
}
|
||||||
|
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
$forms = Form::all();
|
$forms = Form::all();
|
||||||
|
@ -45,60 +53,38 @@ class FormController extends Controller
|
||||||
|
|
||||||
public function saveForm(Request $request)
|
public function saveForm(Request $request)
|
||||||
{
|
{
|
||||||
$this->authorize('create', Form::class);
|
$form = $this->formService->addForm($request->all());
|
||||||
$fields = $request->all();
|
|
||||||
|
|
||||||
if (count($fields) == 2) {
|
// Form is boolean or array
|
||||||
// form is probably empty, since forms with fields will alawys have more than 2 items
|
if ($form)
|
||||||
|
{
|
||||||
$request->session()->flash('error', __('Sorry, but you may not create empty forms.'));
|
return redirect()
|
||||||
|
->back()
|
||||||
return redirect()->to(route('showForms'));
|
->with('success', __('Form created!'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$contextValidation = ContextAwareValidator::getValidator($fields, true, true);
|
return redirect()
|
||||||
|
->back()
|
||||||
if (! $contextValidation->get('validator')->fails()) {
|
->with('errors', $form);
|
||||||
$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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function destroy(Request $request, Form $form)
|
public function destroy(Request $request, Form $form)
|
||||||
{
|
{
|
||||||
$this->authorize('delete', $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)
|
public function preview(Request $request, Form $form)
|
||||||
|
@ -124,22 +110,15 @@ class FormController extends Controller
|
||||||
public function update(Request $request, Form $form)
|
public function update(Request $request, Form $form)
|
||||||
{
|
{
|
||||||
$this->authorize('update', $form);
|
$this->authorize('update', $form);
|
||||||
|
$updatedForm = $this->formService->updateForm($form, $request->all());
|
||||||
|
|
||||||
$contextValidation = ContextAwareValidator::getValidator($request->all(), true);
|
if ($updatedForm instanceof Form) {
|
||||||
$this->authorize('update', $form);
|
return redirect()->to(route('previewForm', ['form' => $updatedForm->id]));
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->to(route('previewForm', ['form' => $form->id]));
|
// array of errors
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->with('errors', $updatedForm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,8 @@ use App\Vacancy;
|
||||||
|
|
||||||
class HomeController extends Controller
|
class HomeController extends Controller
|
||||||
{
|
{
|
||||||
|
// doesn't need a service, because it doesn't contain major logic.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show the application dashboard.
|
* Show the application dashboard.
|
||||||
*
|
*
|
||||||
|
|
|
@ -21,14 +21,25 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Exceptions\InvalidGamePreferenceException;
|
||||||
|
use App\Exceptions\OptionNotFoundException;
|
||||||
use App\Facades\Options;
|
use App\Facades\Options;
|
||||||
use App\Options as Option;
|
use App\Options as Option;
|
||||||
|
use App\Services\ConfigurationService;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
class OptionsController extends Controller
|
class OptionsController extends Controller
|
||||||
{
|
{
|
||||||
|
private $configurationService;
|
||||||
|
|
||||||
|
public function __construct(ConfigurationService $configurationService) {
|
||||||
|
|
||||||
|
$this->configurationService = $configurationService;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display a listing of the resource.
|
* Display a listing of the resource.
|
||||||
*
|
*
|
||||||
|
@ -36,7 +47,7 @@ class OptionsController extends Controller
|
||||||
*/
|
*/
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
|
// TODO: Replace with settings package
|
||||||
return view('dashboard.administration.settings')
|
return view('dashboard.administration.settings')
|
||||||
->with([
|
->with([
|
||||||
'options' => Options::getCategory('notifications'),
|
'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')) {
|
try {
|
||||||
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);
|
|
||||||
|
|
||||||
$errorCond = true;
|
if (Auth::user()->can('admin.settings.edit')) {
|
||||||
$request->session()->flash('error', __('An error occurred while trying to save settings: :message ', ['message' => $ex->getMessage()]));
|
$this->configurationService->saveConfiguration($request->all());
|
||||||
}
|
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->with('success', __('Options updated successfully!'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! isset($errorCond)) {
|
} catch (OptionNotFoundException | \Exception $ex) {
|
||||||
$request->session()->flash('success', __('Settings saved successfully!'));
|
|
||||||
}
|
return redirect()
|
||||||
} else {
|
->back()
|
||||||
$request->session()->flash('error', __('You do not have permission to update this resource.'));
|
->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)
|
public function saveGameIntegration(Request $request)
|
||||||
{
|
{
|
||||||
$supportedGames = [
|
try {
|
||||||
'RUST',
|
|
||||||
'MINECRAFT',
|
|
||||||
'SE',
|
|
||||||
'GMOD'
|
|
||||||
];
|
|
||||||
|
|
||||||
if (!is_null($request->gamePref) && in_array($request->gamePref, $supportedGames))
|
$this->configurationService->saveGameIntegration($request->gamePref);
|
||||||
{
|
return redirect()
|
||||||
Options::changeOption('currentGame', $request->gamePref);
|
->back()
|
||||||
$request->session()->flash('success', __('Updated current game.'));
|
->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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Facades\IP;
|
use App\Facades\IP;
|
||||||
use App\Http\Requests\ProfileSave;
|
use App\Http\Requests\ProfileSave;
|
||||||
|
use App\Services\ProfileService;
|
||||||
use App\User;
|
use App\User;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
@ -31,6 +32,12 @@ use Spatie\Permission\Models\Role;
|
||||||
|
|
||||||
class ProfileController extends Controller
|
class ProfileController extends Controller
|
||||||
{
|
{
|
||||||
|
private $profileService;
|
||||||
|
|
||||||
|
public function __construct(ProfileService $profileService) {
|
||||||
|
$this->profileService = $profileService;
|
||||||
|
}
|
||||||
|
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
return view('dashboard.user.directory')
|
return view('dashboard.user.directory')
|
||||||
|
@ -39,6 +46,7 @@ class ProfileController extends Controller
|
||||||
|
|
||||||
public function showProfile()
|
public function showProfile()
|
||||||
{
|
{
|
||||||
|
// TODO: Come up with cleaner social media solution, e.g. social media object
|
||||||
$socialLinks = Auth::user()->profile->socialLinks ?? '[]';
|
$socialLinks = Auth::user()->profile->socialLinks ?? '[]';
|
||||||
$socialMediaProfiles = json_decode($socialLinks, true);
|
$socialMediaProfiles = json_decode($socialLinks, true);
|
||||||
|
|
||||||
|
@ -52,8 +60,7 @@ class ProfileController extends Controller
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Route model binding
|
public function showSingleProfile(User $user)
|
||||||
public function showSingleProfile(Request $request, User $user)
|
|
||||||
{
|
{
|
||||||
$socialMediaProfiles = json_decode($user->profile->socialLinks, true);
|
$socialMediaProfiles = json_decode($user->profile->socialLinks, true);
|
||||||
$createdDate = Carbon::parse($user->created_at);
|
$createdDate = Carbon::parse($user->created_at);
|
||||||
|
@ -102,36 +109,9 @@ class ProfileController extends Controller
|
||||||
|
|
||||||
public function saveProfile(ProfileSave $request)
|
public function saveProfile(ProfileSave $request)
|
||||||
{
|
{
|
||||||
$profile = User::find(Auth::user()->id)->profile;
|
$this->profileService->updateProfile(Auth::user()->id, $request);
|
||||||
$social = [];
|
return redirect()
|
||||||
|
->back()
|
||||||
if (! is_null($profile)) {
|
->with('success', __('Profile updated.'));
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Facades\Options;
|
use App\Facades\Options;
|
||||||
use App\Http\Requests\SaveSecuritySettings;
|
use App\Http\Requests\SaveSecuritySettings;
|
||||||
|
use App\Services\SecuritySettingsService;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
@ -11,38 +12,24 @@ use function PHPSTORM_META\map;
|
||||||
|
|
||||||
class SecuritySettingsController extends Controller
|
class SecuritySettingsController extends Controller
|
||||||
{
|
{
|
||||||
|
private $securityService;
|
||||||
|
|
||||||
|
public function __construct(SecuritySettingsService $securityService) {
|
||||||
|
$this->securityService = $securityService;
|
||||||
|
}
|
||||||
|
|
||||||
public function save(SaveSecuritySettings $request)
|
public function save(SaveSecuritySettings $request)
|
||||||
{
|
{
|
||||||
$validPolicies = [
|
$this->securityService->save($request->secPolicy, [
|
||||||
'off',
|
'graceperiod' => $request->graceperiod,
|
||||||
'low',
|
'pwExpiry' => $request->pwExpiry,
|
||||||
'medium',
|
'enforce2fa' => $request->enforce2fa,
|
||||||
'high'
|
'requirePMC' => $request->requirePMC
|
||||||
];
|
]);
|
||||||
|
|
||||||
if (in_array($request->secPolicy, $validPolicies))
|
return redirect()
|
||||||
{
|
->back()
|
||||||
Options::changeOption('pw_security_policy', $request->secPolicy);
|
->with('success', __('Settings saved.'));
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,13 +21,18 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
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\EditTeamRequest;
|
||||||
use App\Http\Requests\NewTeamRequest;
|
use App\Http\Requests\NewTeamRequest;
|
||||||
use App\Http\Requests\SendInviteRequest;
|
use App\Http\Requests\SendInviteRequest;
|
||||||
use App\Mail\InviteToTeam;
|
use App\Mail\InviteToTeam;
|
||||||
|
use App\Services\TeamService;
|
||||||
use App\Team;
|
use App\Team;
|
||||||
use App\User;
|
use App\User;
|
||||||
use App\Vacancy;
|
use App\Vacancy;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Mail;
|
use Illuminate\Support\Facades\Mail;
|
||||||
|
@ -37,10 +42,15 @@ use Mpociot\Teamwork\TeamInvite;
|
||||||
|
|
||||||
class TeamController extends Controller
|
class TeamController extends Controller
|
||||||
{
|
{
|
||||||
|
private $teamService;
|
||||||
|
|
||||||
|
public function __construct(TeamService $teamService) {
|
||||||
|
$this->teamService = $teamService;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display a listing of the resource.
|
* 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()
|
public function index()
|
||||||
{
|
{
|
||||||
|
@ -56,23 +66,17 @@ class TeamController extends Controller
|
||||||
* Store a newly created resource in storage.
|
* Store a newly created resource in storage.
|
||||||
*
|
*
|
||||||
* @param NewTeamRequest $request
|
* @param NewTeamRequest $request
|
||||||
* @return \Illuminate\Http\RedirectResponse
|
* @return RedirectResponse
|
||||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||||
*/
|
*/
|
||||||
public function store(NewTeamRequest $request)
|
public function store(NewTeamRequest $request)
|
||||||
{
|
{
|
||||||
$this->authorize('create', Team::class);
|
$this->authorize('create', Team::class);
|
||||||
|
$this->teamService->createTeam($request->teamName, Auth::user()->id);
|
||||||
|
|
||||||
$team = Team::create([
|
return redirect()
|
||||||
'name' => $request->teamName,
|
->back()
|
||||||
'owner_id' => Auth::user()->id,
|
->with('success', __('Team successfully created.'));
|
||||||
]);
|
|
||||||
|
|
||||||
Auth::user()->teams()->attach($team->id);
|
|
||||||
|
|
||||||
$request->session()->flash('success', __('Team successfully created.'));
|
|
||||||
|
|
||||||
return redirect()->back();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -98,21 +102,24 @@ class TeamController extends Controller
|
||||||
*
|
*
|
||||||
* @param EditTeamRequest $request
|
* @param EditTeamRequest $request
|
||||||
* @param Team $team
|
* @param Team $team
|
||||||
* @return \Illuminate\Http\Response
|
* @return RedirectResponse
|
||||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
* @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);
|
$this->authorize('update', $team);
|
||||||
|
$team = $this->teamService->updateTeam($team, $request->teamDescription, $team->joinType);
|
||||||
|
|
||||||
|
|
||||||
$team->description = $request->teamDescription;
|
if ($team) {
|
||||||
$team->openJoin = $request->joinType;
|
return redirect()
|
||||||
|
->to(route('teams.index'))
|
||||||
|
->with('success', __('Team updated.'));
|
||||||
|
}
|
||||||
|
|
||||||
$team->save();
|
return redirect()
|
||||||
$request->session()->flash('success', __('Team edited successfully.'));
|
->back()
|
||||||
|
->with('error', __('An error ocurred while trying to update this team.'));
|
||||||
return redirect()->to(route('teams.index'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -126,68 +133,45 @@ class TeamController extends Controller
|
||||||
// wip
|
// wip
|
||||||
}
|
}
|
||||||
|
|
||||||
public function invite(SendInviteRequest $request, Team $team): \Illuminate\Http\RedirectResponse
|
public function invite(SendInviteRequest $request, Team $team): RedirectResponse
|
||||||
{
|
{
|
||||||
$this->authorize('invite', $team);
|
$this->authorize('invite', $team);
|
||||||
|
|
||||||
$user = User::findOrFail($request->user);
|
try {
|
||||||
|
|
||||||
if (! $team->openJoin) {
|
$this->teamService->inviteUser($team, $request->user);
|
||||||
if (! Teamwork::hasPendingInvite($user->email, $team)) {
|
|
||||||
Teamwork::inviteToTeam($user, $team, function (TeamInvite $invite) use ($user) {
|
|
||||||
Mail::to($user)->send(new InviteToTeam($invite));
|
|
||||||
});
|
|
||||||
|
|
||||||
$request->session()->flash('success', __('Invite sent! They can now accept or deny it.'));
|
return redirect()
|
||||||
} else {
|
->back()
|
||||||
$request->session()->flash('error', __('This user has already been invited.'));
|
->with('success', __('User invited successfully!'));
|
||||||
}
|
|
||||||
} else {
|
} catch (UserAlreadyInvitedException | PublicTeamInviteException $ex) {
|
||||||
$request->session()->flash('error', __('You can\'t invite users to public teams.'));
|
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) {
|
try {
|
||||||
case 'accept':
|
|
||||||
|
|
||||||
$invite = Teamwork::getInviteFromAcceptToken($token);
|
$this->teamService->processInvite(Auth::user(), $action, $token);
|
||||||
|
|
||||||
if ($invite && $invite->user->is(Auth::user())) {
|
return redirect()
|
||||||
Teamwork::acceptInvite($invite);
|
->to(route('teams.index'))
|
||||||
$request->session()->flash('success', __('Invite accepted! You have now joined :teamName.', ['teamName' => $invite->team->name]));
|
->with('success', __('Invite processed successfully!'));
|
||||||
} else {
|
|
||||||
$request->session()->flash('error', __('Invalid or expired invite URL.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
} catch (InvalidInviteException $e) {
|
||||||
|
|
||||||
case 'deny':
|
return redirect()
|
||||||
|
->back()
|
||||||
$invite = Teamwork::getInviteFromDenyToken($token);
|
->with('error', $e->getMessage());
|
||||||
|
|
||||||
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.');
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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);
|
$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
|
// 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);
|
$this->authorize('update', $team);
|
||||||
|
$message = $this->teamService->updateVacancies($team, $request->assocVacancies);
|
||||||
|
|
||||||
// P.S. To future developers
|
return redirect()
|
||||||
// This method gave me a lot of trouble lol. It's hard to write code when you're half asleep.
|
->back()
|
||||||
// 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.
|
->with('success', $message);
|
||||||
|
|
||||||
$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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
// 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\TeamFile;
|
||||||
use App\Http\Requests\UploadFileRequest;
|
use App\Http\Requests\UploadFileRequest;
|
||||||
|
|
||||||
|
@ -24,11 +26,16 @@ use Illuminate\Http\Response;
|
||||||
|
|
||||||
class TeamFileController extends Controller
|
class TeamFileController extends Controller
|
||||||
{
|
{
|
||||||
|
private $fileService;
|
||||||
|
|
||||||
|
public function __construct(TeamFileService $fileService) {
|
||||||
|
$this->fileService = $fileService;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display a listing of the resource.
|
* Display a listing of the resource.
|
||||||
*
|
*
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @return Application|Factory|View|Response
|
|
||||||
*/
|
*/
|
||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
|
@ -55,33 +62,24 @@ class TeamFileController extends Controller
|
||||||
{
|
{
|
||||||
$this->authorize('store', TeamFile::class);
|
$this->authorize('store', TeamFile::class);
|
||||||
|
|
||||||
$upload = $request->file('file');
|
try {
|
||||||
|
$caption = $request->caption;
|
||||||
|
$description = $request->description;
|
||||||
|
|
||||||
$file = $upload->store('uploads');
|
$this->fileService->addFile($request->file('file'), Auth::user()->id, Auth::user()->currentTeam->id, $caption, $description);
|
||||||
$originalFileName = $upload->getClientOriginalName();
|
|
||||||
$originalFileExtension = $upload->extension();
|
|
||||||
$originalFileSize = $upload->getSize();
|
|
||||||
|
|
||||||
$fileEntry = TeamFile::create([
|
return redirect()
|
||||||
'uploaded_by' => Auth::user()->id,
|
->back()
|
||||||
'team_id' => Auth::user()->currentTeam->id,
|
->with('success', __('File uploaded successfully.'));
|
||||||
'name' => $originalFileName,
|
|
||||||
'caption' => $request->caption,
|
} catch (FileUploadException $uploadException) {
|
||||||
'description' => $request->description,
|
|
||||||
'fs_location' => $file,
|
return redirect()
|
||||||
'extension' => $originalFileExtension,
|
->back()
|
||||||
'size' => $originalFileSize
|
->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.
|
* Remove the specified resource from storage.
|
||||||
*
|
*
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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,
|
||||||
|
])));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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!');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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.");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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.';
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue