Skip to content

Commit

Permalink
Merge branch 'feature/manage-users'
Browse files Browse the repository at this point in the history
  • Loading branch information
guillaumebriday committed Jun 13, 2018
2 parents e9a165d + cb20385 commit 3dd4fc0
Show file tree
Hide file tree
Showing 9 changed files with 267 additions and 2 deletions.
37 changes: 37 additions & 0 deletions app/Http/Controllers/V1/UsersController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace App\Http\Controllers\V1;

use App\Http\Controllers\Controller;
use App\Http\Requests\User\UserRequest;
use App\Http\Resources\UserResource;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

class UsersController extends Controller
{
/**
* Update the specified resource in storage.
*/
public function update(UserRequest $request, User $user): UserResource
{
$user->update($request->validated());

return new UserResource($user);
}

/**
* Remove the specified resource from storage.
*/
public function destroy(Request $request, User $user): JsonResponse
{
DB::transaction(function () use ($user) {
$user->tasks()->delete();
$user->delete();
});

return response()->noContent();
}
}
40 changes: 40 additions & 0 deletions app/Http/Requests/User/UserRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

namespace App\Http\Requests\User;

use App\Rules\CurrentPassword;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

class UserRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}

/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'name' => 'alpha_dash|max:255',
'email' => [
'email',
'max:255',
Rule::unique('users')->ignore(auth()->user()->id),
],
'current_password' => [
'required_with:password',
new CurrentPassword
],
'password' => 'confirmed'
];
}
}
19 changes: 19 additions & 0 deletions app/Policies/UserPolicy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace App\Policies;

use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;

class UserPolicy
{
use HandlesAuthorization;

/**
* Determine whether the user can view the model.
*/
public function manage(User $user, User $model): bool
{
return $user->id === $model->id;
}
}
4 changes: 3 additions & 1 deletion app/Providers/AuthServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace App\Providers;

use App\Models\User;
use App\Policies\UserPolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
Expand All @@ -12,7 +14,7 @@ class AuthServiceProvider extends ServiceProvider
* @var array
*/
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
User::class => UserPolicy::class
];

/**
Expand Down
25 changes: 25 additions & 0 deletions app/Rules/CurrentPassword.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;
use Illuminate\Support\Facades\Hash;

class CurrentPassword implements Rule
{
/**
* Determine if the validation rule passes.
*/
public function passes($attribute, $value): bool
{
return Hash::check($value, auth()->user()->password);
}

/**
* Get the validation error message.
*/
public function message(): string
{
return trans('validation.current_password');
}
}
1 change: 1 addition & 0 deletions resources/lang/en/validation.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
'unique' => 'The :attribute has already been taken.',
'uploaded' => 'The :attribute failed to upload.',
'url' => 'The :attribute format is invalid.',
'current_password' => "The current password is invalid.",

/*
|--------------------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions routes/api.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
Route::get('me', 'AuthController@me');
});

Route::ApiResource('users', 'UsersController')->only(['update', 'destroy'])->middleware('can:manage,user');
Route::ApiResource('tasks', 'TasksController');
Route::delete('tasks', 'TasksController@deleteCompletedTasks');
});
Expand Down
3 changes: 2 additions & 1 deletion tests/Feature/V1/Auth/AuthTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class ExampleTest extends TestCase
class AuthTest extends TestCase
{
use RefreshDatabase;

Expand All @@ -23,6 +23,7 @@ public function user_can_retrieve_a_jwt()
'access_token',
'token_type',
'expires_in',
'user_id',
])
->assertJson([
'token_type' => 'bearer',
Expand Down
139 changes: 139 additions & 0 deletions tests/Feature/V1/UserTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<?php

namespace Tests\Feature\V1;

use App\Models\Task;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Hash;
use Tests\TestCase;

class UserTest extends TestCase
{
use RefreshDatabase;

/** @test */
public function user_can_delete_his_account()
{
$anakin = $this->anakin();
$tasks = factory(Task::class, 3)->create(['user_id' => $anakin]);

$this->actingAs($anakin)
->json('DELETE', "/api/v1/users/{$anakin->id}")
->assertStatus(204);

$this->assertEmpty(Task::all());
$this->assertDatabaseMissing('users', $anakin->toArray());
}

/** @test */
public function user_can_update_his_account()
{
$anakin = $this->anakin();

$this->actingAs($anakin)
->json('PATCH', "/api/v1/users/{$anakin->id}", [
'email' => '[email protected]',
'name' => 'Ben',
])
->assertStatus(200)
->assertJsonStructure([
'data' => [
'id',
'name',
'email',
],
])
->assertJson([
'data' => [
'id' => $anakin->id,
'name' => 'Ben',
'email' => '[email protected]',
]
])
->assertJsonCount(1);

$anakin->refresh();

$this->assertEquals('[email protected]', $anakin->email);
$this->assertEquals('Ben', $anakin->name);
}

/** @test */
public function user_cannot_update_another_account()
{
$user = $this->user();

$this->actingAs($this->anakin())
->json('PATCH', "/api/v1/users/{$user->id}", [
'email' => '[email protected]',
'name' => 'Ben',
])
->assertStatus(403)
->assertJson([
'message' => 'This action is unauthorized.'
]);
}

/** @test */
public function user_can_update_his_password()
{
$anakin = $this->anakin();

$this->actingAs($anakin)
->json('PATCH', "/api/v1/users/{$anakin->id}", [
'current_password' => '4nak1n',
'password' => '4_n3w_h0p3',
'password_confirmation' => '4_n3w_h0p3'
])
->assertStatus(200)
->assertJsonStructure([
'data' => [
'id',
'name',
'email',
],
])
->assertJson([
'data' => [
'id' => $anakin->id,
'name' => $anakin->name,
'email' => $anakin->email,
]
])
->assertJsonCount(1);

$this->assertTrue(Hash::check('4_n3w_h0p3', $anakin->refresh()->password));
}

/** @test */
public function user_cannot_update_his_password_without_current_password_and_password_confirmation()
{
$anakin = $this->anakin();

$this->actingAs($anakin)
->json('PATCH', "/api/v1/users/{$anakin->id}", [
'password' => '4_n3w_h0p3',
])
->assertStatus(422)
->assertJsonStructure([
'message',
'errors' => [
'current_password',
'password',
],
])
->assertJson([
'message' => 'The given data was invalid.',
'errors' => [
'current_password' => [
'The current password field is required when password is present.'
],
'password' => [
'The password confirmation does not match.'
],
]
]);

$this->assertFalse(Hash::check('4_n3w_h0p3', $anakin->refresh()->password));
}
}

0 comments on commit 3dd4fc0

Please sign in to comment.