107 Commits

Author SHA1 Message Date
1aee4c053f Fix broken code 2021-10-13 04:07:16 +01:00
a880eb65b0 Revert last commits
This reverts commit 2ddef6058d.
2021-10-13 02:57:00 +01:00
2ddef6058d Revert "Revert "Revert "Merge branch 'master' into develop"""
This reverts commit 427c9d1c57.
*facepalm*
2021-10-13 01:55:12 +01:00
41d86de5b0 Revert "Reverse L8 upgrade"
This reverts commit 64d8ffa9d9.
2021-10-13 01:47:23 +01:00
427c9d1c57 Revert "Revert "Merge branch 'master' into develop""
This reverts commit fc6d7d2b18.
2021-10-13 01:47:16 +01:00
64d8ffa9d9 Reverse L8 upgrade 2021-10-13 00:24:52 +00:00
4594973a21 Prev changes 2021-10-13 01:19:56 +01:00
fc6d7d2b18 Revert "Merge branch 'master' into develop"
This reverts commit 42178e26de, reversing
changes made to d876dd6055.
2021-10-13 01:19:04 +01:00
42178e26de Merge branch 'master' into develop 2021-10-12 23:03:36 +00:00
b2b29382bf Merge branch 'develop' 2021-10-13 00:02:42 +01:00
92679e94d5 Add unknown MC status placeholder to cache 2021-10-12 23:01:15 +01:00
d8e836980a Move seeder to right place 2021-10-12 22:46:43 +01:00
8d1e39c43c Replaced ConnectException with ConnectionException
This exception is thrown and handled when Mojang servers are down.
2021-10-12 22:41:24 +01:00
a3d0730808 Update to Laravel & PHP 8 2021-10-12 18:08:15 +01:00
d876dd6055 Update 'README.md' 2021-09-04 13:27:37 +00:00
521810c23c Fix team update issue 2021-09-04 13:50:43 +01:00
e6f84cd09a Fixed broken Laravel Mix
Also disabled IP history logger for demo mode
2021-09-04 02:56:18 +01:00
3f4bc28fd4 Added Demo mode
Demo mode allows to safely run a demo version of the app, with destructive features limited.

Some bugs were also fixed in this commit.
2021-09-04 00:44:54 +01:00
8942623bde Added services
This commit moves most controller logic onto Services. Services are part of the Service-Repository pattern. The models act as repositories.

Services are easily testable and are needed for the upcoming API, in order to avoid duplicated code and to maintain a single source of "truth".

 The User, Vacancy and Vote controllers still need their logic moved onto services.
2021-07-25 22:54:15 +01:00
c739933668 Push suspension logic onto Service
In preparation to the upcoming API, most logic is being moved to services. Models function as repositories.
2021-07-21 19:33:52 +01:00
cbcc1f025a Refactored ban system
Implemented a Reddit-like account suspension system (similar to subreddit bans). This makes it easier to ban users from the app, and the code has also been cleaned up.

The interface was also revamped.
2021-07-20 22:35:49 +01:00
6cda1fe183 More refactoring
Refactored some old code and added missing translation calls.
2021-07-20 10:32:43 +01:00
620453c1e4 Separation of Concerns + old coude cleanup 2021-07-20 00:35:03 +01:00
9baa249ba7 Added logging to API calls 2021-03-31 19:53:23 +01:00
f62ea9669b Finished applications API endpoint group 2021-03-31 19:39:42 +01:00
Miguel N
f267da3760 Update 'README.md' 2021-03-31 15:16:17 +00:00
6940b1816e Merge remote-tracking branch 'origin/develop' into develop 2021-03-31 15:50:55 +01:00
ddd79d38fe API Resources and first endpoints 2021-03-31 03:55:09 +01:00
2bc07d8ca0 Add key management page 2021-03-30 18:16:01 +01:00
99779c9053 API key management interface 2021-03-30 01:27:49 +01:00
6d94263ede JSON response provider 2021-03-29 23:47:55 +01:00
Miguel N
242ba7b31e Update 'resources/views/dashboard/application-rendering/apply.blade.php' 2021-01-29 17:34:45 +00:00
Miguel N
5b39c573b3 Add Markdown support notice 2021-01-29 17:32:43 +00:00
Miguel N
638b2719a8 Blade change 2021-01-29 17:28:29 +00:00
Miguel N
aad04d6d14 Support markdown for application fields 2021-01-29 17:27:03 +00:00
Miguel N
5f6f6f693d Remove linebreak processing
This approach would allow users to directly use HTML in their responses. We'd need to purify HTML on the way out to only allow <br>, otherwise, there'd be XSS concerns.

Rendering as Markdown and letting users know they can use it is a better approach in the long run.
2021-01-29 17:20:28 +00:00
Miguel N
f83b3a6860 Updated linebreak processing 2021-01-29 17:12:36 +00:00
Miguel N
f17bb0e3cc Update 'app/Http/Controllers/ApplicationController.php' 2021-01-29 17:00:35 +00:00
Miguel N
f941980602 % to $ 2021-01-29 16:57:57 +00:00
Miguel N
0f5e812e03 Usability: Turn CRLF/CR/LF into BR 2021-01-29 16:56:29 +00:00
Miguel N
3a56d2bfb2 Removed 100 character limit from submissions 2021-01-29 16:27:39 +00:00
Miguel N
26e5a53efb Reverse route add 2021-01-27 02:35:55 +00:00
Miguel N
685ec75d0c Reverse 2021-01-27 02:31:57 +00:00
Miguel N
8769f279ac Add confirmation route 2021-01-27 02:26:12 +00:00
Miguel N
d234415d38 Logic changes for confirmation dialog
This commit changes the deletion mechanism for forms. Currently, it sets the wanted deletion ID to the session, and redirects the user to the previous page, to open a confirmation dialog for deletion.
2021-01-27 02:23:30 +00:00
Miguel N
9c597eeb65 Update 'resources/views/dashboard/administration/formbuilder.blade.php' 2021-01-27 02:07:34 +00:00
Miguel N
7319d091e2 Add exit popup 2021-01-27 02:01:59 +00:00
14a8e9e9d5 Force users to change password
This commit applies the password_expiration setting to all users.
Users won't be able to do anything other than update password until it's done.
2021-01-06 05:03:38 +00:00
aa2bfac3e5 Show current pw policy in register page
Also adds a warning for when pw policy is set to off
2021-01-06 03:48:14 +00:00
42868be96e Fix 2021-01-06 03:25:19 +00:00
d1142d3e0c Apply license and password settings 2021-01-06 03:21:53 +00:00
3b28bf1cfe Track IP changes 2021-01-06 02:11:47 +00:00
5cf6b2b241 Selectable game integration 2021-01-06 01:55:22 +00:00
3e1a75dfea Set default 0 for password_expiry 2021-01-06 01:30:57 +00:00
3156b0d17d Remove ignoreOptions 2021-01-06 01:29:47 +00:00
abace4e85b Setting categorization system
This categorization system aims to prevent mixing different options together.
2021-01-06 01:29:01 +00:00
baddf3fc76 Save value instead of raw model to cache 2021-01-06 01:02:47 +00:00
2ec2a92645 Saveable settings 2021-01-06 00:57:27 +00:00
33960270f3 Added Sanctum + new options to seeder 2021-01-01 21:01:23 +00:00
8be29f9739 Merge branch 'develop' of git.spacejewel-hosting.com:spacejewelhosting/staffmanager into develop 2020-12-30 00:12:05 +00:00
8b9cb7fbc4 Add 3rd party integrations
This commit adds third party integrations to the settings page.
It will allow users to link the games and services their community is using, to facilitate recruiting in each service.
These settings are not saved yet.
2020-12-30 00:11:50 +00:00
Proximity
e0fc9b2d94 Addeed Development branch warning 2020-12-24 03:59:35 +00:00
29f697d7b2 More footer info 2020-12-21 01:20:46 +00:00
1c0eeb4bb0 Added Gate authorization arguments
Gate Auth arguments were missing for TeamFile and Team controllers.
This means that Gate has no idea where to look for policies, meaning that
the ability passed is perceived literally, causing an Unauthorized error.

Adding the Model with which to authorize the request solved the error since
Gate now knows which policy to look in for permission logic.
2020-12-21 01:02:05 +00:00
2f0fc14825 Merge remote-tracking branch 'origin/develop' into develop 2020-12-21 00:48:35 +00:00
bc8570019c Force TeamSeeder to skip creating existing permissions
TeamSeeder previously created permissions that already existed, if the migration had ran twice. This commit forces the seeder to verify if the permission it's attempting to create already exists, and if not, it creates it and assigns them at the end.

THis prevents an error from appearing if these permissions were created manually. In this case, the seeder will only assign the permissions instead of creating them.
2020-12-21 00:48:21 +00:00
aaaf56d415 Removed stray message from players tab 2020-12-20 12:57:41 -05:00
79571d8b4c Fixed variable caps 2020-12-20 12:14:27 -05:00
035e94421e Fixed variable . 2020-12-20 12:12:58 -05:00
a72abb9147 Updated footer variables 2020-12-20 12:11:25 -05:00
Proximity
96ebdc554e Update Description again 2020-12-20 12:10:13 -05:00
Proximity
ece01fc71f Change header description 2020-12-20 12:06:48 -05:00
root
6f08b852f4 Merge branch 'develop' of https://code.spacejewel-hosting.com/spacejewelhosting/staffmanager into develop 2020-12-20 11:53:40 -05:00
Miguel Nogueira
889c771454 Remove StyleCI used at bitbucket 2020-12-20 16:53:14 +00:00
root
2e78d8c321 Updated Font Awesome 2020-12-20 11:53:09 -05:00
Proximity
d7331b2dc1 Added GNU License comment. 2020-12-20 16:39:30 +00:00
Miguel Nogueira
a8107a5421 Merge pull request 'Footer changes' (#5) from Proximity/staffmanager:develop into develop
Reviewed-on: https://code.spacejewel-hosting.com/spacejewelhosting/staffmanager/pulls/5
2020-12-20 16:36:33 +00:00
Proximity
bf9b6d43f3 Update from GitHub to Gitea 2020-12-20 15:37:30 +00:00
Proximity
7fef7a0ac7 Added APP_NAME/URL to main page footer. 2020-12-20 15:36:32 +00:00
Miguel Nogueira
7c663dcc02 Merge pull request 'Make pretty' (#4) from Proximity/staffmanager:develop into develop
Reviewed-on: https://code.spacejewel-hosting.com/spacejewelhosting/staffmanager/pulls/4
2020-12-20 14:15:47 +00:00
Proximity
37c22f4377 Merge branch 'develop' into develop 2020-12-20 11:30:19 +00:00
Proximity
d3d1f6f2b1 Update 'README.md' 2020-12-20 11:29:44 +00:00
Proximity
36f9e4f9f0 Added links to used 2020-12-20 11:28:57 +00:00
Proximity
d8dac401cb Make pretty 2020-12-20 05:55:03 +00:00
Miguel Nogueira
a04510ac36 Merge pull request 'Fixed carousel width' (#3) from Proximity/staffmanager:develop into develop
Reviewed-on: https://code.spacejewel-hosting.com/spacejewelhosting/staffmanager/pulls/3
2020-12-20 04:40:54 +00:00
Proximity
a7d0e05188 Fixed carousel width 2020-12-20 04:37:00 +00:00
975f0a69c7 Removed now-unsupported message option 2020-12-19 15:37:15 +00:00
a1edef026f Fix naming typo 2020-12-19 04:09:32 +00:00
cf65569702 Un-hardcode configuration value 2020-12-19 03:13:27 +00:00
85c719c24d Fix bad key name 2020-12-19 01:33:59 +00:00
0c667d74ef Refactored seeders 2020-12-08 14:56:19 +00:00
a206782187 Added TeamFile Authorization Policy 2020-12-08 03:09:17 +00:00
7323ffec2b Added Team Authorization Policy 2020-12-08 02:58:10 +00:00
6518ad3447 Fixed #2
This method would always fail for cached options because at the end it was expecting an Eloquent model, which would never be populated if the requested value was cached in the first place.

Uncached requests wouldn't fail because the exec path would lead to $value being a Model.

Also removed reference to old feature causing application page to crash.
2020-12-08 00:07:53 +00:00
8882804c6e Menu changes 2020-12-07 20:28:18 +00:00
1fae91a62b Remove view file button 2020-12-07 18:42:56 +00:00
0bdb6cf2fb Minor fixes 2020-12-07 17:48:15 +00:00
32c01f6e0b Removed useless feature 2020-11-03 03:00:03 +00:00
d53e8135ee Reverted recent accidental changes 2020-11-02 22:04:57 +00:00
e4fb438721 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	app/Http/Controllers/TeamFileController.php
#	app/TeamFile.php
#	config/adminlte.php
#	routes/web.php
2020-11-02 21:50:30 +00:00
96aa01b9c6 Recent changes 2020-11-02 21:44:05 +00:00
5df3f965ef Merged in revert-pr-6 (pull request #7)
Revert "Apply fixes from StyleCI (pull request #6)"
2020-10-21 00:31:11 +00:00
4eb115d165 Revert "Apply fixes from StyleCI (pull request #6)"
This reverts pull request #6.

> This pull request applies code style fixes from an analysis carried out by [StyleCI](https://bitbucket.styleci.io).
> 
> For more information, click [here](https://bitbucket.styleci.io/analyses/a2Jl7D).
2020-10-21 00:29:50 +00:00
0433ce7693 Merged in analysis-a2Jl7D (pull request #6)
Apply fixes from StyleCI
2020-10-21 00:24:54 +00:00
773ec570d9 Apply fixes from StyleCI 2020-10-21 00:01:41 +00:00
53c23f3698 Changed branding for the repo 2020-10-21 00:01:32 +00:00
2bdb2f2dce Merged in analysis-aoLDWj (pull request #5)
Apply fixes from StyleCI
2020-10-11 01:56:01 +00:00
584 changed files with 8899 additions and 5322 deletions

0
.editorconfig Normal file → Executable file
View File

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

@@ -7,10 +7,18 @@ APP_LOGO="https://www.raspberrypi.org/app/uploads/2020/05/Raspberry-Pi-OS-downlo
APP_SITEHOMEPAGE=""
# This can be your main homepage, other than this site itself
LOG_CHANNEL=stack
# Forces ssl connections even if the environment is set to "local".
# Void if env is production.
NONPROD_FORCE_SECURE=false
# Disables certain features for security purposes while running an open authentication system
# Enable only for demonostration purposes
DEMO_MODE=false
LOG_CHANNEL=daily
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_HOST=z
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
@@ -29,7 +37,7 @@ IPGEO_API_URL="https://api.ipgeolocation.io/ipgeo"
ARCANEDEV_LOGVIEWER_MIDDLEWARE=web,auth,can:admin.maintenance.logs.view
RELEASE=staffmanagement@0.6.1
RELEASE=0.6.2
SLACK_INTEGRATION_WEBHOOK=

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

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

@@ -1,153 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/database/factories" isTestSource="false" packagePrefix="Database\Factories\" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" packagePrefix="Tests\" />
<sourceFolder url="file://$MODULE_DIR$/database/seeders" isTestSource="false" packagePrefix="Database\Seeders\" />
<sourceFolder url="file://$MODULE_DIR$/spec" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/app" isTestSource="false" packagePrefix="App\" />
<excludeFolder url="file://$MODULE_DIR$/vendor/almasaeed2010/adminlte" />
<excludeFolder url="file://$MODULE_DIR$/vendor/asm89/stack-cors" />
<excludeFolder url="file://$MODULE_DIR$/vendor/awssat/discord-notification-channel" />
<excludeFolder url="file://$MODULE_DIR$/vendor/barryvdh/laravel-debugbar" />
<excludeFolder url="file://$MODULE_DIR$/vendor/berkayk/onesignal-laravel" />
<excludeFolder url="file://$MODULE_DIR$/vendor/brick/math" />
<excludeFolder url="file://$MODULE_DIR$/vendor/clue/stream-filter" />
<excludeFolder url="file://$MODULE_DIR$/vendor/composer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/dnoegel/php-xdg-base-dir" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/cache" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/dbal" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/event-manager" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/inflector" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/instantiator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/lexer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/dragonmantank/cron-expression" />
<excludeFolder url="file://$MODULE_DIR$/vendor/egulias/email-validator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/facade/flare-client-php" />
<excludeFolder url="file://$MODULE_DIR$/vendor/facade/ignition" />
<excludeFolder url="file://$MODULE_DIR$/vendor/facade/ignition-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/fideloper/proxy" />
<excludeFolder url="file://$MODULE_DIR$/vendor/filp/whoops" />
<excludeFolder url="file://$MODULE_DIR$/vendor/fruitcake/laravel-cors" />
<excludeFolder url="file://$MODULE_DIR$/vendor/fzaninotto/faker" />
<excludeFolder url="file://$MODULE_DIR$/vendor/graham-campbell/result-type" />
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/guzzle" />
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/promises" />
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/psr7" />
<excludeFolder url="file://$MODULE_DIR$/vendor/hamcrest/hamcrest-php" />
<excludeFolder url="file://$MODULE_DIR$/vendor/http-interop/http-factory-guzzle" />
<excludeFolder url="file://$MODULE_DIR$/vendor/jean85/pretty-package-versions" />
<excludeFolder url="file://$MODULE_DIR$/vendor/jeroennoten/laravel-adminlte" />
<excludeFolder url="file://$MODULE_DIR$/vendor/laravel/framework" />
<excludeFolder url="file://$MODULE_DIR$/vendor/laravel/tinker" />
<excludeFolder url="file://$MODULE_DIR$/vendor/laravel/ui" />
<excludeFolder url="file://$MODULE_DIR$/vendor/league/commonmark" />
<excludeFolder url="file://$MODULE_DIR$/vendor/league/flysystem" />
<excludeFolder url="file://$MODULE_DIR$/vendor/league/mime-type-detection" />
<excludeFolder url="file://$MODULE_DIR$/vendor/maximebf/debugbar" />
<excludeFolder url="file://$MODULE_DIR$/vendor/mcamara/laravel-localization" />
<excludeFolder url="file://$MODULE_DIR$/vendor/mockery/mockery" />
<excludeFolder url="file://$MODULE_DIR$/vendor/monolog/monolog" />
<excludeFolder url="file://$MODULE_DIR$/vendor/mpociot/teamwork" />
<excludeFolder url="file://$MODULE_DIR$/vendor/myclabs/deep-copy" />
<excludeFolder url="file://$MODULE_DIR$/vendor/nesbot/carbon" />
<excludeFolder url="file://$MODULE_DIR$/vendor/nikic/php-parser" />
<excludeFolder url="file://$MODULE_DIR$/vendor/nunomaduro/collision" />
<excludeFolder url="file://$MODULE_DIR$/vendor/opis/closure" />
<excludeFolder url="file://$MODULE_DIR$/vendor/paragonie/random_compat" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phar-io/manifest" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phar-io/version" />
<excludeFolder url="file://$MODULE_DIR$/vendor/php-http/client-common" />
<excludeFolder url="file://$MODULE_DIR$/vendor/php-http/discovery" />
<excludeFolder url="file://$MODULE_DIR$/vendor/php-http/guzzle6-adapter" />
<excludeFolder url="file://$MODULE_DIR$/vendor/php-http/httplug" />
<excludeFolder url="file://$MODULE_DIR$/vendor/php-http/message" />
<excludeFolder url="file://$MODULE_DIR$/vendor/php-http/message-factory" />
<excludeFolder url="file://$MODULE_DIR$/vendor/php-http/promise" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpdocumentor/reflection-common" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpdocumentor/reflection-docblock" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpdocumentor/type-resolver" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpoption/phpoption" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpspec/prophecy" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-code-coverage" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-file-iterator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-invoker" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-text-template" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-timer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-token-stream" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/phpunit" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/container" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/event-dispatcher" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-client" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-factory" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-message" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/log" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/simple-cache" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psy/psysh" />
<excludeFolder url="file://$MODULE_DIR$/vendor/ralouphie/getallheaders" />
<excludeFolder url="file://$MODULE_DIR$/vendor/ramsey/collection" />
<excludeFolder url="file://$MODULE_DIR$/vendor/ramsey/uuid" />
<excludeFolder url="file://$MODULE_DIR$/vendor/scrivo/highlight.php" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/cli-parser" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/code-unit" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/code-unit-reverse-lookup" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/comparator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/complexity" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/diff" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/environment" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/exporter" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/global-state" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/lines-of-code" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/object-enumerator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/object-reflector" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/recursion-context" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/resource-operations" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/type" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/version" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sentry/sentry" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sentry/sentry-laravel" />
<excludeFolder url="file://$MODULE_DIR$/vendor/spatie/laravel-permission" />
<excludeFolder url="file://$MODULE_DIR$/vendor/swiftmailer/swiftmailer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/console" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/css-selector" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/debug" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/deprecation-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/error-handler" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/event-dispatcher" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/event-dispatcher-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/finder" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/http-client" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/http-client-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/http-foundation" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/http-kernel" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/mime" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/options-resolver" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-ctype" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-iconv" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-grapheme" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-idn" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-normalizer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-mbstring" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php72" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php73" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php80" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-uuid" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/process" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/psr-http-message-bridge" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/routing" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/service-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/string" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/translation" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/translation-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/var-dumper" />
<excludeFolder url="file://$MODULE_DIR$/vendor/theseer/tokenizer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/tijsverkoyen/css-to-inline-styles" />
<excludeFolder url="file://$MODULE_DIR$/vendor/vlucas/phpdotenv" />
<excludeFolder url="file://$MODULE_DIR$/vendor/voku/portable-ascii" />
<excludeFolder url="file://$MODULE_DIR$/vendor/webmozart/assert" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

0
.idea/laravel-plugin.xml generated Normal file → Executable file
View File

0
.idea/misc.xml generated Normal file → Executable file
View File

2
.idea/modules.xml generated Normal file → Executable file
View File

@@ -2,7 +2,7 @@
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/hrm-mcserver.iml" filepath="$PROJECT_DIR$/.idea/hrm-mcserver.iml" />
<module fileurl="file://$PROJECT_DIR$/../rbrecruiter/.idea/rbrecruiter.iml" filepath="$PROJECT_DIR$/../rbrecruiter/.idea/rbrecruiter.iml" />
</modules>
</component>
</project>

1
.idea/php.xml generated Normal file → Executable file
View File

@@ -153,6 +153,7 @@
<path value="$PROJECT_DIR$/vendor/awssat/discord-notification-channel" />
<path value="$PROJECT_DIR$/vendor/berkayk/onesignal-laravel" />
<path value="$PROJECT_DIR$/vendor/symfony/psr-http-message-bridge" />
<path value="$PROJECT_DIR$/vendor/laravel/sanctum" />
</include_path>
</component>
<component name="PhpProjectSharedConfiguration" php_language_level="7.3" />

0
.idea/phpunit.xml generated Normal file → Executable file
View File

0
.idea/vcs.xml generated Normal file → Executable file
View File

0
.phive/phars.xml Normal file → Executable file
View File

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

0
.vscode/launch.json vendored Normal file → Executable file
View File

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

View File

@@ -1,6 +1,6 @@
# Raspberry Teams - The Simple Staff Application Manager v 0.6.2 [![Crowdin](https://badges.crowdin.net/raspberry-staff-manager/localized.svg)](https://crowdin.com/project/raspberry-staff-manager) [![StyleCI](https://bitbucket.styleci.io/repos/2513833655827911319/shield?branch=develop)](https://bitbucket.styleci.io/repos/2513833655827911319?branch=develop)
## The quick and pain-free staff application manager
# RB Recruiter v 0.6.2 [![Crowdin](https://badges.crowdin.net/raspberry-staff-manager/localized.svg)](https://crowdin.com/project/raspberry-staff-manager)
## 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?
@@ -42,11 +42,13 @@ Many other features are currently planned for this app, such as:
# Technical overview
Tech stack:
- [Laravel 7](https://laravel.com/)
- Eloquent ORM
- AdminLTE / Bootstrap 4
- jQuery / Plain Javascript
- vueJS (in the future)
- [Laravel 8](https://laravel.com/)
- [Eloquent ORM](https://laravel.com/docs/5.0/eloquent)
- [AdminLTE](https://adminlte.io/) /
- [Bootstrap 4](https://getbootstrap.com/docs/4.0/getting-started/introduction/)
- [jQuery](https://jquery.com/)
- [Bootstrap 4](https://getbootstrap.com/)
- [Icons by FontAwesome](https://fontawesome.com/)
# Stability
@@ -63,7 +65,7 @@ Tech stack:
# Software Requirements
- ``composer`` (min version: 1.8.4)
- ``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

0
SECURITY.md Normal file → Executable file
View File

25
app/ApiKey.php Normal file
View File

@@ -0,0 +1,25 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class ApiKey extends Model
{
use HasFactory;
protected $fillable = [
'name',
'status',
'discriminator',
'last_used',
'secret',
'owner_user_id'
];
public function user()
{
return $this->belongsTo('App\User', 'owner_user_id', 'id');
}
}

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

@@ -33,6 +33,11 @@ class Application extends Model
];
public function oneoffApplicant()
{
return $this->hasOne('App\OneoffApplicant', 'application_id', 'id');
}
public function user()
{
return $this->belongsTo('App\User', 'applicantUserID', 'id');
@@ -64,4 +69,12 @@ class Application extends Model
'applicationStatus' => $status,
]);
}
public function isOneoff()
{
return $this->user->id == 1; // ID 1 is always the ghost
}
}

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

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

@@ -30,13 +30,13 @@ class Ban extends Model
'userID',
'reason',
'bannedUntil',
'userAgent',
'isPermanent',
'authorUserID',
];
public $dates = [
'bannedUntil',
'suspendedUntil',
];
public function user()

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

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

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

View File

@@ -1,24 +1,5 @@
<?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 Illuminate\Console\Command;
@@ -58,89 +39,103 @@ class Install extends Command
public function handle()
{
$basePath = base_path();
if (Storage::disk('local')->missing('INSTALLED')) {
$this->info('[!! Welcome to Rasberry Teams !!]');
$this->info('>> Installing...');
$this->call('down', [
'--message' => 'Down for maintenance. We\'ll be right back!',
]);
if (Storage::disk('local')->missing('INSTALLED'))
{
copy($basePath.'/.env.example', $basePath.'/.env');
$this->call('key:generate');
$this->info('>> Installing and preparing dependencies. This may take a while, depending on your computer.');
$this->info('[!! Welcome to Rasberry Teams !!]');
$this->info('>> Installing...');
$this->call('down', [
'--message' => 'Down for maintenance. We\'ll be right back!'
]);
$npmOut = 0;
$npmMessages = [];
copy($basePath . '/.env.example', $basePath . '/.env');
$this->call('key:generate');
$npmBuildOut = 0;
$npmBuildMessages = [];
$this->info('>> Installing and preparing dependencies. This may take a while, depending on your computer.');
exec('cd '.$basePath.' && npm install --silent', $npmBuildOut, $npmOut);
exec('cd '.$basePath.'&& npm run dev --silent', $npmBuildMessages, $npmBuildOut);
$npmOut = 0;
$npmMessages = [];
if ($npmOut !== 0 && $npmBuildOut !== 0) {
$this->error('[!] One or more errors have ocurred whilst attempting to install dependencies.');
$this->error('[!] It is recommended to run this command again, and report a bug if it keeps happening.');
$npmBuildOut = 0;
$npmBuildMessages = [];
return false;
}
exec('cd ' . $basePath . ' && npm install --silent', $npmBuildOut, $npmOut);
exec('cd ' . $basePath . '&& npm run dev --silent', $npmBuildMessages, $npmBuildOut);
$settings = [];
$this->info('>> Configuring application - We\'re going to ask a few questions here!');
do {
$this->info('== Database Settings (1/6) ==');
if($npmOut !== 0 && $npmBuildOut !== 0)
{
$this->error('[!] One or more errors have ocurred whilst attempting to install dependencies.');
$this->error('[!] It is recommended to run this command again, and report a bug if it keeps happening.');
$settings['DB_USERNAME'] = $this->ask('Database username');
$settings['DB_PASSWORD'] = $this->secret('Database password (Input won\'t be seen)');
$settings['DB_DATABASE'] = $this->ask('Database name');
$settings['DB_PORT'] = $this->ask('Database port');
$settings['DB_HOST'] = $this->ask('Database hostname');
return false;
}
$this->info('== Antispam Settings (2/6) (Recaptcha v2) ==');
$settings['RECAPTCHA_SITE_KEY'] = $this->ask('Site key');
$settings['RECAPTCHA_PRIVATE_KEY'] = $this->ask('Private site key');
$this->info('== IP Geolocation Settings (3/6) (refer to README.md) ==');
$settings['IPGEO_API_KEY'] = $this->ask('API Key');
$this->info('== Notification Settings (4/6) (Email) ==');
$settings['MAIL_USERNAME'] = $this->ask('SMTP Username');
$settings['MAIL_PASSWORD'] = $this->secret('SMTP Password (Input won\'t be seen)');
$settings['MAIL_PORT'] = $this->ask('SMTP Server Port');
$settings['MAIL_HOST'] = $this->ask('SMTP Server Hostname');
$settings['MAIL_FROM'] = $this->ask('E-mail address to send from: ');
$settings = [];
$this->info('== Notification Settings (5/6) (Slack) ==');
$settings['SLACK_INTEGRATION_WEBHOOK'] = $this->ask('Integration webhook URL');
$this->info('>> Configuring application - We\'re going to ask a few questions here!');
do
{
$this->info('== Database Settings (1/6) ==');
$this->info('== Web Settings (6/6) ==');
$settings['APP_URL'] = $this->ask('Application\'s URL (ex. https://where.you.installed.theapp.com): ');
$settings['APP_LOGO'] = $this->ask('App logo (Link to an image): ');
$settings['APP_SITEHOMEPAGE'] = $this->ask('Site homepage (appears in the main header): ');
} while (! $this->confirm('Are you sure you want to save these settings? You can always go back and try again.'));
$settings['DB_USERNAME'] = $this->ask('Database username');
$settings['DB_PASSWORD'] = $this->secret('Database password (Input won\'t be seen)');
$settings['DB_DATABASE'] = $this->ask('Database name');
$settings['DB_PORT'] = $this->ask('Database port');
$settings['DB_HOST'] = $this->ask('Database hostname');
foreach ($settings as $keyname => $value) {
$this->call('environment:modify', [
'key' => $keyname,
'value' => $value,
]);
}
$this->info('== Antispam Settings (2/6) (Recaptcha v2) ==');
$settings['RECAPTCHA_SITE_KEY'] = $this->ask('Site key');
$settings['RECAPTCHA_PRIVATE_KEY'] = $this->ask('Private site key');
$this->info('>> Saved configuration settings!');
$this->info('>> Preparing database...');
$this->info('== IP Geolocation Settings (3/6) (refer to README.md) ==');
$settings['IPGEO_API_KEY'] = $this->ask('API Key');
$this->callSilent('config:cache');
$this->call('migrate');
$this->call('db:seed');
$this->info('== Notification Settings (4/6) (Email) ==');
$settings['MAIL_USERNAME'] = $this->ask('SMTP Username');
$settings['MAIL_PASSWORD'] = $this->secret('SMTP Password (Input won\'t be seen)');
$settings['MAIL_PORT'] = $this->ask('SMTP Server Port');
$settings['MAIL_HOST'] = $this->ask('SMTP Server Hostname');
$settings['MAIL_FROM_ADDRESS'] = $this->ask('E-mail address to send from');
touch($basePath.'/INSTALLED');
$this->info('== Notification Settings (5/6) (Slack) ==');
$settings['SLACK_INTEGRATION_WEBHOOK'] = $this->ask('Integration webhook URL');
$this->call('up');
$this->info('>> All done! Visit '.$basePath.' to start using your brand new installation of Raspberry Teams!');
} else {
$this->error('[!] The application is already installed!');
$this->info('== Web Settings (6/6) ==');
$settings['APP_URL'] = $this->ask('Application\'s URL (ex. https://where.you.installed.theapp.com): ');
$settings['APP_LOGO'] = $this->ask('App logo (Link to an image): ');
$settings['APP_SITEHOMEPAGE'] = $this->ask('Site homepage (appears in the main header): ');
} while(!$this->confirm('Are you sure you want to save these settings? You can always go back and try again.'));
foreach($settings as $keyname => $value)
{
$this->call('environment:modify', [
'key' => $keyname,
'value' => $value
]);
}
$this->info('>> Saved configuration settings!');
$this->info('>> Preparing database...');
$this->callSilent('config:cache');
$this->call('migrate');
$this->call('db:seed');
touch($basePath . '/INSTALLED');
$this->call('up');
$this->info('>> All done! Visit ' . $basePath . ' to start using your brand new installation of Raspberry Teams!');
}
else
{
$this->error('[!] The application is already installed!');
}
}
}

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

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

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

@@ -21,7 +21,7 @@
namespace App\Console;
use App\Jobs\CleanBans;
use App\Jobs\ProcessDueSuspensions;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@@ -50,7 +50,7 @@ class Kernel extends ConsoleKernel
->daily();
// Production value: Every day
$schedule->job(new CleanBans)
$schedule->job(new ProcessDueSuspensions)
->daily();
// Production value: Every day
}

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

@@ -38,13 +38,18 @@ class IP
'ip' => $IP,
];
// TODO: Maybe unwrap this? Methods are chained here
return json_decode(Cache::remember($IP, 3600, function () use ($IP) {
return Http::get(config('general.urls.ipapi.ipcheck'), [
'apiKey' => config('general.keys.ipapi.apikey'),
'ip' => $IP,
])->body();
}));
if (!config('demo.is_enabled')) {
return json_decode(Cache::remember($IP, 3600, function () use ($IP) {
return Http::get(config('general.urls.ipapi.ipcheck'), [
'apiKey' => config('general.keys.ipapi.apikey'),
'ip' => $IP,
])->body();
}));
}
return new class {
public $message = "This feature is disabled.";
};
}
}

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

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

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

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

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
{
//
}

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

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
{
//
}

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

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';
}
}

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

17
app/Facades/JSON.php Normal 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';
}
}

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

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

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

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

@@ -71,7 +71,7 @@ class ContextAwareValidator
$validator = [];
if ($includeFormName) {
$validator['formName'] = 'required|string|max:100';
$validator['formName'] = 'required|string';
}
foreach ($fields as $fieldName => $field) {

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

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

@@ -21,35 +21,64 @@
namespace App\Helpers;
use App\Exceptions\EmptyOptionsException;
use App\Exceptions\OptionNotFoundException;
use App\Options as Option;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
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
{
/**
* 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
{
$value = Cache::get($option);
if (is_null($value)) {
Log::debug('Option '.$option.'not found in cache, refreshing from database');
$value = Option::where('option_name', $option)->first();
if (is_null($value)) {
throw new \Exception('This option does not exist.');
throw new OptionNotFoundException('This option does not exist.');
}
Cache::put($option, $value);
Cache::put($option, $value->option_value);
Cache::put($option.'_desc', 'Undefined description');
return $value->option_value;
}
return $value->option_value;
return $value;
}
public function setOption(string $option, string $value, string $description)
// 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_name' => $option,
'option_value' => $value,
'friendly_name' => $description,
'option_category' => $category
]);
Cache::put($option, $value, now()->addDay());
@@ -91,7 +120,7 @@ class Options
Cache::put('option_name', $newValue, now()->addDay());
} else {
throw new \Exception('This option does not exist.');
throw new OptionNotFoundException('This option does not exist.');
}
}

View File

@@ -0,0 +1,95 @@
<?php
namespace App\Http\Controllers;
use App\ApiKey;
use App\Http\Requests\CreateApiKeyRequest;
use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
class ApiKeyController extends Controller
{
public function index()
{
$this->authorize('viewAny', ApiKey::class);
return view('dashboard.administration.keys')
->with('keys', ApiKey::all());
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
*/
public function store(CreateApiKeyRequest $request)
{
$this->authorize('create', ApiKey::class);
$discriminator = "#" . bin2hex(random_bytes(7));
$secret = bin2hex(random_bytes(32));
$key = ApiKey::create([
'name' => $request->keyName,
'discriminator' => $discriminator,
'secret' => Hash::make($secret),
'status' => 'active',
'owner_user_id' => Auth::user()->id
]);
if ($key)
{
$request->session()->flash('success', __('Key successfully registered!'));
$request->session()->flash('finalKey', $discriminator . '.' . $secret);
return redirect()
->back();
}
return redirect()
->back()
->with('error', __('An error occurred whilst trying to create an API key.'));
}
public function revokeKey(Request $request, ApiKey $key)
{
$this->authorize('update', $key);
if ($key->status == 'active')
{
$key->status = 'disabled';
$key->save();
}
else
{
return redirect()
->back()
->with('error', __('Key already revoked.'));
}
return redirect()
->back()
->with('success', __('Key revoked. Apps using this key will stop working.'));
}
/**
* Remove the specified resource from storage.
*/
public function destroy($id)
{
$key = ApiKey::findOrFail($id);
$this->authorize('delete', $key);
$key->delete();
return redirect()
->back()
->with('success', __('Key deleted successfully. Apps using this key will stop working.'));
}
}

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

@@ -22,32 +22,25 @@
namespace App\Http\Controllers;
use App\Application;
use App\Events\ApplicationDeniedEvent;
use App\Notifications\ApplicationMoved;
use App\Notifications\NewApplicant;
use App\Response;
use App\User;
use App\Vacancy;
use ContextAwareValidator;
use App\Exceptions\ApplicationNotFoundException;
use App\Exceptions\IncompleteApplicationException;
use App\Exceptions\UnavailableApplicationException;
use App\Exceptions\VacancyNotFoundException;
use App\Services\ApplicationService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
class ApplicationController extends Controller
{
private function canVote($votes)
{
$allvotes = collect([]);
foreach ($votes as $vote) {
if ($vote->userID == Auth::user()->id) {
$allvotes->push($vote);
}
}
private $applicationService;
return ($allvotes->count() == 1) ? false : true;
public function __construct(ApplicationService $applicationService) {
$this->applicationService = $applicationService;
}
public function showUserApps()
{
return view('dashboard.user.applications')
@@ -58,7 +51,7 @@ class ApplicationController extends Controller
{
$this->authorize('view', $application);
if (! is_null($application)) {
if (!is_null($application)) {
return view('dashboard.user.viewapp')
->with(
[
@@ -67,178 +60,88 @@ class ApplicationController extends Controller
'structuredResponses' => json_decode($application->response->responseData, true),
'formStructure' => $application->response->form,
'vacancy' => $application->response->vacancy,
'canVote' => $this->canVote($application->votes),
'canVote' => $this->applicationService->canVote($application->votes),
]
);
} else {
$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();
}
public function showAllApps()
public function showAllApps(Request $request)
{
$this->authorize('viewAny', Application::class);
return view('dashboard.appmanagement.all')
->with('applications', Application::paginate(6));
->with('applications', Application::all());
}
public function showAllPendingApps()
public function renderApplicationForm($vacancySlug)
{
$this->authorize('viewAny', Application::class);
return view('dashboard.appmanagement.outstandingapps')
->with('applications', Application::where('applicationStatus', 'STAGE_SUBMITTED')->get());
}
public function showPendingInterview()
{
$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;
}
try {
return $this->applicationService->renderForm($vacancySlug);
}
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.');
catch (ApplicationNotFoundException $ex) {
return redirect()
->back()
->with('error', $ex->getMessage());
}
}
public function saveApplicationAnswers(Request $request, $vacancySlug)
{
$vacancy = Vacancy::with('forms')->where('vacancySlug', $vacancySlug)->get();
try {
if ($vacancy->first()->vacancyCount == 0 || $vacancy->first()->vacancyStatus !== 'OPEN') {
$request->session()->flash('error', 'This application is unavailable.');
$this->applicationService->fillForm(Auth::user(), $request->all(), $vacancySlug);
return redirect()->back();
} catch (VacancyNotFoundException | IncompleteApplicationException | UnavailableApplicationException $e) {
return redirect()
->back()
->with('error', $e->getMessage());
}
Log::info('Processing new application!');
$formStructure = json_decode($vacancy->first()->forms->formStructure, true);
$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();
return redirect()
->to(route('showUserApps'))
->with('success', __('Thank you! Your application has been processed and our team will get to it shortly.'));
}
public function updateApplicationStatus(Request $request, Application $application, $newStatus)
{
$messageIsError = false;
$this->authorize('update', Application::class);
switch ($newStatus) {
case 'deny':
event(new ApplicationDeniedEvent($application));
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.');
try {
$status = $this->applicationService->updateStatus($application, $newStatus);
} catch (\LogicException $ex)
{
return redirect()
->back()
->with('error', $ex->getMessage());
}
return redirect()->back();
return redirect()
->back()
->with('success', $status);
}
/**
* @throws \Illuminate\Auth\Access\AuthorizationException
* @throws \Exception
*/
public function delete(Request $request, Application $application)
{
$this->authorize('delete', $application);
$application->delete(); // observers will run, cleaning it up
$this->applicationService->delete($application);
$request->session()->flash('success', 'Application deleted. Comments, appointments and responses have also been deleted.');
return redirect()
->back()
->with('success', __('Application deleted. Comments, appointments and responses have also been deleted.'));
return redirect()->back();
}
}

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

@@ -23,85 +23,79 @@ namespace App\Http\Controllers;
use App\Application;
use App\Appointment;
use App\Exceptions\InvalidAppointmentException;
use App\Exceptions\InvalidAppointmentStatusException;
use App\Http\Requests\SaveNotesRequest;
use App\Notifications\ApplicationMoved;
use App\Notifications\AppointmentScheduled;
use App\Services\AppointmentService;
use App\Services\MeetingNoteService;
use Carbon\Carbon;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
class AppointmentController extends Controller
{
private $allowedPlatforms = [
'ZOOM',
'DISCORD',
'SKYPE',
'MEET',
'TEAMSPEAK',
private $appointmentService;
private $meetingNoteService;
];
public function saveAppointment(Request $request, Application $application)
{
$this->authorize('create', Appointment::class);
$appointmentDate = Carbon::parse($request->appointmentDateTime);
public function __construct(AppointmentService $appointmentService, MeetingNoteService $meetingNoteService) {
$appointment = Appointment::create([
'appointmentDescription' => $request->appointmentDescription,
'appointmentDate' => $appointmentDate->toDateTimeString(),
'applicationID' => $application->id,
'appointmentLocation' => (in_array($request->appointmentLocation, $this->allowedPlatforms)) ? $request->appointmentLocation : 'DISCORD',
]);
$application->setStatus('STAGE_INTERVIEW_SCHEDULED');
Log::info('User '.Auth::user()->name.' has scheduled an appointment with '.$application->user->name.' for application ID'.$application->id, [
'datetime' => $appointmentDate->toDateTimeString(),
'scheduled' => now(),
]);
$application->user->notify(new AppointmentScheduled($appointment));
$request->session()->flash('success', 'Appointment successfully scheduled @ '.$appointmentDate->toDateTimeString());
return redirect()->back();
$this->appointmentService = $appointmentService;
$this->meetingNoteService = $meetingNoteService;
}
public function updateAppointment(Request $request, Application $application, $status)
public function saveAppointment(Request $request, Application $application): RedirectResponse
{
$this->authorize('create', Appointment::class);
$appointmentDate = Carbon::parse($request->appointmentDateTime);
$this->appointmentService->createAppointment($application, $appointmentDate, $request->appointmentDescription, $request->appointmentLocation);
return redirect()
->back()
->with('success',__('Appointment successfully scheduled @ :appointmentTime', ['appointmentTime', $appointmentDate->toDateTimeString()]));
}
/**
* @throws AuthorizationException
*/
public function updateAppointment(Application $application, $status): RedirectResponse
{
$this->authorize('update', $application->appointment);
$validStatuses = [
'SCHEDULED',
'CONCLUDED',
];
try {
$this->appointmentService->updateAppointment($application, $status);
// NOTE: This is a little confusing, refactor
$application->appointment->appointmentStatus = (in_array($status, $validStatuses)) ? strtoupper($status) : 'SCHEDULED';
$application->appointment->save();
return redirect()
->back()
->with('success', __("Interview finished! Staff members can now vote on it."));
$application->setStatus('STAGE_PEERAPPROVAL');
$application->user->notify(new ApplicationMoved());
$request->session()->flash('success', 'Interview finished! Staff members can now vote on it.');
return redirect()->back();
}
// also updates
public function saveNotes(SaveNotesRequest $request, Application $application)
{
if (! is_null($application)) {
$application->load('appointment');
$application->appointment->meetingNotes = $request->noteText;
$application->appointment->save();
$request->session()->flash('success', 'Meeting notes have been saved.');
} else {
$request->session()->flash('error', 'There\'s no appointment to save notes to!');
}
catch (InvalidAppointmentStatusException $ex) {
return redirect()
->back()
->with('error', $ex->getMessage());
}
return redirect()->back();
}
public function saveNotes(SaveNotesRequest $request, Application $application)
{
try {
$this->meetingNoteService->addToApplication($application, $request->noteText);
return redirect()
->back()
->with('success', 'Saved notes.');
} catch (InvalidAppointmentException $ex) {
return redirect()
->back()
->with('error', $ex->getMessage());
}
}
}

View File

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

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

@@ -25,6 +25,7 @@ use App\Http\Controllers\Controller;
use App\User;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class LoginController extends Controller
{
@@ -77,4 +78,19 @@ class LoginController extends Controller
return $this->originalAttemptLogin($request);
}
public function authenticated(Request $request, User $user)
{
if (!config('demo.is_enabled')) {
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();
}
}
}
}

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

@@ -24,6 +24,7 @@ namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Profile;
use App\User;
use App\Facades\Options;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
@@ -81,11 +82,30 @@ class RegisterController extends Controller
*/
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, [
'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'],
'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.',
]);
@@ -104,11 +124,11 @@ class RegisterController extends Controller
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
'originalIP' => request()->ip(),
'originalIP' => config('demo.is_enabled') ? '0.0.0.0' : request()->ip(),
]);
// 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.
// so this code has been moved to its respective observer, following the separation of concerns pattern.
$user->assignRole('user');

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

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

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

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

@@ -24,58 +24,41 @@ namespace App\Http\Controllers;
use App\Ban;
use App\Events\UserBannedEvent;
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
{
protected $suspensionService;
public function __construct(AccountSuspensionService $suspensionService)
{
// Inject the service via DI
$this->suspensionService = $suspensionService;
}
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]);
if (is_null($user->bans)) {
$reason = $request->reason;
$duration = strtolower($request->durationOperator);
$durationOperand = $request->durationOperand;
$expiryDate = now();
if (!$this->suspensionService->isSuspended($user)) {
if (! empty($duration)) {
switch ($duration) {
case 'days':
$expiryDate->addDays($durationOperand);
break;
$this->suspensionService->suspend($request->reason, $request->duration, $user, $request->suspensionType);
$request->session()->flash('success', __('Account suspended.'));
case 'weeks':
$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!');
$request->session()->flash('error', __('Account already suspended!'));
}
return redirect()->back();
@@ -83,13 +66,21 @@ class BanController extends Controller
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);
if (! is_null($user->bans)) {
$user->bans->delete();
$request->session()->flash('success', 'User unbanned successfully!');
if ($this->suspensionService->isSuspended($user)) {
$this->suspensionService->unsuspend($user);
$request->session()->flash('success', __('Account unsuspended successfully!'));
} else {
$request->session()->flash('error', 'This user isn\'t banned!');
$request->session()->flash('error', __('This account isn\'t suspended!'));
}
return redirect()->back();

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

@@ -24,30 +24,27 @@ namespace App\Http\Controllers;
use App\Application;
use App\Comment;
use App\Http\Requests\NewCommentRequest;
use App\Services\CommentService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class CommentController extends Controller
{
public function index()
{
//
private $commentService;
public function __construct(CommentService $commentService) {
$this->commentService = $commentService;
}
public function insert(NewCommentRequest $request, Application $application)
{
$this->authorize('create', Comment::class);
$comment = Comment::create([
'authorID' => Auth::user()->id,
'applicationID' => $application->id,
'text' => $request->comment,
]);
$comment = $this->commentService->addComment($application, $request->comment);
if ($comment) {
$request->session()->flash('success', 'Comment posted! (:');
$request->session()->flash('success', __('Comment posted!'));
} else {
$request->session()->flash('error', 'Something went wrong while posting your comment!');
$request->session()->flash('error', __('Something went wrong while posting your comment!'));
}
return redirect()->back();
@@ -56,10 +53,10 @@ class CommentController extends Controller
public function delete(Request $request, Comment $comment)
{
$this->authorize('delete', $comment);
$this->commentService->deleteComment($comment);
$comment->delete();
$request->session()->flash('success', 'Comment deleted!');
return redirect()->back();
return redirect()
->back()
->with('success', __('Comment deleted!'));
}
}

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

@@ -21,7 +21,9 @@
namespace App\Http\Controllers;
use App\Exceptions\FailedCaptchaException;
use App\Notifications\NewContact;
use App\Services\ContactService;
use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
@@ -30,47 +32,32 @@ class ContactController extends Controller
{
protected $users;
public function __construct(User $users)
private $contactService;
public function __construct(User $users, ContactService $contactService)
{
$this->contactService = $contactService;
$this->users = $users;
}
public function create(Request $request)
{
$name = $request->name;
$email = $request->email;
$subject = $request->subject;
$msg = $request->msg;
try {
$challenge = $request->input('captcha');
$email = $request->email;
$msg = $request->msg;
$challenge = $request->input('captcha');
// TODO: now: add middleware for this verification, move to invisible captcha
$verifyrequest = Http::asForm()->post(config('recaptcha.verify.apiurl'), [
'secret' => config('recaptcha.keys.secret'),
'response' => $challenge,
'remoteip' => $request->ip(),
]);
$this->contactService->sendMessage($request->ip(), $msg, $email, $challenge);
$response = json_decode($verifyrequest->getBody(), true);
return redirect()
->back()
->with('success',__('Message sent successfully! We usually respond within 48 hours.'));
if (! $response['success']) {
$request->session()->flash('error', 'Beep beep boop... Robot? Submission failed.');
return redirect()->back();
} catch (FailedCaptchaException $ex) {
return redirect()
->back()
->with('error', $ex->getMessage());
}
foreach (User::all() as $user) {
if ($user->hasRole('admin')) {
$user->notify(new NewContact(collect([
'message' => $msg,
'ip' => $request->ip(),
'email' => $email,
])));
}
}
$request->session()->flash('success', 'Message sent successfully! We usually respond within 48 hours.');
return redirect()->back();
}
}

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

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

@@ -24,22 +24,38 @@ namespace App\Http\Controllers;
use App\Application;
use App\User;
use App\Vacancy;
use Illuminate\Support\Facades\Auth;
class DashboardController extends Controller
{
// Note: The dashboard doesn't need a service because it doesn't contain any significant business logic
public function index()
{
$totalPeerReview = Application::where('applicationStatus', 'STAGE_PEERAPPROVAL')->get()->count();
$totalNewApplications = Application::where('applicationStatus', 'STAGE_SUBMITTED')->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')
->with([
'vacancies' => Vacancy::all(),
'vacancies' => $vacancies,
'totalUserCount' => User::all()->count(),
'totalDenied' => $totalDenied,
'totalPeerReview' => $totalPeerReview,
'totalNewApplications' => $totalNewApplications,
'totalNewSingle' => $totalNewSingle->count(),
'totalDeniedSingle' => $totalDeniedSingle->count()
]);
}
}

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

@@ -32,7 +32,7 @@ class DevToolsController extends Controller
protected function isolatedAuthorise()
{
if (! Auth::user()->can('admin.developertools.use')) {
abort(403, 'You\'re not authorized to access this page.');
abort(403, __('You\'re not authorized to access this page.'));
}
}
@@ -52,9 +52,9 @@ class DevToolsController extends Controller
if (! is_null($application)) {
event(new ApplicationApprovedEvent($application));
$request->session()->flash('success', 'Event dispatched! Please check the debug logs for more info');
$request->session()->flash('success', __('Event dispatched! Please check the debug logs for more info'));
} else {
$request->session()->flash('error', 'Application doesn\'t exist!');
$request->session()->flash('error', __('Application doesn\'t exist!'));
}
return redirect()->back();

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

@@ -21,12 +21,21 @@
namespace App\Http\Controllers;
use App\Exceptions\EmptyFormException;
use App\Exceptions\FormHasConstraintsException;
use App\Form;
use App\Services\FormManagementService;
use ContextAwareValidator;
use Illuminate\Http\Request;
class FormController extends Controller
{
private $formService;
public function __construct(FormManagementService $formService) {
$this->formService = $formService;
}
public function index()
{
$forms = Form::all();
@@ -45,58 +54,46 @@ class FormController extends Controller
public function saveForm(Request $request)
{
$this->authorize('create', Form::class);
$fields = $request->all();
if (count($fields) == 2) {
// form is probably empty, since forms with fields will alawys have more than 2 items
$request->session()->flash('error', 'Sorry, but you may not create empty forms.');
return redirect()->to(route('showForms'));
try {
$form = $this->formService->addForm($request->all());
}
catch (EmptyFormException $ex)
{
return redirect()
->back()
->with('exception', $ex->getMessage());
}
$contextValidation = ContextAwareValidator::getValidator($fields, true, true);
if (! $contextValidation->get('validator')->fails()) {
$storableFormStructure = $contextValidation->get('structure');
Form::create(
[
'formName' => $fields['formName'],
'formStructure' => $storableFormStructure,
'formStatus' => 'ACTIVE',
]
);
$request->session()->flash('success', 'Form created! You can now link this form to a vacancy.');
return redirect()->to(route('showForms'));
// Form is boolean or array
if ($form)
{
return redirect()
->back()
->with('success', __('Form created!'));
}
$request->session()->flash('errors', $contextValidation->get('validator')->errors()->getMessages());
return redirect()->back();
return redirect()
->back()
->with('errors', $form);
}
public function destroy(Request $request, Form $form)
{
$this->authorize('delete', $form);
$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)
@@ -122,22 +119,15 @@ class FormController extends Controller
public function update(Request $request, Form $form)
{
$this->authorize('update', $form);
$updatedForm = $this->formService->updateForm($form, $request->all());
$contextValidation = ContextAwareValidator::getValidator($request->all(), true);
$this->authorize('update', $form);
if (! $contextValidation->get('validator')->fails()) {
// Add the new structure into the form. New, subsquent fields will be identified by the "new" prefix
// This prefix doesn't actually change the app's behavior when it receives applications.
// Additionally, old applications won't of course display new and updated fields, because we can't travel into the past and get data for them
$form->formStructure = $contextValidation->get('structure');
$form->save();
$request->session()->flash('success', 'Hooray! Your form was updated. New applications for it\'s vacancy will use it.');
} else {
$request->session()->flash('errors', $contextValidation->get('validator')->errors()->getMessages());
if ($updatedForm instanceof Form) {
return redirect()->to(route('previewForm', ['form' => $updatedForm->id]));
}
return redirect()->to(route('previewForm', ['form' => $form->id]));
// array of errors
return redirect()
->back()
->with('errors', $updatedForm);
}
}

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

@@ -25,6 +25,8 @@ use App\Vacancy;
class HomeController extends Controller
{
// doesn't need a service, because it doesn't contain major logic.
/**
* Show the application dashboard.
*

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

@@ -21,14 +21,25 @@
namespace App\Http\Controllers;
use App\Exceptions\InvalidGamePreferenceException;
use App\Exceptions\OptionNotFoundException;
use App\Facades\Options;
use App\Options as Option;
use App\Services\ConfigurationService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
class OptionsController extends Controller
{
private $configurationService;
public function __construct(ConfigurationService $configurationService) {
$this->configurationService = $configurationService;
}
/**
* Display a listing of the resource.
*
@@ -36,50 +47,59 @@ class OptionsController extends Controller
*/
public function index()
{
// TODO: Obtain this from the facade
$options = Option::all();
// TODO: Replace with settings package
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.
'secPolicy' => Options::getOption('pw_security_policy'),
'graceperiod' => Options::getOption('graceperiod'),
'pwExpiry' => Options::getOption('password_expiry'),
'requiresPMC' => Options::getOption('requireGameLicense'),
'enforce2fa' => Options::getOption('force2fa')
],
'currentGame' => Options::getOption('currentGame')
]);
}
public function saveSettings(Request $request)
public function saveSettings(Request $request): \Illuminate\Http\RedirectResponse
{
if (Auth::user()->can('admin.settings.edit')) {
Log::debug('Updating application options', [
'ip' => $request->ip(),
'ua' => $request->userAgent(),
'username' => Auth::user()->username,
]);
foreach ($request->all() as $optionName => $option) {
try {
Log::debug('Going through option '.$optionName);
if (Options::optionExists($optionName)) {
Log::debug('Option exists, updating to new values', [
'opt' => $optionName,
'new_value' => $option,
]);
Options::changeOption($optionName, $option);
}
} catch (\Exception $ex) {
Log::error('Unable to update options!', [
'msg' => $ex->getMessage(),
'trace' => $ex->getTraceAsString(),
]);
report($ex);
try {
$errorCond = true;
$request->session()->flash('error', 'An error occurred while trying to save settings: '.$ex->getMessage());
}
if (Auth::user()->can('admin.settings.edit')) {
$this->configurationService->saveConfiguration($request->all());
return redirect()
->back()
->with('success', __('Options updated successfully!'));
}
if (! isset($errorCond)) {
$request->session()->flash('success', 'Settings saved successfully!');
}
} else {
$request->session()->flash('error', 'You do not have permission to update this resource.');
} catch (OptionNotFoundException | \Exception $ex) {
return redirect()
->back()
->with('error', $ex->getMessage());
}
return redirect()->back();
return redirect()
->back()
->with('error', __('You do not have permission to update this resource.'));
}
public function saveGameIntegration(Request $request)
{
try {
$this->configurationService->saveGameIntegration($request->gamePref);
return redirect()
->back()
->with('success', __('Game preference updated.'));
} catch (InvalidGamePreferenceException $ex) {
return redirect()
->back()
->with('error', $ex->getMessage());
}
}
}

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

@@ -23,6 +23,7 @@ namespace App\Http\Controllers;
use App\Facades\IP;
use App\Http\Requests\ProfileSave;
use App\Services\ProfileService;
use App\User;
use Carbon\Carbon;
use Illuminate\Http\Request;
@@ -31,6 +32,12 @@ use Spatie\Permission\Models\Role;
class ProfileController extends Controller
{
private $profileService;
public function __construct(ProfileService $profileService) {
$this->profileService = $profileService;
}
public function index()
{
return view('dashboard.user.directory')
@@ -39,6 +46,7 @@ class ProfileController extends Controller
public function showProfile()
{
// TODO: Come up with cleaner social media solution, e.g. social media object
$socialLinks = Auth::user()->profile->socialLinks ?? '[]';
$socialMediaProfiles = json_decode($socialLinks, true);
@@ -52,8 +60,7 @@ class ProfileController extends Controller
]);
}
// Route model binding
public function showSingleProfile(Request $request, User $user)
public function showSingleProfile(User $user)
{
$socialMediaProfiles = json_decode($user->profile->socialLinks, true);
$createdDate = Carbon::parse($user->created_at);
@@ -71,6 +78,17 @@ class ProfileController extends Controller
}
}
$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')
->with([
@@ -82,44 +100,18 @@ class ProfileController extends Controller
'since' => $createdDate->englishMonth.' '.$createdDate->year,
'ipInfo' => IP::lookup($user->originalIP),
'roles' => $roleList,
'suspensionInfo' => $suspensionInfo
]);
} else {
abort(403, 'You cannot view someone else\'s profile.');
abort(403, __('You cannot view someone else\'s profile.'));
}
}
public function saveProfile(ProfileSave $request)
{
$profile = User::find(Auth::user()->id)->profile;
$social = [];
if (! is_null($profile)) {
switch ($request->avatarPref) {
case 'MOJANG':
$avatarPref = 'crafatar';
break;
case 'GRAVATAR':
$avatarPref = strtolower($request->avatarPref);
break;
}
$social['links']['github'] = $request->socialGithub;
$social['links']['twitter'] = $request->socialTwitter;
$social['links']['insta'] = $request->socialInsta;
$social['links']['discord'] = $request->socialDiscord;
$profile->profileShortBio = $request->shortBio;
$profile->profileAboutMe = $request->aboutMe;
$profile->avatarPreference = $avatarPref;
$profile->socialLinks = json_encode($social);
$newProfile = $profile->save();
$request->session()->flash('success', 'Profile settings saved successfully.');
}
return redirect()->back();
$this->profileService->updateProfile(Auth::user()->id, $request);
return redirect()
->back()
->with('success', __('Profile updated.'));
}
}

View File

@@ -1,27 +0,0 @@
<?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;
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,27 +0,0 @@
<?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;
class StaffProfileController extends Controller
{
//
}

210
app/Http/Controllers/TeamController.php Normal file → Executable file
View File

@@ -21,13 +21,18 @@
namespace App\Http\Controllers;
use App\Exceptions\InvalidInviteException;
use App\Exceptions\PublicTeamInviteException;
use App\Exceptions\UserAlreadyInvitedException;
use App\Http\Requests\EditTeamRequest;
use App\Http\Requests\NewTeamRequest;
use App\Http\Requests\SendInviteRequest;
use App\Mail\InviteToTeam;
use App\Services\TeamService;
use App\Team;
use App\User;
use App\Vacancy;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Mail;
@@ -37,91 +42,84 @@ use Mpociot\Teamwork\TeamInvite;
class TeamController extends Controller
{
private $teamService;
public function __construct(TeamService $teamService) {
$this->teamService = $teamService;
}
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$this->authorize('index', Team::class);
$teams = Team::with('users.roles')->get();
return view('dashboard.teams.teams')
->with('teams', $teams);
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
* @param NewTeamRequest $request
* @return RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function store(NewTeamRequest $request)
{
$team = Team::create([
'name' => $request->teamName,
'owner_id' => Auth::user()->id,
]);
$this->authorize('create', Team::class);
$this->teamService->createTeam($request->teamName, Auth::user()->id);
Auth::user()->teams()->attach($team->id);
$request->session()->flash('success', 'Team successfully created.');
return redirect()->back();
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
//
return redirect()
->back()
->with('success', __('Team successfully created.'));
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
* @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)
->with('users', User::all())
->with('vacancies', Vacancy::with('teams')->get()->all());
->with([
'team' => $team,
'users' => User::all(),
'vacancies' => Vacancy::with('teams')->get()->all()
]);
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
* @param EditTeamRequest $request
* @param Team $team
* @return RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function update(EditTeamRequest $request, Team $team)
public function update(EditTeamRequest $request, Team $team): RedirectResponse
{
$team->description = $request->teamDescription;
$team->openJoin = $request->joinType;
$this->authorize('update', $team);
$team = $this->teamService->updateTeam($team, $request->teamDescription, $request->joinType);
$team->save();
$request->session()->flash('success', 'Team edited successfully.');
if ($team) {
return redirect()
->to(route('teams.index'))
->with('success', __('Team updated.'));
}
return redirect()->to(route('teams.index'));
return redirect()
->back()
->with('error', __('An error ocurred while trying to update this team.'));
}
/**
@@ -132,118 +130,70 @@ class TeamController extends Controller
*/
public function destroy($id)
{
//
// wip
}
public function invite(SendInviteRequest $request, Team $team)
public function invite(SendInviteRequest $request, Team $team): RedirectResponse
{
$user = User::findOrFail($request->user);
$this->authorize('invite', $team);
if (! $team->openJoin) {
if (! Teamwork::hasPendingInvite($user->email, $team)) {
Teamwork::inviteToTeam($user, $team, function (TeamInvite $invite) use ($user) {
Mail::to($user)->send(new InviteToTeam($invite));
});
try {
$request->session()->flash('success', 'Invite sent! They can now accept or deny it.');
} else {
$request->session()->flash('error', 'This user has already been invited.');
}
} else {
$request->session()->flash('error', 'You can\'t invite users to public teams.');
$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());
}
return redirect()->back();
}
public function processInviteAction(Request $request, $action, $token)
public function processInviteAction(Request $request, $action, $token): RedirectResponse
{
switch ($action) {
case 'accept':
try {
$invite = Teamwork::getInviteFromAcceptToken($token);
$this->teamService->processInvite(Auth::user(), $action, $token);
if ($invite && $invite->user->is(Auth::user())) {
Teamwork::acceptInvite($invite);
$request->session()->flash('success', 'Invite accepted! You have now joined '.$invite->team->name.'.');
} else {
$request->session()->flash('error', 'Invalid or expired invite URL.');
}
return redirect()
->to(route('teams.index'))
->with('success', __('Invite processed successfully!'));
break;
} catch (InvalidInviteException $e) {
case 'deny':
$invite = Teamwork::getInviteFromDenyToken($token);
if ($invite && $invite->user->is(Auth::user())) {
Teamwork::denyInvite($invite);
$request->session()->flash('success', 'Invite denied! Ask for another invite if this isn\'t what you meant.');
} else {
$request->session()->flash('error', 'Invalid or expired invite URL.');
}
break;
default:
$request->session()->flash('error', 'Sorry, but the invite URL you followed was malformed. Try asking for another invite, or submit a bug report.');
return redirect()
->back()
->with('error', $e->getMessage());
}
// This page will show the user's current teams
return redirect()->to(route('teams.index'));
}
public function switchTeam(Request $request, Team $team)
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.');
$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.');
$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)
public function assignVacancies(Request $request, Team $team): RedirectResponse
{
// P.S. To future developers
// This method gave me a lot of trouble lol. It's hard to write code when you're half asleep.
// There may be an n+1 query in the view and I don't think there's a way to avoid that without writing a lot of extra code.
$this->authorize('update', $team);
$message = $this->teamService->updateVacancies($team, $request->assocVacancies);
$requestVacancies = $request->assocVacancies;
$currentVacancies = $team->vacancies->pluck('id')->all();
if (is_null($requestVacancies)) {
foreach ($team->vacancies as $vacancy) {
$team->vacancies()->detach($vacancy->id);
}
$request->session()->flash('success', 'Removed all vacancy associations.');
return redirect()->back();
}
$vacancyDiff = array_diff($requestVacancies, $currentVacancies);
$deselectedDiff = array_diff($currentVacancies, $requestVacancies);
if (! empty($vacancyDiff) || ! empty($deselectedDiff)) {
foreach ($vacancyDiff as $selectedVacancy) {
$team->vacancies()->attach($selectedVacancy);
}
foreach ($deselectedDiff as $deselectedVacancy) {
$team->vacancies()->detach($deselectedVacancy);
}
} else {
$team->vacancies()->attach($requestVacancies);
}
$request->session()->flash('success', 'Assignments changed successfully.');
return redirect()->back();
return redirect()
->back()
->with('success', $message);
}
}

173
app/Http/Controllers/TeamFileController.php Normal file → Executable file
View File

@@ -1,126 +1,141 @@
<?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;
// 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 Illuminate\Http\Request;
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
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Http\Response
*/
public function index(Request $request)
{
if (is_null(Auth::user()->currentTeam)) {
$request->session()->flash('error', 'Please choose a team before viewing it\'s files.');
$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(20));
->with('files', TeamFile::with('team', 'uploader')->paginate(6));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
* @param UploadFileRequest $request
* @return RedirectResponse
*/
public function store(Request $request)
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)
{
try {
return Storage::download('uploads/'.$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.');
$this->authorize('download', TeamFile::class);
return redirect()->back();
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();
/**
* Display the specified resource.
*
* @param \App\TeamFile $teamFile
* @return \Illuminate\Http\Response
*/
public function show(TeamFile $teamFile)
{
//
}
/**
* Show the form for editing the specified resource.
*
* @param \App\TeamFile $teamFile
* @return \Illuminate\Http\Response
*/
public function edit(TeamFile $teamFile)
{
//
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param \App\TeamFile $teamFile
* @return \Illuminate\Http\Response
*/
public function update(Request $request, TeamFile $teamFile)
{
//
}
}
/**
* Remove the specified resource from storage.
*
* @param \App\TeamFile $teamFile
* @return \Illuminate\Http\Response
* @param Request $request
* @param \App\TeamFile $teamFile
* @return RedirectResponse
*/
public function destroy(TeamFile $teamFile)
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();
}
}

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

@@ -32,6 +32,7 @@ use App\Http\Requests\SearchPlayerRequest;
use App\Http\Requests\UpdateUserRequest;
use App\Notifications\ChangedPassword;
use App\Notifications\EmailChanged;
use App\Traits\DisablesFeatures;
use App\Traits\ReceivesAccountTokens;
use App\User;
use Google2FA;
@@ -110,7 +111,7 @@ class UserController extends Controller
->get();
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')
->with([
@@ -118,7 +119,7 @@ class UserController extends Controller
'bannedUserCount' => Ban::all()->count(),
]);
} 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'));
}
@@ -161,17 +162,24 @@ class UserController extends Controller
'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();
}
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);
if (! is_null($user)) {
$user->password = Hash::make($request->newPassword);
$user->password_last_updated = now();
$user->save();
Log::info('User '.$user->name.' has changed their password', [
@@ -189,6 +197,12 @@ class UserController extends Controller
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);
if (! is_null($user)) {
@@ -202,9 +216,9 @@ class UserController extends Controller
]);
$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 {
$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();
@@ -212,13 +226,19 @@ class UserController extends Controller
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);
if ($request->confirmPrompt == 'DELETE ACCOUNT') {
$user->forceDelete();
$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.');
$request->session()->flash('error', __('Wrong confirmation text! Try again.'));
}
return redirect()->route('registeredPlayerList');
@@ -226,6 +246,11 @@ class UserController extends Controller
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);
// Mass update would not be possible here without extra code, making route model binding useless
@@ -234,8 +259,8 @@ class UserController extends Controller
$user->uuid = $request->uuid;
$existingRoles = Role::all()
->pluck('name')
->all();
->pluck('name')
->all();
$roleDiff = array_diff($existingRoles, $request->roles);
@@ -253,13 +278,19 @@ class UserController extends Controller
}
$user->save();
$request->session()->flash('success', 'User updated successfully!');
$request->session()->flash('success', __('User updated successfully!'));
return redirect()->back();
}
public function add2FASecret(Add2FASecretRequest $request)
{
if (config('demo.is_enabled')) {
return redirect()
->back()
->with('error', 'This feature is disabled');
}
$currentSecret = $request->session()->get('current2FA');
$isValid = Google2FA::verifyKey($currentSecret, $request->otp);
@@ -285,9 +316,9 @@ class UserController extends Controller
$request->session()->forget('twofaAttemptFailed');
}
$request->session()->flash('success', '2FA succesfully enabled! You\'ll now be prompted for an OTP each time you log in.');
$request->session()->flash('success', __('2FA succesfully enabled! You\'ll now be prompted for an OTP each time you log in.'));
} else {
$request->session()->flash('error', 'Incorrect code. Please reopen the 2FA settings panel and try again.');
$request->session()->flash('error', __('Incorrect code. Please reopen the 2FA settings panel and try again.'));
$request->session()->put('twofaAttemptFailed', true);
}
@@ -304,7 +335,7 @@ class UserController extends Controller
$request->user()->twofa_secret = null;
$request->user()->save();
$request->session()->flash('success', 'Two-factor authentication disabled.');
$request->session()->flash('success', __('Two-factor authentication disabled.'));
return redirect()->back();
}
@@ -312,10 +343,15 @@ class UserController extends Controller
public function terminate(Request $request, User $user)
{
$this->authorize('terminate', User::class);
if (config('demo.is_enabled')) {
return redirect()
->back()
->with('error', 'This feature is disabled');
}
// TODO: move logic to policy
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();
}
@@ -329,7 +365,7 @@ class UserController extends Controller
}
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
return redirect()->back();

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

@@ -21,6 +21,7 @@
namespace App\Http\Controllers;
use App\Facades\JSON;
use App\Form;
use App\Http\Requests\VacancyEditRequest;
use App\Http\Requests\VacancyRequest;
@@ -45,7 +46,11 @@ class VacancyController extends Controller
public function store(VacancyRequest $request)
{
$messageIsError = false;
$this->authorize('create', Vacancy::class);
$form = Form::find($request->vacancyFormID);
if (! is_null($form)) {
@@ -67,12 +72,16 @@ class VacancyController extends Controller
]);
$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 {
$request->session()->flash('error', 'You cannot create a vacancy without a valid form.');
$message = __('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)
@@ -85,13 +94,13 @@ class VacancyController extends Controller
switch ($status) {
case 'open':
$vacancy->open();
$message = 'Position successfully opened!';
$message = __('Position successfully opened!');
break;
case 'close':
$vacancy->close();
$message = 'Position successfully closed!';
$message = __('Position successfully closed!');
foreach (User::all() as $user) {
if ($user->isStaffMember()) {
@@ -101,18 +110,19 @@ class VacancyController extends Controller
break;
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';
}
} 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';
}
$request->session()->flash($type, $message);
return redirect()
->back()
->with($type, $message);
return redirect()->back();
}
public function edit(Request $request, Vacancy $vacancy)
@@ -127,14 +137,14 @@ class VacancyController extends Controller
{
$this->authorize('update', $vacancy);
$vacancy->vacancyFullDescription = $request->vacancyFullDescription;
$vacancy->vacancyFullDescription = $request->vacancyFullDescription;
$vacancy->vacancyDescription = $request->vacancyDescription;
$vacancy->vacancyCount = $request->vacancyCount;
$vacancy->save();
$request->session()->flash('success', 'Vacancy successfully updated.');
return redirect()->back();
return redirect()
->back()
->with('success', __('Vacancy successfully updated.'));
}
}

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

@@ -42,7 +42,7 @@ class VoteController extends Controller
Log::info('User '.Auth::user()->name.' has voted in applicant '.$application->user->name.'\'s application', [
'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
return redirect()->back();

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

@@ -21,6 +21,7 @@
namespace App\Http;
use App\Http\Middleware\APIAuthenticationMiddleware;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
@@ -60,6 +61,7 @@ class Kernel extends HttpKernel
'api' => [
'throttle:60,1',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
APIAuthenticationMiddleware::class
],
];
@@ -85,6 +87,8 @@ class Kernel extends HttpKernel
'usernameUUID' => \App\Http\Middleware\UsernameUUID::class,
'forcelogout' => \App\Http\Middleware\ForceLogoutMiddleware::class,
'2fa' => \PragmaRX\Google2FALaravel\Middleware::class,
'passwordexpiration' => \App\Http\Middleware\PasswordExpirationMiddleware::class,
'passwordredirect' => \App\Http\Middleware\PasswordExpirationRedirectMiddleware::class,
'localize' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRoutes::class,
'localizationRedirect' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRedirectFilter::class,
'localeSessionRedirect' => \Mcamara\LaravelLocalization\Middleware\LocaleSessionRedirect::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();
}
}

0
app/Http/Middleware/ApplicationEligibility.php Normal file → Executable file
View File

0
app/Http/Middleware/Authenticate.php Normal file → Executable file
View File

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