Revert "merge 1"

This reverts commit 0bc6c20a6d6ad9b906827b9d3f3034a811178f60.
This commit is contained in:
Miguel Nogueira 2022-10-24 01:03:43 +01:00
parent 0bc6c20a6d
commit 0c463d1f10
No known key found for this signature in database
GPG Key ID: 3C6A7E29AF26D370
166 changed files with 1849 additions and 4266 deletions

View File

@ -8,14 +8,6 @@ APP_AUTH_BANNER=""
APP_SITEHOMEPAGE=""
API_PREFIX="api"
# Add legal document links here. You may also leave them empty.
GUIDELINES_URL="#"
PRIVACY_URL="#"
TERMS_URL="#"
SOURCE_REPO="#"
SUPPORT_EMAIL=
SUPPORT_URL=
# The auth banner is a relative path
# Hides IP addresses
@ -38,6 +30,10 @@ DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=
# Bot token for the Discord notification integration.
# Create an application & bot here: https://discord.com/developers/applications
DISCORD_BOT_TOKEN=
RECAPTCHA_SITE_KEY=
RECAPTCHA_PRIVATE_KEY=
RECAPTCHA_VERIFY_URL="https://www.google.com/recaptcha/api/siteverify"
@ -49,28 +45,11 @@ MOJANG_API_URL="https://api.mojang.com"
IPGEO_API_KEY=""
IPGEO_API_URL="https://api.ipgeolocation.io/ipgeo"
DISCORD_CLIENT_ID=
DISCORD_CLIENT_SECRET=
DISCORD_BOT_TOKEN=
# Don't touch the base url
DISCORD_HOME_GUILD=
DISCORD_STAFF_GUILD=
DISCORD_BASE_URL="https://discord.com/api/v9"
# Defines the basic set of server staff permissions that members should get in each server
# Add your staff role IDs here in a comma-delimited list, like so: "ROLEID1, ROLEID2, ROLEID3" and so on
# Specific permissions can be optionally configured for each vacancy
# For instance, here you may configure role IDs for example role "Server staff", while in a specific vacancy, you may configure example role "Mod"
DISCORD_STAFF_GUILD_ROLES=
DISCORD_HOME_GUILD_ROLES=
ARCANEDEV_LOGVIEWER_MIDDLEWARE=web,auth,can:admin.maintenance.logs.view
RELEASE=0.8.0
SLACK_INTEGRATION_WEBHOOK=
CPANEL_API_TOKEN=
BROADCAST_DRIVER=log
CACHE_DRIVER=file

4
.gitignore vendored
View File

@ -16,6 +16,4 @@ Homestead.yaml
npm-debug.log
yarn-error.log
/.idea/
_ide_helper.php
_ide_helper_models.php
.phpstorm.meta.php
/.idea/

1
Procfile Executable file
View File

@ -0,0 +1 @@
web: vendor/bin/heroku-php-apache2 public/

View File

@ -1,12 +1,16 @@
# RBRecruiter v1.0.0-stable - Integrated Human Resources Management System
# RB Recruiter v 0.8.0 [![Crowdin](https://badges.crowdin.net/raspberry-staff-manager/localized.svg)](https://crowdin.com/project/raspberry-staff-manager) [![Better Uptime Badge](https://betteruptime.com/status-badges/v1/monitor/9n53.svg)](https://betteruptime.com/?utm_source=status_badge)
## The quick and pain-free form management solution for communities
**Are the big name HR apps too bloated and overkill for your online community? Check RBRecruiter out to see if its right for you!**
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 RBRecruiter is for you! It was originally designed and developed for internal use for a gameserver network, but sharing is caring!
Have you ever gotten tired of managing your community's staff member applications through Discord (or anything else) and having to scroll through hundreds of new messages just to find that one applicant's username? Struggling with missing/hard-to-find information? Then RBRecruiter is for you. Streamline your team member vetting/addition process by integrating RBRecruiter with your community, now with custom-made integrations to connect with several social platforms.
# Features (not exhaustive)
- Beautiful (customizable in future releases) landing page for your application management center; It displays all available positions/jobs
- Beautiful (customizable in future releases) landing page for your application management center; It displays all available staff ranks
- Contact form on landing page for those un-registerd users
- User registration/authentication system; Users will be sent to the authentication flow to complete their application, if not logged in
- Candidate tracking system - Applicants will be tracked from start to finish.
- Peer approval system - Have all your staff members vote on applications and decide whether they should be accepted (this is overridable)
@ -14,11 +18,12 @@ Have you ever gotten tired of managing your community's staff member application
- Interview notes: Every staff member is able to add and edit interview notes (how the interview went, etc)
- Application comments: Finally no more having to go to a private Discord channel just to comment on a single application. Comments are organised neatly for every application! This should help in the decision process of voting for an application.
- User profiles - Fill out your profile for others to better find you
- Vacancy management - Add/remove vacancies on demand, that users will be able to apply to
- User directory - Public profile directory for everyone
- Staff rank management - Add/remove ranks on demand, that users will be able to apply to
- Simple form builder - Create your application forms easily!
- Termination - Has a staff member met their untimely demise? Terminate them. This will strip their permissions and roles, and run any other actions for your configured integration
- Termination - Has a staff member met their untimely demise? Terminate them. This will strip their permissions and roles.
- Controllable permissions - Every user has permissions! Control who has access to what (You can skip the application process and add staff members directly here).
- Account suspension system - Having trouble with pesky spammers? Suspend them! This will keep them from signing up or signing in until you lift their suspension.
- Ban system - Having trouble with pesky spammers? Ban them! This will publicly shame their profile and keep them from signing up or logging in.
- Notifications: Notifies slack and email primarily (Slack notifications currently broken)
And many more features!
@ -26,10 +31,11 @@ Have you ever gotten tired of managing your community's staff member application
# Roadmap
Many other features are currently planned for this app, such as:
- Discord role management (approved applicants) - Underway
- Discord role management (approved applicants)
- Flexibility - This app is built on a flexible concept.
- Customisable front page
- Auto provisioning - Sign up on a website and get your instance of RBRecruiter up and running in no time (SaaS)
-
- Suggestions accepted!
@ -57,8 +63,8 @@ Tech stack:
# Software Requirements
- ``composer`` (tested w/ v2.1.14)
- ``npm`` (tested w/ v 8.19.2)
- ``node`` (min version v18.9.0)
- ``npm`` (tested w/ v 8.1.2)
- ``node`` (min version v16.13.2)
- ``php`` (required PHP 8.1 or newer - lower versions unsupported!)
# PHP Extension Requirements
@ -81,4 +87,4 @@ This process will be moved to the browser later.
# Bug reports
Please report any bugs you find to the issues section.
Please report any bugs you find to the issues section here! It'd be immensely helpful. PRs are also accepted.

View File

@ -5,28 +5,25 @@
The following versions are currently supported:
| Version | Supported |
|---------|--------------------|
| ------- | ------------------ |
| 0.1.x | :x: |
| 0.5.x | :x: |
| 0.6.x | :x: |
| 0.7.0 | :x: |
| 0.7.1 | :x: |
| 0.8.0 | :x: |
| 0.8.1 | :x: |
| 0.8.2 | :white_check_mark: |
| 0.9.0 | :white_check_mark: |
Please note that all current versions are pre-release versions. This means that, until version `1.0.0` is reached, anything may change or break at any time, and releases are made often.
However, all commits releases are thoroughly tested before they are pushed out, ensuring that the `main` branch is never broken.
| 0.7.1 | :white_check_mark: |
## Supported PHP Versions
| Version | Supported |
|---------|--------------------|
| ------- | ------------------ |
| 5.x | :x: |
| 7.x | :x: |
| 7.0 | :x: |
| 7.1 | :x: |
| 7.2 | :x: |
| 7.3 | :x: |
| 7.4 | :white_check_mark: |
| 8.0 | :white_check_mark: |
| 8.1 | :white_check_mark: |
## Supported Operating Systems
@ -43,22 +40,75 @@ However, all commits releases are thoroughly tested before they are pushed out,
If you found a critical vunlerability, please do not use the Issues tab to report it.
Instead, please forward an encrypted vulnerability report to ``security@webvokestudio.pt``, so that we have time to open a security advisory and work on a fix.
Unencrypted reports will be ignored.
Use this public PGP key to encrypt your message:
-----BEGIN PGP PUBLIC KEY BLOCK-----
mDMEYigDUBYJKwYBBAHaRw8BAQdAPSmEMgZc23ouaPwdW7IF5Ej5H7VUGR6Um8N4
SAW92i60I01pZ3VlbCBOb2d1ZWlyYSA8bWVAbm9ndWVpcmEuY29kZXM+iJYEExYI
AD4WIQTmfmRZELec2wocoaqNKugPeLO16wUCYigDUAIbAwUJAMo64AULCQgHAgYV
CgkICwIEFgIDAQIeAQIXgAAKCRCNKugPeLO169kWAQCwd4E0WBB98/1IdMrFAFPZ
7zGrIeVGdUD4zg9E8ssLwgEAwYIhnZI9bDaMUit2Fat7PEiqYIiNd1vKev3vO2wm
Iw+4OARiKANQEgorBgEEAZdVAQUBAQdAMqfyInidoScxFQAXOdrmpEJW0auO7D+4
UnCZr87CugUDAQgHiH4EGBYIACYWIQTmfmRZELec2wocoaqNKugPeLO16wUCYigD
UAIbDAUJAMo64AAKCRCNKugPeLO165kGAP4if46/uChEaEwtlQO5fPbMwLmnAKyw
K2ImmP3ksxhh1wEA8R0fD0etl5VfcG2lp7h1e/VckbVaQzYwpyETHku39gM=
=9azb
mQINBF/NUL8BEAC7njBq5IbB80qS8rGaAw+DM9wCTiZBiZu0oLXh6oW/E6NOKX5D
Gd8tcW2auqcO1syBUnla4E8t+3fuJQtee3lgszLKRKACYOqZPIYNBUHbw3VBNhNZ
WESbKh9JxlLJql6fHMfpr3wcTCxLwFGcRln1EPKAMBsxB0YJDeorXuoCTY4+NhGM
8O2aS8Tbq+HxSkhz1p0DdOuaM9C/kQJkOth6sdsBiBE/OsyzPK/yti91WM9fB/Fg
QuYyzFD5OL6H+PKPas/4ndIfXSnSCeqm4gScUBeLReeqQd0o96ALSnOxMz9Qit4j
BYhKxfZnAFiKWeBPZEJY12cu2gv3plkdE89mqJveRUCQ+dfVX471iCW+/YSRGU83
9PG2a1Dalsx46lScHm2RlrXFhUhpVND0y7VBHpKiUJCoykhGbIAr429dITBw7jGa
FAKpJgm2wWRAHeokrQ9l+NTUosTwRoYeWF1tKElKT1sjF5zuWFxOekzGXBtzcSUH
HQrUfzOQTMUciSZ4CjZjDNL1yYEwUOTr4IzNOj/jmKCo/Dbsix9V1LbTPjK8vP4l
jwLOL2dG0jpQRd5FRBWPukpF6LS25vQv0bMRZdsCfh/eOlMldfdiGpx8fN6dkxvm
H0TBdZklp3U91u+ooVIeF3Q1NWmB/tvNJOfFnerHOpDBTy/q5fpT6QaMkwARAQAB
tH5NaWd1ZWwgTm9ndWVpcmEgKElnbm9yZSBhbGwgbXkgb3RoZXIga2V5cyB1bmRl
ciBteSBuYW1lIGFuZCBlbWFpbCAobG9zdCB0aGUgcHJpdmF0ZSBrZXlzKS4pIDxt
aWd1ZWw0NTZAc3BhY2VqZXdlbC1ob3N0aW5nLmNvbT6JAlQEEwEKAD4WIQStTNAz
Se1uoPMcdjTkUwCM6Yt4igUCX81QvwIbLwUJC0c1AAULCQgHAgYVCgkICwIEFgID
AQIeAQIXgAAKCRDkUwCM6Yt4ikBCD/9sPlUItGT1tlFgdSdbLdPsdcb/lpK269zH
0OoMSSKGKNL6RAY2R6++qdBRCC9jdzQFtJO7epYB/mWXnZqYcWemTwaKEW5eHr5E
BFQuSiRREbNLkzCAWWqqaQnCuve4q9DXsWd5NiHcrmNgnSY6Zk6y+GdpQNyzEshf
aCcnkaySIFgV33DDsa6s4zUDZulQbmeg1mfwyP0tv+CZ6Bbk72DxX/A10x00ddv1
arGuLfctIydEcG7mAeAZBeCJEucpFuQbaWybZS2RX3/e6LCOWnt/ZHjqcFln36yq
9GIx28BnElkpxwDV66R0EUpm3cxZ3Kv7bDqm5E2ZzCybOF+a8y3d15qIyrSHvGdr
skVBmRi2zLxCYBEcTocHH0rG6UNTk55wvqMepAnnp12XMQoqCrmw9zUv92qAv+01
lMv9H1AM8jUdkk5H+SgZYFcPL5/4cBEc/ylRKIuiNcKZAfppjdVnslXsgahmQSep
PgcWPwwOujqYLfn/FPKRQuH1GUuNv0UzO6KUQy0gWkDYKrnks4n6lNWXiZY/HU+W
KCgSWRCICHW8JJ8dg97vk6IurtqpG/LUMb/E3mAzPNIyuhK2r1wGOsb83iVIvF5M
3KyAFhaqiwnudt7UXYN3A1we+rm/y/nBNx0GUuC8VJaLz1p9zqEiT1an0vivC92x
m/jSicdIprkCDQRfzVC/ARAAq/tqXnL7JSt3ZaePLP86hE0gOK8mY1F5X9sRQCDT
adqyfnwNIzXGhUFfSdIO2LKnrdZUFXZG4YeK6JG0fu8KdELs+2UlIx0KAokrwfK3
zWHITN6+lvSz3ubVQMYCEgUK9eEPwa/XnhL4vEd61kMdM5OxYsBXQYlraWLJ0rOf
ANQEXb0vm0c4RsO8IubOJT7jxPkgfLesIB7i3O3/Hptc54WHs97p22/Y6oNk42UV
cJB/k3kqTCzfBzDcpmF9RyxpfEDUJAn9QWcxH2UC3kHpr9N5Hfv3Kh3bjaGukzn4
sO+fmukqBTsW7cIMO7slbcsCVJN7QC2GcocgjGHXWVF1Gg234exTX7aDBGNq7TeY
h1N/3lVeZom6ANtA3aOO55NuaXI9IGiW7lNnYcTJkTu3eUnfTpg/FkqdSzOpBOFZ
Gyltjf8pMK0vQXbajov5j/wE/bz2eOPKwyJc0u3B/JqfE/EbPnx5dng7/mgv+x7u
GSMOvovc4SrSZoDp5IWDk1L5s2CHNB88rziPFLB0Tkr6N9MnEWhv4wL7wfNdQ00s
xJpGlrc0nSdGUmHFCExRUb7VIJ0Yfjsj3Xy4PCBXsDTYWusAbp+rbFycRMlGiANj
cQYGbEYMyUqL2upg7kKzrFW45TyGpkE1QHkm0Od7JxGqgfiie+tcG+EUQKBMJsIN
tLMAEQEAAYkEcgQYAQoAJhYhBK1M0DNJ7W6g8xx2NORTAIzpi3iKBQJfzVC/Ahsu
BQkLRzUAAkAJEORTAIzpi3iKwXQgBBkBCgAdFiEEqncyNKRovrMCcN8NLPYbglMW
xqAFAl/NUL8ACgkQLPYbglMWxqBmHhAApxjJmpdifzI23AdRAcO4oChE6ho0a85+
nGXr0qPMySb6/1GiYML3WKNzK02wS7V8A1VlnYvdd3SdmJh9qhH4NAgs4SXxpenp
hEw48M+h7LHJlqkrftHC540eyNlyVyN31kNhibhFrF5eZ9BcIwERrAR4MClG1gwI
bJqiAk5wlYP6YmJHVI+4DKjN+dMCn301hXUCTPoAdHfKTztMe9qhmbgZcyeOkZOr
Ya/7D1AqEaQhfcfOL4mBUdH86GVw9o58Ock7baQHoYlIrwgvW4qRvBjT+mQueHld
J0v/GQM6widHe3vt3HL4vI4vTzoscl8MY4u8YCmvkWGh343G6Ei7m+cQ19GrHSOR
8rMZjHbYaNHstscBJDVx19EZaGc/AV6w38MdkiXbKyyKkZuvAqWkgs+NFRE7lBtI
mNp40aakYNwnn0RP6Wz2LYRWcP5Zm79cUb8++CYK8h5Y5j53tlM8ihF58UhKL6rg
zEk5vYoJzmswO9nA9nNnPscZhZUwfE1x8SVtOOhr8IFlJ3Kh2BpTXJbmqwj71Y6x
pfP6GG4UmPzfLi64ZkvYU1l7nyxlbvJXd8LzBs5oofROiIYKQkrSMEZvPb8XUVNs
cMz434hQFPtwkygjtpA1Io+bx1BV4wtnQ1GsaK2m32hacfF7OvL8iYq92uF//NUm
mhxeh5Jso+/G9xAAqOBen92oFOrg3P+EkmIPRqvcgy4I3Tb22PaB3tGP4+iXpalc
ocMVDzVOLM2pwMwnffnecGv3UotJWdz/80CG8juuwOLXOIsOVAdEbKIXGknwLGyZ
V04+nBQLm+qKhLFigakx43Rmte7Fn3tydlJ+61DHBYtUF8/QRPw/1YN5ybXuZrTj
ci6zUfHxuUczlr8GQhXDy95yvtGlqsGB1roIiCB4Ld7PV3S3YK8+4AjuNFqMQ/+K
KNMsGWFzkXf8NPLe/AneVARsX0f2bV0YYcHYWQCZjrEyKrMcNYuK6uj4Z1Xmwn7f
0gE3kjTPuG562jpHHElWHcZI40uNnYf0DBRyTQWLGqOuAVCDrg8+/gITBW98M3jd
OuPu5RQmtIkF9BFDYl3pdbzJ/4j+StAnRuZMjSSi17okRHFJEL3FZ2LdBim073ra
BCU+tufFOQUtR47mOuIjmRDI0GXjGcs1BcovaVViGisjwB0CSSLgnYIjZH6xsrA1
ICc0bSNBvYpXewPGDQ8AO1k4lNtedDOY/sXr4o9xZVic5eOA740PPrEozpftV1wK
UBxa9dXTy9qaZhcw/x38zVyJTLg4I5sOmG24u/ZPqOblEKyQwFdU4gLyF5CRGwDy
5LmGVc1bTDL8SQXkRG4L/DZVbqgBxBKNonkbnJV2VpjzecfA3O8rHS+uAlQ=
=hbUK
-----END PGP PUBLIC KEY BLOCK-----
You may use [this tool](https://pgptool.org/) to encrypt and sign your message.
You may use [this tool](https://sela.io/pgp-en/) to encrypt your message.

View File

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

View File

@ -36,7 +36,7 @@ class Ban extends Model
];
public $dates = [
'bannedUntil',
'suspendedUntil',
];
public function user()

View File

@ -22,7 +22,6 @@
namespace App\Console;
use App\Jobs\ProcessDueSuspensions;
use App\Jobs\ProcessExpiredAbsences;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@ -55,11 +54,6 @@ class Kernel extends ConsoleKernel
->daily();
// Production value: Every day
// Development value: Every minute
$schedule->job(new ProcessExpiredAbsences)
->daily();
// Production value: Every day
// Development value: Every minute
}
/**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -40,6 +40,6 @@ class Form extends Model
public function responses()
{
return $this->belongsTo('App\Response', 'id', 'responseFormID');
return $this->belongsTo('App\Response', 'id', 'id');
}
}

View File

@ -1,236 +0,0 @@
<?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Helpers;
use App\Exceptions\AccountNotLinkedException;
use App\User;
use Carbon\Carbon;
use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Http;
// Small wrapper for the necessary sections of the Discord API; A library is overkill here
class Discord
{
/**
* The current working guild. Default is Home guild from app config
* @var string
*/
protected string $workingGuild;
/**
* Current user.
*
* @var User The user all methods will affect.
*/
protected User $user;
public function __construct() {
if (isset($this->workingGuild)) {
$this->setWorkingGuild(config('services.discord.home_guild'));
}
}
/**
* Sets the working guild
*
* @param string $workingGuild
* @return Discord
*/
public function setWorkingGuild(string $workingGuild): Discord
{
$this->workingGuild = $workingGuild;
return $this;
}
/**
* Sets the current user, upon validation
*
* @param User $user
* @return Discord
* @throws AccountNotLinkedException
*/
public function setUser(User $user): Discord
{
if ($user->hasDiscordConnection()) {
$this->user = $user;
return $this;
}
throw new AccountNotLinkedException('Specified website user has not linked their Discord account yet.');
}
/**
* Obtains the current user's authentication info. Caches the data for the access_token's TTL, thus
* preventing unnecessary API requests.
*
* @return object Current user's authentication info (has no sensitive fields)
* @throws RequestException
*/
public function getAuthorizationInfo(): object {
if (Cache::has($this->user->discord_user_id)) {
return unserialize(Cache::get($this->user->discord_user_id));
}
else {
$authInfo = (object) Http::withToken($this->user->discord_token)
->get(config('services.discord.base_url') . '/oauth2/@me')
->throw()
->json();
Cache::put($this->user->discord_user_id, serialize($authInfo), Carbon::parse($authInfo->expires));
return $authInfo;
}
}
/**
* Checks if the user's token is close to expiring.
* Tokens should be refreshed the day before they expire.
*
* @return bool Whether the user's token needs to be refreshed
* @throws RequestException
*/
public function needsRefresh(): bool {
return Carbon::parse($this->getAuthorizationInfo()->expires)->diffInDays() == 1;
}
public function exchangeRefreshToken() {
}
/**
* Adds current working user to current working guild. Bot must be member of target guild, and account must be linked
*
* @return object|bool A GuildMember object; false if member is already in guild
* @throws RequestException Any client and server errors
* @see https://discord.com/developers/docs/resources/guild#guild-member-object
*/
public function addGuildMember(): object|bool
{
$params = [
'access_token' => $this->user->discord_token
];
$member = Http::withBody(json_encode($params), 'application/json')
->withHeaders([
'Authorization' => 'Bot ' . config('services.discord.token')
])->put(config('services.discord.base_url') . "/guilds/{$this->workingGuild}/members/{$this->user->discord_user_id}")
->throw();
if ($member->successful() && $member->status() == 204) {
return false;
} else {
return (object) $member->json();
}
}
/**
* Bans a specified user from the guild.
* May be called from the suspension service optionally by the banning user
*
* @param string $reason The reason to supply Discord with
* @return void Nothing on success
* @throws RequestException
* @throws AccountNotLinkedException
*/
public function addGuildBan(string $reason): void {
Http::withHeaders([
'Authorization' => 'Bot ' . config('services.discord.token'),
'X-Audit-Log-Reason' => $reason
])->put(config('services.discord.base_url') . "/guilds/{$this->workingGuild}/bans/{$this->user->discord_user_id}")
->throw();
throw new AccountNotLinkedException('Specified website user has not linked their Discord account yet.');
}
/**
* @param string $reason The removal reason to provide Discord with (e.g. ban expired)
* @return null|bool Null on unnan, false if user is not banned
* @throws RequestException
*/
public function removeGuildBan(string $reason): null|bool {
if ($this->getGuildBan($this->user)) {
Http::withHeaders([
'Authorization' => 'Bot ' . config('services.discord.token'),
'X-Audit-Log-Reason' => $reason
])->delete(config('services.discord.base_url') . "/guilds/{$this->workingGuild}/bans/{$this->user->discord_user_id}")
->throw();
return null;
}
return false;
}
/**
* Gets (possible) ban for current user.
*
* @return object|bool Ban object if user is banned. Null
* @throws RequestException
* @see https://discord.com/developers/docs/resources/guild#ban-object
*/
public function getGuildBan(): object|bool
{
$ban = Http::withHeaders([
'Authorization' => 'Bot ' . config('services.discord.token')
])->get(config('services.discord.base_url') . "/guilds/{$this->workingGuild}/bans/{$this->user->discord_user_id}");
if ($ban->status() == 404) {
return false;
}
return ($ban->successful()) ? (object) $ban->json() : $ban->throwIf($ban->status() !== 404);
}
/**
* Retrieves list of Role objects
* @see https://discord.com/developers/docs/topics/permissions#role-object
* @return array List of role objects
*
* @throws RequestException
*/
public function getGuildRoles(): array {
return Http::withHeaders([
'Authorization' => 'Bot ' . config('services.discord.token')
])->get(config('services.discord.base_url') . "/guilds/{$this->workingGuild}/roles")
->throw()
->json();
}
}

View File

@ -37,9 +37,8 @@ class Options
/**
* Returns an assortment of settings found in the mentioned category
*
* @param string $category The category
* @param $category The category
* @return Collection The settings in this category
* @throws EmptyOptionsException
*/
public function getCategory(string $category): Collection
{

View File

@ -6,9 +6,7 @@ use App\Absence;
use App\Exceptions\AbsenceNotActionableException;
use App\Http\Requests\StoreAbsenceRequest;
use App\Http\Requests\UpdateAbsenceRequest;
use App\Services\AbsenceService;
use App\User;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Carbon;
@ -17,12 +15,28 @@ use Illuminate\Support\Facades\Auth;
class AbsenceController extends Controller
{
private AbsenceService $absenceService;
/**
* Determines whether someone already has an active leave of absence request
*
* @param User $user The user to check
* @return bool Their status
*/
private function hasActiveRequest(Authenticatable $user): bool {
public function __construct (AbsenceService $absenceService) {
$absences = Absence::where('requesterID', $user->id)->get();
$this->absenceService = $absenceService;
foreach ($absences as $absence) {
// Or we could adjust the query (using a model scope) to only return valid absences;
// If there are any, refuse to store more, but this approach also works
// A model scope that only returns cancelled, declined and ended absences could also be implemented for future use
if (in_array($absence->getRawOriginal('status'), ['PENDING', 'APPROVED']))
{
return true;
}
}
return false;
}
/**
@ -43,7 +57,7 @@ class AbsenceController extends Controller
* Display a listing of absences belonging to the current user.
*
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
* @throws AuthorizationException
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function showUserAbsences()
{
@ -62,14 +76,14 @@ class AbsenceController extends Controller
/**
* Show the form for creating a new absence request.
*
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
* @return \Illuminate\Http\Response
*/
public function create()
{
$this->authorize('create', Absence::class);
return view('dashboard.absences.create')
->with('activeRequest', $this->absenceService->hasActiveRequest(Auth::user()));
->with('activeRequest', $this->hasActiveRequest(Auth::user()));
}
/**
@ -82,13 +96,21 @@ class AbsenceController extends Controller
{
$this->authorize('create', Absence::class);
if ($this->absenceService->hasActiveRequest(Auth::user())) {
if ($this->hasActiveRequest(Auth::user())) {
return redirect()
->back()
->with('error', __('You already have an active request. Cancel it or let it expire first.'));
}
$absence = $this->absenceService->createAbsence(Auth::user(), $request);
$absence = Absence::create([
'requesterID' => Auth::user()->id,
'start' => $request->start_date,
'predicted_end' => $request->predicted_end,
'available_assist' => $request->available_assist == "on",
'reason' => $request->reason,
'status' => 'PENDING',
]);
return redirect()
->to(route('absences.show', ['absence' => $absence->id]))
@ -98,8 +120,7 @@ class AbsenceController extends Controller
/**
* Display the specified absence request.
*
* @param \App\Absence $absence
* @throws AuthorizationException
* @param \App\Absence $absence
*/
public function show(Absence $absence)
{
@ -117,7 +138,7 @@ class AbsenceController extends Controller
*
* @param Absence $absence
* @return RedirectResponse
* @throws AuthorizationException
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function approveAbsence(Absence $absence): RedirectResponse
{
@ -125,7 +146,7 @@ class AbsenceController extends Controller
try
{
$this->absenceService->approveAbsence($absence);
$absence->setApproved();
}
catch (AbsenceNotActionableException $notActionableException)
{
@ -145,7 +166,7 @@ class AbsenceController extends Controller
*
* @param Absence $absence
* @return RedirectResponse
* @throws AuthorizationException
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function declineAbsence(Absence $absence): RedirectResponse
{
@ -153,7 +174,7 @@ class AbsenceController extends Controller
try
{
$this->absenceService->declineAbsence($absence);
$absence->setDeclined();
} catch (AbsenceNotActionableException $notActionableException)
{
return redirect()
@ -172,7 +193,7 @@ class AbsenceController extends Controller
*
* @param Absence $absence
* @return \Illuminate\Http\RedirectResponse
* @throws AuthorizationException
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function cancelAbsence(Absence $absence): \Illuminate\Http\RedirectResponse
{
@ -180,7 +201,7 @@ class AbsenceController extends Controller
try
{
$this->absenceService->cancelAbsence($absence);
$absence->setCancelled();
}
catch (AbsenceNotActionableException $notActionableException)
{
@ -204,7 +225,7 @@ class AbsenceController extends Controller
{
$this->authorize('delete', $absence);
if ($this->absenceService->removeAbsence($absence)) {
if ($absence->delete()) {
return redirect()
->to(route('absences.index'))
->with('success', __('Absence request deleted.'));

View File

@ -23,15 +23,11 @@ namespace App\Http\Controllers;
use App\Application;
use App\Exceptions\ApplicationNotFoundException;
use App\Exceptions\DiscordAccountRequiredException;
use App\Exceptions\IncompatibleAgeException;
use App\Exceptions\IncompleteApplicationException;
use App\Exceptions\InvalidAgeException;
use App\Exceptions\UnavailableApplicationException;
use App\Exceptions\VacancyNotFoundException;
use App\Facades\IP;
use App\Services\ApplicationService;
use App\Vacancy;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
@ -86,13 +82,6 @@ class ApplicationController extends Controller
}
public function discordApply(Request $request, $vacancySlug) {
$request->session()->put('discordApplicationRedirectedSlug', $vacancySlug);
return redirect(route('discordRedirect'));
}
public function renderApplicationForm($vacancySlug)
{
try {
@ -102,47 +91,25 @@ class ApplicationController extends Controller
return redirect()
->back()
->with('error', $ex->getMessage());
} catch (DiscordAccountRequiredException $e) {
\Log::info('Redirecting user: ' . $e->getMessage(), [
'user' => Auth::user()->email
]);
request()->session()->put('discordApplicationRedirectedSlug', $vacancySlug);
return redirect(route('discordRedirect'));
} catch (IncompatibleAgeException $e) {
return redirect()
->to(route('dashboard'))
->with('error', $e->getMessage());
} catch (InvalidAgeException $e) {
return view('dashboard.application-rendering.add-age');
}
}
public function saveApplicationAnswers(Request $request, $vacancySlug)
{
if (Auth::user()->isEligible()) {
try {
$this->applicationService->fillForm(Auth::user(), $request->all(), $vacancySlug);
try {
} catch (VacancyNotFoundException | IncompleteApplicationException | UnavailableApplicationException $e) {
$this->applicationService->fillForm(Auth::user(), $request->all(), $vacancySlug);
return redirect()
->back()
->with('error', $e->getMessage());
}
} catch (VacancyNotFoundException | IncompleteApplicationException | UnavailableApplicationException $e) {
return redirect()
->to(route('showUserApps'))
->with('success', __('Thank you! Your application has been processed and our team will get to it shortly.'));
->back()
->with('error', $e->getMessage());
}
return redirect()
->to(route('showUserApps'))
->with('error', __('Your account is not eligible to submit a new application.'));
->with('success', __('Thank you! Your application has been processed and our team will get to it shortly.'));
}
public function updateApplicationStatus(Request $request, Application $application, $newStatus)

View File

@ -1,101 +0,0 @@
<?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers\Auth;
use App\Facades\Discord;
use App\Facades\Options;
use App\Http\Controllers\Controller;
use App\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Laravel\Socialite\Facades\Socialite;
use Laravel\Socialite\Two\InvalidStateException;
class DiscordController extends Controller
{
public function discordRedirect() {
return Socialite::driver('discord')
->scopes(['email', 'guilds.join', 'guilds.members.read', 'guilds'])
->redirect();
}
public function discordCallback() {
try {
$discordUser = Socialite::driver('discord')->user();
} catch (InvalidStateException $stateException) {
Log::warning('Invalid state for social authentication: ', [
'message' => $stateException->getMessage(),
'ua' => request()->userAgent(),
'ip' => request()->ip()
]);
return redirect(route('discordRedirect'));
}
$appUser = User::where('email', $discordUser->getEmail())->first();
if ($appUser) {
$appUser->discord_token = $discordUser->token;
$appUser->discord_refresh_token = $discordUser->refreshToken;
$appUser->discord_user_id = $discordUser->getId();
$appUser->discord_pfp = $discordUser->getAvatar();
$appUser->save();
Auth::login($appUser, true);
} else {
$oAuthUser = User::create([
'uuid' => null,
'name' => $discordUser->getName(),
'email' => $discordUser->getEmail(),
'email_verified_at' => now(), // verify the account since it came from a trusted provider
'username' => $discordUser->getNickname(),
'currentIp' => \request()->ip(),
'registrationIp' => request()->ip(),
'discord_user_id' => $discordUser->getId(),
'discord_pfp' => $discordUser->getAvatar(),
'discord_token' => $discordUser->token,
'discord_refresh_token' => $discordUser->refreshToken
]);
$oAuthUser->assignRole('user');
Auth::login($oAuthUser, true);
}
if (session()->has('discordApplicationRedirectedSlug')) {
return redirect(route('renderApplicationForm', ['vacancySlug' => session()->pull('discordApplicationRedirectedSlug')]));
}
return redirect()
->route('dashboard');
}
}

View File

@ -26,11 +26,8 @@ use App\Services\AccountSuspensionService;
use App\User;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Log;
use App\Facades\IP;
use Illuminate\Support\Str;
use Laravel\Socialite\Facades\Socialite;
class LoginController extends Controller
@ -79,14 +76,6 @@ class LoginController extends Controller
$isLocked = $service->isLocked($user);
if ($isBanned || $isLocked) {
Log::alert('Restricted user attempting to login.', [
'ip' => $request->ip(),
'email' => $user->email,
'isBanned' => $isBanned,
'isLocked' => $isLocked
]);
return false;
} else {
return $this->originalAttemptLogin($request);
@ -105,11 +94,17 @@ class LoginController extends Controller
'prev' => $user->originalIP,
'new' => $request->ip()
]);
$user->currentIp = $request->ip();
$user->originalIP = $request->ip();
$user->save();
}
}
}
public function discordRedirect() {
return Socialite::driver('discord')->redirect();
}
public function discordCallback() {
// TODO;
}
}

View File

@ -23,13 +23,11 @@ namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Profile;
use App\Services\AccountSuspensionService;
use App\User;
use App\Facades\Options;
use App\Facades\IP;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
class RegisterController extends Controller
@ -64,6 +62,19 @@ class RegisterController extends Controller
$this->middleware('guest');
}
public function showRegistrationForm()
{
$users = User::where('originalIP', \request()->ip())->get();
foreach ($users as $user) {
if ($user && $user->isBanned()) {
abort(403, 'You do not have permission to access this page.');
}
}
return view('auth.register');
}
/**
* Get a validator for an incoming registration request.
*
@ -95,14 +106,9 @@ class RegisterController extends Controller
'uuid' => (Options::getOption('requireGameLicense') && Options::getOption('currentGame') == 'MINECRAFT') ? ['required', 'string', 'unique:users', 'min:32', 'max:32'] : ['nullable', 'string'],
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'dob' => ['required', 'string', 'date_format:Y-m-d', 'before:-13 years'],
'acceptTerms' => ['required', 'accepted'],
'password' => $password,
], [
'dob.before' => __('You must be 13 years of age or older in order to sign up for an account.'),
'dob.required' => __('Please enter your date of birth.'),
'uuid.required' => __('Please enter a valid (and Premium) Minecraft username! We do not support cracked users.'),
'acceptTerms.required' => __('Please accept the Community Guidelines, Terms of Service and Privacy Policy to continue.')
'uuid.required' => 'Please enter a valid (and Premium) Minecraft username! We do not support cracked users.',
]);
}
@ -114,16 +120,12 @@ class RegisterController extends Controller
*/
protected function create(array $data)
{
$ip = IP::shouldCollect() ? request()->ip() : '0.0.0.0';
$user = User::create([
'uuid' => $data['uuid'] ?? "disabled",
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
'registrationIp' => $ip,
'currentIp' => $ip,
'dob' => $data['dob']
'originalIP' => IP::shouldCollect() ? request()->ip() : '0.0.0.0',
]);
$user->assignRole('user');

View File

@ -0,0 +1,88 @@
<?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers;
use App\Ban;
use App\Events\UserBannedEvent;
use App\Http\Requests\BanUserRequest;
use App\Services\AccountSuspensionService;
use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class BanController extends Controller
{
protected $suspensionService;
public function __construct(AccountSuspensionService $suspensionService)
{
// Inject the service via DI
$this->suspensionService = $suspensionService;
}
public function insert(BanUserRequest $request, User $user)
{
if (config('demo.is_enabled')) {
return redirect()
->back()
->with('error', __('This feature is disabled'));
}
$this->authorize('create', [Ban::class, $user]);
if (!$this->suspensionService->isSuspended($user)) {
$this->suspensionService->suspend($request->reason, $request->duration, $user, $request->suspensionType);
$request->session()->flash('success', __('Account suspended.'));
} else {
$request->session()->flash('error', __('Account already suspended!'));
}
return redirect()->back();
}
public function delete(Request $request, User $user)
{
if (config('demo.is_enabled')) {
return redirect()
->back()
->with('error', __('This feature is disabled'));
}
$this->authorize('delete', $user->bans);
if ($this->suspensionService->isSuspended($user)) {
$this->suspensionService->unsuspend($user);
$request->session()->flash('success', __('Account unsuspended successfully!'));
} else {
$request->session()->flash('error', __('This account isn\'t suspended!'));
}
return redirect()->back();
}
}

View File

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

View File

@ -24,12 +24,10 @@ namespace App\Http\Controllers;
use App\Application;
use App\Events\ApplicationApprovedEvent;
use App\Events\ApplicationDeniedEvent;
use App\Services\AbsenceService;
use App\Services\AccountSuspensionService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
class DevToolsController extends Controller
{
@ -107,16 +105,4 @@ class DevToolsController extends Controller
->with('error', __('There were no expired suspensions (or no suspensions at all) to purge.'));
}
public function endAbsencesNow(AbsenceService $service)
{
$this->singleAuthorise();
$service->endExpired();
Log::alert('(absence cleaner) Forcefully started absence expiration check!');
return redirect()
->back()
->with('success', 'Cleaned up expired absences.');
}
}

View File

@ -41,4 +41,11 @@ class HomeController extends Controller
return view('home')
->with('positions', $positions);
}
public function pageGiveaway()
{
return view('giveaway');
}
}

View File

@ -21,12 +21,8 @@
namespace App\Http\Controllers;
use App\Exceptions\ProfileAlreadyExistsException;
use App\Exceptions\ProfileCreationFailedException;
use App\Exceptions\ProfileNotFoundException;
use App\Facades\IP;
use App\Http\Requests\ProfileSave;
use App\Services\AccountSuspensionService;
use App\Services\ProfileService;
use App\User;
use Carbon\Carbon;
@ -36,12 +32,18 @@ use Spatie\Permission\Models\Role;
class ProfileController extends Controller
{
private ProfileService $profileService;
private $profileService;
public function __construct(ProfileService $profileService) {
$this->profileService = $profileService;
}
public function index()
{
return view('dashboard.user.directory')
->with('users', User::with('profile', 'bans')->paginate(9));
}
public function showProfile()
{
// TODO: Come up with cleaner social media solution, e.g. social media object
@ -58,23 +60,26 @@ class ProfileController extends Controller
]);
}
public function showSingleProfile(AccountSuspensionService $accountSuspensionService, User $user)
public function showSingleProfile(User $user)
{
if (is_null($user->profile)) {
return redirect()
->back()
->with('error', "This user doesn't have a profile.");
}
$socialMediaProfiles = json_decode($user->profile->socialLinks, true);
$createdDate = Carbon::parse($user->created_at);
$systemRoles = Role::all()->pluck('name')->all();
$userRoles = $user->roles->pluck('name')->all();
$roleList = [];
foreach ($systemRoles as $role) {
if (in_array($role, $userRoles)) {
$roleList[$role] = true;
} else {
$roleList[$role] = false;
}
}
$suspensionInfo = null;
if ($accountSuspensionService->isSuspended($user))
if ($user->isBanned())
{
$suspensionInfo = [
@ -93,7 +98,8 @@ class ProfileController extends Controller
'insta' => $socialMediaProfiles['links']['insta'] ?? 'UpdateMe',
'discord' => $socialMediaProfiles['links']['discord'] ?? 'UpdateMe#12345',
'since' => $createdDate->englishMonth.' '.$createdDate->year,
'ipInfo' => IP::lookup($user->currentIp),
'ipInfo' => IP::lookup($user->originalIP),
'roles' => $roleList,
'suspensionInfo' => $suspensionInfo
]);
} else {
@ -108,44 +114,4 @@ class ProfileController extends Controller
->back()
->with('success', __('Profile updated.'));
}
public function createProfile(Request $request)
{
try {
$this->profileService->createProfile($request->user());
} catch (\Exception $e) {
return redirect()
->back()
->with('error', $e->getMessage());
}
return redirect()
->back()
->with('success', __('Your profile has been created.'));
}
public function deleteProfile(Request $request)
{
try {
$this->profileService->deleteProfile($request->user());
} catch (ProfileNotFoundException $e) {
return redirect()
->back()
->with('error', $e->getMessage());
}
return redirect()
->back()
->with('success', __('Profile deleted successfully.'));
}
}

View File

@ -22,36 +22,22 @@
namespace App\Http\Controllers;
use App\Ban;
use App\Facades\IP;
use App\Facades\Options;
use App\Http\Requests\Add2FASecretRequest;
use App\Http\Requests\AddDobRequest;
use App\Http\Requests\BanUserRequest;
use App\Http\Requests\ChangeEmailRequest;
use App\Http\Requests\ChangePasswordRequest;
use App\Http\Requests\DeleteUserRequest;
use App\Http\Requests\FlushSessionsRequest;
use App\Http\Requests\Remove2FASecretRequest;
use App\Http\Requests\Reset2FASecretRequest;
use App\Http\Requests\SearchPlayerRequest;
use App\Http\Requests\SetNewPasswordRequest;
use App\Http\Requests\UpdateUserRequest;
use App\Notifications\ChangedPassword;
use App\Notifications\EmailChanged;
use App\Notifications\PasswordAdminResetNotification;
use App\Notifications\TwoFactorResetNotification;
use App\Services\AccountSuspensionService;
use App\Services\DiscordService;
use App\Traits\DisablesFeatures;
use App\Traits\HandlesAccountDeletion;
use App\Traits\ReceivesAccountTokens;
use App\User;
use Google2FA;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Http\Client\RequestException;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Redirector;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
@ -59,20 +45,13 @@ use Spatie\Permission\Models\Role;
class UserController extends Controller
{
use HandlesAccountDeletion, DisablesFeatures;
use HandlesAccountDeletion;
/**
* Shows list of users
*
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function showUsers()
{
$this->authorize('viewPlayers', User::class);
return view('dashboard.administration.users')
return view('dashboard.administration.players')
->with([
'users' => User::with('roles')->paginate('6'),
'numUsers' => count(User::all()),
@ -80,15 +59,6 @@ class UserController extends Controller
]);
}
/**
* Searches for a player with the given search query.
*
* @deprecated Until Algolia implementation
* @param SearchPlayerRequest $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function showPlayersLike(SearchPlayerRequest $request)
{
$this->authorize('viewPlayers', User::class);
@ -102,7 +72,7 @@ class UserController extends Controller
if (! $matchingUsers->isEmpty()) {
$request->session()->flash('success', __('There were :usersCount user(s) matching your search.', ['usersCount' => $matchingUsers->count()]));
return view('dashboard.administration.users')
return view('dashboard.administration.players')
->with([
'users' => $matchingUsers,
'numUsers' => count(User::all()),
@ -115,16 +85,6 @@ class UserController extends Controller
}
}
/**
* Shows the user account's settings page
*
* @param Request $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
* @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException
* @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException
* @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException
*/
public function showAccount(Request $request)
{
$QRCode = null;
@ -149,58 +109,6 @@ class UserController extends Controller
->with('twofaQRCode', $QRCode);
}
/**
* Show account management screen
*
* @param AccountSuspensionService $suspensionService
* @param Request $request
* @param User $user
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function showAcocuntManagement(AccountSuspensionService $suspensionService, Request $request, User $user)
{
$this->authorize('adminEdit', $user);
$systemRoles = Role::all()->pluck('name')->all();
$userRoles = $user->roles->pluck('name')->all();
$roleList = [];
foreach ($systemRoles as $role) {
if (in_array($role, $userRoles)) {
$roleList[$role] = true;
} else {
$roleList[$role] = false;
}
}
return view('dashboard.user.manage')
->with([
'user' => $user,
'roles' => $roleList,
'isVerified' => $user->isVerified(),
'isLocked' => $suspensionService->isLocked($user),
'isSuspended' => $suspensionService->isSuspended($user),
'hasDiscord' => $user->hasDiscordConnection(),
'hasPassword' => $user->hasPassword(),
'requireLicense' => Options::getOption('requireGameLicense'),
'suspensionReason' => $suspensionService->getSuspensionReason($user),
'suspensionDuration' => $suspensionService->getSuspensionDuration($user),
'has2FA' => $user->has2FA(),
'applications' => $user->applications()->get()
]);
}
/**
* Log out other sessions for the current user
*
* @param FlushSessionsRequest $request
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\AuthenticationException
*/
public function flushSessions(FlushSessionsRequest $request)
{
// TODO: Move all log calls to a listener, which binds to an event fired by each significant event, such as this one
@ -219,14 +127,6 @@ class UserController extends Controller
return redirect()->back();
}
/**
* Change the current user's password
*
* @param ChangePasswordRequest $request
* @return \Illuminate\Http\RedirectResponse|void
*/
public function changePassword(ChangePasswordRequest $request)
{
if (config('demo.is_enabled')) {
@ -255,80 +155,13 @@ class UserController extends Controller
}
}
/**
* Sets a new password for the user.
*
* @param SetNewPasswordRequest $request
* @return Application|RedirectResponse|Redirector
*/
public function setPassword(SetNewPasswordRequest $request) {
if (!Auth::user()->hasPassword()) {
Auth::user()->password = Hash::make($request->newpass);
Auth::user()->save();
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect(route('login'));
}
return redirect()
->back()
->with('error', __('Your account already has a password.'));
}
/**
* Sets a user's password and removes their discord information from storage
*
* @param User $user
* @param SetNewPasswordRequest $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function unlinkDiscordAccount(Request $request, DiscordService $discordService)
{
if ($request->user()->hasPassword()) {
try {
$discordService->revokeAccountTokens(Auth::user());
Log::warning('Revoking social account tokens, user initiated', [
'user' => Auth::user()->email
]);
} catch (RequestException $requestException) {
if ($requestException->getCode() == 401) {
return redirect(route('discordRedirect'));
}
Log::error('Error while trying to revoke Discord credentials', [$requestException->getMessage()]);
return redirect()
->back()
->with('error', __('An unknown error ocurred. Please try again later.'));
}
$request->session()->flash('success', __('Discord account unlinked successfully. Link it again by re-authorizing the app with the same account in the login screen, or through your account settings.'));
return redirect()->back();
}
return redirect()
->back()
->with('error', __('Please set a password for your account first before trying to unlink Discord.'));
}
/**
* Change the current user's email address
*
* @param ChangeEmailRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function changeEmail(ChangeEmailRequest $request)
{
$this->disable();
if (config('demo.is_enabled')) {
return redirect()
->back()
->with('error', __('This feature is disabled'));
}
$user = User::find(Auth::user()->id);
@ -351,68 +184,13 @@ class UserController extends Controller
return redirect()->back();
}
/**
* Removes the user's password and notifies them.
*
* @param User $user The user to remove the password for
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function forcePasswordReset(User $user) {
$this->authorize('adminEdit', $user);
if ($user->hasPassword()) {
$user->notify(new PasswordAdminResetNotification());
$user->password = null;
$user->save();
Log::alert("Removed account password", [
'target' => $user,
'actor' => Auth::user()
]);
return redirect()
->back()
->with('success', __('Account password removed.'));
}
return redirect()
->back()
->with('error', __('This user doesn\'t have a password to reset.'));
}
/**
* Adds a user's date of birth if they don't have one.
*
* @param AddDobRequest $request
* @return RedirectResponse
*/
public function addDob(AddDobRequest $request) {
Auth::user()->dob = $request->dob;
Auth::user()->save();
return redirect()
->back();
}
/**
* Delete the given user's account
*
* @param DeleteUserRequest $request
* @param User $user
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function delete(DeleteUserRequest $request, User $user)
{
$this->disable();
if (config('demo.is_enabled')) {
return redirect()
->back()
->with('error', _('This feature is disabled'));
}
$this->authorize('delete', $user);
@ -426,19 +204,14 @@ class UserController extends Controller
return redirect()->route('registeredPlayerList');
}
/**
* Update a given user's details
*
* @param UpdateUserRequest $request
* @param User $user
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function update(UpdateUserRequest $request, User $user)
{
if (config('demo.is_enabled')) {
return redirect()
->back()
->with('error', __('This feature is disabled'));
}
$this->authorize('adminEdit', $user);
$this->disable();
// Mass update would not be possible here without extra code, making route model binding useless
$user->email = $request->email;
@ -470,16 +243,6 @@ class UserController extends Controller
return redirect()->back();
}
/**
* Generate and add a 2FA secret for the current user
*
* @param Add2FASecretRequest $request
* @return \Illuminate\Http\RedirectResponse
* @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException
* @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException
* @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException
*/
public function add2FASecret(Add2FASecretRequest $request)
{
if (config('demo.is_enabled')) {
@ -522,13 +285,6 @@ class UserController extends Controller
return redirect()->back();
}
/**
* Remove the current user's two factor secret key
*
* @param Remove2FASecretRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function remove2FASecret(Remove2FASecretRequest $request)
{
Log::warning('SECURITY: Disabling two factor authentication (user initiated)', [
@ -544,94 +300,34 @@ class UserController extends Controller
return redirect()->back();
}
/**
* Remove the given user's two factor secret key
*
* @param Reset2FASecretRequest $request
* @param User $user
* @return \Illuminate\Http\RedirectResponse
*/
public function reset2FASecret(Reset2FASecretRequest $request, User $user) {
// note: could invalidate other sessions for increased security
if ($user->has2FA()) {
Log::warning('SECURITY: Disabling two factor authentication (admin initiated)', [
'initiator' => $request->user()->email,
'target' => $user->email,
'ip' => $request->ip(),
]);
$user->twofa_secret = null;
$user->password = null;
$user->save();
$user->notify(new TwoFactorResetNotification());
public function terminate(Request $request, User $user)
{
$this->authorize('terminate', User::class);
if (config('demo.is_enabled')) {
return redirect()
->back()
->with('success', __('Two factor removed & user notified.'));
->with('error', __('This feature is disabled'));
}
return redirect()
->back()
->with('error', 'This user does not have two-factor authentication enabled.');
}
// TODO: move logic to policy
if (! $user->isStaffMember() || $user->is(Auth::user())) {
$request->session()->flash('error', __('You cannot terminate this user.'));
/**
* Suspend the given user
*
* @param AccountSuspensionService $suspensionService
* @param BanUserRequest $request
* @param User $user
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function suspend(AccountSuspensionService $suspensionService, BanUserRequest $request, User $user)
{
$this->authorize('create', [Ban::class, $user]);
$this->disable();
if ($suspensionService->isSuspended($user))
{
return redirect()
->back()
->with('error', __('Account already suspended.'));
return redirect()->back();
}
if ($request->suspensionType = "on") {
$suspensionService->suspend($user, $request->reason, $request->duration);
}
else {
$suspensionService->suspend($user, $request->reason);
foreach ($user->roles as $role) {
if ($role->name == 'user') {
continue;
}
$user->removeRole($role->name);
}
Log::info('User '.$user->name.' has just been demoted.');
$request->session()->flash('success', __('User terminated successfully.'));
//TODO: Dispatch event
return redirect()->back();
}
/**
* Unsuspend the given user
*
* @param AccountSuspensionService $suspensionService
* @param Request $request
* @param User $user
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function unsuspend(AccountSuspensionService $suspensionService, Request $request, User $user)
{
$this->authorize('delete', $user->bans);
$this->disable();
if ($suspensionService->isSuspended($user)) {
$suspensionService->unsuspend($user);
$request->session()->flash('success', __('Account unsuspended successfully!'));
} else {
$request->session()->flash('error', __('This account isn\'t suspended!'));
}
return redirect()->back();
}
}

View File

@ -70,8 +70,6 @@ class VacancyController extends Controller
'discordRoleID' => $request->discordRole,
'vacancyFormID' => $request->vacancyFormID,
'vacancyCount' => $request->vacancyCount,
'requiresDiscord' => $request->requireDiscordAccount == 'on',
'requiredAge' => $request->requiredAge
]);
@ -144,8 +142,6 @@ class VacancyController extends Controller
$vacancy->vacancyFullDescription = $request->vacancyFullDescription;
$vacancy->vacancyDescription = $request->vacancyDescription;
$vacancy->vacancyCount = $request->vacancyCount;
$vacancy->requiresDiscord = $request->requireDiscordAccount == 'on';
$vacancy->requiredAge = $request->requiredAge;
$vacancy->save();
@ -157,18 +153,10 @@ class VacancyController extends Controller
public function delete(Request $request, Vacancy $vacancy)
{
$this->authorize('delete', $vacancy);
if ($vacancy->teams->isEmpty()) {
$vacancy->delete();
return redirect()
->back()
->with('success', __('Vacancy deleted. All applications associated with it are now gone too.'));
}
$vacancy->delete();
return redirect()
->back()
->with('error', __('Please detach any teams that may be using this vacancy first.'));
->with('success', __('Vacancy deleted. All applications associated with it are now gone too.'));
}
}

View File

@ -89,10 +89,10 @@ class Kernel extends HttpKernel
'2fa' => \PragmaRX\Google2FALaravel\Middleware::class,
'passwordexpiration' => \App\Http\Middleware\PasswordExpirationMiddleware::class,
'passwordredirect' => \App\Http\Middleware\PasswordExpirationRedirectMiddleware::class,
'localize' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRoutes::class,
'localizationRedirect' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRedirectFilter::class,
'localeSessionRedirect' => \Mcamara\LaravelLocalization\Middleware\LocaleSessionRedirect::class,
'localeCookieRedirect' => \Mcamara\LaravelLocalization\Middleware\LocaleCookieRedirect::class,
'localeViewPath' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationViewPath::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,
];
}

View File

@ -22,11 +22,8 @@
namespace App\Http\Middleware;
use App\Application;
use App\User;
use Carbon\Carbon;
use Closure;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\View;
@ -36,42 +33,34 @@ class ApplicationEligibility
/**
* Handle an incoming request.
*
* @deprecated Deprecated in 0.9.0
* @see User::isEligible()
* @param Request $request
* @param Closure $next
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
* @throws Exception
* @throws \Exception
*/
public function handle($request, Closure $next)
{
$eligible = false;
$daysRemaining = __('N/A');
$curtime = new Carbon(now());
if (Auth::check()) {
$applications = Application::where('applicantUserID', Auth::user()->id)->get();
$eligible = true;
$lastApplication = Application::where('applicantUserID', Auth::user()->id)->latest()->first();
$daysRemaining = 0;
if (is_null($lastApplication)) {
View::share('isEligibleForApplication', true);
View::share('eligibilityDaysRemaining', 0);
if (! $applications->isEmpty()) {
foreach ($applications as $application) {
$appTime = Carbon::parse($application->created_at);
if ($appTime->isSameMonth($curtime)) {
Log::warning('Notice: Application ID '.$application->id.' was found to be in the same month as today\'s time, making the user '.Auth::user()->name.' ineligible for application');
$eligible = false;
}
}
return $next($request);
$allowedTime = Carbon::parse($applications->last()->created_at)->addMonth();
$daysRemaining = $allowedTime->diffInDays(now());
}
$daysRemaining = $lastApplication->created_at->addMonth()->diffInDays(now());
if ($lastApplication->created_at->diffInMonths(now()) > 1 && in_array($lastApplication->applicationStatus, ['DENIED', 'APPROVED'])) {
$eligible = true;
}
Log::debug('Perfomed application eligibility check', [
'eligible' => $eligible,
'daysRemaining' => $daysRemaining,
'ipAddress' => Auth::user()->originalIP,
'checkUserID' => Auth::user()->id
]);
View::share('isEligibleForApplication', $eligible);
View::share('eligibilityDaysRemaining', $daysRemaining);
}

View File

@ -21,21 +21,12 @@
namespace App\Http\Middleware;
use App\Services\AccountSuspensionService;
use Closure;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\View;
class Bancheck
{
private $suspensionService;
public function __construct(AccountSuspensionService $suspensionService) {
$this->suspensionService = $suspensionService;
}
/**
* Handle an incoming request.
*
@ -46,11 +37,11 @@ class Bancheck
public function handle($request, Closure $next)
{
$userIP = $request->ip();
$anonymousUser = User::where('currentIp', $userIP)->get();
$anonymousUser = User::where('ipAddress', $userIP)->get();
if (Auth::check() && $this->suspensionService->isSuspended($anonymousUser)) {
if (Auth::check() && Auth::user()->isBanned()) {
View::share('isBanned', true);
} elseif (! $anonymousUser->isEmpty() && $this->suspensionService->isSuspended(User::find($anonymousUser->id))) {
} elseif (! $anonymousUser->isEmpty() && User::find($anonymousUser->id)->isBanned()) {
View::share('isBanned', true);
} else {
View::share('isBanned', false);

View File

@ -21,7 +21,6 @@
namespace App\Http\Middleware;
use App\Services\AccountSuspensionService;
use Closure;
use Illuminate\Support\Facades\Auth;
@ -36,10 +35,10 @@ class ForceLogoutMiddleware
*/
public function handle($request, Closure $next)
{
if ((new AccountSuspensionService())->isSuspended(Auth::user())) {
if (Auth::user()->isBanned()) {
Auth::logout();
$request->session()->flash('error', __('Your account is suspended. If you think this was a mistake, please contact an admin.'));
$request->session()->flash('error', __('Your account is suspended. You will not be able to login or register until the suspension is lifted.'));
return redirect('/');
}

View File

@ -1,25 +0,0 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
class AddDobRequest extends FormRequest
{
public function rules(): array
{
return [
'dob' => 'required|string|date_format:Y-m-d|before:-13 years',
];
}
public function authorize(): bool
{
if (is_null(Auth::user()->dob)) {
return true;
}
return false;
}
}

View File

@ -1,28 +0,0 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
class AdminPasswordResetRequest extends FormRequest
{
public function rules(): array
{
if (Auth::user()->has2FA()) {
return [
'currentPassword' => 'required|current_password:web',
'otp' => 'required|integer|max:6',
];
}
return [
'currentPassword' => 'required|current_password:web',
];
}
public function authorize(): bool
{
return true;
}
}

View File

@ -4,7 +4,7 @@ namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class SetNewPasswordRequest extends FormRequest
class HomeContactRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
@ -13,11 +13,7 @@ class SetNewPasswordRequest extends FormRequest
*/
public function authorize()
{
if (\Auth::user()->hasDiscordConnection()) {
return true;
}
return false;
return true;
}
/**
@ -28,7 +24,9 @@ class SetNewPasswordRequest extends FormRequest
public function rules()
{
return [
'newpass' => 'required|string|min:10|confirmed',
'email' => 'required|email',
'msg' => 'required|string',
'captcha' => 'required|string'
];
}
}

View File

@ -44,6 +44,7 @@ class Remove2FASecretRequest extends FormRequest
{
return [
'currentPassword' => 'required|current_password',
'consent' => 'required|accepted',
];
}
}

View File

@ -1,20 +0,0 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class Reset2FASecretRequest extends FormRequest
{
public function rules(): array
{
return [
'currentPassword' => 'required|current_password',
];
}
public function authorize(): bool
{
return true;
}
}

View File

@ -46,7 +46,7 @@ class UpdateUserRequest extends FormRequest
return [
'email' => 'required|email',
'name' => 'required|string',
'uuid' => 'nullable|max:32|min:32',
'uuid' => 'required|max:32|min:32',
'roles' => 'required_without_all',
];
}

View File

@ -47,8 +47,6 @@ class VacancyEditRequest extends FormRequest
'vacancyDescription' => 'required|string',
'vacancyFullDescription' => 'nullable|string',
'vacancyCount' => 'required|integer|min:1',
'requireDiscordAccount' => 'required|string',
'requiredAge' => 'required|integer|numeric|min:13|max:100'
];
}
}

View File

@ -25,8 +25,6 @@ use Illuminate\Foundation\Http\FormRequest;
class VacancyRequest extends FormRequest
{
public mixed $requiresDiscordAccount;
/**
* Determine if the user is authorized to make this request.
*
@ -48,12 +46,10 @@ class VacancyRequest extends FormRequest
'vacancyName' => 'required|string',
'vacancyDescription' => 'required|string',
'vacancyFullDescription' => 'nullable|string',
'permissionGroup' => 'nullable|string',
'discordRole' => 'nullable|string',
'permissionGroup' => 'required|string',
'discordRole' => 'required|string',
'vacancyCount' => 'required|integer',
'vacancyFormID' => 'required|integer',
'requireDiscordAccount' => 'required|string',
'requiredAge' => 'required|integer|numeric|min:13|max:100'
];
}
}

0
app/Jobs/ProcessAccountDelete.php Executable file → Normal file
View File

View File

@ -1,39 +0,0 @@
<?php
namespace App\Jobs;
use App\Services\AbsenceService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
class ProcessExpiredAbsences implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Execute the job.
*
* @return void
*/
public function handle(AbsenceService $absenceService)
{
Log::info('(absence cleaner) Ending all expired absences.');
$absenceService->endExpired();
}
}

View File

@ -1,30 +0,0 @@
<?php
namespace App\Listeners;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class NewUser
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
//
}
}

View File

@ -49,11 +49,10 @@ class OnUserRegistration
// TODO: Send push notification to online admins via browser (w/ pusher)
Log::info('User '.$event->user->name.' has just registered for an account.');
User::whereHas('roles', function ($q) {
$q->where('name', 'admin');
})->get()->each(function ($user, $key) use ($event) {
$user->notify(new NewUser($event->user));
});
foreach (User::all() as $user) {
if ($user->hasRole('admin')) {
$user->notify(new NewUser($event->user));
}
}
}
}

View File

@ -45,15 +45,11 @@ class PromoteUser
*/
public function handle(ApplicationApprovedEvent $event)
{
Log::info('User promoted automatically (application approved)', [
'user' => $event->application->user->name,
'vacancy' => $event->application->response->vacancy->vacancyName,
'role' => 'staff'
]);
Log::info('User '.$event->application->user->name . 'has just been promoted (application approved)');
$event->application->setStatus('APPROVED');
$event->application->response->vacancy->decrease();
$event->application->user->assignRole('staff');
$event->application->user->assignRole('reviewer');
$event->application->user->notify(new ApplicationApproved($event->application));
}

View File

@ -1,68 +0,0 @@
<?php
namespace App\Notifications;
use App\Absence;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class AbsenceRequestApproved extends Notification implements ShouldQueue
{
use Queueable;
public Absence $absence;
/**
* Create a new notification instance.
*
* @return void
*/
public function __construct(Absence $absence)
{
$this->absence = $absence;
}
/**
* 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)
{
return (new MailMessage)
->greeting('Hi ' . $notifiable->name . ',')
->from(config('notification.sender.address'), config('notification.sender.name'))
->subject(config('app.name').' - absence request approved')
->line("Your recent Leave of Absence request from {$this->absence->created_at} has just been approved by an admin.")
->line('Your inactivity during the period you selected won\'t be counted. You will receive another email notification when your request ends, or if you decide to cancel it.')
->action('View your request', url(route('absences.show', ['absence' => $this->absence->id])))
->salutation('The team at ' . config('app.name'));
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}

View File

@ -1,68 +0,0 @@
<?php
namespace App\Notifications;
use App\Absence;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class AbsenceRequestCancelled extends Notification implements ShouldQueue
{
use Queueable;
public Absence $absence;
/**
* Create a new notification instance.
*
* @return void
*/
public function __construct(Absence $absence)
{
$this->absence = $absence;
}
/**
* 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)
{
return (new MailMessage)
->greeting('Hi ' . $notifiable->name . ',')
->from(config('notification.sender.address'), config('notification.sender.name'))
->subject(config('app.name').' - absence request cancelled')
->line("This notification confirms that your recent Leave of Absence from {$this->absence->created_at} has just been cancelled by you.")
->line('Please note that any inactivity will be counted in our activity metrics. You may also make a new request if you wish.')
->action('Send new request', url(route('absences.create')))
->salutation('The team at ' . config('app.name'));
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}

View File

@ -1,68 +0,0 @@
<?php
namespace App\Notifications;
use App\Absence;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class AbsenceRequestDeclined extends Notification implements ShouldQueue
{
use Queueable;
public Absence $absence;
/**
* Create a new notification instance.
*
* @return void
*/
public function __construct(Absence $absence)
{
$this->absence = $absence;
}
/**
* 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)
{
return (new MailMessage)
->greeting('Hi ' . $notifiable->name . ',')
->from(config('notification.sender.address'), config('notification.sender.name'))
->subject(config('app.name').' - absence request declined')
->line("Your recent Leave of Absence request from {$this->absence->created_at} has just been declined by an admin.")
->line('Please note that any inactivity will be counted in our activity metrics. You may make a new request, but we recommend you ask your team lead regarding your declined request.')
->action('Send new request', url(route('absences.create')))
->salutation('The team at ' . config('app.name'));
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}

View File

@ -1,68 +0,0 @@
<?php
namespace App\Notifications;
use App\Absence;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class AbsenceRequestEnded extends Notification implements ShouldQueue
{
use Queueable;
public Absence $absence;
/**
* Create a new notification instance.
*
* @return void
*/
public function __construct(Absence $absence)
{
$this->absence = $absence;
}
/**
* 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)
{
return (new MailMessage)
->greeting('Hi ' . $notifiable->name . ',')
->from(config('notification.sender.address'), config('notification.sender.name'))
->subject(config('app.name').' - absence request expired')
->line("Your Leave of Absence request from {$this->absence->created_at} (until {$this->absence->predicted_end}) has expired today.")
->line('Please note that any inactivity will be counted in our activity metrics. You may now make a new request if you still need more time.')
->action('Send new request', url(route('absences.create')))
->salutation('The team at ' . config('app.name'));
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}

0
app/Notifications/AccountDeleted.php Executable file → Normal file
View File

0
app/Notifications/AccountLocked.php Executable file → Normal file
View File

0
app/Notifications/AccountUnlocked.php Executable file → Normal file
View File

View File

@ -1,68 +0,0 @@
<?php
namespace App\Notifications;
use App\Absence;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class NewAbsenceRequest extends Notification implements ShouldQueue
{
use Queueable;
public Absence $absence;
/**
* Create a new notification instance.
*
* @return void
*/
public function __construct(Absence $absence)
{
$this->absence = $absence;
}
/**
* 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)
{
return (new MailMessage)
->greeting('Hi ' . $notifiable->name . ',')
->from(config('notification.sender.address'), config('notification.sender.name'))
->subject(config('app.name').' - new absence request pending review')
->line("A new absence request has just been submitted, scheduled to end {$this->absence->predicted_end}. Please review this request and take the appropriate action(s). The requester will be notified of your decision by email.")
->line("You are receiving this email because you're a site admin.")
->action('Review request', url(route('absences.show', ['absence' => $this->absence->id])))
->salutation('The team at ' . config('app.name'));
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}

View File

@ -1,35 +0,0 @@
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class PasswordAdminResetNotification extends Notification implements ShouldQueue
{
use Queueable;
public function __construct()
{
}
public function via($notifiable): array
{
return ['mail'];
}
public function toMail($notifiable): MailMessage
{
return (new MailMessage)
->from(config('notification.sender.address'), config('notification.sender.name'))
->subject(config('app.name').' - account password invalidated')
->markdown('mail.adminreset', ['name' => $notifiable->name]);
}
public function toArray($notifiable): array
{
return [];
}
}

View File

@ -1,35 +0,0 @@
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class TwoFactorResetNotification extends Notification implements ShouldQueue
{
use Queueable;
public function __construct()
{
}
public function via($notifiable): array
{
return ['mail'];
}
public function toMail($notifiable): MailMessage
{
return (new MailMessage)
->from(config('notification.sender.address'), config('notification.sender.name'))
->subject(config('app.name').' - your second factor has been reset')
->markdown('mail.two-factor-reset', ['name' => $notifiable->name]);
}
public function toArray($notifiable): array
{
return [];
}
}

0
app/Notifications/UserDeletedAccount.php Executable file → Normal file
View File

View File

@ -21,10 +21,7 @@
namespace App\Observers;
use App\Exceptions\ProfileAlreadyExistsException;
use App\Exceptions\ProfileCreationFailedException;
use App\Profile;
use App\Services\ProfileService;
use App\User;
use Illuminate\Support\Facades\Log;
@ -43,24 +40,15 @@ class UserObserver
*/
public function created(User $user)
{
$profileService = new ProfileService();
// TODO: Make sure that the profile is created, throw an exception if otherwise, or try to recreate the profile.
Profile::create([
try
{
$profileService->createProfile($user);
}
catch (ProfileAlreadyExistsException $exception)
{
Log::error('Attempting to create profile that already exists!', [
'trace' => $exception->getTrace()
]);
}
catch (ProfileCreationFailedException $e)
{
Log::error('Failed creating a new profile!', [
'trace' => $e->getTrace()
]);
}
'profileShortBio' => 'Write a one-liner about you here!',
'profileAboutMe' => 'Tell us a bit about you.',
'socialLinks' => '{}',
'userID' => $user->id,
]);
}
/**

View File

@ -47,7 +47,6 @@ use App\Vote;
use Illuminate\Auth\Notifications\VerifyEmail;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Support\Facades\Gate;
class AuthServiceProvider extends ServiceProvider
{
@ -91,17 +90,5 @@ class AuthServiceProvider extends ServiceProvider
->salutation('The team at ' . config('app.name'));
});
Gate::define('viewLogViewer', function (?User $user){
return $user->hasPermissionTo('admin.developertools.use');
});
Gate::define('downloadLogFile', function (User $user){
return $user->hasPermissionTo('admin.developertools.use');
});
Gate::define('deleteLogFile', function (User $user){
return $user->hasPermissionTo('admin.developertools.use');
});
}
}

View File

@ -1,31 +0,0 @@
<?php
namespace App\Providers;
use App\Helpers\Discord;
use Illuminate\Support\ServiceProvider;
class DiscordOuthProvider extends ServiceProvider
{
/**
* Register services.
*
* @return void
*/
public function register()
{
//
}
/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
\App::bind('discordServiceFacade', function () {
return new Discord();
});
}
}

View File

@ -70,7 +70,6 @@ class EventServiceProvider extends ServiceProvider
'App\Events\UserBannedEvent' => [
'App\Listeners\OnUserBanned',
],
];
/**

View File

@ -31,7 +31,7 @@ class RouteServiceProvider extends ServiceProvider
*
* @var string
*/
public const HOME = '/dashboard';
public const HOME = '/home';
/**
* Define your route model bindings, pattern filters, etc.

View File

@ -1,183 +0,0 @@
<?php
namespace App\Services;
use App\Absence;
use App\Exceptions\AbsenceNotActionableException;
use App\Notifications\AbsenceRequestApproved;
use App\Notifications\AbsenceRequestCancelled;
use App\Notifications\AbsenceRequestDeclined;
use App\Notifications\AbsenceRequestEnded;
use App\Notifications\NewAbsenceRequest;
use App\User;
use Carbon\Carbon;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Spatie\Permission\Models\Role;
class AbsenceService
{
/**
* Determines whether someone already has an active leave of absence request
*
* @param User $user The user to check
* @return bool Their status
*/
public function hasActiveRequest(Authenticatable $user): bool {
$absences = Absence::where('requesterID', $user->id)->get();
foreach ($absences as $absence) {
// Or we could adjust the query (using a model scope) to only return valid absences;
// If there are any, refuse to store more, but this approach also works
// A model scope that only returns cancelled, declined and ended absences could also be implemented for future use
if (in_array($absence->getRawOriginal('status'), ['PENDING', 'APPROVED']))
{
return true;
}
}
return false;
}
public function createAbsence(Authenticatable $requester, Request $request)
{
$absence = Absence::create([
'requesterID' => $requester->id,
'start' => $request->start_date,
'predicted_end' => $request->predicted_end,
'available_assist' => $request->available_assist == "on",
'reason' => $request->reason,
'status' => 'PENDING',
]);
foreach(User::role('admin')->get() as $admin) {
$admin->notify(new NewAbsenceRequest($absence));
}
Log::info('Processing new leave of absence request.', [
'requesting_user' => $requester->email,
'absenceid' => $absence->id,
'reason' => $request->reason
]);
return $absence;
}
/**
* Sets an absence as Approved.
*
* @param Absence $absence The absence to approve.
* @return Absence The approved absence.
* @throws AbsenceNotActionableException
*/
public function approveAbsence(Absence $absence)
{
Log::info('An absence request has just been approved.', [
'absenceid' => $absence->id,
'reviewing_admim' => Auth::user()->email,
'new_status' => 'APPROVED'
]);
return $absence
->setApproved()
->requester->notify(new AbsenceRequestApproved($absence));
}
/**
* Sets an absence as Declined.
*
* @param Absence $absence The absence to decline.
* @return Absence The declined absence.
* @throws AbsenceNotActionableException
*/
public function declineAbsence(Absence $absence)
{
Log::warning('An absence request has just been declined.', [
'absenceid' => $absence->id,
'reviewing_admim' => Auth::user()->email,
'new_status' => 'DECLINED'
]);
return $absence
->setDeclined()
->requester->notify(new AbsenceRequestDeclined($absence));
}
/**
* Sets an absence as Cancelled.
*
* @param Absence $absence The absence to cancel.
* @return Absence The cancelled absence.
* @throws AbsenceNotActionableException
*/
public function cancelAbsence(Absence $absence)
{
Log::warning('An absence request has just been cancelled (only cancellable by requester).', [
'absenceid' => $absence->id,
'new_status' => 'CANCELLED'
]);
return $absence
->setCancelled()
->requester->notify(new AbsenceRequestCancelled($absence));
}
/**
* Sets an absence as Ended.
*
* @param Absence $absence
* @return bool
*/
public function endAbsence(Absence $absence)
{
Log::info('An absence request has just expired.', [
'absenceid' => $absence->id,
'new_status' => 'ENDED'
]);
return $absence
->setEnded()
->requester->notify(new AbsenceRequestEnded($absence));
}
/**
* Removes an absence
*
* @param Absence $absence The absence to remove.
* @return bool Whether the absence was removed.
*/
public function removeAbsence(Absence $absence): bool
{
Log::alert('An absence request has just been removed.', [
'absence_details' => $absence,
'reviewing_admim' => Auth::user()->email,
]);
return $absence->delete();
}
public function endExpired()
{
foreach (Absence::all() as $absence)
{
if (!Carbon::parse($absence->predicted_end)->isFuture()) {
$this->endAbsence($absence);
}
}
}
}

View File

@ -16,14 +16,18 @@ class AccountSuspensionService
/**
* Suspends a user account, with given $reason.
* Permanent if no duration given.
*
* @param User $target Who to suspend.
* This method will take the target user and add a suspension to the database,
* effectively banning the user from the app. Suspensions may be temporary or permanent.
* Suspensions also block registration attempts.
*
* @param string $reason Suspension reason.
* @param int|null $duration Duration in days
* @param string $duration Duration. This is a timestamp.
* @param User $target Who to suspend.
* @param string $type Permanent or temporary?
* @return Ban The ban itself
*/
public function suspend(User $target, string $reason, int $duration = null): Ban {
public function suspend($reason, $duration, User $target, $type = "on"): Ban {
Log::alert("An user account has just been suspended.", [
'taget_email' => $target->email,
@ -31,17 +35,19 @@ class AccountSuspensionService
'reason' => $reason
]);
if ($duration > 0) {
if ($type == "on") {
$expiryDate = now()->addDays($duration);
}
return Ban::create([
$ban = Ban::create([
'userID' => $target->id,
'reason' => $reason,
'bannedUntil' => ($duration > 0) ? $expiryDate->format('Y-m-d H:i:s') : null,
'bannedUntil' => ($type == "on") ? $expiryDate->format('Y-m-d H:i:s') : null,
'authorUserID' => Auth::user()->id,
'isPermanent' => ($duration == 0) ? true : false
'isPermanent' => ($type == "off") ? true : false
]);
return $ban;
}
/**
@ -58,16 +64,6 @@ class AccountSuspensionService
$user->bans->delete();
}
/**
* Checks whether a user is suspended
*
* @param User $user The user to check
* @return bool Whether the mentioned user is suspended
*/
public function isSuspended(User $user): bool {
return !is_null($user->bans);
}
@ -111,6 +107,19 @@ class AccountSuspensionService
return $user->save();
}
/**
* Checks whether a user is suspended
*
* @param User $user The user to check
* @return bool Whether the mentioned user is suspended
*/
public function isSuspended(User $user): bool {
return !is_null($user->bans);
}
/**
* Checks whether an account is locked
*
@ -122,21 +131,21 @@ class AccountSuspensionService
}
/**
* Retrieves the reason for the user's suspension.
* Takes a suspension directly and makes it permanent.
*
* @param User $user The user account to check
* @return string|bool Reason for the suspension, false if not suspended
* @param Ban $ban The suspension to make permanent
*/
public function getSuspensionReason(User $user): string|bool {
return ($this->isSuspended($user)) ? $user->bans->reason : false;
}
public function makePermanent(Ban $ban): void {
public function getSuspensionDuration(User $user): string|null {
if ($this->isSuspended($user) && !is_null($user->bans->bannedUntil)) {
return $user->bans->bannedUntil->diffForHumans();
}
Log::alert('A suspension has just been made permanent.', [
'target_email' => $ban->user->email
]);
$ban->bannedUntil = null;
$ban->isPermanent = true;
$ban->save();
return null;
}
/**

View File

@ -3,11 +3,7 @@
namespace App\Services;
use App\Exceptions\DiscordAccountRequiredException;
use App\Exceptions\IncompatibleAgeException;
use App\Exceptions\InvalidAgeException;
use App\Notifications\ApplicationConfirmed;
use Carbon\Carbon;
use ContextAwareValidator;
use App\Application;
use App\Events\ApplicationDeniedEvent;
@ -26,27 +22,12 @@ use Illuminate\Support\Facades\Log;
class ApplicationService
{
/**
* @throws DiscordAccountRequiredException
* @throws IncompatibleAgeException
* @throws InvalidAgeException
*/
public function renderForm($vacancySlug)
{
$vacancyWithForm = Vacancy::with('forms')->where('vacancySlug', $vacancySlug)->get();
$firstVacancy = $vacancyWithForm->first();
if (is_null(Auth::user()->dob)) {
throw new InvalidAgeException("User must have added their age to apply for this vacancy.");
} elseif(Carbon::parse(Auth::user()->dob)->age < $firstVacancy->requiredAge) {
throw new IncompatibleAgeException("Sorry, you must be {$firstVacancy->requiredAge} or older to apply to {$firstVacancy->vacancyName}.");
}
if ($firstVacancy->requiresDiscord && !Auth::user()->hasDiscordConnection()) {
throw new DiscordAccountRequiredException('A discord account is required beyond this point.');
}
if (!$vacancyWithForm->isEmpty() && $firstVacancy->vacancyCount !== 0 && $firstVacancy->vacancyStatus == 'OPEN') {
return view('dashboard.application-rendering.apply')
->with([
@ -55,7 +36,7 @@ class ApplicationService
]);
} else {
throw new ApplicationNotFoundException(__('The application you\'re looking for could not be found or it is currently unavailable.'), 404);
throw new ApplicationNotFoundException('The application you\'re looking for could not be found or it is currently unavailable.', 404);
}
}
@ -113,12 +94,11 @@ class ApplicationService
'applicant' => $applicant->name
]);
User::whereHas('roles', function ($q) {
$q->where('name', 'admin');
})->get()->each(function ($user, $key) use ($application, $vacancy) {
$user->notify((new NewApplicant($application, $vacancy->first())));
});
foreach (User::all() as $user) {
if ($user->hasRole('admin')) {
$user->notify((new NewApplicant($application, $vacancy->first())));
}
}
$application->user->notify(new ApplicationConfirmed($application));
return true;

View File

@ -1,42 +0,0 @@
<?php
namespace App\Services;
use App\User;
use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Http;
class DiscordService
{
/**
* Sends a token revocation request to Discord to invalidate a specific $user's tokens.
* Please ensure you have the user set a password for their account after this, or request new tokens.
*
* @see https://www.rfc-editor.org/rfc/rfc7009
* @param User $user
* @return bool
* @throws RequestException
*/
public function revokeAccountTokens(User $user): bool
{
$req = Http::asForm()->post(config('services.discord.base_url') . '/oauth2/token/revoke', [
'client_id' => config('services.discord.client_id'),
'client_secret' => config('services.discord.client_secret'),
'token' => $user->discord_token,
])->throw();
$user->discord_token = null;
$user->discord_user_id = null;
$user->discord_refresh_token = null;
$user->discord_pfp = null;
$user->save();
return $req->ok();
}
}

View File

@ -39,7 +39,7 @@ class FormManagementService
$deletable = true;
if (! is_null($form->vacancies) && $form->vacancies->count() !== 0 || ! is_null($form->responses)) {
if (! is_null($form) && ! is_null($form->vacancies) && $form->vacancies->count() !== 0 || ! is_null($form->responses)) {
$deletable = false;
}

View File

@ -4,56 +4,15 @@
namespace App\Services;
use App\Exceptions\ProfileAlreadyExistsException;
use App\Exceptions\ProfileCreationFailedException;
use App\Exceptions\ProfileNotFoundException;
use App\Profile;
use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
class ProfileService
{
/**
* Creates a new profile for the specified $targetUser.
*
* @param User $targetUser The user to create the profile for.
* @return bool
* @throws ProfileAlreadyExistsException
* @throws ProfileCreationFailedException
*/
public function createProfile(User $targetUser): Profile {
if (is_null($targetUser->profile)) {
$profile = Profile::create([
'profileShortBio' => 'Write a one-liner about you here!',
'profileAboutMe' => 'Tell us a bit about you.',
'socialLinks' => '{}',
'userID' => $targetUser->id,
]);
if (is_null($profile)) {
throw new ProfileCreationFailedException(__('Could not create profile! Please try again later.'));
}
Log::info('Created profile for new user', [
'userid' => $targetUser->id
]);
return $profile;
}
throw new ProfileAlreadyExistsException(__('Profile already exists!'));
}
/**
* Updates the user's profile.
*
* @throws ProfileNotFoundException
*/
public function updateProfile($userID, Request $request) {
@ -88,26 +47,4 @@ class ProfileService
throw new ProfileNotFoundException("This profile does not exist.");
}
/**
* Delete specified user's profile
*
* @param User $targetUser
* @return bool
* @throws ProfileNotFoundException
*/
public function deleteProfile(User $targetUser): bool
{
if (!is_null($targetUser->profile)) {
Log::alert('Deleted user profile', [
'userid' => $targetUser->id
]);
return $targetUser->profile->delete();
}
throw new ProfileNotFoundException(__('Attempting to delete non-existant profile!'));
}
}

View File

@ -1,25 +0,0 @@
<?php
namespace App\Traits;
use Illuminate\Http\RedirectResponse;
trait DisablesFeatures
{
/**
* Checks if demo mode is active. If so, it stops any more logic from running.
*
* @return RedirectResponse|null
*/
protected function disable(): RedirectResponse|null
{
if (config('demo.is_enabled')) {
return redirect()
->back()
->with('error', __('This feature is disabled'));
}
return null;
}
}

View File

@ -24,21 +24,15 @@ namespace App;
use App\Services\AccountSuspensionService;
use App\Traits\HandlesAccountTokens;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\View;
use Mpociot\Teamwork\Traits\UserHasTeams;
use Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable implements MustVerifyEmail
{
use UserHasTeams, Notifiable, HasRoles, HasFactory;
use UserHasTeams, Notifiable, HasRoles;
/**
* The attributes that are mass assignable.
@ -46,19 +40,7 @@ class User extends Authenticatable implements MustVerifyEmail
* @var array
*/
protected $fillable = [
'name',
'email',
'password',
'originalIP',
'registrationIp',
'username',
'uuid',
'dob',
'email_verified_at',
'currentIp',
'discord_user_id',
'discord_token',
'discord_refresh_token'
'name', 'email', 'password', 'originalIP', 'username', 'uuid', 'dob',
];
/**
@ -67,7 +49,7 @@ class User extends Authenticatable implements MustVerifyEmail
* @var array
*/
protected $hidden = [
'password', 'remember_token', 'discord_token', 'discord_refresh_token'
'password', 'remember_token',
];
/**
@ -77,8 +59,6 @@ class User extends Authenticatable implements MustVerifyEmail
*/
protected $casts = [
'email_verified_at' => 'datetime',
'discord_token' => 'encrypted',
'discord_refresh_token' => 'encrypted'
];
// RELATIONSHIPS
@ -91,7 +71,6 @@ class User extends Authenticatable implements MustVerifyEmail
public function votes()
{
return $this->hasMany('App\Vote', 'userID', 'id');
}
public function profile()
@ -120,52 +99,32 @@ class User extends Authenticatable implements MustVerifyEmail
}
public function isEligible(): bool {
$lastApplication = Application::where('applicantUserID', $this->getAttribute('id'))->latest()->first();
if (is_null($lastApplication)) {
return true;
}
if ($lastApplication->created_at->diffInMonths(now()) > 1 && in_array($lastApplication->applicationStatus, ['DENIED', 'APPROVED'])) {
return true;
}
return false;
}
public function isVerified(): bool {
return !is_null($this->email_verified_at);
}
// UTILITY LOGIC
/**
* Checks if user is staff
* Checks if a user is banned.
*
* @deprecated This method is being replaced by a better way of checking permissions, rather than checking for group name.
* @return bool
* @deprecated This method is obsolete, as it has been replaced by the suspension service.
* @see AccountSuspensionService::isSuspended()
*
* @return bool Whether the user is banned
*/
public function isStaffMember(): bool
public function isBanned(): bool
{
return ! $this->bans()->get()->isEmpty();
}
public function isStaffMember()
{
return $this->hasAnyRole('reviewer', 'admin', 'hiringManager');
}
/**
* Checks if user has 2fa enabled
*
* @return bool
*/
public function has2FA(): bool
public function has2FA()
{
return ! is_null($this->twofa_secret);
}
/**
* Checks if user has team
*
* @param $team
* @return bool
*/
public function hasTeam($team): bool
{
if ($team instanceof Team || is_int($team))
@ -181,23 +140,9 @@ class User extends Authenticatable implements MustVerifyEmail
}
}
/**
* Check if user linked their Discord account
*
* @return bool
*/
public function hasDiscordConnection(): bool {
return !is_null($this->discord_token) && !is_null($this->discord_refresh_token);
public function routeNotificationForSlack($notification)
{
return config('slack.webhook.integrationURL');
}
/**
* Check if user has a password
*
* @return bool
*/
public function hasPassword(): bool {
return !is_null($this->password);
}
}

View File

@ -44,8 +44,6 @@ class Vacancy extends Model
'vacancyStatus',
'vacancySlug',
'team_id',
'requiresDiscord',
'requiredAge'
];

View File

@ -7,27 +7,16 @@ use Illuminate\View\Component;
class AccountStatus extends Component
{
public bool
$isVerified,
$isSuspended,
$isLocked,
$has2FA,
$hasDiscord,
$hasPassword;
public $user;
/**
* Create a new component instance.
*
* @return void
*/
public function __construct($isVerified, $isSuspended, $isLocked, $has2FA, $hasDiscord, $hasPassword)
public function __construct($userId)
{
$this->isVerified = $isVerified;
$this->isSuspended = $isSuspended;
$this->isLocked = $isLocked;
$this->has2FA = $has2FA;
$this->hasDiscord = $hasDiscord;
$this->hasPassword = $hasPassword;
$this->user = User::findOrFail($userId);
}
/**

View File

@ -1,14 +0,0 @@
<?php
namespace App\View\Components;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class ConfirmPassword extends Component
{
public function render(): View
{
return view('components.confirm-password');
}
}

View File

@ -1,14 +0,0 @@
<?php
namespace App\View\Components;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class ConfirmSecondFactor extends Component
{
public function render(): View
{
return view('components.confirm-second-factor');
}
}

View File

@ -10,6 +10,7 @@
"require": {
"php": "^8.0",
"ext-json": "*",
"arcanedev/log-viewer": "^9.0.0",
"doctrine/dbal": "^2.10",
"fruitcake/laravel-cors": "^2.1",
"geo-sot/laravel-env-editor": "^1.1.0",
@ -20,12 +21,11 @@
"laravel-notification-channels/pusher-push-notifications": "^3.0",
"laravel/framework": "^9.0",
"laravel/slack-notification-channel": "^2.0",
"laravel/socialite": "^5.5",
"laravel/socialite": "^5.2",
"laravel/tinker": "^2.0",
"laravel/ui": "^3.0",
"mcamara/laravel-localization": "^1.7",
"mpociot/teamwork": "^7.0",
"opcodesio/log-viewer": "^1.2",
"pragmarx/google2fa-laravel": "^1.3",
"sentry/sentry-laravel": "2.11.*",
"socialiteproviders/discord": "^4.1",
@ -35,9 +35,7 @@
"ext-xdebug": "*",
"amirami/localizator": "^0.9.0@alpha",
"barryvdh/laravel-debugbar": "^3.3",
"barryvdh/laravel-ide-helper": "^2.12",
"fakerphp/faker": "^1.19",
"laravel/sail": "^1.15",
"mockery/mockery": "^1.3.1",
"nunomaduro/collision": "^6.1",
"phpunit/phpunit": "^9.3",

View File

@ -230,12 +230,13 @@ return [
[
'text' => 'm_home',
'icon' => 'fas fa-home',
'url' => '/',
'url' => 'dashboard',
],
[
'text' => 'Dashboard',
'icon' => 'fas fa-tachometer-alt',
'url' => '/dashboard'
'text' => 'm_directory',
'icon' => 'fas fa-users',
'url' => 'users/directory',
'can' => 'profiles.view.others',
],
[
'header' => 'h_applications',
@ -385,16 +386,16 @@ return [
[
'text' => 'm_devtools',
'icon' => 'fas fa-code',
'route' => 'devTools',
'url' => '/admin/devtools',
'can' => 'admin.developertools.use',
],
],
],
[
'text' => 'App logs',
'url' => '/admin/developers/logs',
'text' => 'm_s_logs',
'url' => '/admin/maintenance/system-logs',
'icon' => 'fas fa-clipboard-list',
'can' => 'admin.developertools.use',
'can' => 'admin.maintenance.logs.view',
],
],

View File

@ -94,36 +94,6 @@ return [
*/
'hide_ips' => env('HIDE_IPS'),
/*
|--------------------------------------------------------------------------
| Legal documents & source code
|--------------------------------------------------------------------------
|
| Every website needs a collection of legal documents in order to remain compliant with
| international & local privacy laws. These are often best known as:
|
| - Privacy Policy, where you outline your data protection practices;
| - Terms of Service, where you outline acceptable usage of your services;
| - Community Guidelines, where you outline acceptable behavior for users on your platforms.
|
| RBRecruiter will display these URLs at appropriate locations and force users to accept them,
| if legally necessary, such as in the registration & application form pages.
|
| Additionally, you can also specify a support email and URL where your users/customers can send inquiries if necessary.
|
| You can leave these URLs empty if your website hasn't entered production yet, but we recommend
| you draft these documents as soon as possible.
|
*/
'terms_url' => env('TERMS_URL', '#'),
'privacy_url' => env('PRIVACY_URL', '#'),
'guidelines_url' => env('GUIDELINES_URL', '#'),
'support_url' => env('SUPPORT_URL', 'https://support.example.com'),
'support_email' => env('SUPPORT_EMAIL', 'support@example.com'),
'source_repo' => env('SOURCE_REPO', 'https://code.webvokestudio.pt/miguel456/rbrecruiter'),
/*
|--------------------------------------------------------------------------
| Application Environment
@ -291,7 +261,6 @@ return [
\App\Providers\OptionsProvider::class,
App\Providers\DigitalStorageProvider::class,
App\Providers\JSONProvider::class,
App\Providers\DiscordOuthProvider::class,
NotificationChannels\Discord\DiscordServiceProvider::class,
],
@ -350,8 +319,7 @@ return [
'Markdown' => GrahamCampbell\Markdown\Facades\Markdown::class,
'ContextAwareValidator' => App\Facades\ContextAwareValidation::class,
'Settings' => App\Facades\Options::class,
'JSON' => App\Facades\JSON::class,
'DiscordOauth' => App\Facades\Discord::class
'JSON' => App\Facades\JSON::class
],

2
config/localizator.php Executable file → Normal file
View File

@ -40,7 +40,7 @@ return [
* Add here any custom defined functions.
* NOTE: The translation string should always be the first argument.
*/
'functions' => ['__', 'trans_choice', 'trans', '@lang']
'functions' => ['__', 'trans', '@lang']
],
/**

View File

@ -1,76 +1,160 @@
<?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
use Arcanedev\LogViewer\Contracts\Utilities\Filesystem;
return [
/*
|--------------------------------------------------------------------------
| Log Viewer Route
|--------------------------------------------------------------------------
| Log Viewer will be available under this URL.
|
*/
/* -----------------------------------------------------------------
| Log files storage path
| -----------------------------------------------------------------
*/
'route_path' => 'admin/developers/logs',
'storage-path' => storage_path('logs'),
/*
|--------------------------------------------------------------------------
| Back to system URL
|--------------------------------------------------------------------------
| When set, displays a link to easily get back to this URL.
| Set to `null` to hide this link.
|
| Optional label to display for the above URL.
|
*/
/* -----------------------------------------------------------------
| Log files pattern
| -----------------------------------------------------------------
*/
'back_to_system_url' => '/admin/developers',
'back_to_system_label' => 'Back to Developer Tools', // Displayed by default: "Back to {{ app.name }}"
/*
|--------------------------------------------------------------------------
| Log Viewer route middleware.
|--------------------------------------------------------------------------
| The middleware should enable session and cookies support in order for the Log Viewer to work.
| The 'web' middleware will be applied automatically if empty.
|
*/
'middleware' => ['web', 'auth'],
/*
|--------------------------------------------------------------------------
| Include file patterns
|--------------------------------------------------------------------------
|
*/
'include_files' => ['*.log'],
/*
|--------------------------------------------------------------------------
| Exclude file patterns.
|--------------------------------------------------------------------------
| This will take precedence over included files.
|
*/
'exclude_files' => [
//'my_secret.log'
'pattern' => [
'prefix' => Filesystem::PATTERN_PREFIX, // 'laravel-'
'date' => Filesystem::PATTERN_DATE, // '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]'
'extension' => Filesystem::PATTERN_EXTENSION, // '.log'
],
/*
|--------------------------------------------------------------------------
| Shorter stack trace filters.
|--------------------------------------------------------------------------
| Lines containing any of these strings will be excluded from the full log.
| This setting is only active when the function is enabled via the user interface.
|
*/
/* -----------------------------------------------------------------
| Locale
| -----------------------------------------------------------------
| Supported locales :
| 'auto', 'ar', 'bg', 'de', 'en', 'es', 'et', 'fa', 'fr', 'hu', 'hy', 'id', 'it', 'ja', 'ko', 'nl',
| 'pl', 'pt-BR', 'ro', 'ru', 'sv', 'th', 'tr', 'zh-TW', 'zh'
*/
'shorter_stack_trace_excludes' => [
'/vendor/symfony/',
'/vendor/laravel/framework/',
'/vendor/barryvdh/laravel-debugbar/',
'locale' => 'auto',
/* -----------------------------------------------------------------
| Theme
| -----------------------------------------------------------------
| Supported themes :
| 'bootstrap-3', 'bootstrap-4'
| Make your own theme by adding a folder to the views directory and specifying it here.
*/
'theme' => 'bootstrap-4',
/* -----------------------------------------------------------------
| Route settings
| -----------------------------------------------------------------
*/
'route' => [
'enabled' => true,
'attributes' => [
'prefix' => 'admin/maintenance/system-logs',
'middleware' => env('ARCANEDEV_LOGVIEWER_MIDDLEWARE') ? explode(',', env('ARCANEDEV_LOGVIEWER_MIDDLEWARE')) : null,
],
],
/* -----------------------------------------------------------------
| Log entries per page
| -----------------------------------------------------------------
| This defines how many logs & entries are displayed per page.
*/
'per-page' => 30,
/* -----------------------------------------------------------------
| Download settings
| -----------------------------------------------------------------
*/
'download' => [
'prefix' => 'rpnetlog-',
'extension' => 'log',
],
/* -----------------------------------------------------------------
| Menu settings
| -----------------------------------------------------------------
*/
'menu' => [
'filter-route' => 'log-viewer::logs.filter',
'icons-enabled' => true,
],
/* -----------------------------------------------------------------
| Icons
| -----------------------------------------------------------------
*/
'icons' => [
/**
* Font awesome >= 4.3
* http://fontawesome.io/icons/.
*/
'all' => 'fa fa-fw fa-list', // http://fontawesome.io/icon/list/
'emergency' => 'fa fa-fw fa-bug', // http://fontawesome.io/icon/bug/
'alert' => 'fa fa-fw fa-bullhorn', // http://fontawesome.io/icon/bullhorn/
'critical' => 'fa fa-fw fa-heartbeat', // http://fontawesome.io/icon/heartbeat/
'error' => 'fa fa-fw fa-times-circle', // http://fontawesome.io/icon/times-circle/
'warning' => 'fa fa-fw fa-exclamation-triangle', // http://fontawesome.io/icon/exclamation-triangle/
'notice' => 'fa fa-fw fa-exclamation-circle', // http://fontawesome.io/icon/exclamation-circle/
'info' => 'fa fa-fw fa-info-circle', // http://fontawesome.io/icon/info-circle/
'debug' => 'fa fa-fw fa-life-ring', // http://fontawesome.io/icon/life-ring/
],
/* -----------------------------------------------------------------
| Colors
| -----------------------------------------------------------------
*/
'colors' => [
'levels' => [
'empty' => '#D1D1D1',
'all' => '#8A8A8A',
'emergency' => '#B71C1C',
'alert' => '#D32F2F',
'critical' => '#F44336',
'error' => '#FF5722',
'warning' => '#FF9100',
'notice' => '#4CAF50',
'info' => '#1976D2',
'debug' => '#90CAF9',
],
],
/* -----------------------------------------------------------------
| Strings to highlight in stack trace
| -----------------------------------------------------------------
*/
'highlight' => [
'^#\d+',
'^Stack trace:',
],
];

View File

@ -37,18 +37,12 @@ return [
'client_id' => env('DISCORD_CLIENT_ID'),
'client_secret' => env('DISCORD_CLIENT_SECRET'),
'redirect' => env('DISCORD_REDIRECT_URI'),
'base_url' => env('DISCORD_BASE_URL'),
'token' => env('DISCORD_BOT_TOKEN'),
'staff_guild_roles' => env('DISCORD_STAFF_GUILD_ROLES'),
'staff_home_roles' => env('DISCORD_HOME_GUILD_ROLES'),
// optional
'allow_gif_avatars' => (bool)env('DISCORD_AVATAR_GIF', true),
'avatar_default_extension' => env('DISCORD_EXTENSION_DEFAULT', 'jpg'), // only pick from jpg, png, webp
'home_guild' => env('DISCORD_HOME_GUILD'),
'staff_guild' => env('DISCORD_STAFF_GUILD', null)
],
'mailgun' => [
@ -72,7 +66,4 @@ return [
'beams_secret_key' => 'Your Secret Key',
],
'cpanel' => [
'api_token' => env('CPANEL_API_TOKEN', null)
]
];

View File

@ -1,23 +0,0 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Profile>
*/
class ProfileFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition()
{
return [
//
];
}
}

View File

@ -1,36 +1,47 @@
<?php
/*
* Copyright © 2020 Miguel Nogueira
*
* This file is part of Raspberry Staff Manager.
*
* Raspberry Staff Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Raspberry Staff Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Raspberry Staff Manager. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Carbon;
use App\User;
use Faker\Generator as Faker;
use Illuminate\Support\Str;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\User>
*/
class UserFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition()
{
return [
'uuid' => $this->faker->uuid(),
'name' => $this->faker->name(),
'email' => $this->faker->unique()->safeEmail(),
'email_verified_at' => Carbon::now(),
'username' => $this->faker->userName(),
'dob' => Carbon::now(),
'password' => bcrypt($this->faker->password),
'created_at' => Carbon::now(),
'updated_at' => Carbon::now(),
'password_last_updated' => Carbon::now(),
'administratively_locked' => false,
'registrationIp' => $this->faker->ipv4(),
'currentIp' => $this->faker->ipv4(),
];
}
}
/*
|--------------------------------------------------------------------------
| Model Factories
|--------------------------------------------------------------------------
|
| This directory should contain each of the model factory definitions for
| your application. Factories provide a convenient way to generate new
| model instances for testing / seeding your application's database.
|
*/
$factory->define(User::class, function (Faker $faker) {
return [
'name' => $faker->name,
'email' => $faker->unique()->safeEmail,
'email_verified_at' => now(),
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
'remember_token' => Str::random(10),
];
});

0
database/migrations/.gitkeep Executable file → Normal file
View File

View File

@ -1,34 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->renameColumn('originalIP', 'currentIp');
$table->ipAddress('registrationIp')->after('originalIP');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->renameColumn('currentIp', 'originalIP');
$table->removeColumn('ipAddress');
});
}
};

View File

@ -1,41 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('password')->nullable()->change(); // social login requires no pw (still required upon normal reg)
$table->string('registrationIp')->nullable()->change();
$table->unsignedBigInteger('discord_user_id')->after('remember_token')->nullable();
$table->longText('discord_token')->after('discord_user_id')->nullable();
$table->longText('discord_refresh_token')->after('discord_token')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('discord_token', 'discord_refresh_token', 'discord_user_id');
$table->string('password')->nullable(false)->change();
$table->string('registrationIp')->nullable(false)->change();
});
}
};

View File

@ -1,22 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddIntProfilePicToUsers extends Migration
{
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->longText('discord_pfp')->after('discord_user_id')->nullable();
});
}
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('discord_pfp');
});
}
}

View File

@ -1,22 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddLocaleToUsers extends Migration
{
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('locale')->after('email')->default('en-US');
});
}
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('locale');
});
}
}

View File

@ -1,24 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class UpdateVacancyIntegrationNullable extends Migration
{
public function up()
{
Schema::table('vacancies', function (Blueprint $table) {
$table->string('permissionGroupName')->nullable()->change();
$table->string('discordRoleID')->nullable()->change();
});
}
public function down()
{
Schema::table('vacancies', function (Blueprint $table) {
$table->string('permissionGroupName')->nullable(false)->change();
$table->string('discordRoleID')->nullable(false)->change();
});
}
}

View File

@ -1,24 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddDiscordRequirementToVacancies extends Migration
{
public function up()
{
Schema::table('vacancies', function (Blueprint $table) {
$table->boolean('requiresDiscord')
->default(false)
->after('vacancyStatus');
});
}
public function down()
{
Schema::table('vacancies', function (Blueprint $table) {
$table->dropColumn('requiresDiscord');
});
}
}

View File

@ -1,22 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddRequiredAgeToVacancies extends Migration
{
public function up()
{
Schema::table('vacancies', function (Blueprint $table) {
$table->integer('requiredAge')->default(16)->after('vacancyStatus');
});
}
public function down()
{
Schema::table('vacancies', function (Blueprint $table) {
$table->dropColumn('requiredAge');
});
}
}

View File

@ -42,10 +42,6 @@ class PermissionSeeder extends Seeder
]
);
$staff = Role::create([
'name' => 'staff'
]);
$reviewer = Role::create(
[
'name' => 'reviewer'
@ -112,16 +108,13 @@ class PermissionSeeder extends Seeder
'profiles.view.others'
]);
$staff->givePermissionTo([
'reviewer.viewAbsence',
'reviewer.requestAbsence',
'reviewer.withdrawAbsence',
]);
// Able to view applications and vote on them once they reach the right stage, but not approve applications up to said stage
$reviewer->givePermissionTo([
'applications.view.all',
'applications.vote'
'applications.vote',
'reviewer.viewAbsence',
'reviewer.requestAbsence',
'reviewer.withdrawAbsence',
]);
$hiringManager->givePermissionTo('appointments.*', 'applications.*', 'admin.hiring.*');

View File

@ -34,36 +34,144 @@ class UserSeeder extends Seeder
*/
public function run()
{
$ghostAccount = User::create([
'uuid' => 'b741345057274a519144881927be0290', // Ghost
'name' => 'Ghost (deleted account)',
'email' => 'blackhole@example.com',
'email_verified_at' => now(),
'username' => 'ghost',
'registrationIp' => '0.0.0.0',
'currentIp' => '0.0.0.0',
'originalIP' => '0.0.0.0',
'password' => 'locked'
])->assignRole('user'); // There can't be role-less users
$admin = User::create(([
'uuid' => 'b741345057274a519144881927be0290', // Ghost
'name' => 'Admin',
'email' => 'admin@webvokestudio.pt',
'email_verified_at' => now(),
'username' => 'admin',
'registrationIp' => '0.0.0.0',
'currentIp' => '0.0.0.0',
'password' => Hash::make('admin')
]))->assignRole('user', 'reviewer', 'hiringManager', 'admin');
$staffUsers = [
$users = User::factory()
->count(1500)
->create();
[
'uuid' => 'd2b321b56ff1445db9d7794701983cad',
'name' => 'Robot 1',
'email' => 'tester1@example.com',
'username' => 'tester1',
'originalIP' => '99.18.146.235',
'password' => Hash::make('password')
],
[
'uuid' => 'ab22b5da02644953ace969fce85c0819',
'name' => 'Robot 2',
'email' => 'tester2@example.com',
'username' => 'tester2',
'originalIP' => '141.239.229.53',
'password' => Hash::make('password')
],
[
'uuid' => 'df38e6bf762944d3a600ded59a693ad1',
'name' => 'Robot 3',
'email' => 'tester3@example.com',
'username' => 'tester3',
'originalIP' => '25.63.20.97',
'password' => Hash::make('password')
],
[
'uuid' => '689e446484824f6bad5064e3df0aaa96',
'name' => 'Robot 4',
'email' => 'tester4@example.com',
'username' => 'tester4',
'originalIP' => '220.105.223.142',
'password' => Hash::make('password')
],
[
'uuid' => '172391f917bf418ab1c40ebc041ed5ba',
'name' => 'Robot 5',
'email' => 'tester5@example.com',
'username' => 'tester5',
'originalIP' => '224.66.76.60',
'password' => Hash::make('password')
],
[
'uuid' => '371f34dcce2a4457bf385ab9417a2345',
'name' => 'Robot 6',
'email' => 'tester6@example.com',
'username' => 'tester6',
'originalIP' => '97.113.131.0',
'password' => Hash::make('password')
],
[
'uuid' => '89aa5222855542bebe7a7780248ef5f9',
'name' => 'Robot 7',
'email' => 'tester7@example.com',
'username' => 'tester7',
'originalIP' => '15.160.137.222',
'password' => Hash::make('password')
],
foreach ($users as $user) {
$user->assignRole('user');
];
$regularUsers = [
[
'uuid' => '20f69f47e72f463493b5b91d1c05452f',
'name' => 'User 1',
'email' => 'user1@example.com',
'username' => 'user1',
'originalIP' => '253.25.237.78',
'password' => Hash::make('password')
],
[
'uuid' => '5f900018241e4aaba7883f2d5c5c2357',
'name' => 'User 2',
'email' => 'user2@example.com',
'username' => 'user2',
'originalIP' => '82.92.156.176',
'password' => Hash::make('password')
],
[
'uuid' => 'ba9780c3270745c6840eaabe1bf8aa14',
'name' => 'User 3',
'email' => 'user3@example.com',
'username' => 'user3',
'originalIP' => '224.123.129.17',
'password' => Hash::make('password')
]
];
foreach ($regularUsers as $regularUser)
{
$user = User::create($regularUser);
Profile::create([
'profileShortBio' => 'Random data ' . rand(0,1000),
'profileAboutMe' => 'Random data ' . rand(0, 1000),
'socialLinks' => "[]", // empty json set, not an array
'avatarPreference' => 'gravatar',
'userID' => $user->id
]);
}
foreach($staffUsers as $staffUser)
{
$user = User::create($staffUser);
Profile::create([
'profileShortBio' => 'Random data ' . rand(0,1000),
'profileAboutMe' => 'Random data ' . rand(0, 1000),
'socialLinks' => "[]",
'avatarPreference' => 'gravatar',
'userID' => $user->id
]);
}
User::create([
'uuid' => '6102256abd284dd7b68e4c96ef313734',
'name' => 'Admin',
'email' => 'admin@example.com',
'username' => 'admin',
'originalIP' => '192.168.1.2',
'password' => Hash::make('password')
]);
foreach (User::all() as $user)
{
$user->assignRole('reviewer', 'user');
}
}
}

0
dependabot.yml Executable file → Normal file
View File

48
lang/en.json Executable file → Normal file
View File

@ -39,7 +39,6 @@
"Account": "Account",
"Account already suspended!": "Account already suspended!",
"Account deletion": "Account deletion",
"Account Management": "Account Management",
"Account management (admin)": "Account management (admin)",
"Account Security": "Account Security",
"Account Settings": "Account Settings",
@ -58,7 +57,6 @@
"Administration": "Administration",
"Administrative actions such as:": "Administrative actions such as:",
"Admin logs": "Admin logs",
"After you delete your profile, the following will happen:": "After you delete your profile, the following will happen:",
"A leave of absence allows you to step away from your duties for a period of time. To request one, simply fill the form to your left, and enter the reason for which you're stepping away. You will also need to specify when you will be unavailable, and when you predict to be back.": "A leave of absence allows you to step away from your duties for a period of time. To request one, simply fill the form to your left, and enter the reason for which you're stepping away. You will also need to specify when you will be unavailable, and when you predict to be back.",
"A leave of absence is a time period in which an employee takes personal time off, for a multitude of reasons. It's a prolonged, authorized absence form work and\/or other duties, communicated in advance, usually via letter or via an HR system.": "A leave of absence is a time period in which an employee takes personal time off, for a multitude of reasons. It's a prolonged, authorized absence form work and\/or other duties, communicated in advance, usually via letter or via an HR system.",
"All Applications": "All Applications",
@ -77,7 +75,6 @@
"Applicant IP Address": "Applicant IP Address",
"Applicant Name": "Applicant Name",
"Application": "Application",
"Application-specific commands": "Application-specific commands",
"Application access denied": "Application access denied",
"Application Date": "Application Date",
"Application deleted. Comments, appointments and responses have also been deleted.": "Application deleted. Comments, appointments and responses have also been deleted.",
@ -85,8 +82,6 @@
"Application form management tool": "Application form management tool",
"Application ID": "Application ID",
"Application Management": "Application Management",
"Application Override: Approve": "Application Override: Approve",
"Application Override: Decline": "Application Override: Decline",
"Application Process": "Application Process",
"Applications": "Applications",
"Applications Closed": "Applications Closed",
@ -107,7 +102,6 @@
"Are you sure you want to cancel this appointment? The user will be notified of this via email, and you will be able to reschedule.": "Are you sure you want to cancel this appointment? The user will be notified of this via email, and you will be able to reschedule.",
"Are you sure you want to delete this log file: :date ?": "Are you sure you want to delete this log file: :date ?",
"Are you sure you want to DELETE this log file: :date ?": "Are you sure you want to DELETE this log file: :date ?",
"Are you sure you want to delete your profile?": "Are you sure you want to delete your profile?",
"Are you sure you want to deny this application? Please keep in mind that this user will only be allowed to apply 30 days after their first application.": "Are you sure you want to deny this application? Please keep in mind that this user will only be allowed to apply 30 days after their first application.",
"Are you sure you want to leave the form builder? You have unsaved work.": "Are you sure you want to leave the form builder? You have unsaved work.",
"Are you sure you want to submit your application? Please review each of your answers carefully before doing so.": "Are you sure you want to submit your application? Please review each of your answers carefully before doing so.",
@ -146,7 +140,6 @@
"Choose a field type": "Choose a field type",
"Choose a game in the section below, if applicable.": "Choose a game in the section below, if applicable.",
"Choose an application": "Choose an application",
"Choose an application to override": "Choose an application to override",
"Choose a security policy": "Choose a security policy",
"Choose a user to invite": "Choose a user to invite",
"Choose File (max. :maxFileSizeSettingValue)": "Choose File (max. :maxFileSizeSettingValue)",
@ -176,14 +169,11 @@
"Context": "Context",
"Contextual information": "Contextual information",
"Continue": "Continue",
"Copyright &copy; :currentYear :authorName &mdash; <a href=\"licenseTextURL\">:licenseFullName<\/a>": "Copyright &copy; :currentYear :authorName &mdash; <a href=\"licenseTextURL\">:licenseFullName<\/a>",
"Count all votes now": "Count all votes now",
"Counts and processes all backlogged votes, for all applications.": "Counts and processes all backlogged votes, for all applications.",
"Create": "Create",
"Create a form first, then, create a vacancy.": "Create a form first, then, create a vacancy.",
"Create a profile": "Create a profile",
"Created at": "Created at",
"Create profile": "Create profile",
"Current Email Address": "Current Email Address",
"Current from (uneditable)": "Current from (uneditable)",
"Current Password": "Current Password",
@ -192,17 +182,14 @@
"Dashboard": "Dashboard",
"Date": "Date",
"Decision & Moderation Tools": "Decision & Moderation Tools",
"Decline application": "Decline application",
"Declined": "Declined",
"Declined by an admin or withdrawn by the requester": "Declined by an admin or withdrawn by the requester",
"Delete": "Delete",
"Delete Account": "Delete Account",
"Delete log file": "Delete log file",
"Delete Profile": "Delete Profile",
"Delete request": "Delete request",
"Deleting a user's account is an irreversible process. Historic and current applications, votes, and profile content, as well as any personally identifiable information will be immediately erased.": "Deleting a user's account is an irreversible process. Historic and current applications, votes, and profile content, as well as any personally identifiable information will be immediately erased.",
"Deleting your account is an irreversible process. The following data will be deleted (including personally identifiable data):": "Deleting your account is an irreversible process. The following data will be deleted (including personally identifiable data):",
"Deleting your profile is an irreversible operation. You will not be able to recover any previously entered information.": "Deleting your profile is an irreversible operation. You will not be able to recover any previously entered information.",
"Demo accounts:": "Demo accounts:",
"Demo mode disables some app features in order to preserve it's integrity for everyone who wants to test it. Here's what's disabled: ": "Demo mode disables some app features in order to preserve it's integrity for everyone who wants to test it. Here's what's disabled: ",
"Demo mode is active on this instance. The database is refreshed daily and some features are disabled for security reasons.": "Demo mode is active on this instance. The database is refreshed daily and some features are disabled for security reasons.",
@ -221,7 +208,6 @@
"Discord Handle": "Discord Handle",
"Discord Role ID": "Discord Role ID",
"Discord role ID": "Discord role ID",
"Discord tag: :discordTag": "Discord tag: :discordTag",
"Dispatch approval event": "Dispatch approval event",
"Dispatches an approval event for the selected application": "Dispatches an approval event for the selected application",
"Dispatches a rejection event for the selected application": "Dispatches a rejection event for the selected application",
@ -236,10 +222,8 @@
"Duration": "Duration",
"e.g. Spamming": "e.g. Spamming",
"Edit": "Edit",
"Edit account": "Edit account",
"Edit Account": "Edit Account",
"Edit form": "Edit form",
"Editing :formTitle...": "Editing :formTitle...",
"Editor": "Editor",
"Edit Team": "Edit Team",
"Edit vacancies": "Edit vacancies",
@ -252,7 +236,6 @@
"Ended": "Ended",
"entries": "entries",
"ENV": "ENV",
"Error": "Error",
"European?": "European?",
"Event dispatched; Candidate approval sequence initiated.": "Event dispatched; Candidate approval sequence initiated.",
"Event dispatched; Candidate rejection sequence initiated.": "Event dispatched; Candidate rejection sequence initiated.",
@ -305,20 +288,14 @@
"High (╯°□°)╯︵ ┻━┻": "High (╯°□°)╯︵ ┻━┻",
"Home": "Home",
"Hooray! 2FA is setup correctly for your account. A code will be asked each time you login.": "Hooray! 2FA is setup correctly for your account. A code will be asked each time you login.",
"Housekeeping": "Housekeeping",
"Housekeeping jobs usually run once every day, but if one of them has failed for some reason, you can manually run them here.": "Housekeeping jobs usually run once every day, but if one of them has failed for some reason, you can manually run them here.",
"How to request a leave of absence": "How to request a leave of absence",
"Human Resources": "Human Resources",
"I am 13 years of age or older and have read and agree with the <a href=\":communityGuidelinesUrlConfigValue\" target=\"_blank\">Community Guidelines<\/a>, <a href=\":privacyPolicyUrlConfigValue\" target=\"_blank\">Privacy Policy<\/a> and <a href=\":termsUrlConfigValue\" target=\"_blank\">Terms of Service<\/a> set forth.": "I am 13 years of age or older and have read and agree with the <a href=\":communityGuidelinesUrlConfigValue\" target=\"_blank\">Community Guidelines<\/a>, <a href=\":privacyPolicyUrlConfigValue\" target=\"_blank\">Privacy Policy<\/a> and <a href=\":termsUrlConfigValue\" target=\"_blank\">Terms of Service<\/a> set forth.",
"If an interview is scheduled, you'll need to open your application here and confirm the time, date, and location assigned for you.": "If an interview is scheduled, you'll need to open your application here and confirm the time, date, and location assigned for you.",
"If the setting \"Require Valid Game License\" is activated, editing this field may have unintended consequences. Proceed with caution.": "If the setting \"Require Valid Game License\" is activated, editing this field may have unintended consequences. Proceed with caution.",
"If this form has been updated, new fields and updated questions will not show up here!": "If this form has been updated, new fields and updated questions will not show up here!",
"If you'd like to learn more about our community, make sure to visit our <a href=\":mainWebsiteUrlConfigValue\" target=\"_blank\">main website<\/a>!": "If you'd like to learn more about our community, make sure to visit our <a href=\":mainWebsiteUrlConfigValue\" target=\"_blank\">main website<\/a>!",
"If you're seeing this message in error, please contact your system administrator.": "If you're seeing this message in error, please contact your system administrator.",
"If you change your mind and want your profile back, you will be able to create a new blank profile from your profile configuration page, restoring access to the features mentioned above.": "If you change your mind and want your profile back, you will be able to create a new blank profile from your profile configuration page, restoring access to the features mentioned above.",
"If you weren't present during this meeting, you can view the shared meeting notepad to help you make a decision.": "If you weren't present during this meeting, you can view the shared meeting notepad to help you make a decision.",
"Image Describing Access Denied": "Image Describing Access Denied",
"In a future update, the public Directory page might become inaccessible to users without a profile.": "In a future update, the public Directory page might become inaccessible to users without a profile.",
"Incorrect code. Please reopen the 2FA settings panel and try again.": "Incorrect code. Please reopen the 2FA settings panel and try again.",
"in days": "in days",
"Ineligible (:days) day(s) remaining": "Ineligible (:days) day(s) remaining",
@ -382,7 +359,6 @@
"Mojang Logo (Minecraft)": "Mojang Logo (Minecraft)",
"Mojang UUID (deprecated)": "Mojang UUID (deprecated)",
"Move to next stage": "Move to next stage",
"Multi-factor authentication is enabled for your account.": "Multi-factor authentication is enabled for your account.",
"Must contain numerical characters": "Must contain numerical characters",
"Must contain special characters": "Must contain special characters",
"Must contain upper and lower case characters": "Must contain upper and lower case characters",
@ -407,7 +383,6 @@
"No": "No",
"No details yet... Add some!": "No details yet... Add some!",
"None yet": "None yet",
"No profile found!": "No profile found!",
"No requests": "No requests",
"Not available": "Not available",
"Note! The database is wiped every six hours during demo mode.": "Note! The database is wiped every six hours during demo mode.",
@ -431,7 +406,6 @@
"Our services are currently undergoing routine maintenance. We are sorry for any inconveniences caused! We'll be back ASAP.": "Our services are currently undergoing routine maintenance. We are sorry for any inconveniences caused! We'll be back ASAP.",
"Outstanding": "Outstanding",
"Outstanding (Submitted)": "Outstanding (Submitted)",
"Override now": "Override now",
"Page :current of :last": "Page :current of :last",
"Password": "Password",
"Password change": "Password change",
@ -445,7 +419,6 @@
"permanently.": "permanently.",
"Permission group": "Permission group",
"Platform": "Platform",
"Please accept the Community Guidelines, Terms of Service and Privacy Policy to continue.": "Please accept the Community Guidelines, Terms of Service and Privacy Policy to continue.",
"Please allow up to three days for your application to be processed. Your application will be reviewed by every team member, and will move up in stages.": "Please allow up to three days for your application to be processed. Your application will be reviewed by every team member, and will move up in stages.",
"Please authenticate": "Please authenticate",
"Please change update your password now. You won't be able to use the site until you do this.": "Please change update your password now. You won't be able to use the site until you do this.",
@ -457,7 +430,6 @@
"Please confirm that you want to suspend this account. You'll need to add a reason and expiration date to confirm this.": "Please confirm that you want to suspend this account. You'll need to add a reason and expiration date to confirm this.",
"Please contact your administrator if you believe this was in error.": "Please contact your administrator if you believe this was in error.",
"Please do not tamper with the URLs. To report a bug, please contact an administrator.": "Please do not tamper with the URLs. To report a bug, please contact an administrator.",
"Please enter a valid (and Premium) Minecraft username! We do not support cracked users.": "Please enter a valid (and Premium) Minecraft username! We do not support cracked users.",
"Please fill out the form below. Keep all answers concise and complete. Please keep in mind that the age requirement is at least :ageUpperLimitSettingValue years old.": "Please fill out the form below. Keep all answers concise and complete. Please keep in mind that the age requirement is at least :ageUpperLimitSettingValue years old.",
"Please note: Applications CANNOT be modified once they're submitted!": "Please note: Applications CANNOT be modified once they're submitted!",
"Please type the above text": "Please type the above text",
@ -474,7 +446,6 @@
"Private": "Private",
"Privilege editing": "Privilege editing",
"Profile": "Profile",
"Profile deleted successfully.": "Profile deleted successfully.",
"Profile updated.": "Profile updated.",
"Public note": "Public note",
"Public Team": "Public Team",
@ -508,9 +479,6 @@
"Reviewer": "Reviewer",
"Reviewing admin": "Reviewing admin",
"Roles": "Roles",
"Run task: end expired absence requests": "Run task: end expired absence requests",
"Run task: lift expired suspensions": "Run task: lift expired suspensions",
"Run task: process pending votes": "Run task: process pending votes",
"Same as Medium, but: ": "Same as Medium, but: ",
"Save & Quit": "Save & Quit",
"Save and Close": "Save and Close",
@ -542,7 +510,6 @@
"Sign-in": "Sign-in",
"Sign in": "Sign in",
"Sign in here": "Sign in here",
"Sign in to your account": "Sign in to your account",
"Sign out": "Sign out",
"Sign up": "Sign up",
"Sign up for an account": "Sign up for an account",
@ -567,7 +534,6 @@
"Submit for approval": "Submit for approval",
"Submitted": "Submitted",
"Submitted at": "Submitted at",
"Success": "Success",
"Successfully logged out other devices. Remember to change your password if you think you've been compromised.": "Successfully logged out other devices. Remember to change your password if you think you've been compromised.",
"Supported apps you can install:": "Supported apps you can install:",
"Suspend": "Suspend",
@ -610,7 +576,6 @@
"The password is \":demoPassword\" for all accounts.": "The password is \":demoPassword\" for all accounts.",
"The permission group from your server\/network's permissions manager. Compatible with Luckperms and PEX (This feature is deprecated and will be removed on a future version).": "The permission group from your server\/network's permissions manager. Compatible with Luckperms and PEX (This feature is deprecated and will be removed on a future version).",
"The position you're trying to update doesn't exist!": "The position you're trying to update doesn't exist!",
"There are many benefits to creating a profile! For instance, you'll be able to set a profile picture, as well as sharing your social media and anything else you'd like. Creating a profile is instant and you can begin configuring it right away. If you later change your mind, you may also delete your profile at any time.": "There are many benefits to creating a profile! For instance, you'll be able to set a profile picture, as well as sharing your social media and anything else you'd like. Creating a profile is instant and you can begin configuring it right away. If you later change your mind, you may also delete your profile at any time.",
"There are no applications here": "There are no applications here",
"There are no comments here! Comments are only visible to staff members. Be the first to share your input! Commenting may help with decision-making when time comes to vote for an application.": "There are no comments here! Comments are only visible to staff members. Be the first to share your input! Commenting may help with decision-making when time comes to vote for an application.",
"There are no notes yet. Add some!": "There are no notes yet. Add some!",
@ -623,7 +588,6 @@
"There was an error deleting the file: :msg": "There was an error deleting the file: :msg",
"There were :usersCount user(s) matching your search.": "There were :usersCount user(s) matching your search.",
"There were no expired suspensions (or no suspensions at all) to purge.": "There were no expired suspensions (or no suspensions at all) to purge.",
"These tools were intended for development purposes. Unless you know exactly what each command does, we recommend you don't use any of them.": "These tools were intended for development purposes. Unless you know exactly what each command does, we recommend you don't use any of them.",
"The vacancies you select determine what applications your team members see. All applications under the vacancies you choose will be displayed.": "The vacancies you select determine what applications your team members see. All applications under the vacancies you choose will be displayed.",
"This account has been suspended :suspensionTypeValue": "This account has been suspended :suspensionTypeValue",
"This account isn't suspended!": "This account isn't suspended!",
@ -635,12 +599,10 @@
"This is how your form looks like to applicants.": "This is how your form looks like to applicants.",
"This is the name team members will see.": "This is the name team members will see.",
"This opening does not have any details yet.": "This opening does not have any details yet.",
"This panel allows you to override statuses for specific applications. Overriding them will trigger the correct events as well. Note that this system entirely ignores the voting system because these statuses ignore all other logic.": "This panel allows you to override statuses for specific applications. Overriding them will trigger the correct events as well. Note that this system entirely ignores the voting system because these statuses ignore all other logic.",
"THIS PROCESS IS IRREVERSIBLE AND IMMEDIATE": "THIS PROCESS IS IRREVERSIBLE AND IMMEDIATE",
"This query didn't return any results.": "This query didn't return any results.",
"This request reached its predicted end date": "This request reached its predicted end date",
"This setting controls whether people can join the team freely.": "This setting controls whether people can join the team freely.",
"This user has been suspended.": "This user has been suspended.",
"This user has been suspended by the admins. Admins suspend accounts for a variety of reasons, including spam.": "This user has been suspended by the admins. Admins suspend accounts for a variety of reasons, including spam.",
"This user will receive an email notification asking them to join your team.": "This user will receive an email notification asking them to join your team.",
"This vacancy does not have any details yet.": "This vacancy does not have any details yet.",
@ -676,7 +638,6 @@
"User invited successfully!": "User invited successfully!",
"User profile picture": "User profile picture",
"Users": "Users",
"Users \/ Accounts \/ Admin": "Users \/ Accounts \/ Admin",
"Users will be locked out after this time period if they fail to enable 2FA. Leave empty to disable.": "Users will be locked out after this time period if they fail to enable 2FA. Leave empty to disable.",
"User terminated successfully.": "User terminated successfully.",
"User updated successfully!": "User updated successfully!",
@ -721,7 +682,6 @@
"Will you be available to assist occasionally during your absence?": "Will you be available to assist occasionally during your absence?",
"Wrong confirmation text! Try again.": "Wrong confirmation text! Try again.",
"Yes": "Yes",
"Yes, delete my profile": "Yes, delete my profile",
"You're looking at all applications ever received": "You're looking at all applications ever received",
"You're not authorized to access this page.": "You're not authorized to access this page.",
"You've been redirected here because your password has expired. All users must change their password every :numDaysChangePw days. This is put in place to make sure user accounts remain secure.": "You've been redirected here because your password has expired. All users must change their password every :numDaysChangePw days. This is put in place to make sure user accounts remain secure.",
@ -743,10 +703,8 @@
"You may only have one active request at the same time, which will have to be either approved or declined by the admins. Please keep in mind that you will not be able to delete any of your requests.": "You may only have one active request at the same time, which will have to be either approved or declined by the admins. Please keep in mind that you will not be able to delete any of your requests.",
"You may vote on as many applications as needed; However, you can only vote once per application.": "You may vote on as many applications as needed; However, you can only vote once per application.",
"You need to be authenticated to access this page. Believe this is a mistake? Contact us and let us know! ": "You need to be authenticated to access this page. Believe this is a mistake? Contact us and let us know! ",
"Your account currently has no profile on file. This means that your account will not be visibile in the public profile Directory, and you will not be able to access your profile until you create one.": "Your account currently has no profile on file. This means that your account will not be visibile in the public profile Directory, and you will not be able to access your profile until you create one.",
"Your account is not permitted to submit another application. Please wait :applicationThrottleLimitSettingValue more days before trying to submit an application.": "Your account is not permitted to submit another application. Please wait :applicationThrottleLimitSettingValue more days before trying to submit an application.",
"Your account will be locked during this process.": "Your account will be locked during this process.",
"Your account will no longer be listed on the public Directory page.": "Your account will no longer be listed on the public Directory page.",
"Your comments": "Your comments",
"Your current password security policy is set to <b>off<\/b>. This allows users to choose potentially unsafe passwords. We strongly recommend you update this value to <b>Medium<\/b>.": "Your current password security policy is set to <b>off<\/b>. This allows users to choose potentially unsafe passwords. We strongly recommend you update this value to <b>Medium<\/b>.",
"Your current session: logged in from :ipAddress": "Your current session: logged in from :ipAddress",
@ -755,16 +713,12 @@
"Your message": "Your message",
"Your password has expired": "Your password has expired",
"Your previous applications": "Your previous applications",
"Your profile configuration page will become inaccessible with an error message.": "Your profile configuration page will become inaccessible with an error message.",
"Your profile data and preferences": "Your profile data and preferences",
"Your profile has been created.": "Your profile has been created.",
"Your profile page will become inaccesible to everyone with an error message.": "Your profile page will become inaccesible to everyone with an error message.",
"Your request": "Your request",
"Your roles": "Your roles",
"Your search term did not return any results.": "Your search term did not return any results.",
"Your upcoming interviews": "Your upcoming interviews",
"Your vote has been counted!": "Your vote has been counted!",
"You will also need to agree to the terms of a LOA. Additionally, you may also specify whether you'll be available to chat occasionally during your absence, but not perform your full duties.": "You will also need to agree to the terms of a LOA. Additionally, you may also specify whether you'll be available to chat occasionally during your absence, but not perform your full duties.",
"Zoom": "Zoom",
"{1} There is :count open position!|[2,*] There are :count open positions!": "{1} There is :count open position!|[2,*] There are :count open positions!"
"Zoom": "Zoom"
}

0
lang/en/adminlte::adminlte.php Executable file → Normal file
View File

0
lang/en/log-viewer::general.php Executable file → Normal file
View File

28
lang/en/messages.php Normal file
View File

@ -0,0 +1,28 @@
<?php
return [
'2fa_txt' => '',
'choose_app' => '',
'dashboard' => '',
'details_m_title' => '',
'dispatch_event' => '',
'edt_action' => '',
'global_error' => '',
'global_success' => '',
'modal_close' => '',
'open' => '',
'ppolicy' => '',
'profile' => [
'discord_tag' => '',
'edituser' => '',
'edituser_consequence' => '',
],
'reusable' => [
'confirm' => '',
],
'signin_cta' => '',
'terms' => '',
'view_app' => [
'denyapp' => '',
],
];

0
lang/en/pagination.php Executable file → Normal file
View File

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