forked from miguel456/rbrecruiter
Compare commits
205 Commits
0.4.0
...
feature/RS
Author | SHA1 | Date | |
---|---|---|---|
82123b1c26 | |||
2a43e213f9 | |||
b2adcee51e | |||
077ead9612 | |||
6cc99d2ebe | |||
0930c29b9a | |||
596a469e15 | |||
75f4404259 | |||
7c0c244e21 | |||
982854d5c6 | |||
551741c687 | |||
dbeddd6fdb | |||
b68449d3bf | |||
67d1df7571 | |||
d4f1b433dc | |||
f1db159eee | |||
0d14a65ee5 | |||
2942157603 | |||
11f3fb90d0 | |||
937a0206a5 | |||
3598a32ecf | |||
ac8b303e2c | |||
e93abd2ab7 | |||
20ab381076 | |||
e566e40404 | |||
b0a935b8b3 | |||
0dfb68dba2 | |||
24303052ad | |||
178bc31a6e | |||
98e557a840 | |||
95bf7c239e | |||
4d2595dd39 | |||
4e81a41210 | |||
1319ce6b86 | |||
bea83b650c | |||
675cc3c329 | |||
e8119b763c | |||
04838048ce | |||
87f8e63b24 | |||
7292aab4b7 | |||
9f3780d844 | |||
e37b38f2d9 | |||
c83e720a6d | |||
9241a83844 | |||
c6685331a8 | |||
9da9b8e6fc | |||
bb019f71e2 | |||
0cde3444ce | |||
b61fb5642e | |||
f7614916bf | |||
2ad1548cd6 | |||
43579c8fc9 | |||
0c9cea5c05 | |||
4371dd971c | |||
c57ace1ad9 | |||
c35b37d9b3 | |||
f25c9f7bc7 | |||
25cebeefab | |||
8b47dbe2e0 | |||
290dbe99b6 | |||
d7b506ec52 | |||
d41d94b934 | |||
b571d72eaf | |||
0c1f6f75eb | |||
5ea9e11a62 | |||
fbd1e83306 | |||
50ed47964c | |||
58d6a8ef1f | |||
d988b9a5cb | |||
eeae03dec5 | |||
c374100eed | |||
01e3a9edce | |||
d93170b555 | |||
8e85e08171 | |||
de3dba3627 | |||
f7a18816bc | |||
369185c4ed | |||
5ee79880d5 | |||
75d7181bca | |||
3fe3df7357 | |||
7e58c3af6b | |||
5ca155ba42 | |||
7c7c20d5b2 | |||
fdb508fd5a | |||
cbe660f4ad | |||
6e34b6b8fa | |||
60874c046f | |||
e9dd1567b8 | |||
a95c9518b3 | |||
af96d193a4 | |||
48054f0837 | |||
9b5e35b241 | |||
4c7783f366 | |||
af2c23a73c | |||
af17bbe468 | |||
17f61f0d6b | |||
a5aef7deb7 | |||
f32c4dc68c | |||
687316d77a | |||
ff70c21283 | |||
727c14d0c2 | |||
6b7d2db612 | |||
d743554df6 | |||
fd8bf4f0f2 | |||
bd530696d2 | |||
3040afd730 | |||
e7d2c548c0 | |||
f001a16d4d | |||
d8dbb1a0a2 | |||
4e1b4f5afd | |||
81c0b65404 | |||
a10f3f9c96 | |||
326e0f8c7c | |||
a0192cdb02 | |||
8f45563b24 | |||
004e9edcb0 | |||
fafc9dca87 | |||
800d205c74 | |||
3cd7292c36 | |||
36db8a1337 | |||
bcd11c462a | |||
6703ac89c1 | |||
fda34ad8bf | |||
2cde1cdbbe | |||
0bca7619f7 | |||
d223515d19 | |||
93fb7a8432 | |||
6bf0d9f373 | |||
db5d150758 | |||
348b1a37d0 | |||
86bf02bb42 | |||
6c45573fbe | |||
910863bdea | |||
884ff74f42 | |||
4f1935fbf2 | |||
3a53f3bbc2 | |||
6c08e839d6 | |||
362ce6c866 | |||
d336354482 | |||
88cf53c53d | |||
a5568be339 | |||
d393a8cedc | |||
62d5f68279 | |||
5c068a325d | |||
15c02c1de1 | |||
3782f79b51 | |||
0c53757912 | |||
6db69f997d | |||
edb2e4b2d6 | |||
baac37e967 | |||
5952ed9248 | |||
356483ef7b | |||
b80e168dfb | |||
9b469c434b | |||
ca3a06f248 | |||
1e2f331778 | |||
4a09fa581d | |||
c58b5b56d7 | |||
bf5d4058ad | |||
f871e14307 | |||
1e78a8e6d9 | |||
17fb0e236f | |||
27b1f3170b | |||
00cc36246f | |||
41e3e817a2 | |||
2afea88846 | |||
ea96cbc1f5 | |||
2996e66c8b | |||
a32af7c464 | |||
cd874c5f58 | |||
42de40e320 | |||
faa3a65e2b | |||
ba3a139d1c | |||
25ddf81118 | |||
9431eb5036 | |||
a3071dccf9 | |||
b0cbf65cfc | |||
6be5e241d4 | |||
d6c49a5cf0 | |||
075617fd32 | |||
da73c91b4a | |||
ca82f5882d | |||
88c36dd3f8 | |||
535a2c3973 | |||
ad5c3404cc | |||
64d418c590 | |||
62b063ee63 | |||
2c0c404d73 | |||
168f08bd96 | |||
94d08f1886 | |||
0cf6208577 | |||
9255a6c88d | |||
098205a969 | |||
bf426e3bdd | |||
02059bbcb0 | |||
91627decbe | |||
2763f777ab | |||
d392c0593f | |||
5f1f92a9ce | |||
9e2d571298 | |||
e16be5dc46 | |||
1a04880489 | |||
3693ce3431 | |||
4a766620ff | |||
bca6020ab0 |
12
.env.example
12
.env.example
@@ -3,6 +3,9 @@ APP_ENV=local
|
||||
APP_KEY=
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://localhost
|
||||
APP_LOGO="https://www.raspberrypi.org/app/uploads/2020/05/Raspberry-Pi-OS-downloads-image-150x150-1.png"
|
||||
APP_SITEHOMEPAGE=""
|
||||
# This can be your main homepage, other than this site itself
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
|
||||
@@ -18,9 +21,6 @@ RECAPTCHA_PRIVATE_KEY=
|
||||
RECAPTCHA_VERIFY_URL="https://www.google.com/recaptcha/api/siteverify"
|
||||
# WARNING: Your contact form will be useless if you change this value. Only change this URL if Google updates it.
|
||||
|
||||
IPGEO_API_KEY=""
|
||||
IPGEO_API_URL=""
|
||||
|
||||
MOJANG_STATUS_URL="https://status.mojang.com/check"
|
||||
MOJANG_API_URL="https://api.mojang.com"
|
||||
|
||||
@@ -29,7 +29,7 @@ IPGEO_API_URL="https://api.ipgeolocation.io/ipgeo"
|
||||
|
||||
ARCANEDEV_LOGVIEWER_MIDDLEWARE=web,auth,can:admin.maintenance.logs.view
|
||||
|
||||
RELEASE=staffmanagement@0.2.0
|
||||
RELEASE=staffmanagement@0.6.1
|
||||
|
||||
SLACK_INTEGRATION_WEBHOOK=
|
||||
|
||||
@@ -65,4 +65,8 @@ PUSHER_APP_CLUSTER=mt1
|
||||
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
|
||||
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
|
||||
|
||||
# Mostly for developers, but with Papertrail, you can easily see what the app's users are doing without relying on
|
||||
# the internal log viewer.
|
||||
SENTRY_LARAVEL_DSN=
|
||||
PAPERTRAIL_URL=
|
||||
PAPERTRAIL_PORT
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,6 +4,7 @@
|
||||
/public/storage
|
||||
/storage/*.key
|
||||
/vendor
|
||||
/tools
|
||||
.env
|
||||
.env.backup
|
||||
.phpunit.result.cache
|
||||
|
14
.idea/hrm-mcserver.iml
generated
14
.idea/hrm-mcserver.iml
generated
@@ -2,11 +2,14 @@
|
||||
<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/brick/math" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/clue/stream-filter" />
|
||||
@@ -27,6 +30,7 @@
|
||||
<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" />
|
||||
@@ -39,9 +43,12 @@
|
||||
<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" />
|
||||
@@ -64,6 +71,7 @@
|
||||
<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" />
|
||||
@@ -80,12 +88,16 @@
|
||||
<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" />
|
||||
@@ -104,6 +116,8 @@
|
||||
<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" />
|
||||
|
26
.idea/php.xml
generated
26
.idea/php.xml
generated
@@ -127,9 +127,33 @@
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/string" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-grapheme" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php80" />
|
||||
<path value="$PROJECT_DIR$/vendor/bacon/bacon-qr-code" />
|
||||
<path value="$PROJECT_DIR$/vendor/dasprid/enum" />
|
||||
<path value="$PROJECT_DIR$/vendor/geo-sot/laravel-env-editor" />
|
||||
<path value="$PROJECT_DIR$/vendor/laravel/slack-notification-channel" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php70" />
|
||||
<path value="$PROJECT_DIR$/vendor/pragmarx/google2fa-laravel" />
|
||||
<path value="$PROJECT_DIR$/vendor/pragmarx/google2fa" />
|
||||
<path value="$PROJECT_DIR$/vendor/pragmarx/google2fa-qrcode" />
|
||||
<path value="$PROJECT_DIR$/vendor/arcanedev/log-viewer" />
|
||||
<path value="$PROJECT_DIR$/vendor/arcanedev/support" />
|
||||
<path value="$PROJECT_DIR$/vendor/paragonie/constant_time_encoding" />
|
||||
<path value="$PROJECT_DIR$/vendor/graham-campbell/markdown" />
|
||||
<path value="$PROJECT_DIR$/vendor/league/mime-type-detection" />
|
||||
<path value="$PROJECT_DIR$/vendor/mcamara/laravel-localization" />
|
||||
<path value="$PROJECT_DIR$/vendor/mpociot/teamwork" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/code-unit" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/cli-parser" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/complexity" />
|
||||
<path value="$PROJECT_DIR$/vendor/sebastian/lines-of-code" />
|
||||
<path value="$PROJECT_DIR$/vendor/graham-campbell/result-type" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/http-client" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/http-client-contracts" />
|
||||
<path value="$PROJECT_DIR$/vendor/phpunit/php-invoker" />
|
||||
<path value="$PROJECT_DIR$/vendor/awssat/discord-notification-channel" />
|
||||
</include_path>
|
||||
</component>
|
||||
<component name="PhpProjectSharedConfiguration" php_language_level="7.2" />
|
||||
<component name="PhpProjectSharedConfiguration" php_language_level="7.3" />
|
||||
<component name="PhpUnit">
|
||||
<phpunit_settings>
|
||||
<PhpUnitSettings configuration_file_path="$PROJECT_DIR$/phpunit.xml" custom_loader_path="$PROJECT_DIR$/vendor/autoload.php" use_configuration_file="true" />
|
||||
|
5
.phive/phars.xml
Normal file
5
.phive/phars.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phive xmlns="https://phar.io/phive">
|
||||
<phar name="phpunit" version="^9.2.5" installed="9.2.5" location="./tools/phpunit" copy="false"/>
|
||||
<phar name="php-cs-fixer" version="^2.16.4" installed="2.16.4" location="./tools/php-cs-fixer" copy="false"/>
|
||||
</phive>
|
6
.vscode/launch.json
vendored
6
.vscode/launch.json
vendored
@@ -4,11 +4,15 @@
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
|
||||
{
|
||||
"name": "Listen for XDebug",
|
||||
"type": "php",
|
||||
"request": "launch",
|
||||
"port": 9000
|
||||
"port": 9000,
|
||||
"ignore": [
|
||||
"**/vendor/**/*.php"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Launch currently open script",
|
||||
|
76
CODE_OF_CONDUCT.md
Normal file
76
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at support@spacejewel-hosting.com. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
29
CONTRIBUTING.md
Normal file
29
CONTRIBUTING.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Thank you for contributing!
|
||||
|
||||
Read this file carefully before contributing to the project. It's important that everyone follows these rules to ensure smooth contribution.
|
||||
|
||||
## General workflow
|
||||
|
||||
Since the project is under version 1.0.0, the master branch can be quite unstable, and even unusable. For this reason, I recommend you stick to the published
|
||||
releases, unless you intend on helping out with the project.
|
||||
|
||||
New features are commited directly to the ``master`` branch, while translations are commited to a special service branch, merged onto ``translate``, tested, and
|
||||
merged back to master. Above version 1.0.0, new features should follow the same procedure as translations.
|
||||
|
||||
## Before commiting
|
||||
|
||||
Before commiting, make sure your code adheres to the Laravel coding guidelines, as well as PSR-4. I'll personally review and merge each PR.
|
||||
Thank you for your interest!
|
||||
|
||||
|
||||
# Bug reports
|
||||
|
||||
As always, bug reports should stick to the bug report template. GitHub makes this easy for you by letting you choose which issue template you'd like to use
|
||||
before reporting an isuse. This helps everyone stay in the same page.
|
||||
|
||||
Issues published without a template might take longer to be resolved, or may be ignored and marked ``wontfix``.
|
||||
|
||||
|
||||
# Licensing
|
||||
|
||||
Any contributions you make will be under the GNU GPL v3 license, which is the license that covers this project.
|
17
README.md
17
README.md
@@ -1,10 +1,11 @@
|
||||
# Raspberry Teams - The Simple Staff Application Manager v 0.1.0
|
||||
## The quick and pain-free staff application manager (for Minecraft)
|
||||
|
||||
# Raspberry Teams - The Simple Staff Application Manager v 0.6.2 [](https://crowdin.com/project/raspberry-staff-manager)
|
||||
## The quick and pain-free staff application manager
|
||||
|
||||
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?
|
||||
|
||||
|
||||
Wish you had a better application managemet strategy? Well, then Raspberry Teams is for you! It was originally designed and developed for internal use, but sharing is caring! After noticing a worrying lack of "human resources" management systems on SpigotMC's resources section (There was only one outdated/unsupported project), I've decided to take it up into my own terms and start working on it.
|
||||
Wish you had a better application managemet strategy? Well, then Raspberry Teams is for you! It was originally designed and developed for internal use for a gameserver network, but sharing is caring!
|
||||
|
||||
|
||||
# Features (not exhaustive)
|
||||
@@ -46,6 +47,14 @@ Tech stack:
|
||||
- AdminLTE / Bootstrap 4
|
||||
- jQuery / Plain Javascript
|
||||
- vueJS (in the future)
|
||||
|
||||
# Stability
|
||||
|
||||
Currently, the ``master`` branch is highly unstable, since it's under active development. Expect it to break with each commit. Even though I make an effort to make sure each commit is good to go before pushing, things might still break unexpectedly, and you may find a lot of bugs (which you should report).
|
||||
|
||||
Every released version is currently pre-release. If you really want to run this before version ``1.0.0`` comes out, always stay on the latest version, as those will always be tested before release, ensuring less chaos.
|
||||
|
||||
*Note: This application is NOT production ready! It won't be until the first stable release comes out, which might take a bit longer.
|
||||
|
||||
# Operating System Requirements
|
||||
|
||||
@@ -60,7 +69,7 @@ Tech stack:
|
||||
|
||||
- JSON
|
||||
- Curl (highly recommended)
|
||||
|
||||
- Image Magick (imagick) for 2FA support
|
||||
|
||||
# Installation
|
||||
|
||||
|
19
SECURITY.md
Normal file
19
SECURITY.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
The following versions are currently supported:
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 0.1.x | :x: |
|
||||
| 0.5.x | :x: |
|
||||
| 0.6.x | :white_check_mark: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
To securely report a vulnerability, you may send me an email directly containing the details of said vulnerability: ``me@nogueira.codes``.
|
||||
|
||||
You may optionally encrypt your message with my [public PGP key](http://pool.sks-keyservers.net/pks/lookup?op=get&search=0x48DF709E7405702B).
|
||||
|
||||
Use this free [online encryption tool](https://www.igolder.com/pgp/encryption/) if you don't know how to use PGP on your desktop.
|
@@ -7,11 +7,13 @@ use Illuminate\Database\Eloquent\Model;
|
||||
class Appointment extends Model
|
||||
{
|
||||
public $fillable = [
|
||||
'appointmentDescription',
|
||||
'appointmentDate',
|
||||
'applicationID',
|
||||
'appointmentDescription',
|
||||
'appointmentDate',
|
||||
'applicationID',
|
||||
'appointmentStatus',
|
||||
'appointmentLocation'
|
||||
'appointmentLocation',
|
||||
'meetingNotes',
|
||||
'userAccepted'
|
||||
];
|
||||
|
||||
public function application()
|
||||
|
@@ -8,7 +8,7 @@ class Ban extends Model
|
||||
{
|
||||
|
||||
public $fillable = [
|
||||
|
||||
|
||||
'userID',
|
||||
'reason',
|
||||
'bannedUntil',
|
||||
@@ -16,7 +16,11 @@ class Ban extends Model
|
||||
'authorUserID'
|
||||
|
||||
];
|
||||
|
||||
|
||||
public $dates = [
|
||||
'bannedUntil'
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo('App\User', 'userID', 'id');
|
||||
|
@@ -99,12 +99,16 @@ class Install extends Command
|
||||
$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: ');
|
||||
|
||||
$this->info('== Notification Settings (5/6) (Slack) ==');
|
||||
$settings['SLACK_INTEGRATION_WEBHOOK'] = $this->ask('Integration webhook URL');
|
||||
|
||||
$this->info('== Web Settings (6/6) ==');
|
||||
$settings['APP_URL'] = $this->ask('Application\'s URL');
|
||||
$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.'));
|
||||
|
||||
|
@@ -16,11 +16,6 @@ class IP
|
||||
public function lookup(string $IP): object
|
||||
{
|
||||
|
||||
if (empty($IP))
|
||||
{
|
||||
throw new LogicException(__METHOD__ . 'is missing parameter IP!');
|
||||
}
|
||||
|
||||
$params = [
|
||||
'apiKey' => config('general.keys.ipapi.apikey'),
|
||||
'ip' => $IP
|
||||
|
14
app/Facades/ContextAwareValidation.php
Normal file
14
app/Facades/ContextAwareValidation.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace App\Facades;
|
||||
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
|
||||
class ContextAwareValidation extends Facade
|
||||
{
|
||||
|
||||
protected static function getFacadeAccessor()
|
||||
{
|
||||
return 'contextAwareValidator';
|
||||
}
|
||||
|
||||
}
|
23
app/Facades/Options.php
Normal file
23
app/Facades/Options.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Facades;
|
||||
use \Illuminate\Support\Facades\Facade;
|
||||
use phpDocumentor\Reflection\Types\Boolean;
|
||||
|
||||
/**
|
||||
* Class Options
|
||||
* @package App\Facades
|
||||
*
|
||||
* @method static void setOption(string $option, string $value, string $description)
|
||||
* @method static string getOption(string $option)
|
||||
* @method static void changeOption(string $option, string $newValue)
|
||||
* @method static Boolean optionExists(string $option)
|
||||
*/
|
||||
class Options extends Facade
|
||||
{
|
||||
public static function getFacadeAccessor()
|
||||
{
|
||||
return 'smOptions';
|
||||
}
|
||||
}
|
138
app/Helpers/ContextAwareValidator.php
Normal file
138
app/Helpers/ContextAwareValidator.php
Normal file
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class ContextAwareValidator
|
||||
{
|
||||
|
||||
/**
|
||||
* The excludedNames array will make the validator ignore any of these names when including names into the rules.
|
||||
* @var array
|
||||
*/
|
||||
private $excludedNames = [
|
||||
'_token',
|
||||
'_method',
|
||||
'formName'
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Utility wrapper for json_encode.
|
||||
*
|
||||
* @param array $value The array to be converted.
|
||||
* @return string The JSON representation of $value
|
||||
*/
|
||||
private function encode(array $value) : string
|
||||
{
|
||||
return json_encode($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* The getValidator() method will take an array of fields from the request body, iterates through them,
|
||||
* and dynamically adds validation rules for them. Depending on parameters, it may or may not generate
|
||||
* a form structure for rendering purposes.
|
||||
*
|
||||
* This method is mostly meant by internal use by means of static proxies (Facades), in order to reduce code repetition;
|
||||
* Using it outside it's directed scope may cause unexpected results; For instance, the method expects inputs to be in array format, e.g. myFieldNameID1[],
|
||||
* myFieldNameID2[], and so on and so forth.
|
||||
*
|
||||
* This isn't checked by the code yet, but if you're implementing it this way in the HTML markup, make sure it's consistent (e.g. use a loop).
|
||||
*
|
||||
* P.S This method automatically ignores the CSRF token for validation.
|
||||
*
|
||||
* @param array $fields The request form fields
|
||||
* @param bool $generateStructure Whether to incldue a JSON-ready form structure for rendering
|
||||
* @param bool $includeFormName Whether to include formName in the list of validation rules
|
||||
* @return Validator|Collection A validator instance you can use to check for validity, or a Collection with a validator and structure (validator, structure)
|
||||
*/
|
||||
public function getValidator(array $fields, bool $generateStructure = false, bool $includeFormName = false)
|
||||
{
|
||||
$formStructure = [];
|
||||
$validator = [];
|
||||
|
||||
if ($includeFormName)
|
||||
{
|
||||
$validator['formName'] = 'required|string|max:100';
|
||||
}
|
||||
|
||||
foreach ($fields as $fieldName => $field)
|
||||
{
|
||||
if(!in_array($fieldName, $this->excludedNames))
|
||||
{
|
||||
$validator[$fieldName . ".0"] = 'required|string';
|
||||
$validator[$fieldName . ".1"] = 'required|string';
|
||||
|
||||
if ($generateStructure)
|
||||
{
|
||||
$formStructure['fields'][$fieldName]['title'] = $field[0];
|
||||
$formStructure['fields'][$fieldName]['type'] = $field[1];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
$validatorInstance = Validator::make($fields, $validator);
|
||||
|
||||
return ($generateStructure) ?
|
||||
collect([
|
||||
'validator' => $validatorInstance,
|
||||
'structure' => $this->encode($formStructure)
|
||||
])
|
||||
: $validatorInstance;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The getResponseValidator method is similar to the getValidator method; It basically takes
|
||||
* an array of fields from a previous form (that probably went through the other method) and adds validation
|
||||
* to the field names.
|
||||
*
|
||||
* Also generates the storable response structure if you tell it to.
|
||||
*
|
||||
* @param array $fields The received fields
|
||||
* @param array $formStructure The form structure - You must supply this if you want the response structure
|
||||
* @param bool $generateResponseStructure Whether to generate the response structure
|
||||
* @return Validator|Collection A collection or a validator, depending on the args. Will return validatior if only fields are supplied.
|
||||
*/
|
||||
public function getResponseValidator(array $fields, array $formStructure = [], bool $generateResponseStructure = true)
|
||||
{
|
||||
|
||||
$responseStructure = [];
|
||||
$validator = [];
|
||||
|
||||
if (empty($formStructure) && $generateResponseStructure)
|
||||
{
|
||||
throw new \InvalidArgumentException('Illegal combination of arguments supplied! Please check the method\'s documentation.');
|
||||
}
|
||||
|
||||
foreach($fields as $fieldName => $value)
|
||||
{
|
||||
if(!in_array($fieldName, $this->excludedNames))
|
||||
{
|
||||
$validator[$fieldName] = 'required|string';
|
||||
|
||||
if ($generateResponseStructure)
|
||||
{
|
||||
$responseStructure['responses'][$fieldName]['type'] = $formStructure['fields'][$fieldName]['type'] ?? 'Unavailable';
|
||||
$responseStructure['responses'][$fieldName]['title'] = $formStructure['fields'][$fieldName]['title'];
|
||||
$responseStructure['responses'][$fieldName]['response'] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$validatorInstance = Validator::make($fields, $validator);
|
||||
|
||||
return ($generateResponseStructure) ?
|
||||
collect([
|
||||
'validator' => $validatorInstance,
|
||||
'responseStructure' => $this->encode($responseStructure)
|
||||
])
|
||||
: $validatorInstance;
|
||||
|
||||
}
|
||||
|
||||
}
|
99
app/Helpers/Options.php
Normal file
99
app/Helpers/Options.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use App\Options as Option;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class Options
|
||||
{
|
||||
|
||||
public function getOption(string $option): string
|
||||
{
|
||||
$value = Cache::get($option);
|
||||
$fromCache = true;
|
||||
|
||||
if (is_null($value))
|
||||
{
|
||||
$fromCache = false;
|
||||
|
||||
Log::debug('Option ' . $option . 'not found in cache, refreshing from database');
|
||||
$value = Option::where('option_name', $option)->first();
|
||||
if (is_null($value))
|
||||
throw new \Exception('This option does not exist.');
|
||||
|
||||
Cache::put($option, $value);
|
||||
Cache::put($option . '_desc', 'Undefined description');
|
||||
}
|
||||
|
||||
return (!$fromCache)
|
||||
? $value->option_value
|
||||
: $value;
|
||||
}
|
||||
|
||||
public function setOption(string $option, string $value, string $description)
|
||||
{
|
||||
Option::create([
|
||||
'option_name' => $option,
|
||||
'option_value' => $value,
|
||||
'friendly_name' => $description
|
||||
]);
|
||||
|
||||
Cache::put($option, $value, now()->addDay());
|
||||
Cache::put($option . '_desc', $description, now()->addDay());
|
||||
}
|
||||
|
||||
public function pullOption($option): array
|
||||
{
|
||||
$oldOption = Option::where('option_name', $option)->first();
|
||||
Option::find($oldOption->id)->delete();
|
||||
|
||||
// putMany is overkill here
|
||||
return [
|
||||
Cache::pull($option),
|
||||
Cache::pull($option . '_desc')
|
||||
];
|
||||
}
|
||||
|
||||
public function changeOption($option, $newValue)
|
||||
{
|
||||
$dbOption = Option::where('option_name', $option);
|
||||
|
||||
if ($dbOption->first())
|
||||
{
|
||||
$dbOptionInstance = Option::find($dbOption->first()->id);
|
||||
Cache::forget($option);
|
||||
|
||||
Log::debug('Changing db configuration option', [
|
||||
'old_value' => $dbOptionInstance->option_value,
|
||||
'new_value' => $newValue
|
||||
]);
|
||||
|
||||
$dbOptionInstance->option_value = $newValue;
|
||||
$dbOptionInstance->save();
|
||||
|
||||
Log::debug('New db configuration option saved',
|
||||
[
|
||||
'option' => $dbOptionInstance->option_value
|
||||
]);
|
||||
|
||||
Cache::put('option_name', $newValue, now()->addDay());
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new \Exception('This option does not exist.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function optionExists(string $option): bool
|
||||
{
|
||||
$dbOption = Option::where('option_name', $option)->first();
|
||||
$locallyCachedOption = Cache::get($option);
|
||||
|
||||
return !is_null($dbOption) || !is_null($locallyCachedOption);
|
||||
}
|
||||
|
||||
}
|
@@ -18,8 +18,11 @@ use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
use ContextAwareValidator;
|
||||
|
||||
class ApplicationController extends Controller
|
||||
{
|
||||
|
||||
private function canVote($votes)
|
||||
{
|
||||
$allvotes = collect([]);
|
||||
@@ -45,11 +48,8 @@ class ApplicationController extends Controller
|
||||
}
|
||||
|
||||
|
||||
public function showUserApp(Request $request, $applicationID)
|
||||
public function showUserApp(Request $request, Application $application)
|
||||
{
|
||||
// TODO: Inject it instead (do this where there is no injection, not just here)
|
||||
$application = Application::find($applicationID);
|
||||
|
||||
$this->authorize('view', $application);
|
||||
|
||||
if (!is_null($application))
|
||||
@@ -78,6 +78,8 @@ class ApplicationController extends Controller
|
||||
|
||||
public function showAllApps()
|
||||
{
|
||||
$this->authorize('viewAny', Application::class);
|
||||
|
||||
return view('dashboard.appmanagement.all')
|
||||
->with('applications', Application::paginate(6));
|
||||
}
|
||||
@@ -186,36 +188,16 @@ class ApplicationController extends Controller
|
||||
Log::info('Processing new application!');
|
||||
|
||||
$formStructure = json_decode($vacancy->first()->forms->formStructure, true);
|
||||
$responseStructure = [];
|
||||
|
||||
$excludedNames = [
|
||||
'_token',
|
||||
];
|
||||
|
||||
$validator = [];
|
||||
|
||||
foreach($request->all() as $fieldName => $value)
|
||||
{
|
||||
if(!in_array($fieldName, $excludedNames))
|
||||
{
|
||||
$validator[$fieldName] = 'required|string';
|
||||
|
||||
$responseStructure['responses'][$fieldName]['type'] = $formStructure['fields'][$fieldName]['type'] ?? 'Unavailable';
|
||||
$responseStructure['responses'][$fieldName]['title'] = $formStructure['fields'][$fieldName]['title'];
|
||||
$responseStructure['responses'][$fieldName]['response'] = $value;
|
||||
}
|
||||
}
|
||||
$responseValidation = ContextAwareValidator::getResponseValidator($request->all(), $formStructure);
|
||||
|
||||
Log::info('Built response & validator structure!');
|
||||
|
||||
$validation = Validator::make($request->all(), $validator);
|
||||
|
||||
if (!$validation->fails())
|
||||
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' => json_encode($responseStructure)
|
||||
'responseData' => $responseValidation->get('responseStructure')
|
||||
]);
|
||||
|
||||
Log::info('Registered form response for user ' . Auth::user()->name . ' for vacancy ' . $vacancy->first()->vacancyName);
|
||||
@@ -249,35 +231,27 @@ class ApplicationController extends Controller
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function updateApplicationStatus(Request $request, $applicationID, $newStatus)
|
||||
public function updateApplicationStatus(Request $request, Application $application, $newStatus)
|
||||
{
|
||||
$application = Application::find($applicationID);
|
||||
$this->authorize('update', Application::class);
|
||||
|
||||
if (!is_null($application))
|
||||
switch ($newStatus)
|
||||
{
|
||||
switch ($newStatus)
|
||||
{
|
||||
case 'deny':
|
||||
case 'deny':
|
||||
|
||||
event(new ApplicationDeniedEvent($application));
|
||||
break;
|
||||
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');
|
||||
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;
|
||||
$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.');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$request->session()->flash('The application you\'re trying to update does not exist.');
|
||||
default:
|
||||
$request->session()->flash('error', 'There are no suitable statuses to update to. Do not mess with the URL.');
|
||||
}
|
||||
|
||||
return redirect()->back();
|
||||
@@ -291,7 +265,7 @@ class ApplicationController extends Controller
|
||||
|
||||
$request->session()->flash('success', 'Application deleted. Comments, appointments and responses have also been deleted.');
|
||||
return redirect()->back();
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -24,84 +24,60 @@ class AppointmentController extends Controller
|
||||
|
||||
];
|
||||
|
||||
public function saveAppointment(Request $request, $applicationID)
|
||||
public function saveAppointment(Request $request, Application $application)
|
||||
{
|
||||
// Unrelated TODO: change if's in application page to a switch statement, & have the row encompass it
|
||||
|
||||
$this->authorize('create', Appointment::class);
|
||||
$appointmentDate = Carbon::parse($request->appointmentDateTime);
|
||||
|
||||
$app = Application::find($applicationID);
|
||||
|
||||
if (!is_null($app))
|
||||
{
|
||||
// make sure this is a valid date by parsing it first
|
||||
$appointmentDate = Carbon::parse($request->appointmentDateTime);
|
||||
$appointment = Appointment::create([
|
||||
'appointmentDescription' => $request->appointmentDescription,
|
||||
'appointmentDate' => $appointmentDate->toDateTimeString(),
|
||||
'applicationID' => $application->id,
|
||||
'appointmentLocation' => (in_array($request->appointmentLocation, $this->allowedPlatforms)) ? $request->appointmentLocation : 'DISCORD',
|
||||
]);
|
||||
$application->setStatus('STAGE_INTERVIEW_SCHEDULED');
|
||||
|
||||
|
||||
$appointment = Appointment::create([
|
||||
'appointmentDescription' => $request->appointmentDescription,
|
||||
'appointmentDate' => $appointmentDate->toDateTimeString(),
|
||||
'applicationID' => $applicationID,
|
||||
'appointmentLocation' => (in_array($request->appointmentLocation, $this->allowedPlatforms)) ? $request->appointmentLocation : 'DISCORD',
|
||||
]);
|
||||
$app->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());
|
||||
|
||||
Log::info('User ' . Auth::user()->name . ' has scheduled an appointment with ' . $app->user->name . ' for application ID' . $app->id, [
|
||||
'datetime' => $appointmentDate->toDateTimeString(),
|
||||
'scheduled' => now()
|
||||
]);
|
||||
|
||||
$app->user->notify(new AppointmentScheduled($appointment));
|
||||
$request->session()->flash('success', 'Appointment successfully scheduled @ ' . $appointmentDate->toDateTimeString());
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
$request->session()->flash('error', 'Cant\'t schedule an appointment for an application that doesn\'t exist.');
|
||||
}
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function updateAppointment(Request $request, $applicationID, $status)
|
||||
public function updateAppointment(Request $request, Application $application, $status)
|
||||
{
|
||||
$this->authorize('update', $application->appointment);
|
||||
|
||||
$this->authorize('update', Appointment::class);
|
||||
|
||||
$application = Application::find($applicationID);
|
||||
$validStatuses = [
|
||||
'SCHEDULED',
|
||||
'CONCLUDED'
|
||||
];
|
||||
|
||||
// NOTE: This is a little confusing, refactor
|
||||
$application->appointment->appointmentStatus = (in_array($status, $validStatuses)) ? strtoupper($status) : 'SCHEDULED';
|
||||
$application->appointment->save();
|
||||
|
||||
if (!is_null($application))
|
||||
{
|
||||
// NOTE: This is a little confusing, refactor
|
||||
$application->appointment->appointmentStatus = (in_array($status, $validStatuses)) ? strtoupper($status) : 'SCHEDULED';
|
||||
$application->appointment->save();
|
||||
$application->setStatus('STAGE_PEERAPPROVAL');
|
||||
$application->user->notify(new ApplicationMoved());
|
||||
|
||||
$application->setStatus('STAGE_PEERAPPROVAL');
|
||||
$application->user->notify(new ApplicationMoved());
|
||||
|
||||
$request->session()->flash('success', 'Interview finished! Staff members can now vote on it.');
|
||||
}
|
||||
else
|
||||
{
|
||||
$request->session()->flash('error', 'The application you\'re trying to update doesn\'t exist or have an appointment.');
|
||||
}
|
||||
|
||||
$request->session()->flash('success', 'Interview finished! Staff members can now vote on it.');
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
// also updates
|
||||
public function saveNotes(SaveNotesRequest $request, $applicationID)
|
||||
public function saveNotes(SaveNotesRequest $request, Application $application)
|
||||
{
|
||||
$application = Application::find($applicationID);
|
||||
|
||||
if (!is_null($application))
|
||||
{
|
||||
$application->load('appointment');
|
||||
|
||||
$application->appointment->meetingNotes = $request->noteText;
|
||||
$application->appointment->save();
|
||||
|
||||
@@ -109,7 +85,7 @@ class AppointmentController extends Controller
|
||||
}
|
||||
else
|
||||
{
|
||||
$request->session()->flash('error', 'Sanity check failed: There\'s no appointment to save notes to!');
|
||||
$request->session()->flash('error', 'There\'s no appointment to save notes to!');
|
||||
}
|
||||
|
||||
return redirect()->back();
|
||||
|
@@ -44,7 +44,7 @@ class LoginController extends Controller
|
||||
|
||||
// We can't customise the error message, since that would imply overriding the login method, which is large.
|
||||
// Also, the user should never know that they're banned.
|
||||
public function attemptLogin(Request $request)
|
||||
public function attemptLogin(Request $request)
|
||||
{
|
||||
$user = User::where('email', $request->email)->first();
|
||||
|
||||
@@ -60,9 +60,9 @@ class LoginController extends Controller
|
||||
return $this->originalAttemptLogin($request);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $this->originalAttemptLogin($request);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@@ -70,7 +70,7 @@ class RegisterController extends Controller
|
||||
'uuid' => ['required', 'string', 'unique:users', 'min:32', 'max:32'],
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
|
||||
'password' => ['required', 'string', 'min:8', 'confirmed'],
|
||||
'password' => ['required', 'string', 'min:10', 'confirmed', 'regex:/^.*(?=.{3,})(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[\d\x])(?=.*[!$#%]).*$/'],
|
||||
], [
|
||||
'uuid.required' => 'Please enter a valid (and Premium) Minecraft username! We do not support cracked users.'
|
||||
]);
|
||||
|
16
app/Http/Controllers/Auth/TwofaController.php
Normal file
16
app/Http/Controllers/Auth/TwofaController.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Traits\AuthenticatesTwoFactor;
|
||||
|
||||
class TwofaController extends Controller
|
||||
{
|
||||
use AuthenticatesTwoFactor;
|
||||
|
||||
|
||||
protected $redirectTo = '/dashboard';
|
||||
|
||||
}
|
@@ -15,11 +15,7 @@ class BanController extends Controller
|
||||
public function insert(BanUserRequest $request, User $user)
|
||||
{
|
||||
|
||||
if ($user->is(Auth::user()))
|
||||
{
|
||||
$request->session()->flash('error', 'You can\'t ban yourself!');
|
||||
return redirect()->back();
|
||||
}
|
||||
$this->authorize('create', [Ban::class, $user]);
|
||||
|
||||
if (is_null($user->bans))
|
||||
{
|
||||
@@ -28,35 +24,39 @@ class BanController extends Controller
|
||||
$duration = strtolower($request->durationOperator);
|
||||
$durationOperand = $request->durationOperand;
|
||||
|
||||
$expiryDate = now();
|
||||
|
||||
if (!empty($duration))
|
||||
{
|
||||
$expiryDate = now();
|
||||
|
||||
switch($duration)
|
||||
{
|
||||
case 'days':
|
||||
$expiryDate->addDays($duration);
|
||||
$expiryDate->addDays($durationOperand);
|
||||
break;
|
||||
|
||||
case 'weeks':
|
||||
$expiryDate->addWeeks($duration);
|
||||
$expiryDate->addWeeks($durationOperand);
|
||||
break;
|
||||
|
||||
case 'months':
|
||||
$expiryDate->addMonths($duration);
|
||||
$expiryDate->addMonths($durationOperand);
|
||||
break;
|
||||
|
||||
case 'years':
|
||||
$expiryDate->addYears($duration);
|
||||
$expiryDate->addYears($durationOperand);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Essentially permanent
|
||||
$expiryDate->addYears(5);
|
||||
}
|
||||
|
||||
$ban = Ban::create([
|
||||
'userID' => $user->id,
|
||||
'reason' => $request->reason,
|
||||
'bannedUntil' => $expiryDate->toDateTimeString() ?? null,
|
||||
'reason' => $reason,
|
||||
'bannedUntil' => $expiryDate->format('Y-m-d H:i:s'),
|
||||
'userAgent' => "Unknown",
|
||||
'authorUserID' => Auth::user()->id
|
||||
]);
|
||||
|
@@ -22,7 +22,7 @@ class CommentController extends Controller
|
||||
public function insert(NewCommentRequest $request, Application $application)
|
||||
{
|
||||
$this->authorize('create', Comment::class);
|
||||
|
||||
|
||||
$comment = Comment::create([
|
||||
'authorID' => Auth::user()->id,
|
||||
'applicationID' => $application->id,
|
||||
@@ -32,14 +32,6 @@ class CommentController extends Controller
|
||||
if ($comment)
|
||||
{
|
||||
|
||||
foreach (User::all() as $user)
|
||||
{
|
||||
if ($user->isStaffMember())
|
||||
{
|
||||
$user->notify(new NewComment($comment, $application));
|
||||
}
|
||||
}
|
||||
|
||||
$request->session()->flash('success', 'Comment posted! (:');
|
||||
}
|
||||
else
|
||||
|
@@ -4,11 +4,23 @@ namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use GuzzleHttp;
|
||||
use App\Notifications\NewContact;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
use App\User;
|
||||
|
||||
class ContactController extends Controller
|
||||
{
|
||||
|
||||
protected $users;
|
||||
|
||||
|
||||
public function __construct(User $users)
|
||||
{
|
||||
$this->users = $users;
|
||||
}
|
||||
|
||||
|
||||
public function create(Request $request)
|
||||
{
|
||||
$name = $request->name;
|
||||
@@ -18,12 +30,14 @@ class ContactController extends Controller
|
||||
|
||||
$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' => $_SERVER['REMOTE_ADDR']
|
||||
'remoteip' => $request->ip()
|
||||
]);
|
||||
|
||||
|
||||
$response = json_decode($verifyrequest->getBody(), true);
|
||||
|
||||
if (!$response['success'])
|
||||
@@ -32,7 +46,18 @@ class ContactController extends Controller
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
// TODO: Send mail
|
||||
|
||||
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();
|
||||
|
@@ -6,16 +6,30 @@ use App\Application;
|
||||
use App\Events\ApplicationApprovedEvent;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class DevToolsController extends Controller
|
||||
{
|
||||
|
||||
// The use case for Laravel's gate and/or validation Requests is so tiny here that a full-blown policy would be overkill.
|
||||
protected function isolatedAuthorise()
|
||||
{
|
||||
if (!Auth::user()->can('admin.developertools.use'))
|
||||
{
|
||||
abort(403, 'You\'re not authorized to access this page.');
|
||||
}
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
$this->isolatedAuthorise();
|
||||
return view('dashboard.administration.devtools')
|
||||
->with('applications', Application::where('applicationStatus', 'STAGE_PEERAPPROVAL')->get());
|
||||
}
|
||||
|
||||
public function forceVoteCount(Request $request)
|
||||
{
|
||||
$this->isolatedAuthorise();
|
||||
$application = Application::find($request->application);
|
||||
|
||||
if (!is_null($application))
|
||||
|
@@ -7,6 +7,8 @@ use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
use ContextAwareValidator;
|
||||
|
||||
class FormController extends Controller
|
||||
{
|
||||
|
||||
@@ -29,39 +31,25 @@ class FormController extends Controller
|
||||
{
|
||||
|
||||
$this->authorize('create', Form::class);
|
||||
$fields = $request->all();
|
||||
|
||||
$formFields = $request->all();
|
||||
|
||||
$formStructure = [];
|
||||
$excludedNames = [
|
||||
'_token',
|
||||
'formName' // It's added outside the loop. Not excluding causes unwanted duplication.
|
||||
];
|
||||
$validator = [
|
||||
'formName' => 'required|string|max:100'
|
||||
];
|
||||
|
||||
foreach ($formFields as $fieldName => $field)
|
||||
if (count($fields) == 2)
|
||||
{
|
||||
if(!in_array($fieldName, $excludedNames))
|
||||
{
|
||||
$validator[$fieldName . ".0"] = 'required|string';
|
||||
$validator[$fieldName . ".1"] = 'required|string';
|
||||
// form is probably empty, since forms with fields will alawys have more than 2 items
|
||||
|
||||
$formStructure['fields'][$fieldName]['title'] = $field[0];
|
||||
$formStructure['fields'][$fieldName]['type'] = $field[1];
|
||||
}
|
||||
$request->session()->flash('error', 'Sorry, but you may not create empty forms.');
|
||||
return redirect()->to(route('showForms'));
|
||||
}
|
||||
|
||||
$validation = Validator::make($formFields, $validator);
|
||||
$contextValidation = ContextAwareValidator::getValidator($fields, true, true);
|
||||
|
||||
if (!$validation->fails())
|
||||
if (!$contextValidation->get('validator')->fails())
|
||||
{
|
||||
$storableFormStructure = json_encode($formStructure);
|
||||
$storableFormStructure = $contextValidation->get('structure');
|
||||
|
||||
Form::create(
|
||||
[
|
||||
'formName' => $formFields['formName'],
|
||||
'formName' => $fields['formName'],
|
||||
'formStructure' => $storableFormStructure,
|
||||
'formStatus' => 'ACTIVE'
|
||||
]
|
||||
@@ -71,14 +59,12 @@ class FormController extends Controller
|
||||
return redirect()->to(route('showForms'));
|
||||
}
|
||||
|
||||
$request->session()->flash('errors', $validation->errors()->getMessages());
|
||||
$request->session()->flash('errors', $contextValidation->get('validator')->errors()->getMessages());
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function destroy(Request $request, $id)
|
||||
public function destroy(Request $request, Form $form)
|
||||
{
|
||||
|
||||
$form = Form::find($id);
|
||||
$this->authorize('delete', $form);
|
||||
$deletable = true;
|
||||
|
||||
@@ -105,9 +91,49 @@ class FormController extends Controller
|
||||
|
||||
public function preview(Request $request, Form $form)
|
||||
{
|
||||
$this->authorize('viewAny', Form::class);
|
||||
|
||||
return view('dashboard.administration.formpreview')
|
||||
->with('form', json_decode($form->formStructure, true))
|
||||
->with('title', $form->formName);
|
||||
->with('title', $form->formName)
|
||||
->with('formID', $form->id);
|
||||
}
|
||||
|
||||
public function edit(Request $request, Form $form)
|
||||
{
|
||||
$this->authorize('update', $form);
|
||||
|
||||
return view('dashboard.administration.editform')
|
||||
->with('formStructure', json_decode($form->formStructure, true))
|
||||
->with('title', $form->formName)
|
||||
->with('formID', $form->id);
|
||||
}
|
||||
|
||||
public function update(Request $request, Form $form)
|
||||
{
|
||||
$this->authorize('update', $form);
|
||||
|
||||
$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());
|
||||
}
|
||||
|
||||
return redirect()->to(route('previewForm', ['form' => $form->id]));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
77
app/Http/Controllers/OptionsController.php
Normal file
77
app/Http/Controllers/OptionsController.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Facades\Options;
|
||||
use App\Options as Option;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class OptionsController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Http\Response|\Illuminate\View\View
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
// TODO: Obtain this from the facade
|
||||
$options = Option::all();
|
||||
|
||||
|
||||
return view('dashboard.administration.settings')
|
||||
->with('options', $options);
|
||||
}
|
||||
|
||||
public function saveSettings(Request $request)
|
||||
{
|
||||
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);
|
||||
|
||||
$errorCond = true;
|
||||
$request->session()->flash('error', 'An error occurred while trying to save settings: ' . $ex->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($errorCond))
|
||||
{
|
||||
$request->session()->flash('success', 'Settings saved successfully!');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$request->session()->flash('error', 'You do not have permission to update this resource.');
|
||||
}
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
}
|
@@ -87,7 +87,6 @@ class ProfileController extends Controller
|
||||
|
||||
public function saveProfile(ProfileSave $request)
|
||||
{
|
||||
// TODO: Switch to route model binding
|
||||
$profile = User::find(Auth::user()->id)->profile;
|
||||
$social = [];
|
||||
|
||||
@@ -120,19 +119,6 @@ class ProfileController extends Controller
|
||||
$request->session()->flash('success', 'Profile settings saved successfully.');
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
$gm = 'Guru Meditation #' . rand(0, 1000);
|
||||
Log::alert('[GURU MEDITATION]: Could not find profile for authenticated user ' . Auth::user()->name . 'whilst trying to update it! Please verify that profiles are being created automatically during signup.',
|
||||
[
|
||||
'uuid' => Auth::user()->uuid,
|
||||
'timestamp' => now(),
|
||||
'route' => $request->route()->getName(),
|
||||
'gmcode' => $gm // If this error is reported, the GM code, denoting a severe error, will help us find this entry in the logs
|
||||
|
||||
]);
|
||||
$request->session()->flash('error', 'A technical error has occurred whilst trying to save your profile. Incident details have been recorded. Please report this incident to administrators with the following case number: ' . $gm);
|
||||
}
|
||||
|
||||
return redirect()->back();
|
||||
|
||||
|
262
app/Http/Controllers/TeamController.php
Normal file
262
app/Http/Controllers/TeamController.php
Normal file
@@ -0,0 +1,262 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\EditTeamRequest;
|
||||
use App\Http\Requests\NewTeamRequest;
|
||||
use App\Http\Requests\SendInviteRequest;
|
||||
use App\Mail\InviteToTeam;
|
||||
use App\Team;
|
||||
use App\User;
|
||||
use App\Vacancy;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Mpociot\Teamwork\Exceptions\UserNotInTeamException;
|
||||
use Mpociot\Teamwork\Facades\Teamwork;
|
||||
use Mpociot\Teamwork\TeamInvite;
|
||||
|
||||
|
||||
class TeamController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$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
|
||||
*/
|
||||
public function store(NewTeamRequest $request)
|
||||
{
|
||||
$team = Team::create([
|
||||
'name' => $request->teamName,
|
||||
'owner_id' => Auth::user()->id
|
||||
]);
|
||||
|
||||
Auth::user()->teams()->attach($team->id);
|
||||
|
||||
$request->session()->flash('success', 'Team successfully created.');
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function show($id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function edit(Team $team)
|
||||
{
|
||||
return view('dashboard.teams.edit-team')
|
||||
->with('team', $team)
|
||||
->with('users', User::all())
|
||||
->with('vacancies', Vacancy::with('teams')->get()->all());
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function update(EditTeamRequest $request, Team $team)
|
||||
{
|
||||
$team->description = $request->teamDescription;
|
||||
$team->openJoin = $request->joinType;
|
||||
|
||||
$team->save();
|
||||
|
||||
$request->session()->flash('success', 'Team edited successfully.');
|
||||
return redirect()->to(route('teams.index'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function destroy($id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function invite(SendInviteRequest $request, Team $team)
|
||||
{
|
||||
$user = User::findOrFail($request->user);
|
||||
|
||||
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));
|
||||
});
|
||||
|
||||
$request->session()->flash('success', 'Invite sent! They can now accept or deny it.');
|
||||
}
|
||||
else
|
||||
{
|
||||
$request->session()->flash('error', 'This user has already been invited.');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
$request->session()->flash('error', 'You can\'t invite users to public teams.');
|
||||
}
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function processInviteAction(Request $request, $action, $token)
|
||||
{
|
||||
|
||||
switch($action)
|
||||
{
|
||||
case 'accept':
|
||||
|
||||
$invite = Teamwork::getInviteFromAcceptToken($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.');
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
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.');
|
||||
|
||||
|
||||
}
|
||||
|
||||
// This page will show the user's current teams
|
||||
return redirect()->to(route('teams.index'));
|
||||
|
||||
}
|
||||
|
||||
public function switchTeam(Request $request, Team $team)
|
||||
{
|
||||
try
|
||||
{
|
||||
Auth::user()->switchTeam($team);
|
||||
|
||||
$request->session()->flash('success', 'Switched teams! Your team dashboard will now use this context.');
|
||||
}
|
||||
catch(UserNotInTeamException $ex)
|
||||
{
|
||||
$request->session()->flash('error', 'You can\'t switch to a team you don\'t belong to.');
|
||||
}
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
|
||||
// Since it's a separate form, we shouldn't use the same update method
|
||||
public function assignVacancies(Request $request, Team $team)
|
||||
{
|
||||
// P.S. To future developers
|
||||
// This method gave me a lot of trouble lol. It's hard to write code when you're half asleep.
|
||||
// There may be an n+1 query in the view and I don't think there's a way to avoid that without writing a lot of extra code.
|
||||
|
||||
$requestVacancies = $request->assocVacancies;
|
||||
$currentVacancies = $team->vacancies->pluck('id')->all();
|
||||
|
||||
if (is_null($requestVacancies))
|
||||
{
|
||||
|
||||
foreach ($team->vacancies as $vacancy)
|
||||
{
|
||||
$team->vacancies()->detach($vacancy->id);
|
||||
}
|
||||
|
||||
$request->session()->flash('success', 'Removed all vacancy associations.');
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
$vacancyDiff = array_diff($requestVacancies, $currentVacancies);
|
||||
$deselectedDiff = array_diff($currentVacancies, $requestVacancies);
|
||||
|
||||
|
||||
if (!empty($vacancyDiff) || !empty($deselectedDiff))
|
||||
{
|
||||
foreach ($vacancyDiff as $selectedVacancy)
|
||||
{
|
||||
$team->vacancies()->attach($selectedVacancy);
|
||||
}
|
||||
|
||||
foreach ($deselectedDiff as $deselectedVacancy)
|
||||
{
|
||||
$team->vacancies()->detach($deselectedVacancy);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$team->vacancies()->attach($requestVacancies);
|
||||
}
|
||||
|
||||
$request->session()->flash('success', 'Assignments changed successfully.');
|
||||
return redirect()->back();
|
||||
|
||||
}
|
||||
}
|
@@ -8,6 +8,8 @@ use App\Http\Requests\FlushSessionsRequest;
|
||||
use App\Http\Requests\DeleteUserRequest;
|
||||
use App\Http\Requests\SearchPlayerRequest;
|
||||
use App\Http\Requests\UpdateUserRequest;
|
||||
use App\Http\Requests\Add2FASecretRequest;
|
||||
use App\Http\Requests\Remove2FASecretRequest;
|
||||
|
||||
use App\User;
|
||||
use App\Ban;
|
||||
@@ -21,9 +23,13 @@ use App\Notifications\EmailChanged;
|
||||
use App\Notifications\ChangedPassword;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
use App\Traits\ReceivesAccountTokens;
|
||||
use Google2FA;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
|
||||
use ReceivesAccountTokens;
|
||||
|
||||
public function showStaffMembers()
|
||||
{
|
||||
@@ -112,10 +118,32 @@ class UserController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
public function showAccount()
|
||||
public function showAccount(Request $request)
|
||||
{
|
||||
$QRCode = null;
|
||||
|
||||
if (!$request->user()->has2FA())
|
||||
{
|
||||
if ($request->session()->has('twofaAttemptFailed'))
|
||||
{
|
||||
$twoFactorSecret = $request->session()->get('current2FA');
|
||||
}
|
||||
else
|
||||
{
|
||||
$twoFactorSecret = Google2FA::generateSecretKey(32, '');
|
||||
$request->session()->put('current2FA', $twoFactorSecret);
|
||||
}
|
||||
|
||||
$QRCode = Google2FA::getQRCodeInline(
|
||||
config('app.name'),
|
||||
$request->user()->email,
|
||||
$twoFactorSecret
|
||||
);
|
||||
}
|
||||
|
||||
return view('dashboard.user.profile.useraccount')
|
||||
->with('ip', request()->ip());
|
||||
->with('ip', request()->ip())
|
||||
->with('twofaQRCode', $QRCode);
|
||||
}
|
||||
|
||||
|
||||
@@ -189,9 +217,12 @@ class UserController extends Controller
|
||||
|
||||
public function delete(DeleteUserRequest $request, User $user)
|
||||
{
|
||||
|
||||
$this->authorize('delete', $user);
|
||||
|
||||
if ($request->confirmPrompt == 'DELETE ACCOUNT')
|
||||
{
|
||||
$user->delete();
|
||||
$user->forceDelete();
|
||||
$request->session()->flash('success','User deleted successfully. PII has been erased.');
|
||||
}
|
||||
else
|
||||
@@ -203,9 +234,12 @@ class UserController extends Controller
|
||||
return redirect()->route('registeredPlayerList');
|
||||
}
|
||||
|
||||
|
||||
public function update(UpdateUserRequest $request, User $user)
|
||||
{
|
||||
|
||||
$this->authorize('adminEdit', $user);
|
||||
|
||||
// Mass update would not be possible here without extra code, making route model binding useless
|
||||
$user->email = $request->email;
|
||||
$user->name = $request->name;
|
||||
@@ -242,10 +276,67 @@ class UserController extends Controller
|
||||
|
||||
}
|
||||
|
||||
public function add2FASecret(Add2FASecretRequest $request)
|
||||
{
|
||||
$currentSecret = $request->session()->get('current2FA');
|
||||
$isValid = Google2FA::verifyKey($currentSecret, $request->otp);
|
||||
|
||||
if ($isValid)
|
||||
{
|
||||
$request->user()->twofa_secret = $currentSecret;
|
||||
$request->user()->save();
|
||||
|
||||
Log::warning('SECURITY: User activated two-factor authentication', [
|
||||
'initiator' => $request->user()->email,
|
||||
'ip' => $request->ip()
|
||||
]);
|
||||
|
||||
Google2FA::login();
|
||||
|
||||
Log::warning('SECURITY: Started two factor session automatically', [
|
||||
'initiator' => $request->user()->email,
|
||||
'ip' => $request->ip()
|
||||
]);
|
||||
|
||||
$request->session()->forget('current2FA');
|
||||
|
||||
if ($request->session()->has('twofaAttemptFailed'))
|
||||
$request->session()->forget('twofaAttemptFailed');
|
||||
|
||||
|
||||
$request->session()->flash('success', '2FA succesfully enabled! You\'ll now be prompted for an OTP each time you log in.');
|
||||
}
|
||||
else
|
||||
{
|
||||
$request->session()->flash('error', 'Incorrect code. Please reopen the 2FA settings panel and try again.');
|
||||
$request->session()->put('twofaAttemptFailed', true);
|
||||
}
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function remove2FASecret(Remove2FASecretRequest $request)
|
||||
{
|
||||
Log::warning('SECURITY: Disabling two factor authentication (user initiated)', [
|
||||
'initiator' => $request->user()->email,
|
||||
'ip' => $request->ip()
|
||||
]);
|
||||
|
||||
$request->user()->twofa_secret = null;
|
||||
$request->user()->save();
|
||||
|
||||
$request->session()->flash('success', 'Two-factor authentication disabled.');
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public function terminate(Request $request, User $user)
|
||||
{
|
||||
$this->authorize('terminate', User::class);
|
||||
|
||||
// TODO: move logic to policy
|
||||
if (!$user->isStaffMember() || $user->is(Auth::user()))
|
||||
{
|
||||
$request->session()->flash('error', 'You cannot terminate this user.');
|
||||
@@ -268,4 +359,6 @@ class UserController extends Controller
|
||||
//TODO: Dispatch event
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -64,10 +64,9 @@ class VacancyController extends Controller
|
||||
|
||||
}
|
||||
|
||||
public function updatePositionAvailability(Request $request, $status, $id)
|
||||
public function updatePositionAvailability(Request $request, $status, Vacancy $vacancy)
|
||||
{
|
||||
|
||||
$vacancy = Vacancy::find($id);
|
||||
$this->authorize('update', $vacancy);
|
||||
|
||||
if (!is_null($vacancy))
|
||||
@@ -112,24 +111,24 @@ class VacancyController extends Controller
|
||||
}
|
||||
|
||||
|
||||
public function edit(Request $request, Vacancy $position)
|
||||
public function edit(Request $request, Vacancy $vacancy)
|
||||
{
|
||||
$this->authorize('update', $vacancy);
|
||||
return view('dashboard.administration.editposition')
|
||||
->with('vacancy', $position);
|
||||
->with('vacancy', $vacancy);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function update(VacancyEditRequest $request, Vacancy $position)
|
||||
public function update(VacancyEditRequest $request, Vacancy $vacancy)
|
||||
{
|
||||
$this->authorize('update', $vacancy);
|
||||
|
||||
$position->vacancyFullDescription = $request->vacancyFullDescription;
|
||||
$position->vacancyDescription = $request->vacancyDescription;
|
||||
$position->vacancyCount = $request->vacancyCount;
|
||||
$vacancy->vacancyFullDescription = $request->vacancyFullDescription;
|
||||
$vacancy->vacancyDescription = $request->vacancyDescription;
|
||||
$vacancy->vacancyCount = $request->vacancyCount;
|
||||
|
||||
$position->save();
|
||||
$vacancy->save();
|
||||
|
||||
$request->session()->flash('success', 'Vacancy successfully updated.');
|
||||
return redirect()->back();
|
||||
|
@@ -13,33 +13,23 @@ use Illuminate\Support\Facades\Log;
|
||||
class VoteController extends Controller
|
||||
{
|
||||
|
||||
public function vote(VoteRequest $voteRequest, $applicationID)
|
||||
public function vote(VoteRequest $voteRequest, Application $application)
|
||||
{
|
||||
$application = Application::find($applicationID);
|
||||
$this->authorize('create', Vote::class);
|
||||
|
||||
if (!is_null($application))
|
||||
{
|
||||
$vote = Vote::create([
|
||||
'userID' => Auth::user()->id,
|
||||
'allowedVoteType' => $voteRequest->voteType,
|
||||
]);
|
||||
$vote = Vote::create([
|
||||
'userID' => Auth::user()->id,
|
||||
'allowedVoteType' => $voteRequest->voteType,
|
||||
]);
|
||||
$vote->application()->attach($application->id);
|
||||
|
||||
$vote->application()->attach($applicationID);
|
||||
|
||||
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! You will now be notified about the outcome of this application.');
|
||||
}
|
||||
else
|
||||
{
|
||||
$voteRequest->session()->flash('error', 'Can\t vote a non existant application!');
|
||||
}
|
||||
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!');
|
||||
|
||||
// Cron job will run command that processes votes
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
}
|
||||
|
@@ -64,6 +64,12 @@ class Kernel extends HttpKernel
|
||||
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||
'eligibility' => \App\Http\Middleware\ApplicationEligibility::class,
|
||||
'usernameUUID' => \App\Http\Middleware\UsernameUUID::class,
|
||||
'forcelogout' => \App\Http\Middleware\ForceLogoutMiddleware::class
|
||||
'forcelogout' => \App\Http\Middleware\ForceLogoutMiddleware::class,
|
||||
'2fa' => \PragmaRX\Google2FALaravel\Middleware::class,
|
||||
'localize' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRoutes::class,
|
||||
'localizationRedirect' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRedirectFilter::class,
|
||||
'localeSessionRedirect' => \Mcamara\LaravelLocalization\Middleware\LocaleSessionRedirect::class,
|
||||
'localeCookieRedirect' => \Mcamara\LaravelLocalization\Middleware\LocaleCookieRedirect::class,
|
||||
'localeViewPath' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationViewPath::class
|
||||
];
|
||||
}
|
||||
|
@@ -12,12 +12,12 @@ class TrustProxies extends Middleware
|
||||
*
|
||||
* @var array|string
|
||||
*/
|
||||
protected $proxies;
|
||||
protected $proxies = "*";
|
||||
|
||||
/**
|
||||
* The headers that should be used to detect proxies.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $headers = Request::HEADER_X_FORWARDED_ALL;
|
||||
protected $headers = Request::HEADER_X_FORWARDED_AWS_ELB;
|
||||
}
|
||||
|
31
app/Http/Requests/Add2FASecretRequest.php
Normal file
31
app/Http/Requests/Add2FASecretRequest.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class Add2FASecretRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
// current logic only updates currently authenticated user
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'otp' => 'required|string|min:6|max:6'
|
||||
];
|
||||
}
|
||||
}
|
@@ -27,7 +27,7 @@ class BanUserRequest extends FormRequest
|
||||
{
|
||||
return [
|
||||
'reason' => 'required|string',
|
||||
'durationOperand' => 'nullable|integer',
|
||||
'durationOperand' => 'nullable|string',
|
||||
'durationOperator' => 'nullable|string'
|
||||
];
|
||||
}
|
||||
|
31
app/Http/Requests/EditTeamRequest.php
Normal file
31
app/Http/Requests/EditTeamRequest.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class EditTeamRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'teamDescription' => 'required|string|max:200',
|
||||
'joinType' => 'required|boolean'
|
||||
];
|
||||
}
|
||||
}
|
30
app/Http/Requests/NewTeamRequest.php
Normal file
30
app/Http/Requests/NewTeamRequest.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class NewTeamRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'teamName' => 'required|max:200|string'
|
||||
];
|
||||
}
|
||||
}
|
31
app/Http/Requests/Remove2FASecretRequest.php
Normal file
31
app/Http/Requests/Remove2FASecretRequest.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class Remove2FASecretRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'currentPassword' => 'required|password',
|
||||
'consent' => 'required|accepted'
|
||||
];
|
||||
}
|
||||
}
|
30
app/Http/Requests/SendInviteRequest.php
Normal file
30
app/Http/Requests/SendInviteRequest.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class SendInviteRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'user' => 'required|integer'
|
||||
];
|
||||
}
|
||||
}
|
39
app/Http/Requests/UserDeleteRequest.php
Normal file
39
app/Http/Requests/UserDeleteRequest.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class UserDeleteRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
if (Auth::user()->has2FA())
|
||||
{
|
||||
return [
|
||||
'currentPassword' => 'required|password:web',
|
||||
'otp' => 'required|integer|max:6'
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'currentPassword' => 'required|password:web'
|
||||
];
|
||||
}
|
||||
}
|
45
app/Listeners/LogAuthenticationFailure.php
Normal file
45
app/Listeners/LogAuthenticationFailure.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class LogAuthenticationFailure
|
||||
{
|
||||
/**
|
||||
* Create the event listener.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param object $event
|
||||
* @return void
|
||||
*/
|
||||
public function handle($event)
|
||||
{
|
||||
$targetAccountID = 0;
|
||||
$originalIP = "0.0.0.0";
|
||||
|
||||
if (isset($event->user->id))
|
||||
{
|
||||
$targetAccountID = $event->user->id;
|
||||
}
|
||||
|
||||
Log::alert('SECURITY (login): Detected failed authentication attempt!', [
|
||||
'targetAccountID' => $targetAccountID,
|
||||
'existingAccount' => ($targetAccountID == 0) ? false : true,
|
||||
'sourceIP' => request()->ip(),
|
||||
'matchesAccountLastIP' => request()->ip() == $originalIP,
|
||||
'sourceUserAgent' => request()->userAgent(),
|
||||
]);
|
||||
}
|
||||
}
|
36
app/Listeners/LogAuthenticationSuccess.php
Normal file
36
app/Listeners/LogAuthenticationSuccess.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class LogAuthenticationSuccess
|
||||
{
|
||||
/**
|
||||
* Create the event listener.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param object $event
|
||||
* @return void
|
||||
*/
|
||||
public function handle($event)
|
||||
{
|
||||
Log::info('SECURITY (postauth-pre2fa): Detected successful login attempt', [
|
||||
'accountID' => $event->user->id,
|
||||
'sourceIP' => request()->ip(),
|
||||
'matchesAccountLastIP' => request()->ip() == $event->user->originalIP,
|
||||
'sourceUserAgent' => request()->userAgent(),
|
||||
]);
|
||||
}
|
||||
}
|
55
app/Mail/InviteToTeam.php
Normal file
55
app/Mail/InviteToTeam.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Mpociot\Teamwork\TeamInvite;
|
||||
|
||||
class InviteToTeam extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
|
||||
public $teamName;
|
||||
|
||||
|
||||
public $name;
|
||||
|
||||
|
||||
public $inviterName;
|
||||
|
||||
|
||||
public $denyToken;
|
||||
|
||||
|
||||
public $acceptToken;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(TeamInvite $invite)
|
||||
{
|
||||
$this->teamName = $invite->team->name;
|
||||
$this->name = $invite->user->name;
|
||||
$this->inviterName = $invite->inviter->name;
|
||||
$this->acceptToken = $invite->accept_token;
|
||||
$this->denyToken = $invite->deny_token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the message.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
return $this
|
||||
->subject('You have just been invited to ' . $this->teamName)
|
||||
->view('mail.invited-to-team');
|
||||
}
|
||||
}
|
56
app/Mail/UserAccountDeleteConfirmation.php
Normal file
56
app/Mail/UserAccountDeleteConfirmation.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
use App\User;
|
||||
|
||||
class UserAccountDeleteConfirmation extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
|
||||
|
||||
public $deleteToken;
|
||||
|
||||
|
||||
public $cancelToken;
|
||||
|
||||
|
||||
public $originalIP;
|
||||
|
||||
|
||||
public $name;
|
||||
|
||||
|
||||
public $userID;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(User $user, array $tokens, string $originalIP)
|
||||
{
|
||||
$this->deleteToken = $tokens['delete'];
|
||||
$this->cancelToken = $tokens['cancel'];
|
||||
|
||||
$this->originalIP = $originalIP;
|
||||
$this->name = $user->name;
|
||||
$this->userID = $user->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the message.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
return $this->view('mail.deleted-account');
|
||||
}
|
||||
}
|
@@ -2,6 +2,9 @@
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Facades\Options;
|
||||
use App\Traits\Cancellable;
|
||||
use App\Traits\DiscordRoutable;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
@@ -11,7 +14,7 @@ use App\Application;
|
||||
|
||||
class ApplicationApproved extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
use Queueable, Cancellable, DiscordRoutable;
|
||||
|
||||
public $application;
|
||||
|
||||
@@ -24,15 +27,15 @@ class ApplicationApproved extends Notification implements ShouldQueue
|
||||
{
|
||||
$this->application = $application;
|
||||
}
|
||||
/**
|
||||
* Get the notification's delivery channels.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function via($notifiable)
|
||||
|
||||
public function channels()
|
||||
{
|
||||
return ['mail', 'slack'];
|
||||
return $this->chooseChannelsViaOptions();
|
||||
}
|
||||
|
||||
public function optOut($notifiable)
|
||||
{
|
||||
return Options::getOption('notify_applicant_approved') !== 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,6 +81,11 @@ class ApplicationApproved extends Notification implements ShouldQueue
|
||||
});
|
||||
}
|
||||
|
||||
public function toDiscord($notifiable)
|
||||
{
|
||||
return $this->toSlack($notifiable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array representation of the notification.
|
||||
*
|
||||
|
@@ -2,6 +2,9 @@
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Facades\Options;
|
||||
use App\Traits\DiscordRoutable;
|
||||
use Awssat\Notifications\Messages\DiscordMessage;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
@@ -11,7 +14,7 @@ use App\Application;
|
||||
|
||||
class ApplicationDenied extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
use Queueable, DiscordRoutable;
|
||||
|
||||
|
||||
public $application;
|
||||
@@ -34,14 +37,22 @@ class ApplicationDenied extends Notification implements ShouldQueue
|
||||
*/
|
||||
public function via($notifiable)
|
||||
{
|
||||
return ['mail', 'slack'];
|
||||
$options = ['mail'];
|
||||
|
||||
if (Options::getOption('enable_discord_notifications'))
|
||||
array_push($options, 'discord');
|
||||
|
||||
if (Options::getOption('enable_slack_notifications'))
|
||||
array_push($options, 'slack');
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mail representation of the notification.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return \Illuminate\Notifications\Messages\MailMessage
|
||||
* @return MailMessage
|
||||
*/
|
||||
public function toMail($notifiable)
|
||||
{
|
||||
@@ -52,7 +63,7 @@ class ApplicationDenied extends Notification implements ShouldQueue
|
||||
->line('Your most recent application has been denied.')
|
||||
->line('Our review team denies applications for several reasons, including poor answers.')
|
||||
->line('Please review your application and try again in 30 days.')
|
||||
->action('Review application', url(route('showUserApp', ['id' => $this->application->id])))
|
||||
->action('Review application', url(route('showUserApp', ['application' => $this->application->id])))
|
||||
->line('Better luck next time!');
|
||||
}
|
||||
|
||||
@@ -70,6 +81,13 @@ class ApplicationDenied extends Notification implements ShouldQueue
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public function toDiscord($notifiable)
|
||||
{
|
||||
// SlackMessage is similar to DiscordMessage, so they're compatible
|
||||
return $this->toSlack($notifiable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array representation of the notification.
|
||||
*
|
||||
|
@@ -6,10 +6,12 @@ use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use App\Traits\Cancellable;
|
||||
use App\Facades\Options;
|
||||
|
||||
class ApplicationMoved extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
use Queueable, Cancellable;
|
||||
|
||||
/**
|
||||
* Create a new notification instance.
|
||||
@@ -21,15 +23,9 @@ class ApplicationMoved extends Notification implements ShouldQueue
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification's delivery channels.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function via($notifiable)
|
||||
public function optOut($notifiable)
|
||||
{
|
||||
return ['mail'];
|
||||
return Options::getOption('notify_application_status_change') !== 1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Traits\DiscordRoutable;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
@@ -10,9 +11,12 @@ use Illuminate\Notifications\Notification;
|
||||
use App\Application;
|
||||
use App\Vacancy;
|
||||
|
||||
use App\Traits\Cancellable;
|
||||
use App\Facades\Options;
|
||||
|
||||
class NewApplicant extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
use Queueable, Cancellable, DiscordRoutable;
|
||||
|
||||
|
||||
protected $application;
|
||||
@@ -23,7 +27,8 @@ class NewApplicant extends Notification implements ShouldQueue
|
||||
/**
|
||||
* Create a new notification instance.
|
||||
*
|
||||
* @return void
|
||||
* @param Application $application
|
||||
* @param Vacancy $vacancy
|
||||
*/
|
||||
public function __construct(Application $application, Vacancy $vacancy)
|
||||
{
|
||||
@@ -31,15 +36,14 @@ class NewApplicant extends Notification implements ShouldQueue
|
||||
$this->vacancy = $vacancy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification's delivery channels.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function via($notifiable)
|
||||
public function channels()
|
||||
{
|
||||
return ['slack'];
|
||||
$this->chooseChannelsViaOptions();
|
||||
}
|
||||
|
||||
public function optOut($notifiable)
|
||||
{
|
||||
return Options::getOption('notify_new_user') !== 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,7 +59,7 @@ class NewApplicant extends Notification implements ShouldQueue
|
||||
->subject(config('app.name') . ' - New application')
|
||||
->line('Someone has just applied for a position. Check it out!')
|
||||
->line('You are receiving this because you\'re a staff member at ' . config('app.name') . '.')
|
||||
->action('View Application', url(route('showUserApp', ['id' => $this->application->id])))
|
||||
->action('View Application', url(route('showUserApp', ['application' => $this->application->id])))
|
||||
->line('Thank you!');
|
||||
}
|
||||
|
||||
@@ -67,7 +71,7 @@ class NewApplicant extends Notification implements ShouldQueue
|
||||
$vacancyDetails['name'] = $this->vacancy->vacancyName;
|
||||
$vacancyDetails['slots'] = $this->vacancy->vacancyCount;
|
||||
|
||||
$url = route('showUserApp', ['id' => $this->application->id]);
|
||||
$url = route('showUserApp', ['application' => $this->application->id]);
|
||||
$applicant = $this->application->user->name;
|
||||
|
||||
return (new SlackMessage)
|
||||
@@ -83,6 +87,12 @@ class NewApplicant extends Notification implements ShouldQueue
|
||||
->action('Review application', $url);
|
||||
});
|
||||
}
|
||||
|
||||
public function toDiscord($notifiable)
|
||||
{
|
||||
return $this->toSlack($notifiable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array representation of the notification.
|
||||
*
|
||||
|
@@ -8,10 +8,12 @@ use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use App\Comment;
|
||||
use App\Application;
|
||||
use App\Traits\Cancellable;
|
||||
use App\Facades\Options;
|
||||
|
||||
class NewComment extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
use Queueable, Cancellable;
|
||||
|
||||
|
||||
protected $application;
|
||||
@@ -19,22 +21,17 @@ class NewComment extends Notification implements ShouldQueue
|
||||
/**
|
||||
* Create a new notification instance.
|
||||
*
|
||||
* @return void
|
||||
* @param Comment $comment
|
||||
* @param Application $application
|
||||
*/
|
||||
public function __construct(Comment $comment, Application $application)
|
||||
{
|
||||
$this->application = $application;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification's delivery channels.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function via($notifiable)
|
||||
public function optOut($notifiable)
|
||||
{
|
||||
return ['mail'];
|
||||
return Options::getOption('notify_application_comment') !== 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,7 +47,7 @@ class NewComment extends Notification implements ShouldQueue
|
||||
->subject(config('app.name') . ' - New comment')
|
||||
->line('Someone has just posted a new comment on an application you follow.')
|
||||
->line('You\'re receiving this email because you\'ve voted/commented on this application.')
|
||||
->action('Check it out', url(route('showUserApp', ['id' => $this->application->id])))
|
||||
->action('Check it out', url(route('showUserApp', ['application' => $this->application->id])))
|
||||
->line('Thank you!');
|
||||
}
|
||||
|
||||
|
78
app/Notifications/NewContact.php
Normal file
78
app/Notifications/NewContact.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class NewContact extends Notification
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $message;
|
||||
|
||||
/**
|
||||
* Create a new notification instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Collection $message)
|
||||
{
|
||||
$this->message = $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification's delivery channels.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function via($notifiable)
|
||||
{
|
||||
return ['mail'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mail representation of the notification.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return \Illuminate\Notifications\Messages\MailMessage
|
||||
*/
|
||||
public function toMail($notifiable)
|
||||
{
|
||||
if ($this->message->has([
|
||||
'message',
|
||||
'ip',
|
||||
'email'
|
||||
]))
|
||||
{
|
||||
return (new MailMessage)
|
||||
->line('We\'ve received a new contact form submission in the StaffManagement app center.')
|
||||
->line('This is what they sent: ')
|
||||
->line('')
|
||||
->line($this->message->get('message'))
|
||||
->line('')
|
||||
->line('This message was received from ' . $this->message->get('ip') . ' and submitted by ' . $this->message->get('email') . '.')
|
||||
->action('Sign in', url(route('login')))
|
||||
->line('Thank you!');
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException("Invalid arguments supplied to NewContact!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array representation of the notification.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function toArray($notifiable)
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Traits\DiscordRoutable;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
@@ -10,10 +11,12 @@ use Illuminate\Notifications\Messages\SlackMessage;
|
||||
|
||||
use App\User;
|
||||
use App\Facades\UUID;
|
||||
use App\Traits\Cancellable;
|
||||
use App\Facades\Options;
|
||||
|
||||
class NewUser extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
use Queueable, Cancellable, DiscordRoutable;
|
||||
|
||||
public $user;
|
||||
|
||||
@@ -27,18 +30,14 @@ class NewUser extends Notification implements ShouldQueue
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification's delivery channels.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function via($notifiable)
|
||||
public function channels($notifiable)
|
||||
{
|
||||
return ($notifiable->isStaffMember())
|
||||
? ['slack', 'mail']
|
||||
: ['mail'];
|
||||
return $this->chooseChannelsViaOptions();
|
||||
}
|
||||
|
||||
public function optOut($notifiable)
|
||||
{
|
||||
return Options::getOption('notify_new_user') !== 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,6 +84,11 @@ class NewUser extends Notification implements ShouldQueue
|
||||
});
|
||||
}
|
||||
|
||||
public function toDiscord($notifiable)
|
||||
{
|
||||
return $this->toSlack($notifiable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array representation of the notification.
|
||||
*
|
||||
|
@@ -9,10 +9,12 @@ use Illuminate\Notifications\Notification;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
use App\Vacancy;
|
||||
use App\Facades\Options;
|
||||
use App\Traits\Cancellable;
|
||||
|
||||
class VacancyClosed extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
use Queueable, SerializesModels, Cancellable;
|
||||
|
||||
protected $vacancy;
|
||||
|
||||
@@ -26,15 +28,9 @@ class VacancyClosed extends Notification implements ShouldQueue
|
||||
$this->vacancy = $vacancy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification's delivery channels.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function via($notifiable)
|
||||
public function optOut($notifiable)
|
||||
{
|
||||
return ['mail'];
|
||||
return Options::getOption('notify_vacancystatus_change') !== 1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -3,6 +3,7 @@
|
||||
namespace App\Observers;
|
||||
|
||||
use App\Application;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class ApplicationObserver
|
||||
{
|
||||
@@ -55,7 +56,7 @@ class ApplicationObserver
|
||||
}
|
||||
}
|
||||
|
||||
// application can now be deleted
|
||||
// application can now be deleted
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -8,6 +8,12 @@ use Illuminate\Support\Facades\Log;
|
||||
|
||||
class UserObserver
|
||||
{
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
Log::debug('User observer has been initialised and ready for use!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the user "created" event.
|
||||
*
|
||||
@@ -39,20 +45,28 @@ class UserObserver
|
||||
|
||||
public function deleting(User $user)
|
||||
{
|
||||
$user->profile()->delete();
|
||||
Log::debug('Referential integrity cleanup: Deleted profile!');
|
||||
$applications = $user->applications;
|
||||
|
||||
if (!$applications->isEmpty())
|
||||
if ($user->isForceDeleting())
|
||||
{
|
||||
Log::debug('RIC: Now trying to delete applications and responses...');
|
||||
foreach($applications as $application)
|
||||
$user->profile->delete();
|
||||
Log::debug('Referential integrity cleanup: Deleted profile!');
|
||||
$applications = $user->applications;
|
||||
|
||||
if (!$applications->isEmpty())
|
||||
{
|
||||
// code moved to Application observer, where it gets rid of attached elements individually
|
||||
Log::debug('RIC: Deleting application ' . $application->id);
|
||||
$application->delete();
|
||||
|
||||
Log::debug('RIC: Now trying to delete applications and responses...');
|
||||
foreach($applications as $application)
|
||||
{
|
||||
// code moved to Application observer, where it gets rid of attached elements individually
|
||||
Log::debug('RIC: Deleting application ' . $application->id);
|
||||
$application->delete();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
Log::debug('RIC: Not cleaning up soft deleted models!');
|
||||
}
|
||||
|
||||
Log::debug('RIC: Cleanup done!');
|
||||
@@ -66,7 +80,6 @@ class UserObserver
|
||||
*/
|
||||
public function deleted(User $user)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,6 +101,8 @@ class UserObserver
|
||||
*/
|
||||
public function forceDeleted(User $user)
|
||||
{
|
||||
//
|
||||
Log::info('Model has been force deleted', [
|
||||
'modelID' => $user->id
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
14
app/Options.php
Normal file
14
app/Options.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Options extends Model
|
||||
{
|
||||
public $fillable = [
|
||||
'option_name',
|
||||
'option_value',
|
||||
'friendly_name'
|
||||
];
|
||||
}
|
@@ -13,7 +13,7 @@ class AppointmentPolicy
|
||||
/**
|
||||
* Determine whether the user can view any models.
|
||||
*
|
||||
* @param \App\User $user
|
||||
* @param User $user
|
||||
* @return mixed
|
||||
*/
|
||||
public function viewAny(User $user)
|
||||
@@ -24,8 +24,8 @@ class AppointmentPolicy
|
||||
/**
|
||||
* Determine whether the user can view the model.
|
||||
*
|
||||
* @param \App\User $user
|
||||
* @param \App\Appointment $appointment
|
||||
* @param User $user
|
||||
* @param Appointment $appointment
|
||||
* @return mixed
|
||||
*/
|
||||
public function view(User $user, Appointment $appointment)
|
||||
@@ -36,7 +36,7 @@ class AppointmentPolicy
|
||||
/**
|
||||
* Determine whether the user can create models.
|
||||
*
|
||||
* @param \App\User $user
|
||||
* @param User $user
|
||||
* @return mixed
|
||||
*/
|
||||
public function create(User $user)
|
||||
@@ -47,8 +47,8 @@ class AppointmentPolicy
|
||||
/**
|
||||
* Determine whether the user can update the model.
|
||||
*
|
||||
* @param \App\User $user
|
||||
* @param \App\Appointment $appointment
|
||||
* @param User $user
|
||||
* @param Appointment $appointment
|
||||
* @return mixed
|
||||
*/
|
||||
public function update(User $user, Appointment $appointment)
|
||||
@@ -59,8 +59,8 @@ class AppointmentPolicy
|
||||
/**
|
||||
* Determine whether the user can delete the model.
|
||||
*
|
||||
* @param \App\User $user
|
||||
* @param \App\Appointment $appointment
|
||||
* @param User $user
|
||||
* @param Appointment $appointment
|
||||
* @return mixed
|
||||
*/
|
||||
public function delete(User $user, Appointment $appointment)
|
||||
@@ -71,8 +71,8 @@ class AppointmentPolicy
|
||||
/**
|
||||
* Determine whether the user can restore the model.
|
||||
*
|
||||
* @param \App\User $user
|
||||
* @param \App\Appointment $appointment
|
||||
* @param User $user
|
||||
* @param Appointment $appointment
|
||||
* @return mixed
|
||||
*/
|
||||
public function restore(User $user, Appointment $appointment)
|
||||
@@ -83,8 +83,8 @@ class AppointmentPolicy
|
||||
/**
|
||||
* Determine whether the user can permanently delete the model.
|
||||
*
|
||||
* @param \App\User $user
|
||||
* @param \App\Appointment $appointment
|
||||
* @param User $user
|
||||
* @param Appointment $appointment
|
||||
* @return mixed
|
||||
*/
|
||||
public function forceDelete(User $user, Appointment $appointment)
|
||||
|
@@ -5,6 +5,8 @@ namespace App\Policies;
|
||||
use App\Ban;
|
||||
use App\User;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class BanPolicy
|
||||
{
|
||||
@@ -36,12 +38,19 @@ class BanPolicy
|
||||
/**
|
||||
* Determine whether the user can create models.
|
||||
*
|
||||
* @param \App\User $user
|
||||
* @param \App\User $user
|
||||
* @param User $targetUser
|
||||
* @return mixed
|
||||
*/
|
||||
public function create(User $user)
|
||||
public function create(User $user, User $targetUser)
|
||||
{
|
||||
//
|
||||
Log::debug("Authorization check started", [
|
||||
'requiredRoles' => 'admin',
|
||||
'hasRequiredRole' => $user->hasRole('admin'),
|
||||
'targetUser' => $targetUser->username,
|
||||
'isCurrentUser' => Auth::user()->is($user)
|
||||
]);
|
||||
return $user->hasRole('admin') && $user->isNot($targetUser);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,7 +62,7 @@ class BanPolicy
|
||||
*/
|
||||
public function update(User $user, Ban $ban)
|
||||
{
|
||||
//
|
||||
return $user->hasRole('admin');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -57,7 +57,7 @@ class FormPolicy
|
||||
*/
|
||||
public function update(User $user, Form $form)
|
||||
{
|
||||
// unused
|
||||
return $user->can('admin.hiring.forms');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -24,6 +24,12 @@ class UserPolicy
|
||||
return $authUser->is($user) || $authUser->hasRole('admin');
|
||||
}
|
||||
|
||||
// This refers to the admin tools that let staff update more information than users themselves can
|
||||
public function adminEdit(User $authUser, User $user)
|
||||
{
|
||||
return $authUser->hasRole('admin') && $authUser->isNot($user);
|
||||
}
|
||||
|
||||
public function viewStaff(User $user)
|
||||
{
|
||||
return $user->can('admin.stafflist');
|
||||
@@ -38,4 +44,9 @@ class UserPolicy
|
||||
{
|
||||
return $authUser->hasRole('admin');
|
||||
}
|
||||
|
||||
public function delete(User $authUser, User $subject)
|
||||
{
|
||||
return $authUser->hasRole('admin') && $authUser->isNot($subject);
|
||||
}
|
||||
}
|
||||
|
@@ -2,10 +2,15 @@
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Application;
|
||||
use App\Observers\ApplicationObserver;
|
||||
use App\Observers\UserObserver;
|
||||
use App\User;
|
||||
|
||||
use Illuminate\Pagination\Paginator;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
use Sentry;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
@@ -32,6 +37,13 @@ class AppServiceProvider extends ServiceProvider
|
||||
]);
|
||||
|
||||
Schema::defaultStringLength(191);
|
||||
|
||||
// Keep using Bootstrap; Laravel 8 has the paginator use Tailwind. Quite opinionated tbh
|
||||
Paginator::useBootstrap();
|
||||
|
||||
User::observe(UserObserver::class);
|
||||
Application::observe(ApplicationObserver::class);
|
||||
|
||||
$this->app['request']->server->set('HTTPS', $this->app->environment() != 'local');
|
||||
}
|
||||
}
|
||||
|
@@ -2,15 +2,15 @@
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Http\Controllers\BanController;
|
||||
use App\Http\Controllers\VoteController;
|
||||
use App\Http\Controllers\ProfileController;
|
||||
use App\Http\Controllers\AppointmentController;
|
||||
use App\Policies\ProfilePolicy;
|
||||
use App\Policies\VacancyPolicy;
|
||||
use App\Policies\UserPolicy;
|
||||
use App\Policies\BanPolicy;
|
||||
use App\Policies\FormPolicy;
|
||||
use App\Policies\VotePolicy;
|
||||
use App\Policies\ApplicationPolicy;
|
||||
use App\Policies\AppointmentPolicy;
|
||||
|
||||
use App\User;
|
||||
use App\Form;
|
||||
use App\Vote;
|
||||
@@ -18,6 +18,8 @@ use App\Vacancy;
|
||||
use App\Application;
|
||||
use App\Appointment;
|
||||
use App\Ban;
|
||||
|
||||
|
||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
@@ -36,9 +38,9 @@ class AuthServiceProvider extends ServiceProvider
|
||||
Vacancy::class => VacancyPolicy::class,
|
||||
//Form::class => FormPolicy::class
|
||||
'App\Form' => 'App\Policies\FormPolicy',
|
||||
Vote::class => VoteController::class,
|
||||
Ban::class => BanController::class,
|
||||
Appointment::class => AppointmentController::class
|
||||
Vote::class => VotePolicy::class,
|
||||
Ban::class => BanPolicy::class,
|
||||
Appointment::class => AppointmentPolicy::class
|
||||
];
|
||||
|
||||
/**
|
||||
|
34
app/Providers/ContextAwareValidatorProvider.php
Normal file
34
app/Providers/ContextAwareValidatorProvider.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
use App;
|
||||
|
||||
class ContextAwareValidatorProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
App::bind('contextAwareValidator', function(){
|
||||
|
||||
return new App\Helpers\ContextAwareValidator();
|
||||
|
||||
});
|
||||
}
|
||||
}
|
@@ -2,7 +2,11 @@
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Listeners\LogAuthenticationFailure;
|
||||
use App\Listeners\LogAuthenticationSuccess;
|
||||
use App\Listeners\OnUserRegistration;
|
||||
use Illuminate\Auth\Events\Failed;
|
||||
use Illuminate\Auth\Events\Login;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
|
||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||
@@ -20,6 +24,12 @@ class EventServiceProvider extends ServiceProvider
|
||||
SendEmailVerificationNotification::class,
|
||||
OnUserRegistration::class
|
||||
],
|
||||
Failed::class => [
|
||||
LogAuthenticationFailure::class
|
||||
],
|
||||
Login::class => [
|
||||
LogAuthenticationSuccess::class
|
||||
],
|
||||
'App\Events\ApplicationApprovedEvent' => [
|
||||
'App\Listeners\PromoteUser'
|
||||
],
|
||||
|
31
app/Providers/OptionsProvider.php
Normal file
31
app/Providers/OptionsProvider.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use App;
|
||||
|
||||
class OptionsProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
App::bind('smOptions', function (){
|
||||
return new App\Helpers\Options();
|
||||
});
|
||||
}
|
||||
}
|
@@ -7,14 +7,6 @@ use Illuminate\Support\Facades\Route;
|
||||
|
||||
class RouteServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* This namespace is applied to your controller routes.
|
||||
*
|
||||
* In addition, it is set as the URL generator's root namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'App\Http\Controllers';
|
||||
|
||||
/**
|
||||
* The path to the "home" route for your application.
|
||||
@@ -59,7 +51,6 @@ class RouteServiceProvider extends ServiceProvider
|
||||
protected function mapWebRoutes()
|
||||
{
|
||||
Route::middleware('web')
|
||||
->namespace($this->namespace)
|
||||
->group(base_path('routes/web.php'));
|
||||
}
|
||||
|
||||
|
35
app/Services/VacancyApplicationService.php
Normal file
35
app/Services/VacancyApplicationService.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Vacancy;
|
||||
use App\Application;
|
||||
|
||||
class VacancyApplicationService
|
||||
{
|
||||
|
||||
/**
|
||||
* Finds all applications associated with $model.
|
||||
*
|
||||
* @param Vacancy $model The model you want to search through.
|
||||
* @return Illuminate\Support\Collection A collection of applications
|
||||
*/
|
||||
public function findApplications(Vacancy $model)
|
||||
{
|
||||
|
||||
$applications = collect([]);
|
||||
|
||||
foreach(Application::all() as $application)
|
||||
{
|
||||
if ($application->response->vacancy->id == $model->id)
|
||||
{
|
||||
$applications->push($application);
|
||||
}
|
||||
}
|
||||
|
||||
return $applications;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
22
app/Team.php
Normal file
22
app/Team.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Mpociot\Teamwork\TeamworkTeam;
|
||||
|
||||
class Team extends TeamworkTeam
|
||||
{
|
||||
public $fillable = [
|
||||
'owner_id',
|
||||
'name',
|
||||
'description',
|
||||
'openJoin'
|
||||
];
|
||||
|
||||
|
||||
public function vacancies()
|
||||
{
|
||||
return $this->belongsToMany('App\Vacancy', 'team_has_vacancy');
|
||||
}
|
||||
}
|
40
app/Traits/AuthenticatesTwoFactor.php
Normal file
40
app/Traits/AuthenticatesTwoFactor.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
use Google2FA;
|
||||
use App\Http\Requests\Add2FASecretRequest;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
|
||||
trait AuthenticatesTwoFactor
|
||||
{
|
||||
|
||||
public function verify2FA(Add2FASecretRequest $request)
|
||||
{
|
||||
$isValid = Google2FA::verifyKey($request->user()->twofa_secret, $request->otp);
|
||||
|
||||
if ($isValid)
|
||||
{
|
||||
Google2FA::login();
|
||||
|
||||
Log::info('SECURITY (postauth): One-time password verification succeeded', [
|
||||
'initiator' => $request->user()->email,
|
||||
'ip' => $request->ip()
|
||||
]);
|
||||
|
||||
return redirect()->to($this->redirectTo);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log::warning('SECURITY (preauth): One-time password verification failed', [
|
||||
'initiator' => $request->user()->email,
|
||||
'ip' => $request->ip()
|
||||
]);
|
||||
|
||||
$request->session()->flash('error', 'Your one time password is invalid.');
|
||||
return redirect()->back();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
57
app/Traits/Cancellable.php
Normal file
57
app/Traits/Cancellable.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
|
||||
use App\Facades\Options;
|
||||
|
||||
trait Cancellable
|
||||
{
|
||||
|
||||
// This method is only used if you want this default set of channels;
|
||||
// Other channels can always be configured by overloading the channels method here.
|
||||
public function chooseChannelsViaOptions()
|
||||
{
|
||||
$channels = [];
|
||||
|
||||
if (Options::getOption('enable_slack_notifications') == 1)
|
||||
{
|
||||
array_push($channels, 'slack');
|
||||
}
|
||||
|
||||
if (Options::getOption('enable_email_notifications') == 1)
|
||||
{
|
||||
array_push($channels, 'email');
|
||||
}
|
||||
|
||||
if (Options::getOption('enable_discord_notifications'))
|
||||
{
|
||||
array_push($channels, 'discord');
|
||||
}
|
||||
|
||||
return $channels;
|
||||
}
|
||||
|
||||
public function channels()
|
||||
{
|
||||
return ['mail'];
|
||||
}
|
||||
|
||||
public function via($notifiable)
|
||||
{
|
||||
if ($this->optOut($notifiable))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->channels();
|
||||
}
|
||||
|
||||
|
||||
public function optOut($notifiable)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
15
app/Traits/DiscordRoutable.php
Normal file
15
app/Traits/DiscordRoutable.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
|
||||
trait DiscordRoutable
|
||||
{
|
||||
|
||||
public function routeNotificationForDiscord()
|
||||
{
|
||||
return config('channels.notifications.discord.webhook_url');
|
||||
}
|
||||
|
||||
}
|
51
app/Traits/HandlesAccountTokens.php
Normal file
51
app/Traits/HandlesAccountTokens.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
trait HandlesAccountTokens
|
||||
{
|
||||
|
||||
|
||||
public function generateAccountTokens()
|
||||
{
|
||||
$deleteToken = bin2hex(openssl_random_pseudo_bytes(32));
|
||||
$cancelToken = bin2hex(openssl_random_pseudo_bytes(32));
|
||||
|
||||
$tokens = [
|
||||
|
||||
'delete' => Hash::make($deleteToken),
|
||||
'cancel' => Hash::make($cancelToken)
|
||||
|
||||
];
|
||||
|
||||
$this->account_tokens = json_encode($tokens);
|
||||
$this->save();
|
||||
|
||||
return [
|
||||
|
||||
'delete' => $deleteToken,
|
||||
'cancel' => $cancelToken
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
public function verifyAccountToken(string $token, string $type): bool
|
||||
{
|
||||
$tokens = json_decode($this->account_tokens);
|
||||
|
||||
if ($type == 'deleteToken')
|
||||
{
|
||||
return Hash::check($token, $tokens->delete);
|
||||
}
|
||||
elseif ($type == 'cancelToken')
|
||||
{
|
||||
return Hash::check($token, $tokens->cancel);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
84
app/Traits/ReceivesAccountTokens.php
Normal file
84
app/Traits/ReceivesAccountTokens.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
|
||||
use App\Http\Requests\UserDeleteRequest;
|
||||
use App\Mail\UserAccountDeleteConfirmation;
|
||||
use App\User;
|
||||
|
||||
|
||||
trait ReceivesAccountTokens
|
||||
{
|
||||
public function userDelete(UserDeleteRequest $request)
|
||||
{
|
||||
// a little verbose
|
||||
$user = User::find(Auth::user()->id);
|
||||
$tokens = $user->generateAccountTokens();
|
||||
|
||||
Mail::to($user)->send(new UserAccountDeleteConfirmation($user, $tokens, $request->ip()));
|
||||
|
||||
$user->delete();
|
||||
Auth::logout();
|
||||
|
||||
$request->session()->flash('success', 'Please check your email to finish deleting your account.');
|
||||
return redirect()->to('/');
|
||||
}
|
||||
|
||||
|
||||
public function processDeleteConfirmation(Request $request, $ID, $action, $token)
|
||||
{
|
||||
// We can't rely on Laravel's route model injection, because it'll ignore soft-deleted models,
|
||||
// so we have to use a special scope to find them ourselves.
|
||||
$user = User::withTrashed()->findOrFail($ID);
|
||||
$email = $user->email;
|
||||
|
||||
switch($action)
|
||||
{
|
||||
case 'confirm':
|
||||
|
||||
if ($user->verifyAccountToken($token, 'deleteToken'))
|
||||
{
|
||||
Log::info('SECURITY: User deleted account!', [
|
||||
|
||||
'confirmDeleteToken' => $token,
|
||||
'ipAddress' => $request->ip(),
|
||||
'email' => $user->email
|
||||
|
||||
]);
|
||||
|
||||
|
||||
|
||||
$user->forceDelete();
|
||||
|
||||
$request->session()->flash('success', 'Account permanently deleted. Thank you for using our service.');
|
||||
return redirect()->to('/');
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'cancel':
|
||||
|
||||
if ($user->verifyAccountToken($token, 'cancelToken'))
|
||||
{
|
||||
$user->restore();
|
||||
$request->session()->flash('success', 'Account deletion cancelled! You may now login.');
|
||||
|
||||
return redirect()->to(route('login'));
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
abort(404, 'The page you were trying to access may not exist or may be expired.');
|
||||
}
|
||||
|
||||
}
|
||||
}
|
18
app/User.php
18
app/User.php
@@ -2,16 +2,18 @@
|
||||
|
||||
namespace App;
|
||||
|
||||
use App\Traits\HandlesAccountTokens;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Mpociot\Teamwork\Traits\UserHasTeams;
|
||||
use Spatie\Permission\Traits\HasRoles;
|
||||
|
||||
class User extends Authenticatable
|
||||
class User extends Authenticatable implements MustVerifyEmail
|
||||
{
|
||||
use Notifiable;
|
||||
use HasRoles;
|
||||
//use MustVerifyEmail;
|
||||
use UserHasTeams, Notifiable, HasRoles, SoftDeletes, HandlesAccountTokens;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
@@ -68,20 +70,24 @@ class User extends Authenticatable
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function isBanned()
|
||||
{
|
||||
return !$this->bans()->get()->isEmpty();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function isStaffMember()
|
||||
{
|
||||
return $this->hasAnyRole('reviewer', 'admin', 'hiringManager');
|
||||
}
|
||||
|
||||
public function has2FA()
|
||||
{
|
||||
return !is_null($this->twofa_secret);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function routeNotificationForSlack($notification)
|
||||
{
|
||||
return config('slack.webhook.integrationURL');
|
||||
|
@@ -5,12 +5,16 @@ namespace App;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Mpociot\Teamwork\Traits\UsedByTeams;
|
||||
|
||||
|
||||
use GrahamCampbell\Markdown\Facades\Markdown;
|
||||
|
||||
|
||||
class Vacancy extends Model
|
||||
{
|
||||
//use UsedByTeams;
|
||||
|
||||
public $fillable = [
|
||||
|
||||
'permissionGroupName',
|
||||
@@ -21,7 +25,8 @@ class Vacancy extends Model
|
||||
'vacancyFormID',
|
||||
'vacancyCount',
|
||||
'vacancyStatus',
|
||||
'vacancySlug'
|
||||
'vacancySlug',
|
||||
'team_id'
|
||||
|
||||
];
|
||||
|
||||
@@ -45,6 +50,12 @@ class Vacancy extends Model
|
||||
}
|
||||
|
||||
|
||||
public function teams()
|
||||
{
|
||||
return $this->belongsToMany('App\Team', 'team_has_vacancy');
|
||||
}
|
||||
|
||||
|
||||
public function forms()
|
||||
{
|
||||
return $this->belongsTo('App\Form', 'vacancyFormID', 'id');
|
||||
@@ -69,4 +80,32 @@ class Vacancy extends Model
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if the Modal is attached to the $checkingTeam Model
|
||||
*
|
||||
* @param Team $checkingTeam The mdoel you want to check against
|
||||
* @return boolean Whether the models are attached
|
||||
*/
|
||||
public function hasTeam(Team $checkingTeam): bool
|
||||
{
|
||||
$myTeams = $this->teams;
|
||||
|
||||
if (empty($myTeams))
|
||||
{
|
||||
// no associated teams
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach($myTeams as $team)
|
||||
{
|
||||
if ($team->id === $checkingTeam->id)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -8,30 +8,35 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^7.2.5",
|
||||
"php": "^7.3.4",
|
||||
"ext-imagick": "*",
|
||||
"ext-json": "*",
|
||||
"arcanedev/log-viewer": "^7.0",
|
||||
"arcanedev/log-viewer": "^8.0",
|
||||
"awssat/discord-notification-channel": "^1.4",
|
||||
"doctrine/dbal": "^2.10",
|
||||
"fideloper/proxy": "^4.2",
|
||||
"fruitcake/laravel-cors": "^1.0",
|
||||
"geo-sot/laravel-env-editor": "^0.9.9",
|
||||
"graham-campbell/markdown": "^12.0",
|
||||
"guzzlehttp/guzzle": "^6.5",
|
||||
"graham-campbell/markdown": "^13.1",
|
||||
"guzzlehttp/guzzle": "^7.0.1",
|
||||
"jeroennoten/laravel-adminlte": "^3.2",
|
||||
"laravel/framework": "^7.0",
|
||||
"laravel/framework": "^8.0",
|
||||
"laravel/slack-notification-channel": "^2.0",
|
||||
"laravel/tinker": "^2.0",
|
||||
"laravel/ui": "^2.0",
|
||||
"sentry/sentry-laravel": "1.7.1",
|
||||
"laravel/ui": "^3.0",
|
||||
"mcamara/laravel-localization": "^1.5",
|
||||
"mpociot/teamwork": "^6.0",
|
||||
"pragmarx/google2fa-laravel": "^1.3",
|
||||
"sentry/sentry-laravel": "2.1.1",
|
||||
"spatie/laravel-permission": "^3.13"
|
||||
},
|
||||
"require-dev": {
|
||||
"barryvdh/laravel-debugbar": "^3.3",
|
||||
"facade/ignition": "^2.0",
|
||||
"facade/ignition": "^2.3.6",
|
||||
"fzaninotto/faker": "^1.9.1",
|
||||
"mockery/mockery": "^1.3.1",
|
||||
"nunomaduro/collision": "^4.1",
|
||||
"phpunit/phpunit": "^8.5"
|
||||
"nunomaduro/collision": "^5.0",
|
||||
"phpunit/phpunit": "^9.0"
|
||||
},
|
||||
"config": {
|
||||
"optimize-autoloader": true,
|
||||
@@ -45,12 +50,10 @@
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\": "app/"
|
||||
},
|
||||
"classmap": [
|
||||
"database/seeds",
|
||||
"database/factories"
|
||||
]
|
||||
"App\\": "app/",
|
||||
"Database\\Factories\\": "database/factories/",
|
||||
"Database\\Seeders\\": "database/seeders/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
|
4132
composer.lock
generated
4132
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,7 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'title' => 'Raspberry Net',
|
||||
'title' => env('APP_NAME'),
|
||||
'title_prefix' => '',
|
||||
'title_postfix' => '',
|
||||
|
||||
@@ -45,12 +45,12 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'logo' => 'RaspberryNet Staff',
|
||||
'logo_img' => 'https://www.raspberrypi.org/app/uploads/2020/05/Raspberry-Pi-OS-downloads-image-150x150-1.png',
|
||||
'logo' => env('APP_NAME'),
|
||||
'logo_img' => env('APP_LOGO'),
|
||||
'logo_img_class' => 'brand-image img-circle elevation-3',
|
||||
'logo_img_xl' => null,
|
||||
'logo_img_xl_class' => 'brand-image-xs',
|
||||
'logo_img_alt' => 'Raspberry Network Staff Temporary Logo',
|
||||
'logo_img_alt' => env('APP_NAME') . '\'s Temporary Logo',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@@ -209,74 +209,76 @@ return [
|
||||
|
||||
'menu' => [
|
||||
[
|
||||
'text' => 'Home',
|
||||
'icon' => 'fas fa-home',
|
||||
'url' => 'dashboard'
|
||||
'text' => 'm_home',
|
||||
'icon' => 'fas fa-home',
|
||||
'url' => 'dashboard'
|
||||
],
|
||||
[
|
||||
'text' => 'Directory',
|
||||
'icon' => 'fas fa-users',
|
||||
'url' => 'users/directory',
|
||||
'can' => 'profiles.view.others'
|
||||
'text' => 'm_directory',
|
||||
'icon' => 'fas fa-users',
|
||||
'url' => 'users/directory',
|
||||
'can' => 'profiles.view.others'
|
||||
],
|
||||
[
|
||||
'header' => 'Applications',
|
||||
'header' => 'h_applications',
|
||||
'can' => 'applications.view.own'
|
||||
],
|
||||
[
|
||||
'text' => 'My Applications',
|
||||
'text' => 'm_my_applications',
|
||||
'icon' => 'fas fa-fw fa-list-ul',
|
||||
'can' => 'applications.view.own',
|
||||
'submenu' => [
|
||||
[
|
||||
'text' => 'Current Applications',
|
||||
'text' => 'm_curr_applications',
|
||||
'icon' => 'fas fa-fw fa-check-double',
|
||||
'url' => '/applications/my-applications'
|
||||
]
|
||||
],
|
||||
|
||||
],
|
||||
'My Profile',
|
||||
[
|
||||
'text' => 'Profile Settings',
|
||||
'header' => 'h_my_profile',
|
||||
],
|
||||
[
|
||||
'text' => 'm_profile_settings',
|
||||
'url' => '/profile/settings',
|
||||
'icon' => 'fas fa-fw fa-cog'
|
||||
],
|
||||
[
|
||||
'text' => 'My Account Settings',
|
||||
'text' => 'm_account_settings',
|
||||
'icon' => 'fas fa-user-circle',
|
||||
'url' => '/profile/settings/account'
|
||||
],
|
||||
[
|
||||
'header' => 'Application Management',
|
||||
'header' => 'h_app_management',
|
||||
'can' => ['applications.view.all', 'applications.vote']
|
||||
],
|
||||
[
|
||||
'text' => 'All applications',
|
||||
'url' => 'applications/staff/all',
|
||||
'icon' => 'fas fa-list-ol',
|
||||
'can' => 'applications.view.all'
|
||||
'text' => 'm_all_apps',
|
||||
'url' => 'applications/staff/all',
|
||||
'icon' => 'fas fa-list-ol',
|
||||
'can' => 'applications.view.all'
|
||||
],
|
||||
[
|
||||
'text' => 'Outstanding Applications',
|
||||
'text' => 'm_outstanding_apps',
|
||||
'url' => '/applications/staff/outstanding',
|
||||
'icon' => 'far fa-folder-open',
|
||||
'can' => 'applications.view.all'
|
||||
],
|
||||
[
|
||||
'text' => 'Interview Queue',
|
||||
'text' => 'm_interview_queue',
|
||||
'url' => '/applications/staff/pending-interview',
|
||||
'icon' => 'fas fa-fw fa-microphone-alt',
|
||||
'can' => 'applications.view.all'
|
||||
],
|
||||
[
|
||||
'text' => 'Peer Approval Queue',
|
||||
'text' => 'm_peer_approval',
|
||||
'url' => '/applications/staff/peer-review',
|
||||
'icon' => 'fas fa-fw fa-search',
|
||||
'can' => 'applications.view.all'
|
||||
],
|
||||
[
|
||||
'header' => 'Administration',
|
||||
'header' => 'h_admin',
|
||||
'can' => [ // may need to be modified
|
||||
'admin.hiring.*',
|
||||
'admin.userlist',
|
||||
@@ -286,38 +288,44 @@ return [
|
||||
]
|
||||
],
|
||||
[
|
||||
'text' => 'Staff Members',
|
||||
'text' => 'm_staff_m',
|
||||
'icon' => 'fas fa-fw fa-users',
|
||||
'url' => '/hr/staff-members',
|
||||
'can' => 'admin.stafflist'
|
||||
],
|
||||
[ // players who haven't been promoted yet
|
||||
'text' => 'Registered Players',
|
||||
'text' => 'm_reg_players',
|
||||
'icon' => 'fas fa-fw fa-user-friends',
|
||||
'url' => '/hr/players',
|
||||
'can' => 'admin.userlist'
|
||||
],
|
||||
[
|
||||
'text' => 'Hiring Management',
|
||||
'icon' => 'far fa-calendar-plus',
|
||||
'text' => 'm_teams',
|
||||
'icon' => 'fas fa-user-friends',
|
||||
'url' => 'teams',
|
||||
'can' => 'teams.view'
|
||||
],
|
||||
[
|
||||
'text' => 'sm_hiring_man',
|
||||
'icon' => 'far fa-calendar-plus',
|
||||
'can' => 'admin.hiring.*',
|
||||
'submenu' => [
|
||||
[
|
||||
'text' => 'Open Positions',
|
||||
'text' => 'm_open_pos',
|
||||
'icon' => 'fas fa-box-open',
|
||||
'url' => '/admin/positions'
|
||||
],
|
||||
[
|
||||
'text' => 'Forms',
|
||||
'text' => 'sm_forms',
|
||||
'icon' => 'fab fa-wpforms',
|
||||
'submenu' => [
|
||||
[
|
||||
'text' => 'All forms',
|
||||
'text' => 'sm_all_forms',
|
||||
'icon' => 'far fa-list-alt',
|
||||
'url' => '/admin/forms'
|
||||
],
|
||||
[
|
||||
'text' => 'Form Builder',
|
||||
'text' => 'm_form_builder',
|
||||
'icon' => 'fas fa-fw fa-hammer',
|
||||
'url' => '/admin/forms/builder'
|
||||
]
|
||||
@@ -326,26 +334,26 @@ return [
|
||||
]
|
||||
],
|
||||
[
|
||||
'text' => 'App Settings',
|
||||
'text' => 'sm_app_settings',
|
||||
'icon' => 'fas fa-fw fa-cog',
|
||||
'can' => 'admin.notificationsettings',
|
||||
'submenu' => [
|
||||
[
|
||||
'text' => 'Global Notification Settings',
|
||||
'icon' => 'far fa-bell',
|
||||
'url' => '/admin/notifications',
|
||||
'can' => 'admin.notificationsettings.edit'
|
||||
'text' => 'm_global_app_s',
|
||||
'icon' => 'fas fa-cogs',
|
||||
'url' => '/admin/settings',
|
||||
'can' => 'admin.settings.view'
|
||||
],
|
||||
[
|
||||
'text' => 'Developer Tools',
|
||||
'text' => 'm_devtools',
|
||||
'icon' => 'fas fa-code',
|
||||
'url' => '/admin/devtools',
|
||||
'can' => 'admin.developertools.use'
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
[
|
||||
'text' => 'System Logs',
|
||||
'text' => 'm_s_logs',
|
||||
'url' => '/admin/maintenance/system-logs',
|
||||
'icon' => 'fas fa-clipboard-list',
|
||||
'can' => 'admin.maintenance.logs.view'
|
||||
@@ -368,7 +376,6 @@ return [
|
||||
JeroenNoten\LaravelAdminLte\Menu\Filters\HrefFilter::class,
|
||||
JeroenNoten\LaravelAdminLte\Menu\Filters\SearchFilter::class,
|
||||
JeroenNoten\LaravelAdminLte\Menu\Filters\ActiveFilter::class,
|
||||
JeroenNoten\LaravelAdminLte\Menu\Filters\SubmenuFilter::class,
|
||||
JeroenNoten\LaravelAdminLte\Menu\Filters\ClassesFilter::class,
|
||||
JeroenNoten\LaravelAdminLte\Menu\Filters\GateFilter::class,
|
||||
JeroenNoten\LaravelAdminLte\Menu\Filters\LangFilter::class,
|
||||
@@ -528,6 +535,59 @@ return [
|
||||
]
|
||||
]
|
||||
],
|
||||
|
||||
[
|
||||
'name' => 'CheckboxValues',
|
||||
'active' => true,
|
||||
'files' => [
|
||||
[
|
||||
'type' => 'js',
|
||||
'asset' => false,
|
||||
'location' => '/js/switches.js'
|
||||
]
|
||||
]
|
||||
],
|
||||
[
|
||||
'name' => 'AuthCustomisations',
|
||||
'active' => true,
|
||||
'files' => [
|
||||
[
|
||||
'type' => 'css',
|
||||
'asset' => false,
|
||||
'location' => '/css/authpages.css'
|
||||
]
|
||||
]
|
||||
],
|
||||
[
|
||||
'name' => 'BootstrapToggleButton',
|
||||
'active' => true,
|
||||
'files' => [
|
||||
[
|
||||
'type' => 'css',
|
||||
'asset' => false,
|
||||
'location' => 'https://gitcdn.github.io/bootstrap-toggle/2.2.2/css/bootstrap-toggle.min.css'
|
||||
],
|
||||
[
|
||||
'type' => 'js',
|
||||
'asset' => false,
|
||||
'location' => 'https://gitcdn.github.io/bootstrap-toggle/2.2.2/js/bootstrap-toggle.min.js'
|
||||
]
|
||||
]
|
||||
],
|
||||
[
|
||||
'name' => 'BootstrapMultiselectDropdown',
|
||||
'active' => true,
|
||||
'files' => [
|
||||
[
|
||||
'type' => 'js',
|
||||
'asset' => 'false',
|
||||
'location' => 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap-multiselect/0.9.15/js/bootstrap-multiselect.min.js'
|
||||
],
|
||||
[
|
||||
'type' => 'css',
|
||||
'asset' => false,
|
||||
'location' => 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap-multiselect/0.9.15/css/bootstrap-multiselect.css'
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
];
|
||||
|
@@ -15,6 +15,19 @@ return [
|
||||
|
||||
'name' => env('APP_NAME', 'Laravel'),
|
||||
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Homepage
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value is the application's homepage.
|
||||
| If you have a main website other than this application itself, you can link it here.
|
||||
| It will be used exclusively on the "Homepage" header menu.
|
||||
|
|
||||
*/
|
||||
'sitehomepage' => env('APP_SITEHOMEPAGE', 'https://google.com'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Environment
|
||||
@@ -175,10 +188,12 @@ return [
|
||||
App\Providers\AuthServiceProvider::class,
|
||||
App\Providers\UUIDConversionProvider::class,
|
||||
App\Providers\IPInfoProvider::class,
|
||||
App\Providers\ContextAwareValidatorProvider::class,
|
||||
// App\Providers\BroadcastServiceProvider::class,
|
||||
App\Providers\EventServiceProvider::class,
|
||||
App\Providers\RouteServiceProvider::class,
|
||||
\App\Providers\MojangStatusProvider::class,
|
||||
\App\Providers\OptionsProvider::class
|
||||
|
||||
],
|
||||
|
||||
@@ -234,6 +249,8 @@ return [
|
||||
'UUID' => App\Facades\UUID::class,
|
||||
'IP' => App\Facades\IP::class,
|
||||
'Markdown' => GrahamCampbell\Markdown\Facades\Markdown::class,
|
||||
'ContextAwareValidator' => App\Facades\ContextAwareValidation::class,
|
||||
'Settings' => App\Facades\Options::class
|
||||
|
||||
],
|
||||
|
||||
|
17
config/channels.php
Normal file
17
config/channels.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'notifications' => [
|
||||
|
||||
'discord' => [
|
||||
'webhook_url' => env('DISCORD_INTEGRATION_WEBHOOK')
|
||||
],
|
||||
|
||||
'slack' => [
|
||||
'webhook_url' => env('SLACK_INTEGRATION_WEBHOOK')
|
||||
]
|
||||
|
||||
]
|
||||
|
||||
];
|
79
config/google2fa.php
Normal file
79
config/google2fa.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
* Enable / disable Google2FA.
|
||||
*/
|
||||
'enabled' => env('OTP_ENABLED', true),
|
||||
|
||||
/*
|
||||
* Lifetime in minutes.
|
||||
*
|
||||
* In case you need your users to be asked for a new one time passwords from time to time.
|
||||
*/
|
||||
'lifetime' => env('OTP_LIFETIME', 0), // 0 = eternal
|
||||
|
||||
/*
|
||||
* Renew lifetime at every new request.
|
||||
*/
|
||||
'keep_alive' => env('OTP_KEEP_ALIVE', true),
|
||||
|
||||
/*
|
||||
* Auth container binding.
|
||||
*/
|
||||
'auth' => 'auth',
|
||||
|
||||
/*
|
||||
* 2FA verified session var.
|
||||
*/
|
||||
|
||||
'session_var' => 'google2fa',
|
||||
|
||||
/*
|
||||
* One Time Password request input name.
|
||||
*/
|
||||
'otp_input' => 'otp',
|
||||
|
||||
/*
|
||||
* One Time Password Window.
|
||||
*/
|
||||
'window' => 1,
|
||||
|
||||
/*
|
||||
* Forbid user to reuse One Time Passwords.
|
||||
*/
|
||||
'forbid_old_passwords' => false,
|
||||
|
||||
/*
|
||||
* User's table column for google2fa secret.
|
||||
*/
|
||||
'otp_secret_column' => 'twofa_secret',
|
||||
|
||||
/*
|
||||
* One Time Password View.
|
||||
*/
|
||||
'view' => 'auth.2fa',
|
||||
|
||||
/*
|
||||
* One Time Password error message.
|
||||
*/
|
||||
'error_messages' => [
|
||||
'wrong_otp' => "Your one time code was incorrect.",
|
||||
'cannot_be_empty' => 'The one time code cannot be empty.',
|
||||
'unknown' => 'An unknown error has occurred. Please try again.',
|
||||
],
|
||||
|
||||
/*
|
||||
* Throw exceptions or just fire events?
|
||||
*/
|
||||
'throw_exceptions' => env('OTP_THROW_EXCEPTION', true),
|
||||
|
||||
/*
|
||||
* Which image backend to use for generating QR codes?
|
||||
*
|
||||
* Supports imagemagick, svg and eps
|
||||
*/
|
||||
'qrcode_image_backend' => \PragmaRX\Google2FALaravel\Support\Constants::QRCODE_IMAGE_BACKEND_IMAGEMAGICK,
|
||||
|
||||
];
|
70
config/laravellocalization.php
Normal file
70
config/laravellocalization.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
// Uncomment the languages that your site supports - or add new ones.
|
||||
// These are sorted by the native name, which is the order you might show them in a language selector.
|
||||
// Regional languages are sorted by their base language, so "British English" sorts as "English, British"
|
||||
'supportedLocales' => [
|
||||
'en' => ['name' => 'English', 'script' => 'Latn', 'native' => 'English', 'regional' => 'en_GB'],
|
||||
'es' => ['name' => 'Spanish', 'script' => 'Latn', 'native' => 'Español', 'regional' => 'es_ES'],
|
||||
'pt' => ['name' => 'Portuguese', 'script' => 'Latn', 'native' => 'Português', 'regional' => 'pt_PT'],
|
||||
'fr' => ['name' => 'French', 'script' => 'Latn', 'native' => 'Français', 'regional' => 'fr_FR'],
|
||||
],
|
||||
|
||||
// Requires middleware `LaravelSessionRedirect.php`.
|
||||
//
|
||||
// Automatically determine locale from browser (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language)
|
||||
// on first call if it's not defined in the URL. Redirect user to computed localized url.
|
||||
// For example, if users browser language is `de`, and `de` is active in the array `supportedLocales`,
|
||||
// the `/about` would be redirected to `/de/about`.
|
||||
//
|
||||
// The locale will be stored in session and only be computed from browser
|
||||
// again if the session expires.
|
||||
//
|
||||
// If false, system will take app.php locale attribute
|
||||
'useAcceptLanguageHeader' => true,
|
||||
|
||||
// If `hideDefaultLocaleInURL` is true, then a url without locale
|
||||
// is identical with the same url with default locale.
|
||||
// For example, if `en` is default locale, then `/en/about` and `/about`
|
||||
// would be identical.
|
||||
//
|
||||
// If in addition the middleware `LaravelLocalizationRedirectFilter` is active, then
|
||||
// every url with default locale is redirected to url without locale.
|
||||
// For example, `/en/about` would be redirected to `/about`.
|
||||
// It is recommended to use `hideDefaultLocaleInURL` only in
|
||||
// combination with the middleware `LaravelLocalizationRedirectFilter`
|
||||
// to avoid duplicate content (SEO).
|
||||
//
|
||||
// If `useAcceptLanguageHeader` is true, then the first time
|
||||
// the locale will be determined from browser and redirect to that language.
|
||||
// After that, `hideDefaultLocaleInURL` behaves as usual.
|
||||
'hideDefaultLocaleInURL' => true,
|
||||
|
||||
// If you want to display the locales in particular order in the language selector you should write the order here.
|
||||
//CAUTION: Please consider using the appropriate locale code otherwise it will not work
|
||||
//Example: 'localesOrder' => ['es','en'],
|
||||
'localesOrder' => ['en', 'pt', 'fr', 'es'],
|
||||
|
||||
// If you want to use custom lang url segments like 'at' instead of 'de-AT', you can use the mapping to tallow the LanguageNegotiator to assign the descired locales based on HTTP Accept Language Header. For example you want ot use 'at', so map HTTP Accept Language Header 'de-AT' to 'at' (['de-AT' => 'at']).
|
||||
'localesMapping' => [],
|
||||
|
||||
// Locale suffix for LC_TIME and LC_MONETARY
|
||||
// Defaults to most common ".UTF-8". Set to blank on Windows systems, change to ".utf8" on CentOS and similar.
|
||||
'utf8suffix' => env('LARAVELLOCALIZATION_UTF8SUFFIX', '.UTF-8'),
|
||||
|
||||
// URLs which should not be processed, e.g. '/nova', '/nova/*', '/nova-api/*' or specific application URLs
|
||||
// Defaults to []
|
||||
'urlsIgnored' => [
|
||||
'/js/*',
|
||||
'/img/*',
|
||||
'/css/*',
|
||||
'/vendor/*',
|
||||
'/app.css',
|
||||
'/robots.txt',
|
||||
'/slides/*',
|
||||
'/auth/logout'
|
||||
],
|
||||
|
||||
];
|
@@ -81,7 +81,7 @@ return [
|
||||
*/
|
||||
|
||||
'failed' => [
|
||||
'driver' => env('QUEUE_FAILED_DRIVER', 'database'),
|
||||
'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
|
||||
'database' => env('DB_CONNECTION', 'mysql'),
|
||||
'table' => 'failed_jobs',
|
||||
],
|
||||
|
84
config/teamwork.php
Normal file
84
config/teamwork.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Auth Model
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This is the Auth model used by Teamwork.
|
||||
|
|
||||
*/
|
||||
'user_model' => config('auth.providers.users.model', App\User::class),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Teamwork users Table
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This is the users table name used by Teamwork.
|
||||
|
|
||||
*/
|
||||
'users_table' => 'users',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Teamwork Team Model
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This is the Team model used by Teamwork to create correct relations. Update
|
||||
| the team if it is in a different namespace.
|
||||
|
|
||||
*/
|
||||
'team_model' => Mpociot\Teamwork\TeamworkTeam::class,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Teamwork teams Table
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This is the teams table name used by Teamwork to save teams to the database.
|
||||
|
|
||||
*/
|
||||
'teams_table' => 'teams',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Teamwork team_user Table
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This is the team_user table used by Teamwork to save assigned teams to the
|
||||
| database.
|
||||
|
|
||||
*/
|
||||
'team_user_table' => 'team_user',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| User Foreign key on Teamwork's team_user Table (Pivot)
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
'user_foreign_key' => 'id',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Teamwork Team Invite Model
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This is the Team Invite model used by Teamwork to create correct relations.
|
||||
| Update the team if it is in a different namespace.
|
||||
|
|
||||
*/
|
||||
'invite_model' => Mpociot\Teamwork\TeamInvite::class,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Teamwork team invites Table
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This is the team invites table name used by Teamwork to save sent/pending
|
||||
| invitation into teams to the database.
|
||||
|
|
||||
*/
|
||||
'team_invites_table' => 'team_invites',
|
||||
];
|
5
crowdin.yml
Normal file
5
crowdin.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
files:
|
||||
- source: /**/lang/**/en/*.php
|
||||
ignore:
|
||||
- /**/lang/en/*2.php
|
||||
translation: /**/lang/**/%two_letters_code%/%original_file_name%
|
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddTwofaSecretToUsers extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->string('twofa_secret')->nullable()->after('password');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn('twofa_secret');
|
||||
});
|
||||
}
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateOptionsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('options', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('option_name');
|
||||
$table->string('option_value');
|
||||
$table->string('friendly_name');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('options');
|
||||
}
|
||||
}
|
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
|
||||
class TeamworkSetupTables extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table(\Config::get('teamwork.users_table'), function (Blueprint $table) {
|
||||
$table->integer('current_team_id')->unsigned()->nullable();
|
||||
});
|
||||
|
||||
Schema::create(\Config::get('teamwork.teams_table'), function (Blueprint $table) {
|
||||
$table->increments('id')->unsigned();
|
||||
$table->integer('owner_id')->unsigned()->nullable();
|
||||
$table->string('name');
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create(\Config::get('teamwork.team_user_table'), function (Blueprint $table) {
|
||||
$table->bigInteger('user_id')->unsigned();
|
||||
$table->integer('team_id')->unsigned();
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('user_id')
|
||||
->references(\Config::get('teamwork.user_foreign_key'))
|
||||
->on(\Config::get('teamwork.users_table'))
|
||||
->onUpdate('cascade')
|
||||
->onDelete('cascade');
|
||||
|
||||
$table->foreign('team_id')
|
||||
->references('id')
|
||||
->on(\Config::get('teamwork.teams_table'))
|
||||
->onDelete('cascade');
|
||||
});
|
||||
|
||||
Schema::create(\Config::get('teamwork.team_invites_table'), function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->bigInteger('user_id')->unsigned();
|
||||
$table->integer('team_id')->unsigned();
|
||||
$table->enum('type', ['invite', 'request']);
|
||||
$table->string('email');
|
||||
$table->string('accept_token');
|
||||
$table->string('deny_token');
|
||||
$table->timestamps();
|
||||
$table->foreign('team_id')
|
||||
->references('id')
|
||||
->on(\Config::get('teamwork.teams_table'))
|
||||
->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table(\Config::get('teamwork.users_table'), function (Blueprint $table) {
|
||||
$table->dropColumn('current_team_id');
|
||||
});
|
||||
|
||||
Schema::table(\Config::get('teamwork.team_user_table'), function (Blueprint $table) {
|
||||
if (DB::getDriverName() !== 'sqlite') {
|
||||
$table->dropForeign(\Config::get('teamwork.team_user_table').'_user_id_foreign');
|
||||
}
|
||||
if (DB::getDriverName() !== 'sqlite') {
|
||||
$table->dropForeign(\Config::get('teamwork.team_user_table').'_team_id_foreign');
|
||||
}
|
||||
});
|
||||
|
||||
Schema::drop(\Config::get('teamwork.team_user_table'));
|
||||
Schema::drop(\Config::get('teamwork.team_invites_table'));
|
||||
Schema::drop(\Config::get('teamwork.teams_table'));
|
||||
}
|
||||
}
|
40
database/migrations/2020_10_02_112730_add_team_details.php
Normal file
40
database/migrations/2020_10_02_112730_add_team_details.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddTeamDetails extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table(config('teamwork.teams_table'), function(Blueprint $table){
|
||||
|
||||
$table->text('description')->after('name')->nullable();
|
||||
$table->enum('status', ['ACTIVE','SUSPENDED'])->after('description');
|
||||
$table->boolean('openJoin')->default(false)->after('status');
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table(config('teamwork.teams_table'), function(Blueprint $table){
|
||||
|
||||
$table->dropColumn('description');
|
||||
$table->dropColumn('status');
|
||||
$table->dropColumn('openJoin');
|
||||
|
||||
});
|
||||
}
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class VacancyNullableTeamId extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('vacancies', function(Blueprint $table){
|
||||
|
||||
$table->dropForeign('vacancies_ownerteamid_foreign');
|
||||
$table->dropColumn('ownerTeamID');
|
||||
$table->bigInteger('team_id')->nullable()->unsigned();
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('vacancies', function(Blueprint $table){
|
||||
|
||||
$table->dropColumn('team_id');
|
||||
|
||||
});
|
||||
}
|
||||
}
|
43
database/migrations/2020_10_04_163715_team_has_vacancy.php
Normal file
43
database/migrations/2020_10_04_163715_team_has_vacancy.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class TeamHasVacancy extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('team_has_vacancy', function(Blueprint $table){
|
||||
|
||||
$table->id();
|
||||
$table->integer('team_id')->unsigned();
|
||||
$table->bigInteger('vacancy_id')->unsigned();
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('team_id')
|
||||
->references('id')
|
||||
->on(config('teamwork.teams_table'));
|
||||
|
||||
$table->foreign('vacancy_id')
|
||||
->references('id')
|
||||
->on('vacancies');
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddAccountTokensToUser extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->string('account_tokens')->after('password')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn('account_tokens');
|
||||
});
|
||||
}
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddSoftDeletesToUsers extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->softDeletes()->after('account_tokens');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropSoftDeletes();
|
||||
});
|
||||
}
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddUuidToFailedJobs extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('failed_jobs', function (Blueprint $table) {
|
||||
$table->string('uuid')->after('id')->nullable()->unique();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('failed_jobs', function (Blueprint $table) {
|
||||
$table->dropColumn('uuid');
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
@@ -13,5 +14,6 @@ class DatabaseSeeder extends Seeder
|
||||
{
|
||||
$this->call(PermissionSeeder::class);
|
||||
$this->call(UserSeeder::class);
|
||||
$this->call(DefaultOptionsSeeder::class);
|
||||
}
|
||||
}
|
29
database/seeders/DefaultOptionsSeeder.php
Normal file
29
database/seeders/DefaultOptionsSeeder.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Facades\Options;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class DefaultOptionsSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
Options::setOption('notify_new_application_email', true, 'Notify when a new application comes through'); // done
|
||||
Options::setOption('notify_application_comment', false, 'Notify when someone comments on an application'); // done
|
||||
Options::setOption('notify_new_user', true, 'Notify when someone signs up'); // done
|
||||
Options::setOption('notify_application_status_change', true, 'Notify when an application changes status'); // done
|
||||
Options::setOption('notify_applicant_approved', true, 'Notify when an applicant is approved'); // done
|
||||
Options::setOption('notify_vacancystatus_change', false, 'Notify when a vacancy\'s status changes'); // done
|
||||
|
||||
|
||||
Options::setOption('enable_slack_notifications', true, 'Enable slack notifications');
|
||||
Options::setOption('enable_email_notifications', true, 'Enable e-mail notifications');
|
||||
Options::setOption('enable_discord_notifications', true, 'Enable discord notifications');
|
||||
|
||||
}
|
||||
}
|
32
database/seeders/NewPermissions.php
Normal file
32
database/seeders/NewPermissions.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
class NewPermissions extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
$developer = Role::create([
|
||||
'name' => 'developer'
|
||||
]);
|
||||
|
||||
$admin = Role::where('name', 'admin')->first();
|
||||
|
||||
Permission::create(['name' => 'admin.settings.view']);
|
||||
Permission::create(['name' => 'admin.settings.edit']);
|
||||
|
||||
$developer->givePermissionTo('admin.developertools.use');
|
||||
$admin->givePermissionTo('admin.settings.view');
|
||||
$admin->givePermissionTo('admin.settings.edit');
|
||||
|
||||
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user