通过 Spatie 权限扩展灯丝多租户

问题描述 投票:0回答:1

我真的很难理解必须如何实施才能协同工作。让我从 Filament 多租户开始。

我有组织,它们与具有多对多关系的用户相关。我希望每个组织对其自己的资源和记录负责,但我也希望有一个可以管理所有其他组织或至少模拟它们的管理组织(我想一旦我接触到它,这将很容易实现。)

这里的问题如下:

  1. 我有组织资源,我希望人们在其中创建、编辑或删除他们自己的组织范围到当前活动的租户。

我有以下内容

OrganizationResource

<?php

namespace App\Filament\Resources;

use App\Filament\Resources\OrganizationResource\Pages;
use App\Models\Organization;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables\Actions\BulkActionGroup;
use Filament\Tables\Actions\DeleteAction;
use Filament\Tables\Actions\DeleteBulkAction;
use Filament\Tables\Actions\EditAction;
use Filament\Tables\Actions\ForceDeleteAction;
use Filament\Tables\Actions\ForceDeleteBulkAction;
use Filament\Tables\Actions\RestoreAction;
use Filament\Tables\Actions\RestoreBulkAction;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\TrashedFilter;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope;
use Illuminate\Support\Str;

class OrganizationResource extends Resource
{
    protected static ?string $model = Organization::class;

    protected static ?string $slug = 'organizations';

    protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack';

    protected static ?string $tenantOwnershipRelationshipName = 'users';

    public static function form(Form $form): Form
    {
        return $form
            ->schema([
                TextInput::make('name')
                    ->required()
                    ->reactive(),

                TextInput::make('description')
                    ->required(),
            ]);
    }

    public static function table(Table $table): Table
    {
        return $table
            ->columns([
                TextColumn::make('name')
                    ->searchable()
                    ->sortable(),

                TextColumn::make('slug')
                    ->searchable()
                    ->sortable(),

                TextColumn::make('description'),

                TextColumn::make('verified_at')
                    ->label('Verified Date')
                    ->date(),
            ])
            ->filters([
                TrashedFilter::make(),
            ])
            ->actions([
                EditAction::make(),
                DeleteAction::make(),
                RestoreAction::make(),
                ForceDeleteAction::make(),
            ])
            ->bulkActions([
                BulkActionGroup::make([
                    DeleteBulkAction::make(),
                    RestoreBulkAction::make(),
                    ForceDeleteBulkAction::make(),
                ]),
            ]);
    }

    public static function getPages(): array
    {
        return [
            'index' => Pages\ListOrganizations::route('/'),
            'create' => Pages\CreateOrganization::route('/create'),
            'edit' => Pages\EditOrganization::route('/{record}/edit'),
        ];
    }

    public static function getEloquentQuery(): Builder
    {
        return parent::getEloquentQuery()
            ->withoutGlobalScopes([
                SoftDeletingScope::class,
            ]);
    }

    public static function getGloballySearchableAttributes(): array
    {
        return ['name', 'slug'];
    }
}

还有以下

Organization Model

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Spatie\Permission\Traits\HasRoles;
use Spatie\Sluggable\HasSlug;
use Spatie\Sluggable\SlugOptions;

class Organization extends Model
{
    use SoftDeletes, HasSlug, HasRoles;

    protected $fillable = [
        'name',
        'slug',
        'description',
        'is_admin',
    ];

    public static function boot()
    {
        parent::boot();

        // here assign this team to a global user with global default role
        self::created(function ($model) {
            // temporary: get session team_id for restore at end
            $session_team_id = config('organizations.default_organization_id');
            // set actual new team_id to package instance
            setPermissionsTeamId($model);
            // restore session team_id to package instance using temporary value stored above
            setPermissionsTeamId($session_team_id);
        });
    }

    protected function casts(): array
    {
        return [
            'verified_at' => 'datetime',
        ];
    }

    public function users(): BelongsToMany
    {
        return $this->belongsToMany(User::class);
    }


    public function getSlugOptions(): SlugOptions
    {
        return SlugOptions::create()
            ->generateSlugsFrom('name')
            ->saveSlugsTo('slug')
            ->doNotGenerateSlugsOnUpdate()
            ->usingSeparator('-');
    }
}

还有以下

AdminPanelProvider

public function panel(Panel $panel): Panel
    {
        return $panel
            ->default()
            ->id('admin')
            ->path('')
            ->login()
            ->tenant(Organization::class, slugAttribute: 'slug')
            ->brandLogo(fn () => view('filament.admin.logo'))
            ->registration(Registration::class)
            ->emailVerification(RegistrationConfirmation::class)
            ->colors([
                'primary' => Color::Amber,
            ])
            ->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
            ->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages')
            ->pages([
                Pages\Dashboard::class,
            ])
            ->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets')
            ->widgets([
                Widgets\AccountWidget::class,
                Widgets\FilamentInfoWidget::class,
            ])
            ->middleware([
                EncryptCookies::class,
                AddQueuedCookiesToResponse::class,
                StartSession::class,
                AuthenticateSession::class,
                ShareErrorsFromSession::class,
                VerifyCsrfToken::class,
                SubstituteBindings::class,
                DisableBladeIconComponents::class,
                DispatchServingFilamentEvent::class,
            ])
            ->tenantMiddleware([
                TeamsPermission::class,
                ApplyTenantScopes::class,
            ], isPersistent: true)
            ->plugins([
                FilamentShieldPlugin::make(),
                FilamentLogManager::make(),
                ResourceLockPlugin::make(),
                FilamentSpatieLaravelHealthPlugin::make(),
                ActivitylogPlugin::make()
                    ->navigationGroup('Audit')
                    ->authorize(fn () => auth()->user()->can('super_admin'))
                    ->navigationSort(3),
            ])
            ->authMiddleware([
                Authenticate::class,
            ])
            ->unsavedChangesAlerts()
            ->databaseTransactions();
    }

如您所见,我已将

protected static ?string $tenantOwnershipRelationshipName = 'users';
设置为组织,但没有显示任何资源。我假设它没有正确确定数据范围,但我不知道如何设置它才能工作。

如果我从此资源中删除租户,并且仅使用我分配给角色的权限来确定其范围,这要归功于 Spatie 包,一切都会按预期工作。

有人可以解释一下我应该如何设置它才能工作吗?我对许多其他具有深层嵌套关系的租赁模型存在问题。

例如媒体 -> 游戏 -> 用户 -> 组织。

提前致谢。

php laravel multi-tenant laravel-filament
1个回答
0
投票

灯丝文档说得很清楚(一开始我不太清楚:D)。

在创建和列出与租户关联的记录时,Filament 需要访问每个资源的两个 Eloquent 关系 - 在资源模型类上定义的“所有权”关系,以及租户模型类上的关系。默认情况下,Filament 将尝试根据标准 Laravel 约定猜测这些关系的名称。例如,如果租户模型是 App\Models\Team,它将在资源模型类上查找 team() 关系。如果资源模型类是 App\Models\Post,它将在租户模型类上查找 posts() 关系。

您希望租户了解的每个模型都必须包含 team_id、organization_id 或您选择的任何内容才能使其正常工作。

这是必要的,以便可以为每个租户确定数据范围并过滤掉数据。

© www.soinside.com 2019 - 2024. All rights reserved.