Skip to content

Commit

Permalink
Merge pull request #559 from ezeanyimhenry/fix/roles-and-permissions
Browse files Browse the repository at this point in the history
Fix/: roles and permissions
  • Loading branch information
Dev-Tonia authored Aug 24, 2024
2 parents 2973bae + 1cf2abe commit 0ad7145
Show file tree
Hide file tree
Showing 17 changed files with 133 additions and 63 deletions.
2 changes: 1 addition & 1 deletion app/Http/Controllers/Api/V1/Admin/BlogController.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ public function store(Request $request)
DB::beginTransaction();

$savedPath = Storage::disk('public')->put('images', $request->file('image_url'));
$imageUrl = url('storage/' . $savedPath);
$imageUrl = 'storage/' . $savedPath;

$blog = Blog::create([
'title' => $request->get('title'),
Expand Down
72 changes: 46 additions & 26 deletions app/Http/Controllers/Api/V1/RoleController.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,14 @@ class RoleController extends Controller
/**
* Store a newly created resource in storage.
*/
public function store(StoreRoleRequest $request)
public function store(StoreRoleRequest $request, $orgId)
{
Log::info('Incoming role creation request', $request->all());

try {
DB::beginTransaction();

$org_id = $request->organisation_id;

// Validate the organisation ID as a UUID
if (!preg_match('/^[a-f0-9-]{36}$/', $org_id)) {
if (!preg_match('/^[a-f0-9-]{36}$/', $orgId)) {
return response()->json([
'status_code' => Response::HTTP_BAD_REQUEST,
'error' => 'Invalid input',
Expand All @@ -44,17 +41,17 @@ public function store(StoreRoleRequest $request)
}

// Check whether the organisation exists
$organisation = Organisation::find($org_id);
$organisation = Organisation::find($orgId);
if (!$organisation) {
return response()->json([
'status_code' => Response::HTTP_NOT_FOUND,
'error' => 'Organisation not found',
'message' => 'The organisation with ID ' . $org_id . ' does not exist',
'message' => 'The organisation with ID ' . $orgId . ' does not exist',
], Response::HTTP_NOT_FOUND);
}

// Check for duplicate role name within the organisation
$existingRole = Role::where('org_id', $org_id)->where('name', $request->role_name)->first();
$existingRole = Role::where('org_id', $orgId)->where('name', $request->name)->first();
if ($existingRole) {
return response()->json([
'status_code' => Response::HTTP_CONFLICT,
Expand All @@ -65,22 +62,34 @@ public function store(StoreRoleRequest $request)

// Creating the role
$role = Role::create([
'name' => $request->role_name,
'name' => $request->name,
'description' => $request->description,
'org_id' => $org_id,
'org_id' => $orgId,
]);

// Attach the permission to the role
$role->permissions()->attach($request->permissions_id);
$role->permissions()->attach($request->permissions);

// Retrieve all attached permissions with additional details
$permissions = $role->permissions->map(function ($permission) {
return [
'id' => $permission->permission_id,
'name' => $permission->name,
];
});

DB::commit();

return response()->json([
'id' => $role->id,
'name' => $role->name,
'description' => $role->description,
'message' => "Role created successfully",
'status_code' => Response::HTTP_CREATED,
'status_code' => 201,
'data' => [
'id' => $role->id,
'name' => $role->name,
'description' => $role->description,
'permissions' => $permissions,
],
'error' => null,
'message' => 'Role created successfully',
], Response::HTTP_CREATED);
} catch (\Exception $e) {
DB::rollBack();
Expand Down Expand Up @@ -158,12 +167,16 @@ public function assignRole(Request $request, $org_id, $user_id)
'role' => 'required|string|exists:roles,name',
]);

if ($validator->fails()) {
return $this->apiResponse($validator->errors(), 422);
}
if ($validator->fails()) {
return $this->apiResponse($validator->errors(), 422);
}

$user = User::find($user_id);
if (!$user) return ResponseHelper::response("User not found", 404, null);
if (!$user)
return ResponseHelper::response("User not found", 404, null);
if ($organisation = Organisation::find($org_id)) {
if (!$organisation->users->contains($user->id))
return ResponseHelper::response("You are not authorised to perform this action", 409, null);
Expand All @@ -173,22 +186,27 @@ public function assignRole(Request $request, $org_id, $user_id)
return ResponseHelper::response("Roles updated successfully", 200, null);
else
return response()->json(['message' => 'Role update failed', 'error' => $result], 400);
} else return ResponseHelper::response("Organisation not found", 404, null);
} else
return ResponseHelper::response("Organisation not found", 404, null);
}

public function update(UpdateRoleRequest $request, $org_id, $role_id)
{
$user = auth('api')->user();
if (!$user) return ResponseHelper::response("Authentication failed", 401, null);
if (!$user)
return ResponseHelper::response("Authentication failed", 401, null);
if ($organisation = Organisation::find($org_id)) {
if (!$organisation->users->contains($user->id)) return ResponseHelper::response("You are not authorised to perform this action", 401, null);
if (!$organisation->users->contains($user->id))
return ResponseHelper::response("You are not authorised to perform this action", 401, null);
if ($role = Role::find($role_id)) {
$role->update($request->only('name', 'description'));
return ResponseHelper::response("Role updated successfully", 200, $role);
} else return ResponseHelper::response("Role not found", 404, null);
} else return ResponseHelper::response("Organisation not found", 404, null);
} else
return ResponseHelper::response("Role not found", 404, null);
} else
return ResponseHelper::response("Organisation not found", 404, null);
}

// To get all roles
public function index($org_id)
{
Expand Down Expand Up @@ -233,7 +251,7 @@ public function show($org_id, $role_id)
{
try {
// Validate UUID format
if (!Str::isUuid($org_id) || !is_numeric($role_id)) {
if (!Str::isUuid($org_id) || !Str::isUuid($role_id)) {
Log::error('Invalid UUID or ID format', [
'org_id' => $org_id,
'role_id' => $role_id,
Expand Down Expand Up @@ -338,7 +356,8 @@ public function assignPermissions(Request $request, $org_id, $role_id)
$payload = Validator::make($request->all(), [
'permission_list' => 'required|array'
]);
if ($payload->fails()) return ResponseHelper::response($payload->errors(), 422, null);
if ($payload->fails())
return ResponseHelper::response($payload->errors(), 422, null);
if ($role && !$payload->fails()) {
foreach ($request->permission_list as $permission => $value) {
$permissionId = Permission::where('name', $permission)->value('id');
Expand All @@ -349,7 +368,8 @@ public function assignPermissions(Request $request, $org_id, $role_id)
}
}
return ResponseHelper::response("Permissions updated successfully", 202, null);
} else return ResponseHelper::response("Role not found", 404, null);
} else
return ResponseHelper::response("Role not found", 404, null);
}

public function destroy(Request $request, $org_id, $role_id)
Expand Down
21 changes: 14 additions & 7 deletions app/Http/Requests/StoreRoleRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,33 @@ public function authorize(): bool
public function rules(): array
{
return [
'role_name' => [
'name' => [
'required',
'string',
'max:255'
],
'organisation_id' => [
'required',
'description' => [
'nullable',
'string',
'max:255',
'exists:organisations,org_id',
Rule::unique('roles', 'org_id')->where('name', $this->input('role_name'))
],
'permissions_id' => 'required',
'permissions' => [
'required',
'array',
],
'permissions.*' => [
'uuid',
'exists:permissions,id',
],
];
}

public function messages()
{
return [
'organisation_id.unique'=> "'".$this->input('role_name')."' role has already been created for the organisation",
'permissions.array' => 'Permissions must be provided as an array.',
'permissions.*.uuid' => 'Each permission ID must be a valid UUID.',
'permissions.*.exists' => 'Each permission ID must be valid.',
];
}
}
6 changes: 4 additions & 2 deletions app/Models/Permission.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@

namespace App\Models;

use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Permission extends Model
{
use HasFactory;
use HasFactory, HasUuids;

protected $fillable = ['name'];

public function roles()
{
return $this->belongsToMany(Role::class, 'roles_permissions');
return $this->belongsToMany(Role::class, 'roles_permissions', 'permission_id', 'role_id')
->using(RolePermission::class);
}

public function users()
Expand Down
6 changes: 4 additions & 2 deletions app/Models/Role.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@

namespace App\Models;

use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Role extends Model
{
use HasFactory;
use HasFactory, HasUuids;

protected $fillable = ['name', 'description', 'org_id', 'is_active', 'is_default'];

public function permissions()
{
return $this->belongsToMany(Permission::class, 'roles_permissions', 'role_id', 'permission_id');
return $this->belongsToMany(Permission::class, 'roles_permissions', 'role_id', 'permission_id')
->using(RolePermission::class);
}

public function users()
Expand Down
12 changes: 12 additions & 0 deletions app/Models/RolePermission.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Relations\Pivot;
use Illuminate\Support\Str;

class RolePermission extends Pivot
{
use HasUuids;
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
public function up(): void
{
Schema::create('permissions', function (Blueprint $table) {
$table->id();
$table->uuid('id')->primary();
$table->string('name');
$table->timestamp('deleted_at')->nullable();
$table->timestamps();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
public function up(): void
{
Schema::create('roles', function (Blueprint $table) {
$table->id();
$table->uuid('id')->primary();
$table->string('name');
$table->text('description')->nullable();
$table->foreignUuid('org_id')->references('org_id')->on('organisations')->constrained()->onDelete('cascade')->onUpdate('cascade');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
public function up(): void
{
Schema::create('roles_permissions', function (Blueprint $table) {
$table->id();
$table->foreignId('role_id')->constrained('roles', 'id')->cascadeOnDelete()->cascadeOnUpdate();
$table->foreignId('permission_id')->constrained('permissions', 'id')->cascadeOnDelete()
$table->uuid('id')->primary();
$table->foreignUuid('role_id')->constrained('roles', 'id')->cascadeOnDelete()->cascadeOnUpdate();
$table->foreignUuid('permission_id')->constrained('permissions', 'id')->cascadeOnDelete()
->cascadeOnUpdate();
$table->timestamps();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public function up(): void
Schema::create('users_permissions', function (Blueprint $table) {
$table->id();
$table->foreignUuid('user_id')->constrained('users', 'id')->cascadeOnDelete()->cascadeOnUpdate();
$table->foreignId('permission_id')->constrained('permissions', 'id')->cascadeOnDelete()->cascadeOnUpdate();
$table->foreignUuid('permission_id')->constrained('permissions', 'id')->cascadeOnDelete()->cascadeOnUpdate();
$table->timestamps();
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public function up(): void
Schema::create('users_roles', function (Blueprint $table) {
$table->id();
$table->foreignUuid('user_id')->constrained('users', 'id')->cascadeOnDelete()->cascadeOnUpdate();
$table->foreignId('role_id')->constrained('roles', 'id')->cascadeOnDelete()->cascadeOnUpdate();
$table->foreignUuid('role_id')->constrained('roles', 'id')->cascadeOnDelete()->cascadeOnUpdate();
$table->timestamps();
});
}
Expand Down
2 changes: 1 addition & 1 deletion routes/api.php
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@
Route::patch('/accounts/deactivate', [AccountController::class, 'deactivate']);

// Roles
Route::post('/organisations/{org_id}/roles', [RoleController::class, 'store']);
Route::post('/organisations/{orgId}/roles', [RoleController::class, 'store']);
Route::put('/organisations/{org_id}/roles/{role_id}', [RoleController::class, 'update']);
Route::put('/organisations/{org_id}/roles/{role_id}/disable', [RoleController::class, 'disableRole']);
Route::get('/organisations/{org_id}/roles', [RoleController::class, 'index']);
Expand Down
2 changes: 1 addition & 1 deletion tests/Feature/BlogControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ public function test_admin_can_create_blog_post()

$image = UploadedFile::fake()->image('image1.jpg');
$path = Storage::putFile('public/images', $image);
$imageUrl = str_replace('public/', 'storage/', $path);
$imageUrl = str_replace('public/', 'storage/', $path);

$response = $this->withHeaders(['Authorization' => "Bearer $token"])->postJson('/api/v1/blogs', [
'title' => 'Test Blog Post',
Expand Down
2 changes: 1 addition & 1 deletion tests/Feature/OrganisationRoleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public function test_get_role_by_org_and_role_id_not_found()
// Send request for a non-existent role
$response = $this->withHeaders([
'Authorization' => "Bearer $token"
])->getJson("/api/v1/organisation/{$organisation->org_id}/roles/999");
])->getJson("/api/v1/organisation/{$organisation->org_id}/roles/00000000-0000-0000-0000-000000000000");

// Assert not found response
$response->assertStatus(404)
Expand Down
Loading

0 comments on commit 0ad7145

Please sign in to comment.