Merge branch 'develop'

This commit is contained in:
Miguel Nogueira 2022-02-24 18:50:42 +00:00
commit 21cd7c2888
671 changed files with 24460 additions and 99768 deletions

0
.editorconfig Normal file → Executable file
View File

26
.env.example Normal file → Executable file
View File

@ -4,13 +4,27 @@ APP_KEY=
APP_DEBUG=true APP_DEBUG=true
APP_URL=http://localhost APP_URL=http://localhost
APP_LOGO="https://www.raspberrypi.org/app/uploads/2020/05/Raspberry-Pi-OS-downloads-image-150x150-1.png" APP_LOGO="https://www.raspberrypi.org/app/uploads/2020/05/Raspberry-Pi-OS-downloads-image-150x150-1.png"
APP_AUTH_BANNER=""
APP_SITEHOMEPAGE="" APP_SITEHOMEPAGE=""
# This can be your main homepage, other than this site itself API_PREFIX="api"
LOG_CHANNEL=stack # The auth banner is a relative path
# Hides IP addresses
HIDE_IPS=false
# Disables certain features for security purposes while running an open authentication system
# Enable only for demonostration purposes
DEMO_MODE=false
# Forces ssl connections even if the environment is set to "local".
# Void if env is production.
NONPROD_FORCE_SECURE=false
LOG_CHANNEL=daily
DB_CONNECTION=mysql DB_CONNECTION=mysql
DB_HOST=127.0.0.1 DB_HOST=z
DB_PORT=3306 DB_PORT=3306
DB_DATABASE=laravel DB_DATABASE=laravel
DB_USERNAME=root DB_USERNAME=root
@ -29,7 +43,7 @@ IPGEO_API_URL="https://api.ipgeolocation.io/ipgeo"
ARCANEDEV_LOGVIEWER_MIDDLEWARE=web,auth,can:admin.maintenance.logs.view ARCANEDEV_LOGVIEWER_MIDDLEWARE=web,auth,can:admin.maintenance.logs.view
RELEASE=staffmanagement@0.6.1 RELEASE=0.6.2
SLACK_INTEGRATION_WEBHOOK= SLACK_INTEGRATION_WEBHOOK=
@ -62,6 +76,10 @@ PUSHER_APP_KEY=
PUSHER_APP_SECRET= PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1 PUSHER_APP_CLUSTER=mt1
BEAMS_INSTANCE_ID=
BEAMS_SECRET_KEY=
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

0
.gitattributes vendored Normal file → Executable file
View File

0
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file → Executable file
View File

0
.github/ISSUE_TEMPLATE/feature_request.md vendored Normal file → Executable file
View File

0
.gitignore vendored Normal file → Executable file
View File

139
.idea/hrm-mcserver.iml generated
View File

@ -1,139 +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$/tests" isTestSource="true" packagePrefix="Tests\" />
<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/barryvdh/laravel-debugbar" />
<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/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/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-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/code-unit-reverse-lookup" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/comparator" />
<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/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-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/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" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="LaravelPluginSettings">
<option name="pluginEnabled" value="true" />
</component>
</project>

6
.idea/misc.xml generated
View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
</project>

8
.idea/modules.xml generated
View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/hrm-mcserver.iml" filepath="$PROJECT_DIR$/.idea/hrm-mcserver.iml" />
</modules>
</component>
</project>

152
.idea/php.xml generated
View File

@ -1,152 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PhpIncludePathManager">
<include_path>
<path value="$PROJECT_DIR$/vendor/phar-io/manifest" />
<path value="$PROJECT_DIR$/vendor/phpspec/prophecy" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-token-stream" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-text-template" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-timer" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-file-iterator" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-code-coverage" />
<path value="$PROJECT_DIR$/vendor/symfony/error-handler" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php73" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php72" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-mbstring" />
<path value="$PROJECT_DIR$/vendor/symfony/http-foundation" />
<path value="$PROJECT_DIR$/vendor/symfony/http-kernel" />
<path value="$PROJECT_DIR$/vendor/symfony/finder" />
<path value="$PROJECT_DIR$/vendor/symfony/css-selector" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-ctype" />
<path value="$PROJECT_DIR$/vendor/symfony/console" />
<path value="$PROJECT_DIR$/vendor/symfony/translation-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/process" />
<path value="$PROJECT_DIR$/vendor/symfony/mime" />
<path value="$PROJECT_DIR$/vendor/symfony/routing" />
<path value="$PROJECT_DIR$/vendor/symfony/service-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/translation" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-iconv" />
<path value="$PROJECT_DIR$/vendor/symfony/event-dispatcher-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-idn" />
<path value="$PROJECT_DIR$/vendor/symfony/event-dispatcher" />
<path value="$PROJECT_DIR$/vendor/theseer/tokenizer" />
<path value="$PROJECT_DIR$/vendor/doctrine/inflector" />
<path value="$PROJECT_DIR$/vendor/doctrine/instantiator" />
<path value="$PROJECT_DIR$/vendor/doctrine/lexer" />
<path value="$PROJECT_DIR$/vendor/hamcrest/hamcrest-php" />
<path value="$PROJECT_DIR$/vendor/fideloper/proxy" />
<path value="$PROJECT_DIR$/vendor/fruitcake/laravel-cors" />
<path value="$PROJECT_DIR$/vendor/phpoption/phpoption" />
<path value="$PROJECT_DIR$/vendor/ralouphie/getallheaders" />
<path value="$PROJECT_DIR$/vendor/sebastian/version" />
<path value="$PROJECT_DIR$/vendor/sebastian/exporter" />
<path value="$PROJECT_DIR$/vendor/sebastian/object-enumerator" />
<path value="$PROJECT_DIR$/vendor/sebastian/global-state" />
<path value="$PROJECT_DIR$/vendor/sebastian/object-reflector" />
<path value="$PROJECT_DIR$/vendor/sebastian/diff" />
<path value="$PROJECT_DIR$/vendor/sebastian/comparator" />
<path value="$PROJECT_DIR$/vendor/sebastian/recursion-context" />
<path value="$PROJECT_DIR$/vendor/sebastian/resource-operations" />
<path value="$PROJECT_DIR$/vendor/sebastian/code-unit-reverse-lookup" />
<path value="$PROJECT_DIR$/vendor/sebastian/environment" />
<path value="$PROJECT_DIR$/vendor/sebastian/type" />
<path value="$PROJECT_DIR$/vendor/webmozart/assert" />
<path value="$PROJECT_DIR$/vendor/fzaninotto/faker" />
<path value="$PROJECT_DIR$/vendor/guzzlehttp/promises" />
<path value="$PROJECT_DIR$/vendor/guzzlehttp/psr7" />
<path value="$PROJECT_DIR$/vendor/guzzlehttp/guzzle" />
<path value="$PROJECT_DIR$/vendor/nunomaduro/collision" />
<path value="$PROJECT_DIR$/vendor/swiftmailer/swiftmailer" />
<path value="$PROJECT_DIR$/vendor/tijsverkoyen/css-to-inline-styles" />
<path value="$PROJECT_DIR$/vendor/dragonmantank/cron-expression" />
<path value="$PROJECT_DIR$/vendor/phpdocumentor/type-resolver" />
<path value="$PROJECT_DIR$/vendor/phpdocumentor/reflection-docblock" />
<path value="$PROJECT_DIR$/vendor/phpdocumentor/reflection-common" />
<path value="$PROJECT_DIR$/vendor/composer" />
<path value="$PROJECT_DIR$/vendor/psy/psysh" />
<path value="$PROJECT_DIR$/vendor/nesbot/carbon" />
<path value="$PROJECT_DIR$/vendor/phpunit/phpunit" />
<path value="$PROJECT_DIR$/vendor/filp/whoops" />
<path value="$PROJECT_DIR$/vendor/nikic/php-parser" />
<path value="$PROJECT_DIR$/vendor/opis/closure" />
<path value="$PROJECT_DIR$/vendor/league/commonmark" />
<path value="$PROJECT_DIR$/vendor/voku/portable-ascii" />
<path value="$PROJECT_DIR$/vendor/asm89/stack-cors" />
<path value="$PROJECT_DIR$/vendor/brick/math" />
<path value="$PROJECT_DIR$/vendor/symfony/var-dumper" />
<path value="$PROJECT_DIR$/vendor/facade/ignition" />
<path value="$PROJECT_DIR$/vendor/psr/http-message" />
<path value="$PROJECT_DIR$/vendor/facade/ignition-contracts" />
<path value="$PROJECT_DIR$/vendor/psr/simple-cache" />
<path value="$PROJECT_DIR$/vendor/facade/flare-client-php" />
<path value="$PROJECT_DIR$/vendor/psr/event-dispatcher" />
<path value="$PROJECT_DIR$/vendor/league/flysystem" />
<path value="$PROJECT_DIR$/vendor/psr/log" />
<path value="$PROJECT_DIR$/vendor/ramsey/uuid" />
<path value="$PROJECT_DIR$/vendor/psr/container" />
<path value="$PROJECT_DIR$/vendor/ramsey/collection" />
<path value="$PROJECT_DIR$/vendor/scrivo/highlight.php" />
<path value="$PROJECT_DIR$/vendor/vlucas/phpdotenv" />
<path value="$PROJECT_DIR$/vendor/dnoegel/php-xdg-base-dir" />
<path value="$PROJECT_DIR$/vendor/egulias/email-validator" />
<path value="$PROJECT_DIR$/vendor/laravel/framework" />
<path value="$PROJECT_DIR$/vendor/laravel/tinker" />
<path value="$PROJECT_DIR$/vendor/mockery/mockery" />
<path value="$PROJECT_DIR$/vendor/monolog/monolog" />
<path value="$PROJECT_DIR$/vendor/myclabs/deep-copy" />
<path value="$PROJECT_DIR$/vendor/phar-io/version" />
<path value="$PROJECT_DIR$/vendor/laravel/ui" />
<path value="$PROJECT_DIR$/vendor/almasaeed2010/adminlte" />
<path value="$PROJECT_DIR$/vendor/jeroennoten/laravel-adminlte" />
<path value="$PROJECT_DIR$/vendor/doctrine/dbal" />
<path value="$PROJECT_DIR$/vendor/doctrine/cache" />
<path value="$PROJECT_DIR$/vendor/doctrine/event-manager" />
<path value="$PROJECT_DIR$/vendor/barryvdh/laravel-debugbar" />
<path value="$PROJECT_DIR$/vendor/maximebf/debugbar" />
<path value="$PROJECT_DIR$/vendor/symfony/debug" />
<path value="$PROJECT_DIR$/vendor/sentry/sentry-laravel" />
<path value="$PROJECT_DIR$/vendor/sentry/sentry" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-uuid" />
<path value="$PROJECT_DIR$/vendor/symfony/options-resolver" />
<path value="$PROJECT_DIR$/vendor/psr/http-client" />
<path value="$PROJECT_DIR$/vendor/psr/http-factory" />
<path value="$PROJECT_DIR$/vendor/php-http/message-factory" />
<path value="$PROJECT_DIR$/vendor/php-http/httplug" />
<path value="$PROJECT_DIR$/vendor/php-http/client-common" />
<path value="$PROJECT_DIR$/vendor/php-http/discovery" />
<path value="$PROJECT_DIR$/vendor/php-http/message" />
<path value="$PROJECT_DIR$/vendor/php-http/promise" />
<path value="$PROJECT_DIR$/vendor/php-http/guzzle6-adapter" />
<path value="$PROJECT_DIR$/vendor/paragonie/random_compat" />
<path value="$PROJECT_DIR$/vendor/clue/stream-filter" />
<path value="$PROJECT_DIR$/vendor/jean85/pretty-package-versions" />
<path value="$PROJECT_DIR$/vendor/http-interop/http-factory-guzzle" />
<path value="$PROJECT_DIR$/vendor/spatie/laravel-permission" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-normalizer" />
<path value="$PROJECT_DIR$/vendor/symfony/deprecation-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/string" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-grapheme" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php80" />
<path value="$PROJECT_DIR$/vendor/bacon/bacon-qr-code" />
<path value="$PROJECT_DIR$/vendor/dasprid/enum" />
<path value="$PROJECT_DIR$/vendor/geo-sot/laravel-env-editor" />
<path value="$PROJECT_DIR$/vendor/laravel/slack-notification-channel" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php70" />
<path value="$PROJECT_DIR$/vendor/pragmarx/google2fa-laravel" />
<path value="$PROJECT_DIR$/vendor/pragmarx/google2fa" />
<path value="$PROJECT_DIR$/vendor/pragmarx/google2fa-qrcode" />
<path value="$PROJECT_DIR$/vendor/arcanedev/log-viewer" />
<path value="$PROJECT_DIR$/vendor/arcanedev/support" />
<path value="$PROJECT_DIR$/vendor/paragonie/constant_time_encoding" />
<path value="$PROJECT_DIR$/vendor/graham-campbell/markdown" />
<path value="$PROJECT_DIR$/vendor/league/mime-type-detection" />
<path value="$PROJECT_DIR$/vendor/mcamara/laravel-localization" />
</include_path>
</component>
<component name="PhpProjectSharedConfiguration" php_language_level="7.2" />
<component name="PhpUnit">
<phpunit_settings>
<PhpUnitSettings configuration_file_path="$PROJECT_DIR$/phpunit.xml" custom_loader_path="$PROJECT_DIR$/vendor/autoload.php" use_configuration_file="true" />
</phpunit_settings>
</component>
</project>

10
.idea/phpunit.xml generated
View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PHPUnit">
<option name="directories">
<list>
<option value="$PROJECT_DIR$/tests" />
</list>
</option>
</component>
</project>

6
.idea/vcs.xml generated
View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<phive xmlns="https://phar.io/phive">
<phar name="phpunit" version="^9.2.5" installed="9.2.5" location="./tools/phpunit" copy="false"/>
<phar name="php-cs-fixer" version="^2.16.4" installed="2.16.4" location="./tools/php-cs-fixer" copy="false"/>
</phive>

24
.styleci.yml Normal file → Executable file
View File

@ -1,13 +1,13 @@
php: risky: false
preset: laravel version: 7
disabled: preset: recommended
- unused_use finder:
finder: exclude:
- "modules"
- "node_modules"
- "storage"
- "vendor"
name: "*.php"
not-name: not-name:
- index.php - "*.blade.php"
- server.php - "_ide_helper.php"
js:
finder:
not-name:
- webpack.mix.js
css: true

22
.vscode/launch.json vendored
View File

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

1
CODEOWNERS Executable file
View File

@ -0,0 +1 @@
* @miguel456

0
CODE_OF_CONDUCT.md Normal file → Executable file
View File

0
CONTRIBUTING.md Normal file → Executable file
View File

0
LICENSE Normal file → Executable file
View File

0
Procfile Normal file → Executable file
View File

19
README.md Normal file → Executable file
View File

@ -1,5 +1,5 @@
# RB Recruiter v 0.6.2 [![Crowdin](https://badges.crowdin.net/raspberry-staff-manager/localized.svg)](https://crowdin.com/project/raspberry-staff-manager) # RB Recruiter v 0.7.1 [![Crowdin](https://badges.crowdin.net/raspberry-staff-manager/localized.svg)](https://crowdin.com/project/raspberry-staff-manager) [![Better Uptime Badge](https://betteruptime.com/status-badges/v1/monitor/9n53.svg)](https://betteruptime.com/?utm_source=status_badge)
## The quick and pain-free form management solution for communities ## The quick and pain-free form management solution for communities
Have you ever gotten tired of managing your Minecraft server/network's applications through Discord (or anything else) and having to scroll through hundreds of new messages just to find that one applicant's username? Have you ever gotten tired of managing your Minecraft server/network's applications through Discord (or anything else) and having to scroll through hundreds of new messages just to find that one applicant's username?
@ -24,7 +24,7 @@ Wish you had a better application managemet strategy? Well, then Raspberry Teams
- Termination - Has a staff member met their untimely demise? Terminate them. This will strip their permissions and roles. - Termination - Has a staff member met their untimely demise? Terminate them. This will strip their permissions and roles.
- Controllable permissions - Every user has permissions! Control who has access to what (You can skip the application process and add staff members directly here). - Controllable permissions - Every user has permissions! Control who has access to what (You can skip the application process and add staff members directly here).
- Ban system - Having trouble with pesky spammers? Ban them! This will publicly shame their profile and keep them from signing up or logging in. - Ban system - Having trouble with pesky spammers? Ban them! This will publicly shame their profile and keep them from signing up or logging in.
- Notifications: Notifies slack and email primarily - Notifications: Notifies slack and email primarily (Slack notifications currently broken)
And many more features! And many more features!
@ -42,11 +42,13 @@ Many other features are currently planned for this app, such as:
# Technical overview # Technical overview
Tech stack: Tech stack:
- [Laravel 7](https://laravel.com/) - [Laravel 8](https://laravel.com/)
- Eloquent ORM - [Eloquent ORM](https://laravel.com/docs/5.0/eloquent)
- AdminLTE / Bootstrap 4 - [AdminLTE](https://adminlte.io/) /
- jQuery / Plain Javascript - [Bootstrap 4](https://getbootstrap.com/docs/4.0/getting-started/introduction/)
- vueJS (in the future) - [jQuery](https://jquery.com/)
- [Bootstrap 4](https://getbootstrap.com/)
- [Icons by FontAwesome](https://fontawesome.com/)
# Stability # Stability
@ -63,13 +65,12 @@ Tech stack:
# Software Requirements # Software Requirements
- ``composer`` (min version: 1.8.4) - ``composer`` (min version: 1.8.4)
- ``npm`` (tested w/ v 5.8.0) - ``npm`` (tested w/ v 5.8.0)
- ``php`` (required PHP 7 or newer - lower versions unsupported!) - ``php`` (required PHP 8 or newer - lower versions unsupported!)
# PHP Extension Requirements # PHP Extension Requirements
- JSON - JSON
- Curl (highly recommended) - Curl (highly recommended)
- Image Magick (imagick) for 2FA support
# Installation # Installation

105
SECURITY.md Normal file → Executable file
View File

@ -1,6 +1,6 @@
# Security Policy # Security Policy
## Supported Versions ## Supported Software Versions
The following versions are currently supported: The following versions are currently supported:
@ -8,12 +8,107 @@ The following versions are currently supported:
| ------- | ------------------ | | ------- | ------------------ |
| 0.1.x | :x: | | 0.1.x | :x: |
| 0.5.x | :x: | | 0.5.x | :x: |
| 0.6.x | :white_check_mark: | | 0.6.x | :x: |
| 0.7.0 | :x: |
| 0.7.1 | :white_check_mark: |
## Supported PHP Versions
| Version | Supported |
| ------- | ------------------ |
| 5.x | :x: |
| 7.0 | :x: |
| 7.1 | :x: |
| 7.2 | :x: |
| 7.3 | :x: |
| 7.4 | :white_check_mark: |
| 8.0 | :white_check_mark: |
## Supported Operating Systems
| Name | Supported |
| ------- | ------------------ |
| Windows NT | :x: |
| MacOS | Docker only |
| Ubuntu 22.10 | :white_check_mark: |
| Other Linux distros | :white_check_mark: |
## Reporting a Vulnerability ## Reporting a Vulnerability
To securely report a vulnerability, you may send me an email directly containing the details of said vulnerability: ``me@nogueira.codes``. If you found a critical vunlerability, please do not use the Issues tab to report it.
You may optionally encrypt your message with my [public PGP key](http://pool.sks-keyservers.net/pks/lookup?op=get&search=0x48DF709E7405702B). Instead, please forward an encrypted vulnerability report to ``security@webvokestudio.pt``, so that we have time to open a security advisory and work on a fix.
Unencrypted reports will be ignored.
Use this free [online encryption tool](https://www.igolder.com/pgp/encryption/) if you don't know how to use PGP on your desktop. Use this public PGP key to encrypt your message:
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBF/NUL8BEAC7njBq5IbB80qS8rGaAw+DM9wCTiZBiZu0oLXh6oW/E6NOKX5D
Gd8tcW2auqcO1syBUnla4E8t+3fuJQtee3lgszLKRKACYOqZPIYNBUHbw3VBNhNZ
WESbKh9JxlLJql6fHMfpr3wcTCxLwFGcRln1EPKAMBsxB0YJDeorXuoCTY4+NhGM
8O2aS8Tbq+HxSkhz1p0DdOuaM9C/kQJkOth6sdsBiBE/OsyzPK/yti91WM9fB/Fg
QuYyzFD5OL6H+PKPas/4ndIfXSnSCeqm4gScUBeLReeqQd0o96ALSnOxMz9Qit4j
BYhKxfZnAFiKWeBPZEJY12cu2gv3plkdE89mqJveRUCQ+dfVX471iCW+/YSRGU83
9PG2a1Dalsx46lScHm2RlrXFhUhpVND0y7VBHpKiUJCoykhGbIAr429dITBw7jGa
FAKpJgm2wWRAHeokrQ9l+NTUosTwRoYeWF1tKElKT1sjF5zuWFxOekzGXBtzcSUH
HQrUfzOQTMUciSZ4CjZjDNL1yYEwUOTr4IzNOj/jmKCo/Dbsix9V1LbTPjK8vP4l
jwLOL2dG0jpQRd5FRBWPukpF6LS25vQv0bMRZdsCfh/eOlMldfdiGpx8fN6dkxvm
H0TBdZklp3U91u+ooVIeF3Q1NWmB/tvNJOfFnerHOpDBTy/q5fpT6QaMkwARAQAB
tH5NaWd1ZWwgTm9ndWVpcmEgKElnbm9yZSBhbGwgbXkgb3RoZXIga2V5cyB1bmRl
ciBteSBuYW1lIGFuZCBlbWFpbCAobG9zdCB0aGUgcHJpdmF0ZSBrZXlzKS4pIDxt
aWd1ZWw0NTZAc3BhY2VqZXdlbC1ob3N0aW5nLmNvbT6JAlQEEwEKAD4WIQStTNAz
Se1uoPMcdjTkUwCM6Yt4igUCX81QvwIbLwUJC0c1AAULCQgHAgYVCgkICwIEFgID
AQIeAQIXgAAKCRDkUwCM6Yt4ikBCD/9sPlUItGT1tlFgdSdbLdPsdcb/lpK269zH
0OoMSSKGKNL6RAY2R6++qdBRCC9jdzQFtJO7epYB/mWXnZqYcWemTwaKEW5eHr5E
BFQuSiRREbNLkzCAWWqqaQnCuve4q9DXsWd5NiHcrmNgnSY6Zk6y+GdpQNyzEshf
aCcnkaySIFgV33DDsa6s4zUDZulQbmeg1mfwyP0tv+CZ6Bbk72DxX/A10x00ddv1
arGuLfctIydEcG7mAeAZBeCJEucpFuQbaWybZS2RX3/e6LCOWnt/ZHjqcFln36yq
9GIx28BnElkpxwDV66R0EUpm3cxZ3Kv7bDqm5E2ZzCybOF+a8y3d15qIyrSHvGdr
skVBmRi2zLxCYBEcTocHH0rG6UNTk55wvqMepAnnp12XMQoqCrmw9zUv92qAv+01
lMv9H1AM8jUdkk5H+SgZYFcPL5/4cBEc/ylRKIuiNcKZAfppjdVnslXsgahmQSep
PgcWPwwOujqYLfn/FPKRQuH1GUuNv0UzO6KUQy0gWkDYKrnks4n6lNWXiZY/HU+W
KCgSWRCICHW8JJ8dg97vk6IurtqpG/LUMb/E3mAzPNIyuhK2r1wGOsb83iVIvF5M
3KyAFhaqiwnudt7UXYN3A1we+rm/y/nBNx0GUuC8VJaLz1p9zqEiT1an0vivC92x
m/jSicdIprkCDQRfzVC/ARAAq/tqXnL7JSt3ZaePLP86hE0gOK8mY1F5X9sRQCDT
adqyfnwNIzXGhUFfSdIO2LKnrdZUFXZG4YeK6JG0fu8KdELs+2UlIx0KAokrwfK3
zWHITN6+lvSz3ubVQMYCEgUK9eEPwa/XnhL4vEd61kMdM5OxYsBXQYlraWLJ0rOf
ANQEXb0vm0c4RsO8IubOJT7jxPkgfLesIB7i3O3/Hptc54WHs97p22/Y6oNk42UV
cJB/k3kqTCzfBzDcpmF9RyxpfEDUJAn9QWcxH2UC3kHpr9N5Hfv3Kh3bjaGukzn4
sO+fmukqBTsW7cIMO7slbcsCVJN7QC2GcocgjGHXWVF1Gg234exTX7aDBGNq7TeY
h1N/3lVeZom6ANtA3aOO55NuaXI9IGiW7lNnYcTJkTu3eUnfTpg/FkqdSzOpBOFZ
Gyltjf8pMK0vQXbajov5j/wE/bz2eOPKwyJc0u3B/JqfE/EbPnx5dng7/mgv+x7u
GSMOvovc4SrSZoDp5IWDk1L5s2CHNB88rziPFLB0Tkr6N9MnEWhv4wL7wfNdQ00s
xJpGlrc0nSdGUmHFCExRUb7VIJ0Yfjsj3Xy4PCBXsDTYWusAbp+rbFycRMlGiANj
cQYGbEYMyUqL2upg7kKzrFW45TyGpkE1QHkm0Od7JxGqgfiie+tcG+EUQKBMJsIN
tLMAEQEAAYkEcgQYAQoAJhYhBK1M0DNJ7W6g8xx2NORTAIzpi3iKBQJfzVC/Ahsu
BQkLRzUAAkAJEORTAIzpi3iKwXQgBBkBCgAdFiEEqncyNKRovrMCcN8NLPYbglMW
xqAFAl/NUL8ACgkQLPYbglMWxqBmHhAApxjJmpdifzI23AdRAcO4oChE6ho0a85+
nGXr0qPMySb6/1GiYML3WKNzK02wS7V8A1VlnYvdd3SdmJh9qhH4NAgs4SXxpenp
hEw48M+h7LHJlqkrftHC540eyNlyVyN31kNhibhFrF5eZ9BcIwERrAR4MClG1gwI
bJqiAk5wlYP6YmJHVI+4DKjN+dMCn301hXUCTPoAdHfKTztMe9qhmbgZcyeOkZOr
Ya/7D1AqEaQhfcfOL4mBUdH86GVw9o58Ock7baQHoYlIrwgvW4qRvBjT+mQueHld
J0v/GQM6widHe3vt3HL4vI4vTzoscl8MY4u8YCmvkWGh343G6Ei7m+cQ19GrHSOR
8rMZjHbYaNHstscBJDVx19EZaGc/AV6w38MdkiXbKyyKkZuvAqWkgs+NFRE7lBtI
mNp40aakYNwnn0RP6Wz2LYRWcP5Zm79cUb8++CYK8h5Y5j53tlM8ihF58UhKL6rg
zEk5vYoJzmswO9nA9nNnPscZhZUwfE1x8SVtOOhr8IFlJ3Kh2BpTXJbmqwj71Y6x
pfP6GG4UmPzfLi64ZkvYU1l7nyxlbvJXd8LzBs5oofROiIYKQkrSMEZvPb8XUVNs
cMz434hQFPtwkygjtpA1Io+bx1BV4wtnQ1GsaK2m32hacfF7OvL8iYq92uF//NUm
mhxeh5Jso+/G9xAAqOBen92oFOrg3P+EkmIPRqvcgy4I3Tb22PaB3tGP4+iXpalc
ocMVDzVOLM2pwMwnffnecGv3UotJWdz/80CG8juuwOLXOIsOVAdEbKIXGknwLGyZ
V04+nBQLm+qKhLFigakx43Rmte7Fn3tydlJ+61DHBYtUF8/QRPw/1YN5ybXuZrTj
ci6zUfHxuUczlr8GQhXDy95yvtGlqsGB1roIiCB4Ld7PV3S3YK8+4AjuNFqMQ/+K
KNMsGWFzkXf8NPLe/AneVARsX0f2bV0YYcHYWQCZjrEyKrMcNYuK6uj4Z1Xmwn7f
0gE3kjTPuG562jpHHElWHcZI40uNnYf0DBRyTQWLGqOuAVCDrg8+/gITBW98M3jd
OuPu5RQmtIkF9BFDYl3pdbzJ/4j+StAnRuZMjSSi17okRHFJEL3FZ2LdBim073ra
BCU+tufFOQUtR47mOuIjmRDI0GXjGcs1BcovaVViGisjwB0CSSLgnYIjZH6xsrA1
ICc0bSNBvYpXewPGDQ8AO1k4lNtedDOY/sXr4o9xZVic5eOA740PPrEozpftV1wK
UBxa9dXTy9qaZhcw/x38zVyJTLg4I5sOmG24u/ZPqOblEKyQwFdU4gLyF5CRGwDy
5LmGVc1bTDL8SQXkRG4L/DZVbqgBxBKNonkbnJV2VpjzecfA3O8rHS+uAlQ=
=hbUK
-----END PGP PUBLIC KEY BLOCK-----
You may use [this tool](https://sela.io/pgp-en/) to encrypt your message.

125
app/Absence.php Executable file
View File

@ -0,0 +1,125 @@
<?php
namespace App;
use App\Exceptions\AbsenceNotActionableException;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Absence extends Model
{
use HasFactory;
protected $fillable = [
'requesterID',
'start',
'predicted_end',
'available_assist',
'reason',
'status',
'reviewer',
'reviewed_date'
];
public function requester(): BelongsTo
{
return $this->belongsTo('App\User', 'requesterID', 'id');
}
/**
* Determines whether this model can be setApproved(), setDeclined() or setCancelled()
*
* @param bool $toCancel Switch the check to cancellability check
* @return bool
*/
public function isActionable(bool $toCancel = false): bool
{
if ($toCancel)
{
return in_array($this->getRawOriginal('status'), ['PENDING', 'APPROVED']);
}
return $this->getRawOriginal('status') == 'PENDING';
}
/**
* Sets the Absence's status as approved
*
* @return Absence
* @throws AbsenceNotActionableException
*/
public function setApproved(): Absence
{
if ($this->isActionable())
{
return tap($this)->update([
'status' => 'APPROVED'
]);
}
throw new AbsenceNotActionableException('This absence is not actionable!');
}
/**
* Sets the absence's status as declined
*
* @return Absence
* @throws AbsenceNotActionableException
*/
public function setDeclined(): Absence
{
if ($this->isActionable()) {
return tap($this)->update([
'status' => 'DECLINED'
]);
}
throw new AbsenceNotActionableException('This absence is not actionable!');
}
/**
* Sets the absence's status as cancelled
*
* @return Absence
* @throws AbsenceNotActionableException Thrown when the switch to this status would be invalid
*/
public function setCancelled(): Absence
{
if ($this->isActionable(true)) {
return tap($this)->update([
'status' => 'CANCELLED'
]);
}
throw new AbsenceNotActionableException('This absence is not actionable!');
}
/**
* Sets the absence's status as ended
*
* @return Absence
*/
public function setEnded(): Absence
{
return tap($this)->update([
'status' => 'ENDED'
]);
}
// Look out when retrieving this value;
//If you need the unaltered version of it, either adapt to its formatting or call getRawOriginal()
protected function status(): Attribute {
return Attribute::make(
get: fn($value) => ucfirst(strtolower($value))
);
}
}

39
app/Application.php Normal file → Executable file
View File

@ -1,5 +1,24 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App; namespace App;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
@ -10,12 +29,14 @@ class Application extends Model
'applicantUserID', 'applicantUserID',
'applicantFormResponseID', 'applicantFormResponseID',
'applicationStatus' 'applicationStatus',
]; ];
public function oneoffApplicant()
{
return $this->hasOne('App\OneoffApplicant', 'application_id', 'id');
}
public function user() public function user()
{ {
@ -37,7 +58,6 @@ class Application extends Model
return $this->belongsToMany('App\Vote', 'votes_has_application'); return $this->belongsToMany('App\Vote', 'votes_has_application');
} }
public function comments() public function comments()
{ {
return $this->hasMany('App\Comment', 'applicationID', 'id'); return $this->hasMany('App\Comment', 'applicationID', 'id');
@ -46,8 +66,15 @@ class Application extends Model
public function setStatus($status) public function setStatus($status)
{ {
return $this->update([ return $this->update([
'applicationStatus' => $status 'applicationStatus' => $status,
]); ]);
} }
public function isOneoff()
{
return $this->user->id == 1; // ID 1 is always the ghost
}
} }

23
app/Appointment.php Normal file → Executable file
View File

@ -1,5 +1,24 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App; namespace App;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
@ -13,7 +32,7 @@ class Appointment extends Model
'appointmentStatus', 'appointmentStatus',
'appointmentLocation', 'appointmentLocation',
'meetingNotes', 'meetingNotes',
'userAccepted' 'userAccepted',
]; ];
public function application() public function application()
@ -25,7 +44,7 @@ class Appointment extends Model
public function setStatus($status) public function setStatus($status)
{ {
$this->update([ $this->update([
'appointmentStatus' => $status 'appointmentStatus' => $status,
]); ]);
} }
} }

27
app/Ban.php Normal file → Executable file
View File

@ -1,29 +1,46 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App; namespace App;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
class Ban extends Model class Ban extends Model
{ {
public $fillable = [ public $fillable = [
'userID', 'userID',
'reason', 'reason',
'bannedUntil', 'bannedUntil',
'userAgent', 'isPermanent',
'authorUserID' 'authorUserID',
]; ];
public $dates = [ public $dates = [
'bannedUntil' 'suspendedUntil',
]; ];
public function user() public function user()
{ {
return $this->belongsTo('App\User', 'userID', 'id'); return $this->belongsTo('App\User', 'userID', 'id');
} }
} }

23
app/Comment.php Normal file → Executable file
View File

@ -1,16 +1,34 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App; namespace App;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
class Comment extends Model class Comment extends Model
{ {
protected $fillable = [ protected $fillable = [
'authorID', 'authorID',
'applicationID', 'applicationID',
'text' 'text',
]; ];
public function application() public function application()
@ -22,5 +40,4 @@ class Comment extends Model
{ {
return $this->belongsTo('App\User', 'authorID', 'id'); return $this->belongsTo('App\User', 'authorID', 'id');
} }
} }

82
app/Console/Commands/CountVotes.php Normal file → Executable file
View File

@ -1,5 +1,24 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Application; use App\Application;
@ -43,28 +62,23 @@ class CountVotes extends Command
$eligibleApps = Application::where('applicationStatus', 'STAGE_PEERAPPROVAL')->get(); $eligibleApps = Application::where('applicationStatus', 'STAGE_PEERAPPROVAL')->get();
$pbar = $this->output->createProgressBar($eligibleApps->count()); $pbar = $this->output->createProgressBar($eligibleApps->count());
if($eligibleApps->isEmpty()) if ($eligibleApps->isEmpty()) {
{
$this->error('𐄂 There are no applications that need to be processed.'); $this->error('𐄂 There are no applications that need to be processed.');
return false; return false;
} }
foreach ($eligibleApps as $application) foreach ($eligibleApps as $application) {
{
$votes = $application->votes; $votes = $application->votes;
$voteCount = $application->votes->count(); $voteCount = $application->votes->count();
$positiveVotes = 0; $positiveVotes = 0;
$negativeVotes = 0; $negativeVotes = 0;
if ($voteCount > 5) if ($voteCount > 5) {
{ $this->info('Counting votes for application ID '.$application->id);
$this->info('Counting votes for application ID ' . $application->id); foreach ($votes as $vote) {
foreach ($votes as $vote) switch ($vote->allowedVoteType) {
{
switch ($vote->allowedVoteType)
{
case 'VOTE_APPROVE': case 'VOTE_APPROVE':
$positiveVotes++; $positiveVotes++;
break; break;
@ -74,7 +88,7 @@ class CountVotes extends Command
} }
} }
$this->info('Total votes for application ID ' . $application->id . ': ' . $voteCount); $this->info('Total votes for application ID '.$application->id.': '.$voteCount);
$this->info('Calculating criteria...'); $this->info('Calculating criteria...');
$negativeVotePercent = floor(($negativeVotes / $voteCount) * 100); $negativeVotePercent = floor(($negativeVotes / $voteCount) * 100);
$positiveVotePercent = floor(($positiveVotes / $voteCount) * 100); $positiveVotePercent = floor(($positiveVotes / $voteCount) * 100);
@ -83,54 +97,42 @@ class CountVotes extends Command
$this->table([ $this->table([
'% of approval votes', '% of approval votes',
'% of denial votes' '% of denial votes',
], [ // array of arrays, e.g. rows ], [ // array of arrays, e.g. rows
[ [
$positiveVotePercent . "%", $positiveVotePercent.'%',
$negativeVotePercent . "%" $negativeVotePercent.'%',
] ],
]); ]);
if ($pollResult) if ($pollResult) {
{ $this->info('✓ Dispatched promotion event for applicant '.$application->user->name);
$this->info('✓ Dispatched promotion event for applicant ' . $application->user->name); if (! $this->option('dryrun')) {
if (!$this->option('dryrun')) $application->response->vacancy->decrease();
{
$application->response->vacancy->vacancyCount -= 1;
$application->response->vacancy->save();
event(new ApplicationApprovedEvent(Application::find($application->id))); event(new ApplicationApprovedEvent(Application::find($application->id)));
} } else {
else
{
$this->warn('Dry run: Event won\'t be dispatched'); $this->warn('Dry run: Event won\'t be dispatched');
} }
$pbar->advance(); $pbar->advance();
} else {
} if (! $this->option('dryrun')) {
else {
if (!$this->option('dryrun'))
{
event(new ApplicationDeniedEvent(Application::find($application->id))); event(new ApplicationDeniedEvent(Application::find($application->id)));
} } else {
else {
$this->warn('Dry run: Event won\'t be dispatched'); $this->warn('Dry run: Event won\'t be dispatched');
} }
$pbar->advance(); $pbar->advance();
$this->error('𐄂 Applicant ' . $application->user->name . ' does not meet vote criteria (Majority)'); $this->error('𐄂 Applicant '.$application->user->name.' does not meet vote criteria (Majority)');
} }
} else {
$this->warn('Application ID'.$application->id.' did not have enough votes for processing (min 5)');
} }
else
{
$this->warn("Application ID" . $application->id . " did not have enough votes for processing (min 5)");
}
} }
$pbar->finish(); $pbar->finish();
return true; return true;
} }
} }

View File

@ -0,0 +1,140 @@
<?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Console\Commands;
use App\Facades\UUID;
use App\Profile;
use App\User;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Hash;
class CreateUser extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'users:create';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Creates an application user. Seeding the database is for testing environments, so use this command in production for your first admin user.';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
do {
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
system('cls');
} else {
system('clear');
}
$this->info('Welcome to the user account creation wizard. If you just installed the application, we recommend you create your first admin user here. If you don\'t, you won\'t gain admin privileges after creating an account in the web interface.');
$this->info('We\'ll ask some questions to get you started.');
$username = $this->ask('Username');
do {
$password = $this->secret('Password');
$password_confirm = $this->secret('Confirm Password');
if ($password === $password_confirm) {
$password = Hash::make($password);
$matches = true;
} else {
$this->error('Password doesn\'t match. Please try again.');
$matches = false;
}
} while (! $matches);
$email = $this->ask('E-mail address');
$name = $this->ask('First/Last Name');
do {
try {
$uuid = UUID::toUUID($this->ask('Minecraft username (Must be a valid Premium account)'));
} catch (\InvalidArgumentException $e) {
$this->error($e->getMessage());
$hasError = true;
}
if (isset($hasError)) {
$continue = true;
} else {
$continue = false;
}
unset($hasError);
} while ($continue);
$this->info('Please check if these details are correct: ');
$this->info('Username: '.$username);
$this->info('Email: '.$email);
$this->info('Name: '.$name);
} while (! $this->confirm('Create user now? You can go back to correct any details.'));
$user = User::create([
'uuid' => $uuid,
'name' => $name,
'email' => $email,
'username' => $username,
'originalIP' => '127.0.0.1',
'password' => $password,
]);
if ($user) {
$user->assignRole('admin', 'reviewer', 'user', 'hiringManager');
Profile::create([
'profileShortBio' => 'Random data '.rand(0, 1000),
'profileAboutMe' => 'Random data '.rand(0, 1000),
'socialLinks' => '[]',
'avatarPreference' => 'gravatar',
'userID' => $user->id,
]);
$this->info('Account created! You may now login at '.route('login').'. Enjoy the app!');
return 0;
} else {
$this->error('There was an unknown problem creating the user. There might have been errors above. Please try again.');
return 1;
}
}
}

0
app/Console/Commands/Install.php Normal file → Executable file
View File

View File

@ -0,0 +1,82 @@
<?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Console\Commands;
use Faker\Factory;
use Faker\Generator;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
class MakeFile extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'files:make {count : How many test files to generate}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Generates test files for the TeamFile model. Use in conjunction with it\'s factory.';
/**
* The faker instance used to obtain dummy text.
*
* @var Generator
*/
private $faker;
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
$this->faker = Factory::create();
parent::__construct();
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$count = $this->argument('count');
$this->info('Creating '.$this->argument('count').' files!');
for ($max = 1; $max < $count; $max++) {
Storage::disk('local')->put('factory_files/testfile_'.rand(0, 5000).'.txt', $this->faker->paragraphs(40, true));
}
$this->info('Finished creating files! They will be randomly picked by the factory.');
return 0;
}
}

31
app/Console/Commands/SetEnv.php Normal file → Executable file
View File

@ -1,9 +1,28 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Console\Commands; namespace App\Console\Commands;
use Illuminate\Console\Command;
use GeoSot\EnvEditor\Facades\EnvEditor; use GeoSot\EnvEditor\Facades\EnvEditor;
use Illuminate\Console\Command;
class SetEnv extends Command class SetEnv extends Command
{ {
@ -41,15 +60,11 @@ class SetEnv extends Command
$key = $this->argument('key'); $key = $this->argument('key');
$value = $this->argument('value'); $value = $this->argument('value');
if (file_exists($path)) {
if (file_exists($path))
{
EnvEditor::editKey($key, $value); EnvEditor::editKey($key, $value);
} } else {
else
{
$this->error('Cannot update a file that doesn\'t exist! Please create .env first.'); $this->error('Cannot update a file that doesn\'t exist! Please create .env first.');
return false; return false;
} }
} }

24
app/Console/Kernel.php Normal file → Executable file
View File

@ -1,10 +1,29 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Console; namespace App\Console;
use App\Jobs\ProcessDueSuspensions;
use Illuminate\Console\Scheduling\Schedule; use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel; use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
use App\Jobs\CleanBans;
class Kernel extends ConsoleKernel class Kernel extends ConsoleKernel
{ {
@ -31,9 +50,10 @@ class Kernel extends ConsoleKernel
->daily(); ->daily();
// Production value: Every day // Production value: Every day
$schedule->job(new CleanBans) $schedule->job(new ProcessDueSuspensions)
->daily(); ->daily();
// Production value: Every day // Production value: Every day
// Development value: Every minute
} }
/** /**

53
app/CustomFacades/IP.php Normal file → Executable file
View File

@ -1,12 +1,49 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\CustomFacades; namespace App\CustomFacades;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
class IP class IP
{ {
// Central source of truth for all operations that deal with IP addresses.
// For views, this is in a service provider, and is shared with all of them
/**
* Determines whether you should collect/display IP addresses in the app.
* @return bool Whether you should collect/display IPs, in the context in which this is called
*/
public function shouldCollect(): bool
{
// should collect or display IPs?
if (config('demo.is_enabled') || config('app.hide_ips'))
{
return false; // do not collect!
}
return true;
}
/** /**
* Looks up information on a specified IP address. Caches results automatically. * Looks up information on a specified IP address. Caches results automatically.
@ -15,23 +52,25 @@ class IP
*/ */
public function lookup(string $IP): object public function lookup(string $IP): object
{ {
$params = [ $params = [
'apiKey' => config('general.keys.ipapi.apikey'), 'apiKey' => config('general.keys.ipapi.apikey'),
'ip' => $IP 'ip' => $IP,
]; ];
// TODO: Maybe unwrap this? Methods are chained here if ($this->shouldCollect()) {
return json_decode(Cache::remember($IP, 3600, function() use ($IP)
{ return json_decode(Cache::remember($IP, 3600, function () use ($IP) {
return Http::get(config('general.urls.ipapi.ipcheck'), [ return Http::get(config('general.urls.ipapi.ipcheck'), [
'apiKey' => config('general.keys.ipapi.apikey'), 'apiKey' => config('general.keys.ipapi.apikey'),
'ip' => $IP 'ip' => $IP,
])->body(); ])->body();
})); }));
} }
return new class {
public $message = "This feature is disabled.";
};
}
} }

25
app/Events/ApplicationApprovedEvent.php Normal file → Executable file
View File

@ -1,13 +1,28 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Events; namespace App\Events;
use App\Application; use App\Application;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
@ -26,6 +41,4 @@ class ApplicationApprovedEvent
{ {
$this->application = $application; $this->application = $application;
} }
} }

24
app/Events/ApplicationDeniedEvent.php Normal file → Executable file
View File

@ -1,13 +1,28 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Events; namespace App\Events;
use App\Application; use App\Application;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
@ -26,5 +41,4 @@ class ApplicationDeniedEvent
{ {
$this->application = $application; $this->application = $application;
} }
} }

22
app/Events/NewApplicationEvent.php Normal file → Executable file
View File

@ -1,12 +1,28 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Events; namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;

30
app/Events/UserBannedEvent.php Normal file → Executable file
View File

@ -1,23 +1,36 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Events; namespace App\Events;
use Illuminate\Broadcasting\Channel; use App\Ban;
use App\User;
use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use App\User;
use App\Ban;
class UserBannedEvent class UserBannedEvent
{ {
use Dispatchable, InteractsWithSockets, SerializesModels; use Dispatchable, InteractsWithSockets, SerializesModels;
public $user; public $user;
public $ban; public $ban;
@ -32,5 +45,4 @@ class UserBannedEvent
$this->user = $user; $this->user = $user;
$this->ban = $ban; $this->ban = $ban;
} }
} }

View File

@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class AbsenceNotActionableException extends Exception
{
//
}

View File

@ -0,0 +1,11 @@
<?php
namespace App\Exceptions;
use Exception;
use Illuminate\Database\Eloquent\ModelNotFoundException;
class ApplicationNotFoundException extends ModelNotFoundException
{
//
}

View File

@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class EmptyFormException extends Exception
{
//
}

View File

@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class EmptyOptionsException extends Exception
{
//
}

View File

@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class FailedCaptchaException extends Exception
{
//
}

View File

@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class FileUploadException extends Exception
{
//
}

View File

@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class FormHasConstraintsException extends Exception
{
//
}

19
app/Exceptions/Handler.php Normal file → Executable file
View File

@ -1,5 +1,24 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Exceptions; namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;

View File

@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class IncompleteApplicationException extends Exception
{
//
}

View File

@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class InvalidAppointmentException extends Exception
{
//
}

View File

@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class InvalidAppointmentStatusException extends Exception
{
//
}

View File

@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class InvalidGamePreferenceException extends Exception
{
//
}

View File

@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class InvalidInviteException extends Exception
{
//
}

View File

@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class OptionCategoryNotFoundException extends Exception
{
//
}

View File

@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class OptionNotFoundException extends Exception
{
//
}

View File

@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class ProfileNotFoundException extends Exception
{
//
}

View File

@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class PublicTeamInviteException extends Exception
{
//
}

View File

@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class UnavailableApplicationException extends Exception
{
//
}

View File

@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class UserAlreadyInvitedException extends Exception
{
//
}

View File

@ -0,0 +1,11 @@
<?php
namespace App\Exceptions;
use Exception;
use Illuminate\Database\Eloquent\ModelNotFoundException;
class VacancyNotFoundException extends ModelNotFoundException
{
//
}

23
app/Facades/ContextAwareValidation.php Normal file → Executable file
View File

@ -1,14 +1,33 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Facades; namespace App\Facades;
use Illuminate\Support\Facades\Facade; use Illuminate\Support\Facades\Facade;
class ContextAwareValidation extends Facade class ContextAwareValidation extends Facade
{ {
// FIXME: Change to a binding key for L9
protected static function getFacadeAccessor() protected static function getFacadeAccessor()
{ {
return 'contextAwareValidator'; return 'contextAwareValidator';
} }
} }

View File

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

19
app/Facades/IP.php Normal file → Executable file
View File

@ -1,5 +1,24 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Facades; namespace App\Facades;
use Illuminate\Support\Facades\Facade; use Illuminate\Support\Facades\Facade;

17
app/Facades/JSON.php Executable file
View File

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

21
app/Facades/Options.php Normal file → Executable file
View File

@ -1,8 +1,27 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Facades; namespace App\Facades;
use \Illuminate\Support\Facades\Facade;
use Illuminate\Support\Facades\Facade;
class Options extends Facade class Options extends Facade
{ {

19
app/Facades/UUID.php Normal file → Executable file
View File

@ -1,5 +1,24 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Facades; namespace App\Facades;
use Illuminate\Support\Facades\Facade; use Illuminate\Support\Facades\Facade;

21
app/Form.php Normal file → Executable file
View File

@ -1,5 +1,24 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App; namespace App;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
@ -10,7 +29,7 @@ class Form extends Model
'formName', 'formName',
'formStructure', 'formStructure',
'formStatus' 'formStatus',
]; ];

67
app/Helpers/ContextAwareValidator.php Normal file → Executable file
View File

@ -1,13 +1,31 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Helpers; namespace App\Helpers;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Validator;
class ContextAwareValidator class ContextAwareValidator
{ {
/** /**
* The excludedNames array will make the validator ignore any of these names when including names into the rules. * The excludedNames array will make the validator ignore any of these names when including names into the rules.
* @var array * @var array
@ -15,17 +33,16 @@ class ContextAwareValidator
private $excludedNames = [ private $excludedNames = [
'_token', '_token',
'_method', '_method',
'formName' 'formName',
]; ];
/** /**
* Utility wrapper for json_encode. * Utility wrapper for json_encode.
* *
* @param array $value The array to be converted. * @param array $value The array to be converted.
* @return string The JSON representation of $value * @return string The JSON representation of $value
*/ */
private function encode(array $value) : string private function encode(array $value): string
{ {
return json_encode($value); return json_encode($value);
} }
@ -53,24 +70,19 @@ class ContextAwareValidator
$formStructure = []; $formStructure = [];
$validator = []; $validator = [];
if ($includeFormName) if ($includeFormName) {
{ $validator['formName'] = 'required|string';
$validator['formName'] = 'required|string|max:100';
} }
foreach ($fields as $fieldName => $field) foreach ($fields as $fieldName => $field) {
{ if (! in_array($fieldName, $this->excludedNames)) {
if(!in_array($fieldName, $this->excludedNames)) $validator[$fieldName.'.0'] = 'required|string';
{ $validator[$fieldName.'.1'] = 'required|string';
$validator[$fieldName . ".0"] = 'required|string';
$validator[$fieldName . ".1"] = 'required|string';
if ($generateStructure) if ($generateStructure) {
{
$formStructure['fields'][$fieldName]['title'] = $field[0]; $formStructure['fields'][$fieldName]['title'] = $field[0];
$formStructure['fields'][$fieldName]['type'] = $field[1]; $formStructure['fields'][$fieldName]['type'] = $field[1];
} }
} }
} }
@ -79,11 +91,9 @@ class ContextAwareValidator
return ($generateStructure) ? return ($generateStructure) ?
collect([ collect([
'validator' => $validatorInstance, 'validator' => $validatorInstance,
'structure' => $this->encode($formStructure) 'structure' => $this->encode($formStructure),
]) ])
: $validatorInstance; : $validatorInstance;
} }
/** /**
@ -100,23 +110,18 @@ class ContextAwareValidator
*/ */
public function getResponseValidator(array $fields, array $formStructure = [], bool $generateResponseStructure = true) public function getResponseValidator(array $fields, array $formStructure = [], bool $generateResponseStructure = true)
{ {
$responseStructure = []; $responseStructure = [];
$validator = []; $validator = [];
if (empty($formStructure) && $generateResponseStructure) if (empty($formStructure) && $generateResponseStructure) {
{
throw new \InvalidArgumentException('Illegal combination of arguments supplied! Please check the method\'s documentation.'); throw new \InvalidArgumentException('Illegal combination of arguments supplied! Please check the method\'s documentation.');
} }
foreach($fields as $fieldName => $value) foreach ($fields as $fieldName => $value) {
{ if (! in_array($fieldName, $this->excludedNames)) {
if(!in_array($fieldName, $this->excludedNames))
{
$validator[$fieldName] = 'required|string'; $validator[$fieldName] = 'required|string';
if ($generateResponseStructure) if ($generateResponseStructure) {
{
$responseStructure['responses'][$fieldName]['type'] = $formStructure['fields'][$fieldName]['type'] ?? 'Unavailable'; $responseStructure['responses'][$fieldName]['type'] = $formStructure['fields'][$fieldName]['type'] ?? 'Unavailable';
$responseStructure['responses'][$fieldName]['title'] = $formStructure['fields'][$fieldName]['title']; $responseStructure['responses'][$fieldName]['title'] = $formStructure['fields'][$fieldName]['title'];
$responseStructure['responses'][$fieldName]['response'] = $value; $responseStructure['responses'][$fieldName]['response'] = $value;
@ -129,10 +134,8 @@ class ContextAwareValidator
return ($generateResponseStructure) ? return ($generateResponseStructure) ?
collect([ collect([
'validator' => $validatorInstance, 'validator' => $validatorInstance,
'responseStructure' => $this->encode($responseStructure) 'responseStructure' => $this->encode($responseStructure),
]) ])
: $validatorInstance; : $validatorInstance;
} }
} }

View File

@ -0,0 +1,107 @@
<?php declare(strict_types=1);
namespace App\Helpers;
/**
* Class DigitalStorageHelper
*
* The digital storage helper class helps you convert bytes into several other units.
* It should be used whenever you need to display a file's size in a human readable way.
*
* It's framework agnostic, meaning you can take it out of context and it'll still work; However, you'll have to instantiate it first.
* @package App\Helpers
*/
class DigitalStorageHelper
{
/**
* The digital storage value to be manipulated.
* @var $value
*/
protected $value;
/**
* Sets the digital storage value for manipulation.
*
* @param int $value The digital storage value in bytes
* @return $this The current instance
*/
public function setValue(int $value): DigitalStorageHelper
{
$this->value = $value;
return $this;
}
/**
* Converts the digital storage value to kilobytes.
*
* @return float|int
*/
public function toKilobytes(): float
{
return $this->value / 1000;
}
/**
* Converts the digital storage value to megabytes.
*
* @return float|int
*/
public function toMegabytes(): float
{
return $this->value / (1 * pow(10, 6)); // 1 times 10 to the power of 6
}
/**
* Convert the digital storage value to gigabytes. Might be an approximation
*
* @return float
*/
public function toGigabytes(): float
{
return $this->value / (1 * pow(10, 9));
}
/**
* Convert the digital storage value to terabytes.
*
* @return float
*/
public function toTerabytes(): float
{
return $this->value / (1 * pow(10, 12));
}
/**
* Format the digital storage value to one of the units: b, kb, mb, gb and tb.
* The method has been adapted to use both MiB and MB values.
*
* @param int $precision The rounding precision
* @param bool $si Use international system units. Defaults to false
* @return string The human readable digital storage value, in either, for instance, MB or MiB
* @see https://stackoverflow.com/a/2510459/11540218 StackOverflow question regarding unit conversion
* @since 7.3.23
*/
public function formatBytes($precision = 2, $si = false): string
{
$units = ['B', 'KiB', 'MiB', 'GiB', 'TiB'];
if ($si)
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
$bytes = max($this->value, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(($si) ? 1000 : 1024));
$pow = min($pow, count($units) - 1);
$bytes /= pow(($si) ? 1000 : 1024, $pow);
return round($bytes, $precision) . ' ' . $units[$pow];
}
}

142
app/Helpers/JSON.php Executable file
View File

@ -0,0 +1,142 @@
<?php
namespace App\Helpers;
/**
* Class JSON - Used for JSON responses.
* @package App\Helpers
*/
class JSON
{
protected $type, $status, $message, $code, $data, $additional;
/**
* @param mixed $type
*/
public function setResponseType($type): JSON
{
$this->type = $type;
return $this;
}
/**
* @param mixed $additional
*/
public function setAdditional($additional)
{
$this->additional = $additional;
return $this;
}
/**
* @return mixed
*/
public function getAdditional()
{
return $this->additional;
}
/**
* @return mixed
*/
public function getType()
{
return $this->type;
}
/**
* @return mixed
*/
public function getStatus()
{
return $this->status;
}
/**
* @param mixed $status
* @return JSON
*/
public function setStatus($status)
{
$this->status = $status;
return $this;
}
/**
* @return mixed
*/
public function getMessage()
{
return $this->message;
}
/**
* @param mixed $message
* @return JSON
*/
public function setMessage($message)
{
$this->message = $message;
return $this;
}
/**
* @return mixed
*/
public function getCode()
{
return $this->code;
}
/**
* @param mixed $code
* @return JSON
*/
public function setCode($code)
{
$this->code = $code;
return $this;
}
/**
* @return mixed
*/
public function getData()
{
return $this->data;
}
/**
* @param mixed $data
* @return JSON
*/
public function setData($data)
{
$this->data = $data;
return $this;
}
public function build($headers = [])
{
// Uses the same structure as model resources, for consistency when they aren't used.
$response = [
'data' => $this->getData(),
'meta' => [
'status' => $this->getStatus(),
'message' => $this->getMessage(),
]
];
if (!empty($this->additional))
{
foreach($this->additional as $additionalKeyName => $key)
{
$response[$additionalKeyName] = $key;
}
}
return response($response, $this->getCode(), $headers);
}
}

86
app/Helpers/Options.php Normal file → Executable file
View File

@ -1,43 +1,88 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
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\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
/**
* The options class. A simple wrapper around the model. Could be a repository, but we're not using that design pattern just yet
*/
class Options class Options
{ {
/**
* Returns an assortment of settings found in the mentioned category
*
* @param $category The category
* @return Collection The settings in this category
*/
public function getCategory(string $category): Collection
{
$options = Option::where('option_category', $category)->get();
if ($options->isEmpty())
{
throw new EmptyOptionsException('There are no options in category ' . $category);
}
return $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))
{
Log::debug('Option ' . $option . 'not found in cache, refreshing from database');
$value = Option::where('option_name', $option)->first();
if (is_null($value))
throw new \Exception('This option does not exist.');
Cache::put($option, $value); if (is_null($value)) {
Cache::put($option . '_desc', 'Undefined description'); Log::debug('Option '.$option.'not found in cache, refreshing from database');
$value = Option::where('option_name', $option)->first();
if (is_null($value)) {
throw new OptionNotFoundException('This option does not exist.');
} }
Cache::put($option, $value->option_value);
Cache::put($option.'_desc', 'Undefined description');
return $value->option_value; return $value->option_value;
} }
public function setOption(string $option, string $value, string $description) return $value;
}
// Null categories are settings without categories and will appear ungrouped
public function setOption(string $option, string $value, string $description, string $category = null)
{ {
Option::create([ Option::create([
'option_name' => $option, 'option_name' => $option,
'option_value' => $value, 'option_value' => $value,
'friendly_name' => $description 'friendly_name' => $description,
'option_category' => $category
]); ]);
Cache::put($option, $value, now()->addDay()); Cache::put($option, $value, now()->addDay());
Cache::put($option . '_desc', $description, now()->addDay()); Cache::put($option.'_desc', $description, now()->addDay());
} }
public function pullOption($option): array public function pullOption($option): array
@ -48,7 +93,7 @@ class Options
// putMany is overkill here // putMany is overkill here
return [ return [
Cache::pull($option), Cache::pull($option),
Cache::pull($option . '_desc') Cache::pull($option.'_desc'),
]; ];
} }
@ -56,14 +101,13 @@ class Options
{ {
$dbOption = Option::where('option_name', $option); $dbOption = Option::where('option_name', $option);
if ($dbOption->first()) if ($dbOption->first()) {
{
$dbOptionInstance = Option::find($dbOption->first()->id); $dbOptionInstance = Option::find($dbOption->first()->id);
Cache::forget($option); Cache::forget($option);
Log::debug('Changing db configuration option', [ Log::debug('Changing db configuration option', [
'old_value' => $dbOptionInstance->option_value, 'old_value' => $dbOptionInstance->option_value,
'new_value' => $newValue 'new_value' => $newValue,
]); ]);
$dbOptionInstance->option_value = $newValue; $dbOptionInstance->option_value = $newValue;
@ -71,24 +115,20 @@ class Options
Log::debug('New db configuration option saved', Log::debug('New db configuration option saved',
[ [
'option' => $dbOptionInstance->option_value 'option' => $dbOptionInstance->option_value,
]); ]);
Cache::put('option_name', $newValue, now()->addDay()); Cache::put('option_name', $newValue, now()->addDay());
} } else {
else throw new OptionNotFoundException('This option does not exist.');
{
throw new \Exception('This option does not exist.');
} }
} }
public function optionExists(string $option): bool public function optionExists(string $option): bool
{ {
$dbOption = Option::where('option_name', $option)->first(); $dbOption = Option::where('option_name', $option)->first();
$locallyCachedOption = Cache::get($option); $locallyCachedOption = Cache::get($option);
return !is_null($dbOption) || !is_null($locallyCachedOption); return ! is_null($dbOption) || ! is_null($locallyCachedOption);
} }
} }

View File

@ -0,0 +1,234 @@
<?php
namespace App\Http\Controllers;
use App\Absence;
use App\Exceptions\AbsenceNotActionableException;
use App\Http\Requests\StoreAbsenceRequest;
use App\Http\Requests\UpdateAbsenceRequest;
use App\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Auth;
class AbsenceController extends Controller
{
/**
* Determines whether someone already has an active leave of absence request
*
* @param User $user The user to check
* @return bool Their status
*/
private function hasActiveRequest(Authenticatable $user): bool {
$absences = Absence::where('requesterID', $user->id)->get();
foreach ($absences as $absence) {
// Or we could adjust the query (using a model scope) to only return valid absences;
// If there are any, refuse to store more, but this approach also works
// A model scope that only returns cancelled, declined and ended absences could also be implemented for future use
if (in_array($absence->getRawOriginal('status'), ['PENDING', 'APPROVED']))
{
return true;
}
}
return false;
}
/**
* Display a listing of absences.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$this->authorize('viewAny', Absence::class);
return view('dashboard.absences.index')
->with('absences', Absence::paginate(6));
}
/**
* Display a listing of absences belonging to the current user.
*
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function showUserAbsences()
{
$this->authorize('viewOwn', Absence::class);
// We can't paginate on the relationship found on the user model
$absences = Absence::where('requesterID', Auth::user()->id)->paginate(6);
return view('dashboard.absences.own')
->with('absences', $absences);
}
/**
* Show the form for creating a new absence request.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
$this->authorize('create', Absence::class);
return view('dashboard.absences.create')
->with('activeRequest', $this->hasActiveRequest(Auth::user()));
}
/**
* Store a newly created request in storage.
*
* @param \App\Http\Requests\StoreAbsenceRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreAbsenceRequest $request)
{
$this->authorize('create', Absence::class);
if ($this->hasActiveRequest(Auth::user())) {
return redirect()
->back()
->with('error', __('You already have an active request. Cancel it or let it expire first.'));
}
$absence = Absence::create([
'requesterID' => Auth::user()->id,
'start' => $request->start_date,
'predicted_end' => $request->predicted_end,
'available_assist' => $request->available_assist == "on",
'reason' => $request->reason,
'status' => 'PENDING',
]);
return redirect()
->to(route('absences.show', ['absence' => $absence->id]))
->with('success', 'Absence request submitted for approval. You will receive email confirmation shortly.');
}
/**
* Display the specified absence request.
*
* @param \App\Absence $absence
*/
public function show(Absence $absence)
{
$this->authorize('view', $absence);
return view('dashboard.absences.view')
->with([
'absence' => $absence,
'totalDays' => Carbon::parse($absence->start)->diffInDays($absence->predicted_end)
]);
}
/**
* Approve the specified absence.
*
* @param Absence $absence
* @return RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function approveAbsence(Absence $absence): RedirectResponse
{
$this->authorize('approve', $absence);
try
{
$absence->setApproved();
}
catch (AbsenceNotActionableException $notActionableException)
{
return redirect()
->back()
->with('error', $notActionableException->getMessage());
}
return redirect()
->back()
->with('success', __('Absence request successfully approved. It will automatically transition to "Ended" on its predicted end date.'));
}
/**
* Decline the specified absence.
*
* @param Absence $absence
* @return RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function declineAbsence(Absence $absence): RedirectResponse
{
$this->authorize('decline', $absence);
try
{
$absence->setDeclined();
} catch (AbsenceNotActionableException $notActionableException)
{
return redirect()
->back()
->with('error', $notActionableException->getMessage());
}
return redirect()
->back()
->with('success', __('Absence request successfully declined.'));
}
/**
* Cancel the specified absence.
*
* @param Absence $absence
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function cancelAbsence(Absence $absence): \Illuminate\Http\RedirectResponse
{
$this->authorize('cancel', $absence);
try
{
$absence->setCancelled();
}
catch (AbsenceNotActionableException $notActionableException)
{
return redirect()
->back()
->with('error', $notActionableException->getMessage());
}
return redirect()
->back()
->with('success', __('Absence request successfully cancelled.'));
}
/**
* Remove the specified resource from storage.
*
* @param \App\Absence $absence
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(Absence $absence)
{
$this->authorize('delete', $absence);
if ($absence->delete()) {
return redirect()
->to(route('absences.index'))
->with('success', __('Absence request deleted.'));
}
}
}

263
app/Http/Controllers/ApplicationController.php Normal file → Executable file
View File

@ -1,59 +1,58 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Application; use App\Application;
use App\Exceptions\ApplicationNotFoundException;
use App\Response; use App\Exceptions\IncompleteApplicationException;
use App\Vacancy; use App\Exceptions\UnavailableApplicationException;
use App\User; use App\Exceptions\VacancyNotFoundException;
use App\Facades\IP;
use App\Events\ApplicationDeniedEvent; use App\Services\ApplicationService;
use App\Notifications\NewApplicant;
use App\Notifications\ApplicationMoved;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Log;
use ContextAwareValidator;
class ApplicationController extends Controller class ApplicationController extends Controller
{ {
private function canVote($votes) private $applicationService;
{
$allvotes = collect([]);
foreach ($votes as $vote) public function __construct(ApplicationService $applicationService) {
{
if ($vote->userID == Auth::user()->id)
{
$allvotes->push($vote);
}
}
return ($allvotes->count() == 1) ? false : true; $this->applicationService = $applicationService;
} }
public function showUserApps() public function showUserApps()
{ {
return view('dashboard.user.applications') return view('dashboard.user.applications')
->with('applications', Auth::user()->applications); ->with('applications', Auth::user()->applications);
} }
public function showUserApp(Request $request, Application $application) public function showUserApp(Request $request, Application $application)
{ {
$this->authorize('view', $application); $this->authorize('view', $application);
if (!is_null($application)) if (!is_null($application)) {
{
return view('dashboard.user.viewapp') return view('dashboard.user.viewapp')
->with( ->with(
[ [
@ -62,210 +61,88 @@ 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 $request->session()->flash('error', __('The application you requested could not be found.'));
{
$request->session()->flash('error', 'The application you requested could not be found.');
} }
return redirect()->back(); return redirect()->back();
} }
public function showAllApps(Request $request)
public function showAllApps()
{ {
$this->authorize('viewAny', Application::class); $this->authorize('viewAny', Application::class);
return view('dashboard.appmanagement.all') return view('dashboard.appmanagement.all')
->with('applications', Application::paginate(6)); ->with('applications', Application::orderBy('applicationStatus', 'ASC')->paginate(6));
} }
public function showAllPendingApps() public function renderApplicationForm($vacancySlug)
{ {
$this->authorize('viewAny', Application::class); try {
return $this->applicationService->renderForm($vacancySlug);
return view('dashboard.appmanagement.outstandingapps')
->with('applications', Application::where('applicationStatus', 'STAGE_SUBMITTED')->get());
} }
catch (ApplicationNotFoundException $ex) {
return redirect()
public function showPendingInterview() ->back()
{ ->with('error', $ex->getMessage());
$this->authorize('viewAny', Application::class);
$applications = Application::with('appointment', 'user')->get();
$count = 0;
$pendingInterviews = collect([]);
$upcomingInterviews = collect([]);
foreach ($applications as $application)
{
if (!is_null($application->appointment) && $application->appointment->appointmentStatus == 'CONCLUDED')
{
$count =+ 1;
} }
switch ($application->applicationStatus)
{
case 'STAGE_INTERVIEW':
$upcomingInterviews->push($application);
break;
case 'STAGE_INTERVIEW_SCHEDULED':
$pendingInterviews->push($application);
break;
} }
}
return view('dashboard.appmanagement.interview')
->with([
'finishedCount' => $count,
'applications' => $pendingInterviews,
'upcomingApplications' => $upcomingInterviews
]);
}
public function showPeerReview()
{
$this->authorize('viewAny', Application::class);
return view('dashboard.appmanagement.peerreview')
->with('applications', Application::where('applicationStatus', 'STAGE_PEERAPPROVAL')->get());
}
public function renderApplicationForm(Request $request, $vacancySlug)
{
// FIXME: Get rid of references to first(), this is a wonky query
$vacancyWithForm = Vacancy::with('forms')->where('vacancySlug', $vacancySlug)->get();
$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->first()->vacancyCount == 0 || $vacancy->first()->vacancyStatus !== 'OPEN') $this->applicationService->fillForm(Auth::user(), $request->all(), $vacancySlug);
{
$request->session()->flash('error', 'This application is unavailable.'); } catch (VacancyNotFoundException | IncompleteApplicationException | UnavailableApplicationException $e) {
return redirect()->back();
return redirect()
->back()
->with('error', $e->getMessage());
} }
Log::info('Processing new application!'); return redirect()
->to(route('showUserApps'))
$formStructure = json_decode($vacancy->first()->forms->formStructure, true); ->with('success', __('Thank you! Your application has been processed and our team will get to it shortly.'));
$responseValidation = ContextAwareValidator::getResponseValidator($request->all(), $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 for user ' . Auth::user()->name . ' for vacancy ' . $vacancy->first()->vacancyName);
$application = Application::create([
'applicantUserID' => Auth::user()->id,
'applicantFormResponseID' => $response->id,
'applicationStatus' => 'STAGE_SUBMITTED',
]);
Log::info('Submitted application for user ' . Auth::user()->name . ' with response ID' . $response->id);
foreach(User::all() as $user)
{
if ($user->hasRole('admin'))
{
$user->notify((new NewApplicant($application, $vacancy->first()))->delay(now()->addSeconds(10)));
}
}
$request->session()->flash('success', 'Thank you for your application! It will be reviewed as soon as possible.');
return redirect()->to(route('showUserApps'));
}
else
{
Log::warning('Application form for ' . Auth::user()->name . ' contained errors, resetting!');
$request->session()->flash('error', 'There are one or more errors in your application. Please make sure none of your fields are empty, since they are all required.');
}
return redirect()->back();
} }
public function updateApplicationStatus(Request $request, Application $application, $newStatus) public function updateApplicationStatus(Request $request, Application $application, $newStatus)
{ {
$messageIsError = false;
$this->authorize('update', Application::class); $this->authorize('update', Application::class);
switch ($newStatus) try {
$status = $this->applicationService->updateStatus($application, $newStatus);
} catch (\LogicException $ex)
{ {
case 'deny': return redirect()
->back()
event(new ApplicationDeniedEvent($application)); ->with('error', $ex->getMessage());
break;
case 'interview':
Log::info('User ' . Auth::user()->name . ' has moved application ID ' . $application->id . 'to interview stage');
$request->session()->flash('success', 'Application moved to interview stage! (:');
$application->setStatus('STAGE_INTERVIEW');
$application->user->notify(new ApplicationMoved());
break;
default:
$request->session()->flash('error', 'There are no suitable statuses to update to. Do not mess with the URL.');
} }
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);
$request->session()->flash('success', 'Application deleted. Comments, appointments and responses have also been deleted.'); return redirect()
return redirect()->back(); ->back()
->with('success', __('Application deleted. Comments, appointments and responses have also been deleted.'));
} }
} }

160
app/Http/Controllers/AppointmentController.php Normal file → Executable file
View File

@ -1,94 +1,124 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Application; use App\Application;
use App\Http\Requests\SaveNotesRequest;
use Carbon\Carbon;
use Illuminate\Http\Request;
use App\Appointment; use App\Appointment;
use App\Notifications\ApplicationMoved; use App\Exceptions\InvalidAppointmentException;
use App\Notifications\AppointmentScheduled; use App\Exceptions\InvalidAppointmentStatusException;
use Illuminate\Support\Facades\Auth; use App\Http\Requests\CancelAppointmentRequest;
use Illuminate\Support\Facades\Log; use App\Http\Requests\SaveNotesRequest;
use App\Services\AppointmentService;
use App\Services\MeetingNoteService;
use Carbon\Carbon;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
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)
{
$this->authorize('create', Appointment::class);
$appointmentDate = Carbon::parse($request->appointmentDateTime);
$appointment = Appointment::create([
'appointmentDescription' => $request->appointmentDescription,
'appointmentDate' => $appointmentDate->toDateTimeString(),
'applicationID' => $application->id,
'appointmentLocation' => (in_array($request->appointmentLocation, $this->allowedPlatforms)) ? $request->appointmentLocation : 'DISCORD',
]);
$application->setStatus('STAGE_INTERVIEW_SCHEDULED');
Log::info('User ' . Auth::user()->name . ' has scheduled an appointment with ' . $application->user->name . ' for application ID' . $application->id, [ public function __construct(AppointmentService $appointmentService, MeetingNoteService $meetingNoteService) {
'datetime' => $appointmentDate->toDateTimeString(),
'scheduled' => now()
]);
$application->user->notify(new AppointmentScheduled($appointment)); $this->appointmentService = $appointmentService;
$request->session()->flash('success', 'Appointment successfully scheduled @ ' . $appointmentDate->toDateTimeString()); $this->meetingNoteService = $meetingNoteService;
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, string $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()
->back()
$request->session()->flash('success', 'Interview finished! Staff members can now vote on it.'); ->with('error', $ex->getMessage());
return redirect()->back(); }
} }
// also updates public function deleteAppointment(CancelAppointmentRequest $request, Application $application)
{
$this->authorize('update', $application->appointment);
try {
$this->appointmentService->deleteAppointment($application, $request->reason);
return redirect()
->back()
->with('success', __('Appointment cancelled.'));
}
catch (\Exception $ex) {
return redirect()
->back()
->with('error', $ex->getMessage());
}
}
public function saveNotes(SaveNotesRequest $request, Application $application) public function saveNotes(SaveNotesRequest $request, Application $application)
{ {
if (!is_null($application)) try {
{
$application->load('appointment');
$application->appointment->meetingNotes = $request->noteText; $this->meetingNoteService->addToApplication($application, $request->noteText);
$application->appointment->save();
$request->session()->flash('success', 'Meeting notes have been saved.'); return redirect()
->back()
->with('success', 'Saved notes.');
} catch (InvalidAppointmentException $ex) {
return redirect()
->back()
->with('error', $ex->getMessage());
} }
else
{
$request->session()->flash('error', 'There\'s no appointment to save notes to!');
} }
return redirect()->back();
}
} }

20
app/Http/Controllers/Auth/ConfirmPasswordController.php Normal file → Executable file
View File

@ -1,9 +1,27 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers\Auth; namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\ConfirmsPasswords; use Illuminate\Foundation\Auth\ConfirmsPasswords;
class ConfirmPasswordController extends Controller class ConfirmPasswordController extends Controller

19
app/Http/Controllers/Auth/ForgotPasswordController.php Normal file → Executable file
View File

@ -1,5 +1,24 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers\Auth; namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;

59
app/Http/Controllers/Auth/LoginController.php Normal file → Executable file
View File

@ -1,12 +1,33 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers\Auth; namespace App\Http\Controllers\Auth;
use App\User;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider; use App\User;
use Illuminate\Foundation\Auth\AuthenticatesUsers; use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use App\Facades\IP;
use Laravel\Socialite\Facades\Socialite;
class LoginController extends Controller class LoginController extends Controller
{ {
@ -48,22 +69,38 @@ class LoginController extends Controller
{ {
$user = User::where('email', $request->email)->first(); $user = User::where('email', $request->email)->first();
if ($user) if ($user) {
{
$isBanned = $user->isBanned(); $isBanned = $user->isBanned();
if ($isBanned) if ($isBanned) {
{
return false; return false;
} else {
return $this->originalAttemptLogin($request);
} }
else }
return $this->originalAttemptLogin($request);
}
public function authenticated(Request $request, User $user)
{ {
return $this->originalAttemptLogin($request); if (IP::shouldCollect()) {
if ($user->originalIP !== $request->ip())
{
Log::alert('User IP address changed from last login. Updating.', [
'prev' => $user->originalIP,
'new' => $request->ip()
]);
$user->originalIP = $request->ip();
$user->save();
}
} }
} }
return $this->originalAttemptLogin($request); public function discordRedirect() {
return Socialite::driver('discord')->redirect();
} }
public function discordCallback() {
// TODO;
}
} }

63
app/Http/Controllers/Auth/RegisterController.php Normal file → Executable file
View File

@ -1,15 +1,34 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers\Auth; namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Profile; use App\Profile;
use App\Providers\RouteServiceProvider;
use App\User; use App\User;
use App\Facades\Options;
use App\Facades\IP;
use Illuminate\Foundation\Auth\RegistersUsers; use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use function GuzzleHttp\Psr7\str;
class RegisterController extends Controller class RegisterController extends Controller
{ {
@ -47,10 +66,8 @@ class RegisterController extends Controller
{ {
$users = User::where('originalIP', \request()->ip())->get(); $users = User::where('originalIP', \request()->ip())->get();
foreach($users as $user) foreach ($users as $user) {
{ if ($user && $user->isBanned()) {
if ($user && $user->isBanned())
{
abort(403, 'You do not have permission to access this page.'); abort(403, 'You do not have permission to access this page.');
} }
} }
@ -66,13 +83,32 @@ class RegisterController extends Controller
*/ */
protected function validator(array $data) protected function validator(array $data)
{ {
$password = ['required', 'string', 'confirmed'];
switch (Options::getOption('pw_security_policy'))
{ // this could be better structured, switch doesn't feel right
case 'off':
$password = ['required', 'string', 'confirmed'];
break;
case 'low':
$password = ['required', 'string', 'min:10', 'confirmed'];
break;
case 'medium':
$password = ['required', 'string', 'confirmed', 'regex:/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[#?!@$%^&*-]).{12,}$/'];
break;
case 'high':
$password = ['required', 'string', 'confirmed', 'regex:/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{20,}$/'];
}
return Validator::make($data, [ return Validator::make($data, [
'uuid' => ['required', 'string', 'unique:users', 'min:32', 'max:32'], 'uuid' => (Options::getOption('requireGameLicense') && Options::getOption('currentGame') == 'MINECRAFT') ? ['required', 'string', 'unique:users', 'min:32', 'max:32'] : ['nullable', 'string'],
'name' => ['required', 'string', 'max:255'], 'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'string', 'min:10', 'confirmed', 'regex:/^.*(?=.{3,})(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[\d\x])(?=.*[!$#%]).*$/'], 'password' => $password,
], [ ], [
'uuid.required' => 'Please enter a valid (and Premium) Minecraft username! We do not support cracked users.' 'uuid.required' => 'Please enter a valid (and Premium) Minecraft username! We do not support cracked users.',
]); ]);
} }
@ -84,19 +120,16 @@ class RegisterController extends Controller
*/ */
protected function create(array $data) protected function create(array $data)
{ {
$user = User::create([ $user = User::create([
'uuid' => $data['uuid'], 'uuid' => $data['uuid'] ?? "disabled",
'name' => $data['name'], 'name' => $data['name'],
'email' => $data['email'], 'email' => $data['email'],
'password' => Hash::make($data['password']), 'password' => Hash::make($data['password']),
'originalIP' => request()->ip() 'originalIP' => IP::shouldCollect() ? request()->ip() : '0.0.0.0',
]); ]);
// It's not the registration controller's concern to create a profile for the user,
// so this code has been moved to it's respective observer, following the separation of concerns pattern.
$user->assignRole('user'); $user->assignRole('user');
return $user; return $user;
} }
} }

20
app/Http/Controllers/Auth/ResetPasswordController.php Normal file → Executable file
View File

@ -1,9 +1,27 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers\Auth; namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\ResetsPasswords; use Illuminate\Foundation\Auth\ResetsPasswords;
class ResetPasswordController extends Controller class ResetPasswordController extends Controller

22
app/Http/Controllers/Auth/TwofaController.php Normal file → Executable file
View File

@ -1,8 +1,26 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers\Auth; namespace App\Http\Controllers\Auth;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Traits\AuthenticatesTwoFactor; use App\Traits\AuthenticatesTwoFactor;
@ -10,7 +28,5 @@ class TwofaController extends Controller
{ {
use AuthenticatesTwoFactor; use AuthenticatesTwoFactor;
protected $redirectTo = '/dashboard'; protected $redirectTo = '/dashboard';
} }

20
app/Http/Controllers/Auth/VerificationController.php Normal file → Executable file
View File

@ -1,9 +1,27 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers\Auth; namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\VerifiesEmails; use Illuminate\Foundation\Auth\VerifiesEmails;
class VerificationController extends Controller class VerificationController extends Controller

112
app/Http/Controllers/BanController.php Normal file → Executable file
View File

@ -1,92 +1,86 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Ban; use App\Ban;
use App\User;
use App\Events\UserBannedEvent; use App\Events\UserBannedEvent;
use App\Http\Requests\BanUserRequest; use App\Http\Requests\BanUserRequest;
use App\Services\AccountSuspensionService;
use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class BanController extends Controller class BanController extends Controller
{ {
protected $suspensionService;
public function __construct(AccountSuspensionService $suspensionService)
{
// Inject the service via DI
$this->suspensionService = $suspensionService;
}
public function insert(BanUserRequest $request, User $user) public function insert(BanUserRequest $request, User $user)
{ {
if (config('demo.is_enabled')) {
return redirect()
->back()
->with('error', 'This feature is disabled');
}
$this->authorize('create', [Ban::class, $user]); $this->authorize('create', [Ban::class, $user]);
if (is_null($user->bans))
{
$reason = $request->reason; if (!$this->suspensionService->isSuspended($user)) {
$duration = strtolower($request->durationOperator);
$durationOperand = $request->durationOperand;
$expiryDate = now(); $this->suspensionService->suspend($request->reason, $request->duration, $user, $request->suspensionType);
$request->session()->flash('success', __('Account suspended.'));
if (!empty($duration)) } else {
{
switch($duration)
{
case 'days':
$expiryDate->addDays($durationOperand);
break;
case 'weeks': $request->session()->flash('error', __('Account already suspended!'));
$expiryDate->addWeeks($durationOperand);
break;
case 'months':
$expiryDate->addMonths($durationOperand);
break;
case 'years':
$expiryDate->addYears($durationOperand);
break;
}
}
else
{
// Essentially permanent
$expiryDate->addYears(5);
}
$ban = Ban::create([
'userID' => $user->id,
'reason' => $reason,
'bannedUntil' => $expiryDate->format('Y-m-d H:i:s'),
'userAgent' => "Unknown",
'authorUserID' => Auth::user()->id
]);
event(new UserBannedEvent($user, $ban));
$request->session()->flash('success', 'User banned successfully! Ban ID: #' . $ban->id);
}
else
{
$request->session()->flash('error', 'User already banned!');
} }
return redirect()->back(); return redirect()->back();
} }
public function delete(Request $request, User $user) public function delete(Request $request, User $user)
{ {
if (config('demo.is_enabled')) {
return redirect()
->back()
->with('error', 'This feature is disabled');
}
$this->authorize('delete', $user->bans); $this->authorize('delete', $user->bans);
if (!is_null($user->bans)) if ($this->suspensionService->isSuspended($user)) {
{
$user->bans->delete(); $this->suspensionService->unsuspend($user);
$request->session()->flash('success', 'User unbanned successfully!'); $request->session()->flash('success', __('Account unsuspended successfully!'));
}
else } else {
{ $request->session()->flash('error', __('This account isn\'t suspended!'));
$request->session()->flash('error', 'This user isn\'t banned!');
} }
return redirect()->back(); return redirect()->back();

65
app/Http/Controllers/CommentController.php Normal file → Executable file
View File

@ -1,57 +1,62 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Application;
use App\Comment;
use App\Http\Requests\NewCommentRequest;
use App\Services\CommentService;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use App\Http\Requests\NewCommentRequest;
use App\Comment;
use App\Application;
use App\Notifications\NewComment;
use App\User;
class CommentController extends Controller class CommentController extends Controller
{ {
private $commentService;
public function index() 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([ if ($comment) {
'authorID' => Auth::user()->id, $request->session()->flash('success', __('Comment posted!'));
'applicationID' => $application->id, } else {
'text' => $request->comment $request->session()->flash('error', __('Something went wrong while posting your comment!'));
]);
if ($comment)
{
$request->session()->flash('success', 'Comment posted! (:');
}
else
{
$request->session()->flash('error', 'Something went wrong while posting your comment!');
} }
return redirect()->back(); return redirect()->back();
} }
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();
} }
} }

76
app/Http/Controllers/ContactController.php Normal file → Executable file
View File

@ -1,65 +1,63 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Http\Request; use App\Exceptions\FailedCaptchaException;
use GuzzleHttp;
use App\Notifications\NewContact; use App\Notifications\NewContact;
use Illuminate\Support\Facades\Http; use App\Services\ContactService;
use App\User; use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
class ContactController extends Controller class ContactController extends Controller
{ {
protected $users; protected $users;
private $contactService;
public function __construct(User $users) 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;
$email = $request->email;
$msg = $request->msg;
$challenge = $request->input('captcha'); $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()
]);
return redirect()
->back()
->with('success',__('Message sent successfully! We usually respond within 48 hours.'));
$response = json_decode($verifyrequest->getBody(), true); } catch (FailedCaptchaException $ex) {
return redirect()
if (!$response['success']) ->back()
{ ->with('error', $ex->getMessage());
$request->session()->flash('error', 'Beep beep boop... Robot? Submission failed.');
return redirect()->back();
} }
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();
} }
} }

19
app/Http/Controllers/Controller.php Normal file → Executable file
View File

@ -1,5 +1,24 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Foundation\Auth\Access\AuthorizesRequests;

46
app/Http/Controllers/DashboardController.php Normal file → Executable file
View File

@ -1,31 +1,61 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Vacancy;
use App\User;
use App\Ban;
use App\Application; use App\Application;
use App\User;
use App\Vacancy;
use Illuminate\Support\Facades\Auth;
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();
$totalNewApplications = Application::where('applicationStatus', 'STAGE_SUBMITTED')->get()->count(); $totalNewApplications = Application::where('applicationStatus', 'STAGE_SUBMITTED')->get()->count();
$totalDenied = Application::where('applicationStatus', 'DENIED')->get()->count(); $totalDenied = Application::where('applicationStatus', 'DENIED')->get()->count();
$vacancies = Vacancy::where('vacancyStatus', '<>', 'CLOSED')->get();
$totalDeniedSingle = Application::where([
['applicationStatus', '=', 'DENIED'],
['applicantUserID', '=', Auth::user()->id]
])->get();
$totalNewSingle = Application::where([
['applicationStatus', '=', 'STAGE_SUBMITTED'],
['applicantUserID', '=', Auth::user()->id]
])->get();
return view('dashboard.dashboard') return view('dashboard.dashboard')
->with([ ->with([
'vacancies' => Vacancy::all(), 'vacancies' => $vacancies,
'totalUserCount' => User::all()->count(), 'totalUserCount' => User::all()->count(),
'totalDenied' => $totalDenied, 'totalDenied' => $totalDenied,
'totalPeerReview' => $totalPeerReview, 'totalPeerReview' => $totalPeerReview,
'totalNewApplications' => $totalNewApplications 'totalNewApplications' => $totalNewApplications,
'totalNewSingle' => $totalNewSingle->count(),
'totalDeniedSingle' => $totalDeniedSingle->count()
]); ]);
} }
} }

104
app/Http/Controllers/DevToolsController.php Normal file → Executable file
View File

@ -1,48 +1,108 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Application; use App\Application;
use App\Events\ApplicationApprovedEvent; use App\Events\ApplicationApprovedEvent;
use App\Events\ApplicationDeniedEvent;
use App\Services\AccountSuspensionService;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
class DevToolsController extends Controller class DevToolsController extends Controller
{ {
public function __construct() {
//
}
// The use case for Laravel's gate and/or validation Requests is so tiny here that a full-blown policy would be overkill. private function singleAuthorise() {
protected function isolatedAuthorise() if (! Auth::user()->can('admin.developertools.use')) {
{ abort(403, __('You\'re not authorized to access this page.'));
if (!Auth::user()->can('admin.developertools.use'))
{
abort(403, 'You\'re not authorized to access this page.');
} }
} }
public function index() public function index() {
{ $this->singleAuthorise();
$this->isolatedAuthorise();
return view('dashboard.administration.devtools') return view('dashboard.administration.devtools')
->with('applications', Application::where('applicationStatus', 'STAGE_PEERAPPROVAL')->get()); ->with('applications', Application::where('applicationStatus', 'STAGE_PEERAPPROVAL')->get())
->with('rejectApplications', Application::all());
} }
public function forceVoteCount(Request $request) /**
{ * Force an application to be approved.
$this->isolatedAuthorise(); */
public function forceApprovalEvent(Request $request) {
$this->singleAuthorise();
$application = Application::find($request->application); $application = Application::find($request->application);
if (!is_null($application))
{
event(new ApplicationApprovedEvent($application)); event(new ApplicationApprovedEvent($application));
$request->session()->flash('success', 'Event dispatched! Please check the debug logs for more info'); return redirect()
} ->back()
else ->with('success', __('Event dispatched; Candidate approval sequence initiated.'));
{
$request->session()->flash('error', 'Application doesn\'t exist!');
} }
return redirect()->back(); /**
* Force an application to be rejected.
*/
public function forceRejectionEvent(Request $request)
{
$this->singleAuthorise();
$application = Application::findOrFail($request->application);
event(new ApplicationDeniedEvent($application));
return redirect()
->back()
->with('success', __('Event dispatched; Candidate rejection sequence initiated.'));
}
public function evaluateVotes() {
$this->singleAuthorise();
$code = Artisan::call("votes:evaluate");
return redirect()
->back()
->with('success', 'Ran vote evaluation logic, with exit code ' . $code);
}
public function purgeSuspensions(AccountSuspensionService $service) {
$this->singleAuthorise();
if ($service->purgeExpired()) {
return redirect()
->back()
->with('success', 'Force purged all expired suspensions.');
}
return redirect()
->back()
->with('error', 'There were no expired suspensions (or no suspensions at all) to purge.');
} }
} }

128
app/Http/Controllers/FormController.php Normal file → Executable file
View File

@ -1,16 +1,40 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Exceptions\EmptyFormException;
use App\Exceptions\FormHasConstraintsException;
use App\Form; use App\Form;
use Illuminate\Http\Request; use App\Services\FormManagementService;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Auth;
use ContextAwareValidator; use ContextAwareValidator;
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()
{ {
@ -24,61 +48,52 @@ class FormController extends Controller
public function showFormBuilder() public function showFormBuilder()
{ {
$this->authorize('viewFormbuilder', Form::class); $this->authorize('viewFormbuilder', Form::class);
return view('dashboard.administration.formbuilder'); return view('dashboard.administration.formbuilder');
} }
public function saveForm(Request $request) public function saveForm(Request $request)
{ {
try {
$this->authorize('create', Form::class); $form = $this->formService->addForm($request->all());
$fields = $request->all(); }
catch (EmptyFormException $ex)
$contextValidation = ContextAwareValidator::getValidator($fields, true, true);
if (!$contextValidation->get('validator')->fails())
{ {
$storableFormStructure = $contextValidation->get('structure'); return redirect()
->back()
Form::create( ->with('exception', $ex->getMessage());
[
'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()); // Form is boolean or array
return redirect()->back(); if ($form)
{
return redirect()
->back()
->with('success', __('Form created!'));
}
return redirect()
->back()
->with('errors', $form);
} }
public function destroy(Request $request, Form $form) public function destroy(Request $request, Form $form)
{ {
$this->authorize('delete', $form); $this->authorize('delete', $form);
$deletable = true; try {
$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)
@ -104,28 +119,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);
} }
} }

32
app/Http/Controllers/HomeController.php Normal file → Executable file
View File

@ -1,13 +1,32 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Vacancy; use App\Vacancy;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
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.
* *
@ -15,13 +34,18 @@ class HomeController extends Controller
*/ */
public function index() public function index()
{ {
$positions = Vacancy::where('vacancyStatus', 'OPEN') $positions = Vacancy::where('vacancyStatus', 'OPEN')
->where('vacancyCount', '<>', 0) ->where('vacancyCount', '<>', 0)
->get(); ->get();
return view('home') return view('home')
->with('positions', $positions); ->with('positions', $positions);
} }
public function pageGiveaway()
{
return view('giveaway');
}
} }

126
app/Http/Controllers/OptionsController.php Normal file → Executable file
View File

@ -1,16 +1,45 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
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.
* *
@ -18,60 +47,59 @@ class OptionsController extends Controller
*/ */
public function index() public function index()
{ {
// TODO: Obtain this from the facade // TODO: Replace with settings package
$options = Option::all();
return view('dashboard.administration.settings') return view('dashboard.administration.settings')
->with('options', $options); ->with([
} 'options' => Options::getCategory('notifications'),
'security' => [ // We could use the method above, but we need to set these names here for greater control in the template. This would nto be feasible for many options, we'd need to use a loop and the category method.
public function saveSettings(Request $request) 'secPolicy' => Options::getOption('pw_security_policy'),
{ 'graceperiod' => Options::getOption('graceperiod'),
if (Auth::user()->can('admin.settings.edit')) 'pwExpiry' => Options::getOption('password_expiry'),
{ 'requiresPMC' => Options::getOption('requireGameLicense'),
Log::debug('Updating application options', [ 'enforce2fa' => Options::getOption('force2fa')
'ip' => $request->ip(), ],
'ua' => $request->userAgent(), 'currentGame' => Options::getOption('currentGame')
'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;
$request->session()->flash('error', 'An error occurred while trying to save settings: ' . $ex->getMessage());
}
} }
if (!isset($errorCond)) public function saveSettings(Request $request): \Illuminate\Http\RedirectResponse
{ {
$request->session()->flash('success', 'Settings saved successfully!'); try {
}
} if (Auth::user()->can('admin.settings.edit')) {
else $this->configurationService->saveConfiguration($request->all());
{
$request->session()->flash('error', 'You do not have permission to update this resource.'); return redirect()
->back()
->with('success', __('Options updated successfully!'));
} }
return redirect()->back(); } catch (OptionNotFoundException | \Exception $ex) {
return redirect()
->back()
->with('error', $ex->getMessage());
}
return redirect()
->back()
->with('error', __('You do not have permission to update this resource.'));
}
public function saveGameIntegration(Request $request)
{
try {
$this->configurationService->saveGameIntegration($request->gamePref);
return redirect()
->back()
->with('success', __('Game preference updated.'));
} catch (InvalidGamePreferenceException $ex) {
return redirect()
->back()
->with('error', $ex->getMessage());
}
} }
} }

116
app/Http/Controllers/ProfileController.php Normal file → Executable file
View File

@ -1,12 +1,30 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Http\Requests\ProfileSave;
use Illuminate\Support\Facades\Log;
use App\Profile;
use App\User;
use App\Facades\IP; use App\Facades\IP;
use App\Http\Requests\ProfileSave;
use App\Services\ProfileService;
use App\User;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
@ -14,18 +32,22 @@ 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')
->with('users', User::with('profile', 'bans')->paginate(9)); ->with('users', User::with('profile', 'bans')->paginate(9));
} }
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);
return view('dashboard.user.profile.userprofile') return view('dashboard.user.profile.userprofile')
@ -36,13 +58,10 @@ class ProfileController extends Controller
'insta' => $socialMediaProfiles['links']['insta'] ?? 'UpdateMe', 'insta' => $socialMediaProfiles['links']['insta'] ?? 'UpdateMe',
'discord' => $socialMediaProfiles['links']['discord'] ?? 'UpdateMe#12345', 'discord' => $socialMediaProfiles['links']['discord'] ?? 'UpdateMe#12345',
]); ]);
} }
// 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);
@ -51,21 +70,26 @@ class ProfileController extends Controller
$roleList = []; $roleList = [];
foreach ($systemRoles as $role) {
foreach($systemRoles as $role) if (in_array($role, $userRoles)) {
{
if (in_array($role, $userRoles))
{
$roleList[$role] = true; $roleList[$role] = true;
} } else {
else
{
$roleList[$role] = false; $roleList[$role] = false;
} }
} }
if (Auth::user()->is($user) || Auth::user()->can('profiles.view.others')) $suspensionInfo = null;
if ($user->isBanned())
{ {
$suspensionInfo = [
'isPermanent' => $user->bans->isPermanent,
'reason' => $user->bans->reason,
'bannedUntil' => $user->bans->bannedUntil
];
}
if (Auth::user()->is($user) || Auth::user()->can('profiles.view.others')) {
return view('dashboard.user.profile.displayprofile') return view('dashboard.user.profile.displayprofile')
->with([ ->with([
'profile' => $user->profile, 'profile' => $user->profile,
@ -73,55 +97,21 @@ class ProfileController extends Controller
'twitter' => $socialMediaProfiles['links']['twitter'] ?? 'UpdateMe', 'twitter' => $socialMediaProfiles['links']['twitter'] ?? 'UpdateMe',
'insta' => $socialMediaProfiles['links']['insta'] ?? 'UpdateMe', 'insta' => $socialMediaProfiles['links']['insta'] ?? 'UpdateMe',
'discord' => $socialMediaProfiles['links']['discord'] ?? 'UpdateMe#12345', 'discord' => $socialMediaProfiles['links']['discord'] ?? 'UpdateMe#12345',
'since' => $createdDate->englishMonth . " " . $createdDate->year, 'since' => $createdDate->englishMonth.' '.$createdDate->year,
'ipInfo' => IP::lookup($user->originalIP), 'ipInfo' => IP::lookup($user->originalIP),
'roles' => $roleList 'roles' => $roleList,
'suspensionInfo' => $suspensionInfo
]); ]);
} else {
abort(403, __('You cannot view someone else\'s profile.'));
} }
else
{
abort(403, 'You cannot view someone else\'s profile.');
}
} }
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();
}
} }

View File

@ -1,10 +0,0 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class ResponseController extends Controller
{
//
}

View File

@ -0,0 +1,35 @@
<?php
namespace App\Http\Controllers;
use App\Facades\Options;
use App\Http\Requests\SaveSecuritySettings;
use App\Services\SecuritySettingsService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use function PHPSTORM_META\map;
class SecuritySettingsController extends Controller
{
private $securityService;
public function __construct(SecuritySettingsService $securityService) {
$this->securityService = $securityService;
}
public function save(SaveSecuritySettings $request)
{
$this->securityService->save($request->secPolicy, [
'graceperiod' => $request->graceperiod,
'pwExpiry' => $request->pwExpiry,
'enforce2fa' => $request->enforce2fa,
'requirePMC' => $request->requirePMC
]);
return redirect()
->back()
->with('success', __('Settings saved.'));
}
}

View File

@ -1,10 +0,0 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class StaffProfileController extends Controller
{
//
}

View File

@ -0,0 +1,199 @@
<?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers;
use App\Exceptions\InvalidInviteException;
use App\Exceptions\PublicTeamInviteException;
use App\Exceptions\UserAlreadyInvitedException;
use App\Http\Requests\EditTeamRequest;
use App\Http\Requests\NewTeamRequest;
use App\Http\Requests\SendInviteRequest;
use App\Mail\InviteToTeam;
use App\Services\TeamService;
use App\Team;
use App\User;
use App\Vacancy;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Mail;
use Mpociot\Teamwork\Exceptions\UserNotInTeamException;
use Mpociot\Teamwork\Facades\Teamwork;
use Mpociot\Teamwork\TeamInvite;
class TeamController extends Controller
{
private $teamService;
public function __construct(TeamService $teamService) {
$this->teamService = $teamService;
}
/**
* Display a listing of the resource.
*
*/
public function index()
{
$this->authorize('index', Team::class);
$teams = Team::with('users.roles')->get();
return view('dashboard.teams.teams')
->with('teams', $teams);
}
/**
* Store a newly created resource in storage.
*
* @param NewTeamRequest $request
* @return RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function store(NewTeamRequest $request)
{
$this->authorize('create', Team::class);
$this->teamService->createTeam($request->teamName, Auth::user()->id);
return redirect()
->back()
->with('success', __('Team successfully created.'));
}
/**
* Show the form for editing the specified resource.
*
* @param Team $team
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Http\Response
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function edit(Team $team)
{
$this->authorize('update', $team);
return view('dashboard.teams.edit-team')
->with([
'team' => $team,
'users' => User::all(),
'vacancies' => Vacancy::with('teams')->get()->all()
]);
}
/**
* Update the specified resource in storage.
*
* @param EditTeamRequest $request
* @param Team $team
* @return RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function update(EditTeamRequest $request, Team $team): RedirectResponse
{
$this->authorize('update', $team);
$team = $this->teamService->updateTeam($team, $request->teamDescription, $request->joinType);
if ($team) {
return redirect()
->to(route('teams.index'))
->with('success', __('Team updated.'));
}
return redirect()
->back()
->with('error', __('An error ocurred while trying to update this team.'));
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
// wip
}
public function invite(SendInviteRequest $request, Team $team): RedirectResponse
{
$this->authorize('invite', $team);
try {
$this->teamService->inviteUser($team, $request->user);
return redirect()
->back()
->with('success', __('User invited successfully!'));
} catch (UserAlreadyInvitedException | PublicTeamInviteException $ex) {
return redirect()
->back()
->with('error', $ex->getMessage());
}
}
public function processInviteAction(Request $request, $action, $token): RedirectResponse
{
try {
$this->teamService->processInvite(Auth::user(), $action, $token);
return redirect()
->to(route('teams.index'))
->with('success', __('Invite processed successfully!'));
} catch (InvalidInviteException $e) {
return redirect()
->back()
->with('error', $e->getMessage());
}
}
public function switchTeam(Request $request, Team $team): RedirectResponse
{
$this->authorize('switchTeam', $team);
try {
Auth::user()->switchTeam($team);
$request->session()->flash('success', __('Switched teams! Your team dashboard will now use this context.'));
} catch (UserNotInTeamException $ex) {
$request->session()->flash('error', __('You can\'t switch to a team you don\'t belong to.'));
}
return redirect()->back();
}
// Since it's a separate form, we shouldn't use the same update method
public function assignVacancies(Request $request, Team $team): RedirectResponse
{
$this->authorize('update', $team);
$message = $this->teamService->updateVacancies($team, $request->assocVacancies);
return redirect()
->back()
->with('success', $message);
}
}

View File

@ -0,0 +1,141 @@
<?php
namespace App\Http\Controllers;
// Most of these namespaces have no effect on the code, however, they're used by IDEs so they can resolve return types and for PHPDocumentor as well
use App\Exceptions\FileUploadException;
use App\Services\TeamFileService;
use App\TeamFile;
use App\Http\Requests\UploadFileRequest;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use League\Flysystem\FileNotFoundException;
// Documentation-purpose namespaces
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contts\View\Factory;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class TeamFileController extends Controller
{
private $fileService;
public function __construct(TeamFileService $fileService) {
$this->fileService = $fileService;
}
/**
* Display a listing of the resource.
*
* @param Request $request
*/
public function index(Request $request)
{
$this->authorize('index', TeamFile::class);
if (is_null(Auth::user()->currentTeam))
{
$request->session()->flash('error', 'Please choose a team before viewing it\'s files.');
return redirect()->to(route('teams.index'));
}
return view('dashboard.teams.team-files')
->with('files', TeamFile::with('team', 'uploader')->paginate(6));
}
/**
* Store a newly created resource in storage.
*
* @param UploadFileRequest $request
* @return RedirectResponse
*/
public function store(UploadFileRequest $request)
{
$this->authorize('store', TeamFile::class);
if (config('demo.is_enabled'))
{
return redirect()
->back()
->with('error', 'This feature is disabled');
}
try {
$caption = $request->caption;
$description = $request->description;
$this->fileService->addFile($request->file('file'), Auth::user()->id, Auth::user()->currentTeam->id, $caption, $description);
return redirect()
->back()
->with('success', __('File uploaded successfully.'));
} catch (FileUploadException $uploadException) {
return redirect()
->back()
->with('error', $uploadException->getMessage());
}
}
public function download(Request $request, TeamFile $teamFile)
{
$this->authorize('download', TeamFile::class);
try
{
return Storage::download($teamFile->fs_location, $teamFile->name);
}
catch (FileNotFoundException $ex)
{
$request->session()->flash('error', 'Sorry, but the requested file could not be found in storage. Sometimes, files may be physically deleted by admins, but not from the app\'s database.');
return redirect()->back();
}
}
/**
* Remove the specified resource from storage.
*
* @param Request $request
* @param \App\TeamFile $teamFile
* @return RedirectResponse
*/
public function destroy(Request $request, TeamFile $teamFile)
{
$this->authorize('delete', $teamFile);
if (config('demo.is_enabled'))
{
return redirect()
->back()
->with('error', 'This feature is disabled');
}
try
{
Storage::delete($teamFile->fs_location);
$teamFile->delete();
$request->session()->flash('success', __('File deleted successfully.'));
}
catch (\Exception $ex)
{
$request->session()->flash('error', __('There was an error deleting the file: :msg', ['msg' => $ex->getMessage()]));
}
return redirect()->back();
}
}

263
app/Http/Controllers/UserController.php Normal file → Executable file
View File

@ -1,95 +1,63 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Ban;
use App\Http\Requests\Add2FASecretRequest;
use App\Http\Requests\ChangeEmailRequest; use App\Http\Requests\ChangeEmailRequest;
use App\Http\Requests\ChangePasswordRequest; use App\Http\Requests\ChangePasswordRequest;
use App\Http\Requests\FlushSessionsRequest;
use App\Http\Requests\DeleteUserRequest; use App\Http\Requests\DeleteUserRequest;
use App\Http\Requests\FlushSessionsRequest;
use App\Http\Requests\Remove2FASecretRequest;
use App\Http\Requests\SearchPlayerRequest; use App\Http\Requests\SearchPlayerRequest;
use App\Http\Requests\UpdateUserRequest; use App\Http\Requests\UpdateUserRequest;
use App\Http\Requests\Add2FASecretRequest; use App\Notifications\ChangedPassword;
use App\Http\Requests\Remove2FASecretRequest; use App\Notifications\EmailChanged;
use App\Traits\DisablesFeatures;
use App\Traits\ReceivesAccountTokens;
use App\User; use App\User;
use App\Ban; use Google2FA;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use App\Facades\UUID;
use App\Notifications\EmailChanged;
use App\Notifications\ChangedPassword;
use Spatie\Permission\Models\Role; use Spatie\Permission\Models\Role;
use Google2FA;
class UserController extends Controller class UserController extends Controller
{ {
use ReceivesAccountTokens;
public function showUsers()
public function showStaffMembers()
{
$this->authorize('viewStaff', User::class);
$staffRoles = [
'reviewer',
'hiringManager',
'admin'
]; // TODO: Un-hardcode this, move to config/roles.php
$users = User::with('roles')->get();
$staffMembers = collect([]);
foreach($users as $user)
{
if (empty($user->roles))
{
Log::debug($user->role->name);
Log::debug('Staff list: User without role detected; Ignoring');
continue;
}
foreach($user->roles as $role)
{
if (in_array($role->name, $staffRoles))
{
$staffMembers->push($user);
continue 2; // Skip directly to the next user instead of comparing more roles for the current user
}
}
}
return view('dashboard.administration.staff-members')
->with([
'users' => $staffMembers
]);
}
public function showPlayers()
{ {
$this->authorize('viewPlayers', User::class); $this->authorize('viewPlayers', User::class);
$users = User::with('roles')->get();
$players = collect([]);
foreach($users as $user)
{
// TODO: Might be problematic if we don't check if the role is user
if (count($user->roles) == 1)
{
$players->push($user);
}
}
return view('dashboard.administration.players') return view('dashboard.administration.players')
->with([ ->with([
'users' => $players, 'users' => User::with('roles')->paginate('6'),
'bannedUserCount' => Ban::all()->count() 'numUsers' => count(User::all()),
'bannedUserCount' => Ban::all()->count(),
]); ]);
} }
public function showPlayersLike(SearchPlayerRequest $request) public function showPlayersLike(SearchPlayerRequest $request)
{ {
$this->authorize('viewPlayers', User::class); $this->authorize('viewPlayers', User::class);
@ -100,18 +68,18 @@ class UserController extends Controller
->orWhere('email', 'LIKE', "%{$searchTerm}%") ->orWhere('email', 'LIKE', "%{$searchTerm}%")
->get(); ->get();
if (!$matchingUsers->isEmpty()) if (! $matchingUsers->isEmpty()) {
{ $request->session()->flash('success', 'There were ' . $matchingUsers->count() . ' user(s) matching your search.'); $request->session()->flash('success', __('There were :usersCount user(s) matching your search.', ['usersCount' => $matchingUsers->count()]));
return view('dashboard.administration.players') return view('dashboard.administration.players')
->with([ ->with([
'users' => $matchingUsers, 'users' => $matchingUsers,
'bannedUserCount' => Ban::all()->count() 'numUsers' => count(User::all()),
'bannedUserCount' => Ban::all()->count(),
]); ]);
} } else {
else $request->session()->flash('error', __('Your search term did not return any results.'));
{
$request->session()->flash('error', 'Your search term did not return any results.');
return redirect(route('registeredPlayerList')); return redirect(route('registeredPlayerList'));
} }
} }
@ -120,14 +88,10 @@ class UserController extends Controller
{ {
$QRCode = null; $QRCode = null;
if (!$request->user()->has2FA()) if (! $request->user()->has2FA()) {
{ if ($request->session()->has('twofaAttemptFailed')) {
if ($request->session()->has('twofaAttemptFailed'))
{
$twoFactorSecret = $request->session()->get('current2FA'); $twoFactorSecret = $request->session()->get('current2FA');
} } else {
else
{
$twoFactorSecret = Google2FA::generateSecretKey(32, ''); $twoFactorSecret = Google2FA::generateSecretKey(32, '');
$request->session()->put('current2FA', $twoFactorSecret); $request->session()->put('current2FA', $twoFactorSecret);
} }
@ -144,97 +108,108 @@ class UserController extends Controller
->with('twofaQRCode', $QRCode); ->with('twofaQRCode', $QRCode);
} }
public function flushSessions(FlushSessionsRequest $request) public function flushSessions(FlushSessionsRequest $request)
{ {
// TODO: Move all log calls to a listener, which binds to an event fired by each significant event, such as this one // TODO: Move all log calls to a listener, which binds to an event fired by each significant event, such as this one
// This will allow for other actions to be performed on certain events (like login failed event) // This will allow for other actions to be performed on certain events (like login failed event)
Auth::logoutOtherDevices($request->currentPasswordFlush); Auth::logoutOtherDevices($request->currentPasswordFlush);
Log::notice('User ' . Auth::user()->name . ' has logged out other devices in their account', Log::notice('User '.Auth::user()->name.' has logged out other devices in their account',
[ [
'originIPAddress' => $request->ip(), 'originIPAddress' => $request->ip(),
'userID' => Auth::user()->id, 'userID' => Auth::user()->id,
'timestamp' => now() 'timestamp' => now(),
]); ]);
$request->session()->flash('success', 'Successfully logged out other devices. Remember to change your password if you think you\'ve been compromised.'); $request->session()->flash('success', __('Successfully logged out other devices. Remember to change your password if you think you\'ve been compromised.'));
return redirect()->back(); return redirect()->back();
} }
public function changePassword(ChangePasswordRequest $request) public function changePassword(ChangePasswordRequest $request)
{ {
if (config('demo.is_enabled')) {
return redirect()
->back()
->with('error', 'This feature is disabled');
}
$user = User::find(Auth::user()->id); $user = User::find(Auth::user()->id);
if (!is_null($user)) if (! is_null($user)) {
{
$user->password = Hash::make($request->newPassword); $user->password = Hash::make($request->newPassword);
$user->password_last_updated = now();
$user->save(); $user->save();
Log::info('User ' . $user->name . ' has changed their password', [ Log::info('User '.$user->name.' has changed their password', [
'originIPAddress' => $request->ip(), 'originIPAddress' => $request->ip(),
'userID' => $user->id, 'userID' => $user->id,
'timestamp' => now() 'timestamp' => now(),
]); ]);
$user->notify(new ChangedPassword()); $user->notify(new ChangedPassword());
Auth::logout(); Auth::logout();
return redirect()->back(); return redirect()->back();
} }
} }
public function changeEmail(ChangeEmailRequest $request) public function changeEmail(ChangeEmailRequest $request)
{ {
if (config('demo.is_enabled')) {
return redirect()
->back()
->with('error', 'This feature is disabled');
}
$user = User::find(Auth::user()->id); $user = User::find(Auth::user()->id);
if (!is_null($user)) if (! is_null($user)) {
{
$user->email = $request->newEmail; $user->email = $request->newEmail;
$user->save(); $user->save();
Log::notice('User ' . $user->name . ' has just changed their contact email address', [ Log::notice('User '.$user->name.' has just changed their contact email address', [
'originIPAddress' => $request->ip(), 'originIPAddress' => $request->ip(),
'userID' => $user->id, 'userID' => $user->id,
'timestamp' => now() 'timestamp' => now(),
]); ]);
$user->notify(new EmailChanged()); $user->notify(new EmailChanged());
$request->session()->flash('success', 'Your email address has been changed!'); $request->session()->flash('success', __('Your email address has been changed!'));
} } else {
else $request->session()->flash('error', __('There has been an error whilst trying to update your account. Please contact administrators.'));
{
$request->session()->flash('error', 'There has been an error whilst trying to update your account. Please contact administrators.');
} }
return redirect()->back(); return redirect()->back();
} }
public function delete(DeleteUserRequest $request, User $user) public function delete(DeleteUserRequest $request, User $user)
{ {
if (config('demo.is_enabled')) {
return redirect()
->back()
->with('error', 'This feature is disabled');
}
$this->authorize('delete', $user); $this->authorize('delete', $user);
if ($request->confirmPrompt == 'DELETE ACCOUNT') if ($request->confirmPrompt == 'DELETE ACCOUNT') {
{
$user->delete(); $user->delete();
$request->session()->flash('success','User deleted successfully. PII has been erased.'); $request->session()->flash('success', __('User deleted successfully.'));
} else {
$request->session()->flash('error', __('Wrong confirmation text! Try again.'));
} }
else
{
$request->session()->flash('error', 'Wrong confirmation text! Try again.');
}
return redirect()->route('registeredPlayerList'); return redirect()->route('registeredPlayerList');
} }
public function update(UpdateUserRequest $request, User $user) public function update(UpdateUserRequest $request, User $user)
{ {
if (config('demo.is_enabled')) {
return redirect()
->back()
->with('error', 'This feature is disabled');
}
$this->authorize('adminEdit', $user); $this->authorize('adminEdit', $user);
// Mass update would not be possible here without extra code, making route model binding useless // Mass update would not be possible here without extra code, making route model binding useless
@ -249,63 +224,60 @@ class UserController extends Controller
$roleDiff = array_diff($existingRoles, $request->roles); $roleDiff = array_diff($existingRoles, $request->roles);
// Adds roles that were selected. Removes roles that aren't selected if the user has them. // Adds roles that were selected. Removes roles that aren't selected if the user has them.
foreach($roleDiff as $deselectedRole) foreach ($roleDiff as $deselectedRole) {
{ if ($user->hasRole($deselectedRole) && $deselectedRole !== 'user') {
if ($user->hasRole($deselectedRole) && $deselectedRole !== 'user')
{
$user->removeRole($deselectedRole); $user->removeRole($deselectedRole);
} }
} }
foreach($request->roles as $role) foreach ($request->roles as $role) {
{ if (! $user->hasRole($role)) {
if (!$user->hasRole($role))
{
$user->assignRole($role); $user->assignRole($role);
} }
} }
$user->save(); $user->save();
$request->session()->flash('success', 'User updated successfully!'); $request->session()->flash('success', __('User updated successfully!'));
return redirect()->back(); return redirect()->back();
} }
public function add2FASecret(Add2FASecretRequest $request) public function add2FASecret(Add2FASecretRequest $request)
{ {
if (config('demo.is_enabled')) {
return redirect()
->back()
->with('error', 'This feature is disabled');
}
$currentSecret = $request->session()->get('current2FA'); $currentSecret = $request->session()->get('current2FA');
$isValid = Google2FA::verifyKey($currentSecret, $request->otp); $isValid = Google2FA::verifyKey($currentSecret, $request->otp);
if ($isValid) if ($isValid) {
{
$request->user()->twofa_secret = $currentSecret; $request->user()->twofa_secret = $currentSecret;
$request->user()->save(); $request->user()->save();
Log::warning('SECURITY: User activated two-factor authentication', [ Log::warning('SECURITY: User activated two-factor authentication', [
'initiator' => $request->user()->email, 'initiator' => $request->user()->email,
'ip' => $request->ip() 'ip' => $request->ip(),
]); ]);
Google2FA::login(); Google2FA::login();
Log::warning('SECURITY: Started two factor session automatically', [ Log::warning('SECURITY: Started two factor session automatically', [
'initiator' => $request->user()->email, 'initiator' => $request->user()->email,
'ip' => $request->ip() 'ip' => $request->ip(),
]); ]);
$request->session()->forget('current2FA'); $request->session()->forget('current2FA');
if ($request->session()->has('twofaAttemptFailed')) if ($request->session()->has('twofaAttemptFailed')) {
$request->session()->forget('twofaAttemptFailed'); $request->session()->forget('twofaAttemptFailed');
$request->session()->flash('success', '2FA succesfully enabled! You\'ll now be prompted for an OTP each time you log in.');
} }
else
{ $request->session()->flash('success', __('2FA succesfully enabled! You\'ll now be prompted for an OTP each time you log in.'));
$request->session()->flash('error', 'Incorrect code. Please reopen the 2FA settings panel and try again.'); } else {
$request->session()->flash('error', __('Incorrect code. Please reopen the 2FA settings panel and try again.'));
$request->session()->put('twofaAttemptFailed', true); $request->session()->put('twofaAttemptFailed', true);
} }
@ -316,42 +288,43 @@ class UserController extends Controller
{ {
Log::warning('SECURITY: Disabling two factor authentication (user initiated)', [ Log::warning('SECURITY: Disabling two factor authentication (user initiated)', [
'initiator' => $request->user()->email, 'initiator' => $request->user()->email,
'ip' => $request->ip() 'ip' => $request->ip(),
]); ]);
$request->user()->twofa_secret = null; $request->user()->twofa_secret = null;
$request->user()->save(); $request->user()->save();
$request->session()->flash('success', 'Two-factor authentication disabled.'); $request->session()->flash('success', __('Two-factor authentication disabled.'));
return redirect()->back(); return redirect()->back();
} }
public function terminate(Request $request, User $user) public function terminate(Request $request, User $user)
{ {
$this->authorize('terminate', User::class); $this->authorize('terminate', User::class);
if (config('demo.is_enabled')) {
return redirect()
->back()
->with('error', 'This feature is disabled');
}
// TODO: move logic to policy // TODO: move logic to policy
if (!$user->isStaffMember() || $user->is(Auth::user())) if (! $user->isStaffMember() || $user->is(Auth::user())) {
{ $request->session()->flash('error', __('You cannot terminate this user.'));
$request->session()->flash('error', 'You cannot terminate this user.');
return redirect()->back(); return redirect()->back();
} }
foreach ($user->roles as $role) foreach ($user->roles as $role) {
{ if ($role->name == 'user') {
if ($role->name == 'user')
{
continue; continue;
} }
$user->removeRole($role->name); $user->removeRole($role->name);
} }
Log::info('User ' . $user->name . ' has just been demoted.'); Log::info('User '.$user->name.' has just been demoted.');
$request->session()->flash('success', 'User terminated successfully.'); $request->session()->flash('success', __('User terminated successfully.'));
//TODO: Dispatch event //TODO: Dispatch event
return redirect()->back(); return redirect()->back();

120
app/Http/Controllers/VacancyController.php Normal file → Executable file
View File

@ -1,40 +1,60 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Http\Requests\VacancyRequest; use App\Facades\JSON;
use App\Http\Requests\VacancyEditRequest;
use App\Vacancy;
use App\User;
use App\Form; use App\Form;
use App\Http\Requests\VacancyEditRequest;
use App\Notifications\VacancyClosed; use App\Http\Requests\VacancyRequest;
use App\Notifications\VacancyStatusUpdated;
use App\User;
use App\Vacancy;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Illuminate\Support\Facades\Auth;
class VacancyController extends Controller class VacancyController extends Controller
{ {
public function index() public function index()
{ {
$this->authorize('viewAny', Vacancy::class); $this->authorize('viewAny', Vacancy::class);
return view('dashboard.administration.positions') return view('dashboard.administration.positions')
->with([ ->with([
'forms' => Form::all(), 'forms' => Form::all(),
'vacancies' => Vacancy::all() 'vacancies' => Vacancy::all(),
]); ]);
} }
public function store(VacancyRequest $request) public function store(VacancyRequest $request)
{ {
$messageIsError = false;
$this->authorize('create', Vacancy::class); $this->authorize('create', Vacancy::class);
$form = Form::find($request->vacancyFormID); $form = Form::find($request->vacancyFormID);
if (!is_null($form)) if (! is_null($form)) {
{
/* note: since we can't convert HTML back to Markdown, we'll have to do the converting when the user requests a page, /* note: since we can't convert HTML back to Markdown, we'll have to do the converting when the user requests a page,
* and leave the database with Markdown only so it can be used and edited everywhere. * and leave the database with Markdown only so it can be used and edited everywhere.
* for several vacancies, this would require looping through all of them and replacing MD with HTML, which is obviously not the most clean solution; * for several vacancies, this would require looping through all of them and replacing MD with HTML, which is obviously not the most clean solution;
@ -49,77 +69,72 @@ class VacancyController extends Controller
'permissionGroupName' => $request->permissionGroup, 'permissionGroupName' => $request->permissionGroup,
'discordRoleID' => $request->discordRole, 'discordRoleID' => $request->discordRole,
'vacancyFormID' => $request->vacancyFormID, 'vacancyFormID' => $request->vacancyFormID,
'vacancyCount' => $request->vacancyCount 'vacancyCount' => $request->vacancyCount,
]); ]);
$request->session()->flash('success', 'Vacancy successfully opened. It will now show in the home page.'); $message = __('Vacancy successfully opened. It will now show in the home page.');
}
else } else {
{ $message = __('You cannot create a vacancy without a valid form.');
$request->session()->flash('error', 'You cannot create a vacancy without a valid form.'); $messageIsError = true;
} }
return redirect()->back(); return redirect()
->back()
->with(($messageIsError) ? 'error' : 'success', $message);
} }
public function updatePositionAvailability(Request $request, $status, Vacancy $vacancy) public function updatePositionAvailability(Request $request, $status, Vacancy $vacancy)
{ {
$this->authorize('update', $vacancy); $this->authorize('update', $vacancy);
if (!is_null($vacancy)) if (! is_null($vacancy)) {
{
$type = 'success'; $type = 'success';
switch ($status) switch ($status) {
{
case 'open': case 'open':
$vacancy->open(); $vacancy->open();
$message = "Position successfully opened!"; $message = __('Position successfully opened!');
break; break;
case 'close': case 'close':
$vacancy->close(); $vacancy->close();
$message = "Position successfully closed!"; $message = __('Position successfully closed!');
foreach(User::all() as $user)
{
if ($user->isStaffMember())
{
$user->notify(new VacancyClosed($vacancy));
}
}
break; break;
default: default:
$message = "Please do not tamper with the button's URLs. To report a bug, please contact an administrator."; $message = __("Please do not tamper with the URLs. To report a bug, please contact an administrator.");
$type = 'error'; $type = 'error';
} }
}
else
{ } else {
$message = "The position you're trying to update doesn't exist!"; $message = __("The position you're trying to update doesn't exist!");
$type = "error"; $type = 'error';
} }
$request->session()->flash($type, $message); if ($type !== 'error') {
return redirect()->back(); Notification::send(User::role('reviewer')->get(), new VacancyStatusUpdated($vacancy, $status));
} }
return redirect()
->back()
->with($type, $message);
}
public function edit(Request $request, Vacancy $vacancy) public function edit(Request $request, Vacancy $vacancy)
{ {
$this->authorize('update', $vacancy); $this->authorize('update', $vacancy);
return view('dashboard.administration.editposition') return view('dashboard.administration.editposition')
->with('vacancy', $vacancy); ->with('vacancy', $vacancy);
} }
public function update(VacancyEditRequest $request, Vacancy $vacancy) public function update(VacancyEditRequest $request, Vacancy $vacancy)
{ {
$this->authorize('update', $vacancy); $this->authorize('update', $vacancy);
@ -130,9 +145,18 @@ class VacancyController extends Controller
$vacancy->save(); $vacancy->save();
$request->session()->flash('success', 'Vacancy successfully updated.'); return redirect()
return redirect()->back(); ->back()
->with('success', __('Vacancy successfully updated.'));
} }
public function delete(Request $request, Vacancy $vacancy)
{
$this->authorize('delete', $vacancy);
$vacancy->delete();
return redirect()
->back()
->with('success', __('Vacancy deleted. All applications associated with it are now gone too.'));
}
} }

29
app/Http/Controllers/VoteController.php Normal file → Executable file
View File

@ -1,18 +1,34 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Application; use App\Application;
use App\Http\Requests\VoteRequest; use App\Http\Requests\VoteRequest;
use App\Jobs\ProcessVoteList;
use App\Vote; use App\Vote;
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 VoteController extends Controller class VoteController extends Controller
{ {
public function vote(VoteRequest $voteRequest, Application $application) public function vote(VoteRequest $voteRequest, Application $application)
{ {
$this->authorize('create', Vote::class); $this->authorize('create', Vote::class);
@ -23,11 +39,10 @@ class VoteController extends Controller
]); ]);
$vote->application()->attach($application->id); $vote->application()->attach($application->id);
Log::info('User '.Auth::user()->name.' has voted in applicant '.$application->user->name.'\'s application', [
Log::info('User ' . Auth::user()->name . ' has voted in applicant ' . $application->user->name . '\'s application', [ 'voteType' => $voteRequest->voteType,
'voteType' => $voteRequest->voteType
]); ]);
$voteRequest->session()->flash('success', 'Your vote has been registered!'); $voteRequest->session()->flash('success', __('Your vote has been counted!'));
// Cron job will run command that processes votes // Cron job will run command that processes votes
return redirect()->back(); return redirect()->back();

25
app/Http/Kernel.php Normal file → Executable file
View File

@ -1,7 +1,27 @@
<?php <?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Http; namespace App\Http;
use App\Http\Middleware\APIAuthenticationMiddleware;
use Illuminate\Foundation\Http\Kernel as HttpKernel; use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel class Kernel extends HttpKernel
@ -41,6 +61,7 @@ class Kernel extends HttpKernel
'api' => [ 'api' => [
'throttle:60,1', 'throttle:60,1',
\Illuminate\Routing\Middleware\SubstituteBindings::class, \Illuminate\Routing\Middleware\SubstituteBindings::class,
APIAuthenticationMiddleware::class
], ],
]; ];
@ -66,10 +87,12 @@ class Kernel extends HttpKernel
'usernameUUID' => \App\Http\Middleware\UsernameUUID::class, 'usernameUUID' => \App\Http\Middleware\UsernameUUID::class,
'forcelogout' => \App\Http\Middleware\ForceLogoutMiddleware::class, 'forcelogout' => \App\Http\Middleware\ForceLogoutMiddleware::class,
'2fa' => \PragmaRX\Google2FALaravel\Middleware::class, '2fa' => \PragmaRX\Google2FALaravel\Middleware::class,
'passwordexpiration' => \App\Http\Middleware\PasswordExpirationMiddleware::class,
'passwordredirect' => \App\Http\Middleware\PasswordExpirationRedirectMiddleware::class,
'localize' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRoutes::class, 'localize' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRoutes::class,
'localizationRedirect' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRedirectFilter::class, 'localizationRedirect' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRedirectFilter::class,
'localeSessionRedirect' => \Mcamara\LaravelLocalization\Middleware\LocaleSessionRedirect::class, 'localeSessionRedirect' => \Mcamara\LaravelLocalization\Middleware\LocaleSessionRedirect::class,
'localeCookieRedirect' => \Mcamara\LaravelLocalization\Middleware\LocaleCookieRedirect::class, 'localeCookieRedirect' => \Mcamara\LaravelLocalization\Middleware\LocaleCookieRedirect::class,
'localeViewPath' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationViewPath::class 'localeViewPath' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationViewPath::class,
]; ];
} }

View File

@ -0,0 +1,65 @@
<?php
namespace App\Http\Middleware;
use App\ApiKey;
use App\Facades\JSON;
use Carbon\Carbon;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str;
class APIAuthenticationMiddleware
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
$key = $request->bearerToken();
if (!is_null($key))
{
// we have a valid discriminator
$discriminator = Str::before($key, '.');
$loneKey = Str::after($key, '.');
$keyRecord = ApiKey::where('discriminator', $discriminator)->first();
if ($keyRecord && Hash::check($loneKey, $keyRecord->secret) && $keyRecord->status == 'active')
{
$keyRecord->last_used = Carbon::now();
$keyRecord->save();
Log::info('Recording API call, see context', [
'uri' => $request->url(),
'name' => Route::currentRouteName(),
'discriminator' => $discriminator,
'ip' => $request->ip()
]);
return $next($request);
}
return JSON::setResponseType('error')
->setStatus('authfail')
->setMessage('Invalid / Revoked API key.')
->setCode(401)
->build();
}
return JSON::setResponseType('error')
->setStatus('malformed_key')
->setMessage('Missing or malformed API key.')
->setCode(400)
->build();
}
}

Some files were not shown because too many files have changed in this diff Show More