diff --git a/src/app/Http/Controllers/API/V4/Admin/UsersController.php b/src/app/Http/Controllers/API/V4/Admin/UsersController.php --- a/src/app/Http/Controllers/API/V4/Admin/UsersController.php +++ b/src/app/Http/Controllers/API/V4/Admin/UsersController.php @@ -69,6 +69,54 @@ } /** + * Suspend the user + * + * @param \Illuminate\Http\Request $request The API request. + * @params string $id User identifier + * + * @return \Illuminate\Http\JsonResponse The response + */ + public function suspend(Request $request, $id) + { + $user = User::find($id); + + if (empty($user)) { + return $this->errorResponse(404); + } + + $user->suspend(); + + return response()->json([ + 'status' => 'success', + 'message' => __('app.user-suspend-success'), + ]); + } + + /** + * Un-Suspend the user + * + * @param \Illuminate\Http\Request $request The API request. + * @params string $id User identifier + * + * @return \Illuminate\Http\JsonResponse The response + */ + public function unsuspend(Request $request, $id) + { + $user = User::find($id); + + if (empty($user)) { + return $this->errorResponse(404); + } + + $user->unsuspend(); + + return response()->json([ + 'status' => 'success', + 'message' => __('app.user-unsuspend-success'), + ]); + } + + /** * Update user data. * * @param \Illuminate\Http\Request $request The API request. diff --git a/src/resources/lang/en/app.php b/src/resources/lang/en/app.php --- a/src/resources/lang/en/app.php +++ b/src/resources/lang/en/app.php @@ -31,9 +31,12 @@ 'domain-verify-success' => 'Domain verified successfully.', 'domain-verify-error' => 'Domain ownership verification failed.', + 'user-update-success' => 'User data updated successfully.', 'user-create-success' => 'User created successfully.', 'user-delete-success' => 'User deleted successfully.', + 'user-suspend-success' => 'User suspended successfully.', + 'user-unsuspend-success' => 'User unsuspended successfully.', 'search-foundxdomains' => ':x domains have been found.', 'search-foundxusers' => ':x user accounts have been found.', diff --git a/src/resources/sass/_variables.scss b/src/resources/sass/_variables.scss --- a/src/resources/sass/_variables.scss +++ b/src/resources/sass/_variables.scss @@ -12,7 +12,7 @@ $purple: #9561e2; $pink: #f66d9b; $red: #e3342f; -$orange: #f6993f; +$orange: #f1a539; $yellow: #ffed4a; $green: #38c172; $teal: #4dc0b5; @@ -22,4 +22,5 @@ // App colors $menu-bg-color: $light; -$main-color: #f1a539; +$main-color: $orange; +$warning: $orange; diff --git a/src/resources/vue/Admin/User.vue b/src/resources/vue/Admin/User.vue --- a/src/resources/vue/Admin/User.vue +++ b/src/resources/vue/Admin/User.vue @@ -74,6 +74,8 @@ {{ user.country }} + + @@ -470,6 +472,24 @@ this.external_email = null // required because of Vue } }) + }, + suspendUser() { + axios.post('/api/v4/users/' + this.user.id + '/suspend', {}) + .then(response => { + if (response.data.status == 'success') { + this.$toast.success(response.data.message) + this.user = Object.assign({}, this.user, { isSuspended: true }) + } + }) + }, + unsuspendUser() { + axios.post('/api/v4/users/' + this.user.id + '/unsuspend', {}) + .then(response => { + if (response.data.status == 'success') { + this.$toast.success(response.data.message) + this.user = Object.assign({}, this.user, { isSuspended: false }) + } + }) } } } diff --git a/src/routes/api.php b/src/routes/api.php --- a/src/routes/api.php +++ b/src/routes/api.php @@ -100,6 +100,8 @@ Route::apiResource('packages', API\V4\Admin\PackagesController::class); Route::apiResource('skus', API\V4\Admin\SkusController::class); Route::apiResource('users', API\V4\Admin\UsersController::class); + Route::post('users/{id}/suspend', 'API\V4\Admin\UsersController@suspend'); + Route::post('users/{id}/unsuspend', 'API\V4\Admin\UsersController@unsuspend'); Route::apiResource('wallets', API\V4\Admin\WalletsController::class); Route::apiResource('discounts', API\V4\Admin\DiscountsController::class); } diff --git a/src/tests/Browser/Admin/UserTest.php b/src/tests/Browser/Admin/UserTest.php --- a/src/tests/Browser/Admin/UserTest.php +++ b/src/tests/Browser/Admin/UserTest.php @@ -3,6 +3,7 @@ namespace Tests\Browser\Admin; use App\Discount; +use App\User; use Tests\Browser; use Tests\Browser\Components\Dialog; use Tests\Browser\Components\Toast; @@ -23,6 +24,7 @@ self::useAdminUrl(); $john = $this->getTestUser('john@kolab.org'); + $john->update(['status' => $john->status ^= User::STATUS_SUSPENDED]); $john->setSettings([ 'phone' => '+48123123123', 'external_email' => 'john.doe.external@gmail.com', @@ -40,6 +42,7 @@ public function tearDown(): void { $john = $this->getTestUser('john@kolab.org'); + $john->update(['status' => $john->status ^= User::STATUS_SUSPENDED]); $john->setSettings([ 'phone' => null, 'external_email' => 'john.doe.external@gmail.com', @@ -407,6 +410,29 @@ } /** + * Test suspending/unsuspending the user + */ + public function testSuspendAndUnsuspend(): void + { + $this->browse(function (Browser $browser) { + $john = $this->getTestUser('john@kolab.org'); + + $browser->visit(new UserPage($john->id)) + ->assertVisible('@user-info #button-suspend') + ->assertMissing('@user-info #button-unsuspend') + ->click('@user-info #button-suspend') + ->assertToast(Toast::TYPE_SUCCESS, 'User suspended successfully.') + ->assertSeeIn('@user-info #status span.text-warning', 'Suspended') + ->assertMissing('@user-info #button-suspend') + ->click('@user-info #button-unsuspend') + ->assertToast(Toast::TYPE_SUCCESS, 'User unsuspended successfully.') + ->assertSeeIn('@user-info #status span.text-success', 'Active') + ->assertVisible('@user-info #button-suspend') + ->assertMissing('@user-info #button-unsuspend'); + }); + } + + /** * Test editing wallet discount * * @depends testUserInfo2 diff --git a/src/tests/Feature/Controller/Admin/UsersTest.php b/src/tests/Feature/Controller/Admin/UsersTest.php --- a/src/tests/Feature/Controller/Admin/UsersTest.php +++ b/src/tests/Feature/Controller/Admin/UsersTest.php @@ -2,6 +2,7 @@ namespace Tests\Feature\Controller\Admin; +use Illuminate\Support\Facades\Queue; use Tests\TestCase; class UsersTest extends TestCase @@ -14,6 +15,8 @@ parent::setUp(); self::useAdminUrl(); + $this->deleteTestUser('UsersControllerTest1@userscontroller.com'); + $jack = $this->getTestUser('jack@kolab.org'); $jack->setSetting('external_email', null); } @@ -23,6 +26,8 @@ */ public function tearDown(): void { + $this->deleteTestUser('UsersControllerTest1@userscontroller.com'); + $jack = $this->getTestUser('jack@kolab.org'); $jack->setSetting('external_email', null); @@ -142,6 +147,66 @@ } /** + * Test user suspending (POST /api/v4/users//suspend) + */ + public function testSuspend(): void + { + Queue::fake(); // disable jobs + + $user = $this->getTestUser('UsersControllerTest1@userscontroller.com'); + $admin = $this->getTestUser('jeroen@jeroen.jeroen'); + + // Test unauthorized access to admin API + $response = $this->actingAs($user)->post("/api/v4/users/{$user->id}/suspend", []); + $response->assertStatus(403); + + $this->assertFalse($user->isSuspended()); + + // Test suspending the user + $response = $this->actingAs($admin)->post("/api/v4/users/{$user->id}/suspend", []); + $response->assertStatus(200); + + $json = $response->json(); + + $this->assertSame('success', $json['status']); + $this->assertSame("User suspended successfully.", $json['message']); + $this->assertCount(2, $json); + + $this->assertTrue($user->fresh()->isSuspended()); + } + + /** + * Test user un-suspending (POST /api/v4/users//unsuspend) + */ + public function testUnsuspend(): void + { + Queue::fake(); // disable jobs + + $user = $this->getTestUser('UsersControllerTest1@userscontroller.com'); + $admin = $this->getTestUser('jeroen@jeroen.jeroen'); + + // Test unauthorized access to admin API + $response = $this->actingAs($user)->post("/api/v4/users/{$user->id}/unsuspend", []); + $response->assertStatus(403); + + $this->assertFalse($user->isSuspended()); + $user->suspend(); + $this->assertTrue($user->isSuspended()); + + // Test suspending the user + $response = $this->actingAs($admin)->post("/api/v4/users/{$user->id}/unsuspend", []); + $response->assertStatus(200); + + $json = $response->json(); + + $this->assertSame('success', $json['status']); + $this->assertSame("User unsuspended successfully.", $json['message']); + $this->assertCount(2, $json); + + $this->assertFalse($user->fresh()->isSuspended()); + } + + /** * Test user update (PUT /api/v4/users/) */ public function testUpdate(): void @@ -150,7 +215,7 @@ $admin = $this->getTestUser('jeroen@jeroen.jeroen'); // Test unauthorized access to admin API - $response = $this->actingAs($user)->get("/api/v4/users/{$user->id}", []); + $response = $this->actingAs($user)->put("/api/v4/users/{$user->id}", []); $response->assertStatus(403); // Test updatig the user data (empty data)