diff --git a/.vscode/launch.json b/.vscode/launch.json index 612eaac..759926c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,11 +4,15 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { "name": "Listen for XDebug", "type": "php", "request": "launch", - "port": 9000 + "port": 9000, + "ignore": [ + "**/vendor/**/*.php" + ] }, { "name": "Launch currently open script", diff --git a/app/Http/Controllers/TeamController.php b/app/Http/Controllers/TeamController.php index 12211de..6743f20 100644 --- a/app/Http/Controllers/TeamController.php +++ b/app/Http/Controllers/TeamController.php @@ -8,9 +8,11 @@ use App\Http\Requests\SendInviteRequest; use App\Mail\InviteToTeam; use App\Team; use App\User; +use App\Vacancy; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Mail; +use Mpociot\Teamwork\Exceptions\UserNotInTeamException; use Mpociot\Teamwork\Facades\Teamwork; use Mpociot\Teamwork\TeamInvite; @@ -23,8 +25,10 @@ class TeamController extends Controller */ public function index() { + $teams = Team::with('users.roles')->get(); + return view('dashboard.teams.teams') - ->with('teams', Team::all()); + ->with('teams', $teams); } /** @@ -45,11 +49,13 @@ class TeamController extends Controller */ public function store(NewTeamRequest $request) { - Team::create([ + $team = Team::create([ 'name' => $request->teamName, 'owner_id' => Auth::user()->id ]); + Auth::user()->teams()->attach($team->id); + $request->session()->flash('success', 'Team successfully created.'); return redirect()->back(); } @@ -75,7 +81,8 @@ class TeamController extends Controller { return view('dashboard.teams.edit-team') ->with('team', $team) - ->with('users', User::all()); + ->with('users', User::all()) + ->with('vacancies', Vacancy::all()); } /** @@ -186,4 +193,20 @@ class TeamController extends Controller return redirect()->to(route('teams.index')); } + + public function switchTeam(Request $request, Team $team) + { + try + { + Auth::user()->switchTeam($team); + + $request->session()->flash('success', 'Switched teams! Your team dashboard will now use this context.'); + } + catch(UserNotInTeamException $ex) + { + $request->session()->flash('error', 'You can\'t switch to a team you don\'t belong to.'); + } + + return redirect()->back(); + } } diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index cb494b6..64ac48f 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -23,11 +23,13 @@ use App\Notifications\EmailChanged; use App\Notifications\ChangedPassword; use Spatie\Permission\Models\Role; +use App\Traits\ReceivesAccountTokens; use Google2FA; class UserController extends Controller { + use ReceivesAccountTokens; public function showStaffMembers() { @@ -220,7 +222,7 @@ class UserController extends Controller if ($request->confirmPrompt == 'DELETE ACCOUNT') { - $user->delete(); + $user->forceDelete(); $request->session()->flash('success','User deleted successfully. PII has been erased.'); } else @@ -232,6 +234,7 @@ class UserController extends Controller return redirect()->route('registeredPlayerList'); } + public function update(UpdateUserRequest $request, User $user) { @@ -356,4 +359,6 @@ class UserController extends Controller //TODO: Dispatch event return redirect()->back(); } + + } diff --git a/app/Http/Requests/UserDeleteRequest.php b/app/Http/Requests/UserDeleteRequest.php new file mode 100644 index 0000000..5135685 --- /dev/null +++ b/app/Http/Requests/UserDeleteRequest.php @@ -0,0 +1,39 @@ +has2FA()) + { + return [ + 'currentPassword' => 'required|password:web', + 'otp' => 'required|integer|max:6' + ]; + } + + return [ + 'currentPassword' => 'required|password:web' + ]; + } +} diff --git a/app/Mail/UserAccountDeleteConfirmation.php b/app/Mail/UserAccountDeleteConfirmation.php new file mode 100644 index 0000000..3ad83b2 --- /dev/null +++ b/app/Mail/UserAccountDeleteConfirmation.php @@ -0,0 +1,56 @@ +deleteToken = $tokens['delete']; + $this->cancelToken = $tokens['cancel']; + + $this->originalIP = $originalIP; + $this->name = $user->name; + $this->userID = $user->id; + } + + /** + * Build the message. + * + * @return $this + */ + public function build() + { + return $this->view('mail.deleted-account'); + } +} diff --git a/app/Observers/UserObserver.php b/app/Observers/UserObserver.php index 38f7660..eab928b 100644 --- a/app/Observers/UserObserver.php +++ b/app/Observers/UserObserver.php @@ -8,6 +8,12 @@ use Illuminate\Support\Facades\Log; class UserObserver { + + public function __construct() + { + Log::debug('User observer has been initialised and ready for use!'); + } + /** * Handle the user "created" event. * @@ -39,20 +45,28 @@ class UserObserver public function deleting(User $user) { - $user->profile()->delete(); - Log::debug('Referential integrity cleanup: Deleted profile!'); - $applications = $user->applications; - - if (!$applications->isEmpty()) + if ($user->isForceDeleting()) { - Log::debug('RIC: Now trying to delete applications and responses...'); - foreach($applications as $application) + $user->profile->delete(); + Log::debug('Referential integrity cleanup: Deleted profile!'); + $applications = $user->applications; + + if (!$applications->isEmpty()) { - // code moved to Application observer, where it gets rid of attached elements individually - Log::debug('RIC: Deleting application ' . $application->id); - $application->delete(); - + Log::debug('RIC: Now trying to delete applications and responses...'); + foreach($applications as $application) + { + // code moved to Application observer, where it gets rid of attached elements individually + Log::debug('RIC: Deleting application ' . $application->id); + $application->delete(); + + } } + + } + else + { + Log::debug('RIC: Not cleaning up soft deleted models!'); } Log::debug('RIC: Cleanup done!'); @@ -66,7 +80,6 @@ class UserObserver */ public function deleted(User $user) { - // } /** @@ -88,6 +101,8 @@ class UserObserver */ public function forceDeleted(User $user) { - // + Log::info('Model has been force deleted', [ + 'modelID' => $user->id + ]); } } diff --git a/app/Services/VacancyApplicationService.php b/app/Services/VacancyApplicationService.php new file mode 100644 index 0000000..94ca873 --- /dev/null +++ b/app/Services/VacancyApplicationService.php @@ -0,0 +1,35 @@ +response->vacancy->id == $model->id) + { + $applications->push($application); + } + } + + return $applications; + + } + + +} \ No newline at end of file diff --git a/app/Team.php b/app/Team.php index d1fbb67..1492b2e 100644 --- a/app/Team.php +++ b/app/Team.php @@ -13,4 +13,10 @@ class Team extends TeamworkTeam 'description', 'openJoin' ]; + + + public function vacancies() + { + return $this->belongsToMany('App\Vacancy', 'team_has_vacancy'); + } } diff --git a/app/Traits/HandlesAccountTokens.php b/app/Traits/HandlesAccountTokens.php new file mode 100644 index 0000000..ce6ee34 --- /dev/null +++ b/app/Traits/HandlesAccountTokens.php @@ -0,0 +1,51 @@ + Hash::make($deleteToken), + 'cancel' => Hash::make($cancelToken) + + ]; + + $this->account_tokens = json_encode($tokens); + $this->save(); + + return [ + + 'delete' => $deleteToken, + 'cancel' => $cancelToken + ]; + + } + + public function verifyAccountToken(string $token, string $type): bool + { + $tokens = json_decode($this->account_tokens); + + if ($type == 'deleteToken') + { + return Hash::check($token, $tokens->delete); + } + elseif ($type == 'cancelToken') + { + return Hash::check($token, $tokens->cancel); + } + + return false; + } + + +} \ No newline at end of file diff --git a/app/Traits/ReceivesAccountTokens.php b/app/Traits/ReceivesAccountTokens.php new file mode 100644 index 0000000..798c3ff --- /dev/null +++ b/app/Traits/ReceivesAccountTokens.php @@ -0,0 +1,84 @@ +id); + $tokens = $user->generateAccountTokens(); + + Mail::to($user)->send(new UserAccountDeleteConfirmation($user, $tokens, $request->ip())); + + $user->delete(); + Auth::logout(); + + $request->session()->flash('success', 'Please check your email to finish deleting your account.'); + return redirect()->to('/'); + } + + + public function processDeleteConfirmation(Request $request, $ID, $action, $token) + { + // We can't rely on Laravel's route model injection, because it'll ignore soft-deleted models, + // so we have to use a special scope to find them ourselves. + $user = User::withTrashed()->findOrFail($ID); + $email = $user->email; + + switch($action) + { + case 'confirm': + + if ($user->verifyAccountToken($token, 'deleteToken')) + { + Log::info('SECURITY: User deleted account!', [ + + 'confirmDeleteToken' => $token, + 'ipAddress' => $request->ip(), + 'email' => $user->email + + ]); + + + + $user->forceDelete(); + + $request->session()->flash('success', 'Account permanently deleted. Thank you for using our service.'); + return redirect()->to('/'); + } + + break; + + case 'cancel': + + if ($user->verifyAccountToken($token, 'cancelToken')) + { + $user->restore(); + $request->session()->flash('success', 'Account deletion cancelled! You may now login.'); + + return redirect()->to(route('login')); + } + + break; + + default: + + abort(404, 'The page you were trying to access may not exist or may be expired.'); + } + + } +} \ No newline at end of file diff --git a/app/User.php b/app/User.php index e4a0ce8..cacb175 100644 --- a/app/User.php +++ b/app/User.php @@ -2,15 +2,18 @@ namespace App; +use App\Traits\HandlesAccountTokens; use Illuminate\Contracts\Auth\MustVerifyEmail; +use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; +use Illuminate\Support\Facades\Hash; use Mpociot\Teamwork\Traits\UserHasTeams; use Spatie\Permission\Traits\HasRoles; class User extends Authenticatable implements MustVerifyEmail { - use UserHasTeams, Notifiable, HasRoles; + use UserHasTeams, Notifiable, HasRoles, SoftDeletes, HandlesAccountTokens; /** * The attributes that are mass assignable. @@ -74,7 +77,6 @@ class User extends Authenticatable implements MustVerifyEmail - public function isStaffMember() { return $this->hasAnyRole('reviewer', 'admin', 'hiringManager'); @@ -85,8 +87,7 @@ class User extends Authenticatable implements MustVerifyEmail return !is_null($this->twofa_secret); } - - + public function routeNotificationForSlack($notification) { return config('slack.webhook.integrationURL'); diff --git a/app/Vacancy.php b/app/Vacancy.php index f740243..3308acf 100644 --- a/app/Vacancy.php +++ b/app/Vacancy.php @@ -13,7 +13,7 @@ use GrahamCampbell\Markdown\Facades\Markdown; class Vacancy extends Model { - use UsedByTeams; + //use UsedByTeams; public $fillable = [ @@ -25,7 +25,8 @@ class Vacancy extends Model 'vacancyFormID', 'vacancyCount', 'vacancyStatus', - 'vacancySlug' + 'vacancySlug', + 'team_id' ]; @@ -49,6 +50,12 @@ class Vacancy extends Model } + public function teams() + { + return $this->belongsToMany('App\Team', 'team_has_vacancy'); + } + + public function forms() { return $this->belongsTo('App\Form', 'vacancyFormID', 'id'); diff --git a/config/adminlte.php b/config/adminlte.php index ef34abb..b0555f4 100644 --- a/config/adminlte.php +++ b/config/adminlte.php @@ -572,6 +572,22 @@ return [ 'location' => 'https://gitcdn.github.io/bootstrap-toggle/2.2.2/js/bootstrap-toggle.min.js' ] ] + ], + [ + 'name' => 'BootstrapMultiselectDropdown', + 'active' => true, + 'files' => [ + [ + 'type' => 'js', + 'asset' => 'false', + 'location' => 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap-multiselect/0.9.15/js/bootstrap-multiselect.min.js' + ], + [ + 'type' => 'css', + 'asset' => false, + 'location' => 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap-multiselect/0.9.15/css/bootstrap-multiselect.css' + ] + ] ] ], ]; diff --git a/database/migrations/2020_10_04_124115_vacancy_nullable_team_id.php b/database/migrations/2020_10_04_124115_vacancy_nullable_team_id.php new file mode 100644 index 0000000..569a9f5 --- /dev/null +++ b/database/migrations/2020_10_04_124115_vacancy_nullable_team_id.php @@ -0,0 +1,38 @@ +dropForeign('vacancies_ownerteamid_foreign'); + $table->dropColumn('ownerTeamID'); + $table->bigInteger('team_id')->nullable()->unsigned(); + + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('vacancies', function(Blueprint $table){ + + $table->dropColumn('team_id'); + + }); + } +} diff --git a/database/migrations/2020_10_04_163715_team_has_vacancy.php b/database/migrations/2020_10_04_163715_team_has_vacancy.php new file mode 100644 index 0000000..f71182b --- /dev/null +++ b/database/migrations/2020_10_04_163715_team_has_vacancy.php @@ -0,0 +1,43 @@ +id(); + $table->integer('team_id')->unsigned(); + $table->bigInteger('vacancy_id')->unsigned(); + $table->timestamps(); + + $table->foreign('team_id') + ->references('id') + ->on(config('teamwork.teams_table')); + + $table->foreign('vacancy_id') + ->references('id') + ->on('vacancies'); + + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + } +} diff --git a/database/migrations/2020_09_10_181914_add_team_assoc_to_vacancies.php b/database/migrations/2020_10_07_095240_add_account_tokens_to_user.php similarity index 50% rename from database/migrations/2020_09_10_181914_add_team_assoc_to_vacancies.php rename to database/migrations/2020_10_07_095240_add_account_tokens_to_user.php index 607cd61..9cd3ffd 100644 --- a/database/migrations/2020_09_10_181914_add_team_assoc_to_vacancies.php +++ b/database/migrations/2020_10_07_095240_add_account_tokens_to_user.php @@ -4,7 +4,7 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -class AddTeamAssocToVacancies extends Migration +class AddAccountTokensToUser extends Migration { /** * Run the migrations. @@ -13,13 +13,8 @@ class AddTeamAssocToVacancies extends Migration */ public function up() { - Schema::table('vacancies', function (Blueprint $table) { - - $table->integer('ownerTeamID')->unsigned()->after('vacancyFormID'); - - $table->foreign('ownerTeamID') - ->references('id') - ->on('teams'); + Schema::table('users', function (Blueprint $table) { + $table->string('account_tokens')->after('password')->nullable(); }); } @@ -30,8 +25,8 @@ class AddTeamAssocToVacancies extends Migration */ public function down() { - Schema::table('vacancies', function (Blueprint $table) { - // + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('account_tokens'); }); } } diff --git a/database/migrations/2020_10_03_153118_add_team_i_d_to_vacancy.php b/database/migrations/2020_10_07_100540_add_soft_deletes_to_users.php similarity index 58% rename from database/migrations/2020_10_03_153118_add_team_i_d_to_vacancy.php rename to database/migrations/2020_10_07_100540_add_soft_deletes_to_users.php index 8ec91ef..cce9928 100644 --- a/database/migrations/2020_10_03_153118_add_team_i_d_to_vacancy.php +++ b/database/migrations/2020_10_07_100540_add_soft_deletes_to_users.php @@ -4,7 +4,7 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -class AddTeamIDToVacancy extends Migration +class AddSoftDeletesToUsers extends Migration { /** * Run the migrations. @@ -13,8 +13,8 @@ class AddTeamIDToVacancy extends Migration */ public function up() { - Schema::table('vacancy', function (Blueprint $table) { - // + Schema::table('users', function (Blueprint $table) { + $table->softDeletes()->after('account_tokens'); }); } @@ -25,8 +25,8 @@ class AddTeamIDToVacancy extends Migration */ public function down() { - Schema::table('vacancy', function (Blueprint $table) { - // + Schema::table('users', function (Blueprint $table) { + $table->dropSoftDeletes(); }); } } diff --git a/public/js/app.js b/public/js/app.js index e95f7a4..129c3c6 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -77279,6 +77279,7 @@ $("#submitComment").on('click', function () { $("#newComment").submit(); }); $("#jointype").bootstrapToggle(); +$("#associatedVacancies").multiselect(); /***/ }), @@ -77408,10 +77409,10 @@ window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; /*! no static exports found */ /***/ (function(module, exports, __webpack_require__) { -__webpack_require__(/*! /home/miguel456/staffmanager/resources/js/app.js */"./resources/js/app.js"); -__webpack_require__(/*! /home/miguel456/staffmanager/resources/js/application_charts.js */"./resources/js/application_charts.js"); -__webpack_require__(/*! /home/miguel456/staffmanager/resources/js/calendar.js */"./resources/js/calendar.js"); -module.exports = __webpack_require__(/*! /home/miguel456/staffmanager/resources/sass/app.scss */"./resources/sass/app.scss"); +__webpack_require__(/*! /home/miguel456/Desktop/Projects/staffmanager/resources/js/app.js */"./resources/js/app.js"); +__webpack_require__(/*! /home/miguel456/Desktop/Projects/staffmanager/resources/js/application_charts.js */"./resources/js/application_charts.js"); +__webpack_require__(/*! /home/miguel456/Desktop/Projects/staffmanager/resources/js/calendar.js */"./resources/js/calendar.js"); +module.exports = __webpack_require__(/*! /home/miguel456/Desktop/Projects/staffmanager/resources/sass/app.scss */"./resources/sass/app.scss"); /***/ }) diff --git a/public/js/globaltooltip.js b/public/js/globaltooltip.js index 3ffa14a..5f4b1ab 100644 --- a/public/js/globaltooltip.js +++ b/public/js/globaltooltip.js @@ -1,4 +1,5 @@ $(document).ready(function() { $('input[rel="txtTooltip"]').tooltip(); $('span[rel="spanTxtTooltip"]').tooltip(); // Also allow span tooltips + $('button[rel="buttonTxtTooltip"]').tooltip(); // button tooltip }); diff --git a/public/js/team-editor.js b/public/js/team-editor.js new file mode 100644 index 0000000..c5c3e15 --- /dev/null +++ b/public/js/team-editor.js @@ -0,0 +1,6 @@ +$(function() { + $('#assocVacancies').multiselect({ + disableIfEmpty: true, + nonSelectedText: 'Choose vacancies...' + }); +}); \ No newline at end of file diff --git a/resources/js/app.js b/resources/js/app.js index b4bbaa0..08bd18f 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -45,3 +45,4 @@ $("#submitComment").on('click', function(){ }); $("#jointype").bootstrapToggle(); + diff --git a/resources/views/dashboard/teams/edit-team.blade.php b/resources/views/dashboard/teams/edit-team.blade.php index 8d57e82..169f4a0 100644 --- a/resources/views/dashboard/teams/edit-team.blade.php +++ b/resources/views/dashboard/teams/edit-team.blade.php @@ -9,6 +9,7 @@ @section('js') + @stop @@ -39,6 +40,69 @@ @endif + + +

Team members and pending invites will appear here.

+ + @if (!$team->users->isEmpty()) + + + + + + + + + + + + + + + @foreach ($team->users as $teammate) + + + + + + + + + + @endforeach + + + +
#NameRolesStatusActions
{{ $teammate->id }}{{ $teammate->name }} + @foreach ($teammate->roles as $teammate_role) + + {{ $teammate_role->name }} + + @endforeach + + @if ($teammate->isOwnerOfTeam($team)) + Team Owner + @else + Team Member + @endif + + +
+ @else + +
+ + There don't seem to be any teammates here! +

Start inviting some people and grow your team.

+ +
+ + @endif + + + +
+
@@ -91,8 +155,42 @@
+ +
+ + + + +
+ +
+ +
+

Team Vacancies

+
+ +
+ + The vacancies you select determine what applications your team members see. All applications under the vacancies you choose will be displayed. + +
+ + + +
+
diff --git a/resources/views/dashboard/teams/teams.blade.php b/resources/views/dashboard/teams/teams.blade.php index be9e071..a858239 100644 --- a/resources/views/dashboard/teams/teams.blade.php +++ b/resources/views/dashboard/teams/teams.blade.php @@ -66,7 +66,16 @@
-

{{ __('messages.teams.m_teams_page') }}

+ +
+ +
+ +

{{ __('messages.teams.m_teams_page') }} {{ (Auth::user()->currentTeam) ? Auth::user()->currentTeam->name : 'Select a team' }}

+ + +
+
@@ -96,7 +105,7 @@ {{ $team->name }} - + @@ -122,6 +131,7 @@ diff --git a/resources/views/dashboard/user/profile/useraccount.blade.php b/resources/views/dashboard/user/profile/useraccount.blade.php index e223a96..fde820d 100644 --- a/resources/views/dashboard/user/profile/useraccount.blade.php +++ b/resources/views/dashboard/user/profile/useraccount.blade.php @@ -20,8 +20,61 @@ @section('content') + + +

Deleting your account is an irreversible process. The following data will be deleted (including personally identifiable data):

+ +

What is not deleted:

+ + + +
+ + @csrf + @method('PATCH') + +
+ + +

For your security, your password is always required for sensitive operations. Forgot your password?

+
+ + @if (Auth::user()->has2FA()) +
+ + + +

You cannot recover lost 2FA secrets.

+ +
+ @endif + +
+ + + + + + + +
+ @if (!Auth::user()->has2FA()) + @@ -170,6 +223,9 @@ +
@@ -250,6 +306,15 @@ + +
+
Danger Zone
+

Careful! Actions in these tab might result in irreversible loss of data.

+ + + +
+ diff --git a/resources/views/mail/deleted-account.blade.php b/resources/views/mail/deleted-account.blade.php new file mode 100644 index 0000000..aea3c12 --- /dev/null +++ b/resources/views/mail/deleted-account.blade.php @@ -0,0 +1,167 @@ + + + + + + Your account has been deleted + + + + + + + + + + +
  +
+ + + + + + + + + + +
+ + + + +
+

Hi {{ $name }},

+

Someone (hopefully you) has requested that your account at {{ config('app.name') }} be deleted.

+

As a security measure, an email is always sent out to make sure you really want to delete your account.

+

This is the IP address the request was made from: {{ $originalIP }}.

+

If you don't do anything, your account will automatically be permanently deleted in 30 days, and will remain unaccessible for that time period.

+

Click one of the buttons below to make a decision.

+ + + + + + + + +
+ + + + + + + +
Delete Account Cancel Deletion
+
+

Thank you!

+
+
+ + + + + + +
+
 
+ + \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index 00f9ea5..397eac1 100644 --- a/routes/web.php +++ b/routes/web.php @@ -30,6 +30,9 @@ Route::group(['prefix' => LaravelLocalization::setLocale(), 'middleware' => [ 'l Route::post('/form/contact', 'ContactController@create') ->name('sendSubmission'); + Route::get('/accounts/danger-zone/{ID}/{action}/{token}', 'UserController@processDeleteConfirmation') + ->name('processDeleteConfirmation'); + Route::group(['middleware' => ['auth', 'forcelogout', '2fa', 'verified']], function(){ @@ -41,8 +44,12 @@ Route::group(['prefix' => LaravelLocalization::setLocale(), 'middleware' => [ 'l ->name('directory'); - Route::post('teams/{team}/invites/send', 'TeamController@invite') - ->name('sendInvite'); + + Route::post('teams/{team}/invites/send', 'TeamController@invite') + ->name('sendInvite'); + + Route::get('teams/{team}/switch', 'TeamController@switchTeam') + ->name('switchTeam'); Route::get('teams/invites/{action}/{token}', 'TeamController@processInviteAction') ->name('processInvite'); @@ -51,6 +58,8 @@ Route::group(['prefix' => LaravelLocalization::setLocale(), 'middleware' => [ 'l Route::resource('teams', 'TeamController'); + + Route::group(['prefix' => '/applications'], function (){ Route::get('/my-applications', 'ApplicationController@showUserApps') @@ -152,6 +161,9 @@ Route::group(['prefix' => LaravelLocalization::setLocale(), 'middleware' => [ 'l Route::patch('/settings/account/twofa/disable', 'UserController@remove2FASecret') ->name('disable2FA'); + Route::patch('/settings/account/dg/delete', 'UserController@userDelete') + ->name('userDelete'); + });