How to make multiple roles in Spatie Laravel package? - laravel

Is possible with Spatie's laravel-permission package to achieve something like what Facebook have with their permissions for pages?
Case study,
On Facebook you can create a page, then automatically you'll become Admin (assuming it's a role) of that page.
Then you can invite you friend and make him/her a Publisher role
After that, your friend can also invite you to their Page and make you an Editor ( that's another role for you)
so at the end you will have two roles for two different profiles/Pages.
I want to achieve something like that on my app.
EDIT
what i know for now is that i can use:
$user->assignRole('editor');
and i can check if the user has a roles with :
$user->hasRole('writer')
But the thing is how do i link that role with the page that i want them to manage?
Like if you have admin role in one page and editor role in another page.
and if you just check if $user->hasRole('admin') the results will always be true. so i want to know if there is anything i can do to assign and also check which page are you admin for.
I hope i am making sense.

Assuming your table structure looks like this:
Pages table
|---------------------|------------------|------------------|------------------|
| id | name | created_at | updated_at |
|---------------------|------------------|------------------|------------------|
| 12 | PHP masters | 2019-02-31 | 2019-03-12 |
|---------------------|------------------|------------------|------------------|
Pages admin
|---------------------|------------------|------------------|------------------|
| id | page_id | role_id | user_id |
|---------------------|------------------|------------------|------------------|
| 1 | 12 | 2 | 2 |
|---------------------|------------------|------------------|------------------|
Roles table
|---------------------|------------------|------------------|------------------|
| id | name | created_at | updated_at |
|---------------------|------------------|------------------|------------------|
| 12 | Editor | 2019-02-31 | 2019-03-12 |
|---------------------|------------------|------------------|------------------|
| 12 | System Admin | 2019-02-31 | 2019-03-12 |
|---------------------|------------------|------------------|------------------|
| 12 | Page Admin | 2019-02-31 | 2019-03-12 |
|---------------------|------------------|------------------|------------------|
Now inside User model add this method
#do not forget to import Roles
#use Spatie\Permission\Models\Role;
public function can_manage($page_id) : bool;
{
#Backdoor for the system admin
if ($this->hasRole(['System Admin'])) {
return true;
}
#now we check if the user has the role for that page
$role = Role::select('name')
->join("page_admins", 'page_admins.role_id', '=', 'roles.id')
->where("page_id", $page_id)
->where("user_id", $this->id)
->first();
#return true if roles was found
if (!empty($role)) {
return true;
}
#else false
return false;
}
Lastly if you want to check if the user can access the page's admin you use:
#for jwt-auth
#$user = $request->user();
if ( $user->can_manage($page_id) )
{
#then do you things
}
Now that you saw how to check if someone is among page administration, you can get creative with that. you can create a method that :
#Get user role in the page
#etc

Yes. You can create the roles with artisan and assign them in php.
php artisan permission:create-role editor
php artisan permission:create-role publisher
$user->assignRole(['editor', 'publisher']);
all of this is easily available in the documentation

Related

Laravel many to many 2 level down pivot table

I am trying to build a Survey Module in Laravel where user can create survey, assign questions and options to the questions in survey.
survey can have multiple questions
survey question can have multiple options
To meet the above requirement I created the following models & tables
Model: Question
Table: questions
| id | question |
| -------- | -------------------------|
| 1 | How is the performance? |
| 2 | How did you know about us|
Model: Option
Table: options
| id | option |
| --- | --------- |
| 1 | Good |
| 2 | Bad |
| 3 | Google |
| 2 | SMS |
Now the relationship between questions and survey will be stored in pivot table
Model: SurveyQuestion
Table: survey_questions
| id | survey_id| question_id |
| ---| -------- |-------------|
| 1 | 1 |1 |
| 1 | 1 |2 |
Upto this point I know how to store data into pivot table using attach/sync.
Now the problem is As per requirement each survey question might have multiple options so I created another pivot table survey_question_option in which I am using survey_questions primary key as foreign key.
Model: SurveyQuestionOption
Table: survey_question_options
| id | survey_question_id| option_id |
| -- | ----------------- |-----------|
| 1 | 1 |1 |
| 1 | 1 |2 |
Now my question is that is it correct to use pivot table primary key as foreign key into another table?
If yes then how can I store data into survey_question_options table using laravel relationships?
If no then what is the better solution?
In this case you can create a Custom Intermediate Table Model (Custom Pivot)
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\Pivot;
class SurveyQuestion extends Pivot
{
public function options()
{
//You need to create a pivot pivot table survey_question_option
//with (survey_question_id, option_id)
return $this->belongsToMany(Option::class, 'survey_question_option');
}
}
Your models will need to recognize this new Pivot Model with the method using(). Here is an example with Survey Model
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Survey extends Model
{
/**
* The users that belong to the role.
*/
public function questions()
{
return $this
->belongsToMany(Question::class)
->using(SurveyQuestion::class);
}
}
And them you will be able to access via eloquent like this
$survey->questions->pivot->options
For more information you can check the documentation here:
https://laravel.com/docs/8.x/eloquent-relationships#defining-custom-intermediate-table-models

Codeigniter 3 Joins in One table

I want to display the User Name Who created, but in the database the create name is in the form of int which is the id of the author.
How can I display the id to be the real name by using Join in one table.
Or if there is another way I will try.
For the table as below
+----+--------------+-------------+
| Id | user_create | name |
+----+--------------+-------------+
| 1 | Null | Admin |
| 2 | 1 | User |
+----+--------------+-------------+
and I want to display it like this
Detail User
Name : User
User Create : Admin
if I remember correctly the CI 3 syntax
$this->db
->select(['t1.name', 't2.name user_create'])
->join('thetable t2', 't2.user_create = t1.id', 'left')
->get('thetable t1');

API RESTful Laravel 6.x Best Practice for Many to Many Relatioship

I'm developing an API with Laravel 6.
I've got 2 models:
card -> table cards with card_id ecc.
user -> table users with user_id ecc.
I've defined into models many to many relationships
User.php
public function cards()
{
return $this->belongsToMany('App\Models\v1\Card');
}
Card.php
public function users() {
return $this->belongsToMany('App\Models\v1\User');
}
The pivot table is called card_user .
Now I've created routes for single entities:
Route::resource('v1/users', 'v1\UsersController');
Route::resource('v1/cards', 'v1\CardsController');
and I need to develop routes and controller for insert and delete rows from pivot table.
What is the best practice for this issue?
I try to solve this with a special controller that respond to a specific endpoint:
Route::resource('v1/cards/{id}/users', 'v1\CardsUsersController')->only([
'index', 'store', 'destroy'
]);
But when I need to store information I need to pass the ids of card and user into the URL and as object in post body like so:
[
'user_id' => $userId,
'card_id' => $cardId
]
Exists a better way to do this?
Thanks a lot!
You can use Nested Resources as described here:
https://laravel.com/docs/6.x/controllers#restful-nested-resources
"Sometimes you may need to define routes to a "nested" resource. For example, a photo resource may have multiple "comments" that may be attached to the photo. To "nest" resource controllers, use "dot" notation in your route declaration:
Route::resource('photos.comments', 'PhotoCommentController');
This route will register a "nested" resource that may be accessed with URLs like the following: photos/{photos}/comments/{comments}."
If you must have separate routes and controller for them, then it would be better to do
Route::resource('v1/card_user', 'v1\CardsUsersController')->only(['index', 'store','destroy']);
Keep the route clean, and don't overcomplicate it. Either You or someone else in the future who views code should be able to understand what it is for.
I would combine both answers. As a relationship, it is technically a nested resource. Also, you really have 2 RESTful actions: store and destroy (which correspond to attach and detach in Laravel). You may also want an index to view all of the relationship. I believe the "create" action is optional, depending on your UI.
// Ability_Role pivot routes
Route::resource('v1/user.cards', 'UserCardController')
->only(['index', 'create', 'store','destroy']);
This will give the following routes:
+--------+----------+------------------------------+--------------------+-------------------------------------------------+------------+
| Domain | Method | URI | Name | Action | Middleware |
+--------+----------+------------------------------+--------------------+-------------------------------------------------+------------+
| | GET|HEAD | v1/user/{user}/cards | user.cards.index | App\Http\Controllers\UserCardController#index | web |
| | POST | v1/user/{user}/cards | user.cards.store | App\Http\Controllers\UserCardController#store | web |
| | GET|HEAD | v1/user/{user}/cards/create | user.cards.create | App\Http\Controllers\UserCardController#create | web |
| | DELETE | v1/user/{user}/cards/{card} | user.cards.destroy | App\Http\Controllers\UserCardController#destroy | web |
+--------+----------+------------------------------+--------------------+-------------------------------------------------+------------+
I chose to label the routes as user.cards because I would think you would more often want to start with the user model and attached the cards.
For the store method, you can post an array of cards to attached to the user.
If you also want to start with cards, and store an array of users, you can also define the inverse relationships (though it would require a 2nd controller with just the create and store routes:
// Inverse create and store routes
Route::get('v1/cards/{card}/users/create', 'CardUserController#create')
->name('cards.users.create');
Route::post('v1/cards/{card}/users', 'CardUserController#store')
->name('cards.users.store');
now you will get 2 more routes added:
+--------+----------+------------------------------+--------------------+-------------------------------------------------+------------+
| Domain | Method | URI | Name | Action | Middleware |
+--------+----------+------------------------------+--------------------+-------------------------------------------------+------------+
| | GET|HEAD | api/user | api. | Closure | api |
| | | | | | auth:api |
| | POST | v1/cards/{card}/users | cards.users.store | App\Http\Controllers\CardUserController#store | web |
| | GET|HEAD | v1/cards/{card}/users/create | cards.users.create | App\Http\Controllers\CardUserController#create | web |
| | GET|HEAD | v1/user/{user}/cards | user.cards.index | App\Http\Controllers\UserCardController#index | web |
| | POST | v1/user/{user}/cards | user.cards.store | App\Http\Controllers\UserCardController#store | web |
| | GET|HEAD | v1/user/{user}/cards/create | user.cards.create | App\Http\Controllers\UserCardController#create | web |
| | DELETE | v1/user/{user}/cards/{card} | user.cards.destroy | App\Http\Controllers\UserCardController#destroy | web |
+--------+----------+------------------------------+--------------------+-------------------------------------------------+------------+

Laravel Copy record and duplicate with User IDs

How can i copy a record and save it with a different value for 1 or more field? for example
I have a table(post table) with following columns - ID | User ID | Post Title | Post Type
And User Table(user) with following columns - ID | Username | Email | Password
What i need to do is - When the saving post data needs to be duplicate with user ids
For example - In user table there is 10 users and when i save the post it should be duplicate for all ten user id
ID | User ID | Post Title | Post Type
1 | 1 | Test Post | Public
2 | 2 | Test Post | Public
3 | 3 | Test Post | Public
4 | 4 | Test Post | Public
5 | 5 | Test Post | Public
6 | 6 | Test Post | Public
7 | 7 | Test Post | Public
8 | 8 | Test Post | Public
As suggested, you can do this using model replication. Like
$post = Post::find(12892); //change with your id.
// get all the users.
$users = User::all();
foreach($users as $user){
$newPost = $post->replicate();
$newPost->user_id = $user->id;
// save post.
$newPost->save();
}

Query data based on pivot table

I have a database design which essentially takes the following form (I have removed a lot of fields as they are not really needed for demonstration purposes). I have a users table
users
+----+---------------+-----------------+
| id | name | email |
+----+---------------+-----------------+
| 1 | ProjectA | Something |
+----+---------------+-----------------+
A user can have many projects
projects
+----+---------------+-----------------+-----------------+
| id | name | description | user_id |
+----+---------------+-----------------+-----------------+
| 1 | ProjectA | Something | 1 |
+----+---------------+-----------------+-----------------+
So that is straight forward enough and very easy to query. If I want all projects for the logged in user I can do
$loggedInUser = Auth::user()->getFirstName() . ' ' . Auth::user()->getLastName();
$loggedInUserId = User::where('userName', '=', $loggedInUser)->first();
$projectss = Project::all()->where('user_id', $loggedInUserId);
This is where things get a little more tricky. I have a pivot table for a users groups. It is essentially this
users_user_groups
+----+---------------+-----------------+
| id | user_id | group_id |
+----+---------------+-----------------+
| 1 | 1 | 2 |
+----+---------------+-----------------+
I then have a user_groups table
user_groups
+----+---------------+
| id | group_name |
+----+---------------+
| 1 | Group A |
+----+---------------+
If I want to find what groups a user is a part of I can do
$userGroups = Auth::user()->getGroups();
My question is this. Above, I demonstrate how I can get all projects for a user. Now I know there is a user_group called Group A. What I essentially want to do is get all projects where the user is apart of Group A. So if 5 users create a project and they are all in Group A, then I should be returned 5 projects.
How would I go about doing something like this?
Thanks
Firstly you can get user id using simply Auth::user()->id
Assuming you are trying to get all the projects that belongs to users of a group You can do this.
$group = Group::with('users.projects')->find(1);
Group model
public function users()
{
return $this->belongsToMany('App\User');
}
User Model
public function projects()
{
return $this->hasMany('App\Project');
}
Of course you would have to make the appropriate relation methods depending on the forign/primary keys you used.

Resources