Loading...

This is taking longer than expected.

Back to the help centre

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

  1. The table needs a team_id column, indexed, pointing at teams.
  2. 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(),
        ];
    }
}
  1. Register it in routes/team.php. That file already runs inside the team group, with the auth, security and team middleware, so the team middleware 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.

  1. 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.

  1. 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').