Build a team-scoped CRUD
The resource base class that keeps a Laravel Front CRUD inside the team, and how to register its route and permissions.
Weblabor Base builds its admin screens with Laravel Front: a resource class declares the fields and the kit renders the list, the forms and the detail page. See The admin panel on the base's site. Inside a team the same tool works, with one base class that keeps every record and every permission check inside the team. This guide shows it.
The base class
app/Front/Resources/Team/Resource.php:
namespace App\Front\Resources\Team;
use App\Front\Resources\Resource as Base;
class Resource extends Base
{
public $guards = ['team'];
public $layout = 'layouts.teams';
public function indexQuery($query)
{
return $query->where('team_id', team()->id)->latest();
}
public function processDataBeforeSaving($data)
{
$data['team_id'] = team()->id;
return $data;
}
}
| What it sets | What it means |
|---|---|
$guards = ['team'] |
The retrieve, create, update and delete permissions of the resource are created on the team guard and checked against the team's roles. |
$layout = 'layouts.teams' |
The screens render inside the team workspace, with its sidebar. |
indexQuery |
The list shows only the current team's records. |
processDataBeforeSaving |
A record created or updated from the form belongs to the current team, whatever the request carried. |
A resource that extends the base's App\Front\Resources\Resource instead is wrong in three ways at once: its permissions land on the web guard, so a member with the right team role is refused; its list shows every team's records; and its records are saved with no team_id.
Writing one
- The table needs a
team_idcolumn, indexed, pointing atteams. - Create the class under
app/Front/Resources/Team/:
namespace App\Front\Resources\Team;
use App\Models\Project as Model;
use WeblaborMx\Front\Inputs;
class Project extends Resource
{
public $base_url = '/team/projects';
public $model = Model::class;
public $icon = 'folder';
public function fields()
{
return [
Inputs\ID::make(),
Inputs\Text::make('Name')->rules('required'),
Inputs\Textarea::make('Description')->hideFromIndex(),
];
}
}
- Register it in
routes/team.php. That file already runs inside the team group, with theauth,securityandteammiddleware, so theteammiddleware sets the context before any check runs, and the line needs no group of its own:
// routes/team.php
Route::front('Team\\Project');
The group asks for no subscription. Your screen is protected by its own permissions, the ones the next step seeds. Requiring a subscription is your decision: when the screen has to stay closed to a team without an active plan, check it in the screen itself, for example with team()->subscription('default')?->active(). Do not use ensure.subscribed:team for it, because it also asks for manage plans and shuts out every member without that permission.
Laravel Front names the routes team.front.projects, team.front.projects.create and so on.
- Seed the permissions:
php artisan db:seed --class=PermissionSeeder
With discover_front_permissions on in config/app.php, the seeder finds the resource and creates retrieve projects, create projects, update projects and delete projects on the team guard. The owner of every team has them from then on; every other member gets them through a role. See Team roles and permissions.
- Add the link to the workspace sidebar, in
resources/views/layouts/teams.blade.php, where the members and roles links are.
The policy
Laravel Front asks the model's policy. Extend App\Policies\BasePolicy and name the permission:
namespace App\Policies;
class ProjectPolicy extends BasePolicy
{
public $name = 'projects';
}
BasePolicy checks the right guard from the context and refuses a record of another team on view, update and delete. Override a method only for a rule of your own, and call the parent first so the team checks stay.
The one in the kit
App\Front\Resources\Team\Role, at /team/roles, is the team's roles CRUD and the example to copy: it extends the base class, scopes its list, and adds the clone action. Its route is registered the same way, with Route::front('Team\\Role').