rupadana / filament-api-service

A simple api service for supporting filamentphp
https://filamentphp.com/plugins/rupadana-api-service
MIT License
95 stars 22 forks source link

[Bug]: Incorrect api operation when working with multi-tenant #16

Closed yahannes closed 5 months ago

yahannes commented 5 months ago

What happened?

Errors regarding token creation in multi-tenancy mode

How to reproduce the bug

Hello! I have been unable to cope with numerous errors related to the creation of the token for several days now. Namely, if I try to go to the page with the creation of tokens, I get an error :
The model [Rupadana\ApiService\Models\Token] does not have a relationship named [company]. You can change the relationship being used by passing it to the [ownershipRelationship] argument of the [tenant()] method in configuration. You can change the relationship being used per-resource by setting it as the [$tenantOwnershipRelationshipName] static property on the [Rupadana\ApiService\Resources\TokenResource] resource class.
But I fixed this error by changing the model Token.php and by changing the migration of create_personal_access_tokens . But now I'm facing a mistake. : SQLSTATE[HY000]: General error: 1364 Field 'tokenable_type' doesn't have a default value INSERT INTO personal_access_tokens( name, token, abilities, expires_at, user_id, updated_at, created_at ) VALUES ( ssaddsa, 9a9284467652ea6ea8e33584f75de3bbfe764c4582120f0dc7b57b9b346c6bde, [ "company-contact:create", "company-contact:update", "company-contact:delete", "company-contact:pagination", "company-contact:detail" ], ?, 1, 2024 -01 -21 21: 31: 00, 2024 -01 -21 21: 31: 00 )

The redesigned version Token.php:


namespace Rupadana\ApiService\Models;

use...

class Token extends PersonalAccessToken
{
    use HasFactory;

    public function company(): BelongsTo
    {
        return $this->belongsTo(Company::class);
    }
    protected $table = 'personal_access_tokens';
}

api-service.php:


// config for Rupadana/ApiService
return [
    'navigation' => [
        'group' => [
            'token' => 'User',
        ],
    ],
];

Company:php:


namespace App\Models;

use ...

class Company extends Model
{
    protected  $fillable = [
        'user_id',
        'name',
        'short_name',
        'type',
    ];
    public static array $allowedFields = [
        'name'
    ];

    // Which fields can be used to sort the results through the query string
    public static array $allowedSorts = [
        'name',
        'created_at'
    ];

    // Which fields can be used to filter the results through the query string
    public static array $allowedFilters = [
        'name'
    ];
    public function token(): HasMany
    {
        return $this->hasMany(Token::class);
    }
    public function users(): BelongsToMany
    {
        return $this->belongsToMany(User::class);
    }
    //other functions ...

User.php:

<?php

namespace App\Models;

use ...

class User extends Authenticatable implements HasTenants
{
    use HasApiTokens, HasFactory, Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array<int, string>
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array<int, string>
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * The attributes that should be cast.
     *
     * @var array<string, string>
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
        'password' => 'hashed',
    ];
    public function getTenants(Panel $panel): Collection
    {
        return $this->companies;
    }

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

    public function canAccessTenant(Model $tenant): bool
    {
        return $this->companies->contains($tenant);
    }

    public function tokens(): HasMany
    {
        return $this->hasMany(Token::class);
    }
}

Package Version

3.0.2

PHP Version

8.1

Laravel Version

10.40

Which operating systems does with happen with?

Windows

Notes

Friends, your help is very important. I tried to contact the official filament discord, but to no avail. I really hope for you.

rupadana commented 5 months ago

I never didnt try tenancy, maybe u can use version ^1.0

rupadana commented 5 months ago

If its fixed by yourself, please make a PR

yahannes commented 5 months ago

OK, friend.Due to my time field, I will address the gaps and try to do a PR in 8 hours.And now I'm going to sleep.😊

yahannes commented 5 months ago

I tried to fix the problem, now I'm stuck on the error : image Now the keys are generated and written to the database. Here is some piece of code where the error is located : User.php:


namespace App\Models;

// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Filament\Models\Contracts\FilamentUser;
use Filament\Models\Contracts\HasTenants;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
use \Filament\Panel;
use \Illuminate\Database\Eloquent\Model;
use \Illuminate\Support\Collection;
use Rupadana\ApiService\Models\Token;

class User extends Authenticatable implements HasTenants
{
    use HasApiTokens, HasFactory, Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array<int, string>
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array<int, string>
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * The attributes that should be cast.
     *
     * @var array<string, string>
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
        'password' => 'hashed',
    ];
    public static array $allowedFields = [
        'name'
    ];

    public static array $allowedSorts = [
        'name',
        'created_at'
    ];

    public static array $allowedFilters = [
        'name'
    ];

    public function companyContacts(): HasMany
    {
        return $this->hasMany(CompanyContact::class);
    }
    public function getTenants(Panel $panel): Collection
    {
        return $this->companies;
    }

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

    public function canAccessTenant(Model $tenant): bool
    {
        return $this->companies->contains($tenant);
    }

    public function tokens(): HasMany
    {
        return $this->hasMany(Token::class);
    }
}

CompanyContact.php:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;

class CompanyContact extends Model
{
    protected $fillable =
        [
            'address',
            'phone',
            'email'

        ];

    public function company() : BelongsTo
    {
        return $this->belongsTo(Company::class);
    }

    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }
    use HasFactory, SoftDeletes;
}

Token.php:

<?php

namespace Rupadana\ApiService\Models;

use App\Models\Company;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Laravel\Sanctum\PersonalAccessToken;

class Token extends PersonalAccessToken
{
    use HasFactory;

    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }
    public function company(): BelongsTo
    {
        return $this->belongsTo(Company::class);
    }
    public static ?string $tenantOwnershipRelationshipName = 'company';
    protected $table = 'personal_access_tokens';
}

CompanyContactResource/Api/Handlers/PaginationHandler.php:

namespace App\Filament\Resources\CompanyContactResource\Api\Handlers;

use App\Models\CompanyContact;
use Illuminate\Http\Request;
use Rupadana\ApiService\Http\Handlers;
use Spatie\QueryBuilder\QueryBuilder;
use App\Filament\Resources\CompanyContactResource;

class PaginationHandler extends Handlers {
    public static string | null $uri = '/';
    public static string | null $resource = CompanyContactResource::class;

    public function handler()
    {
        $model = static::getModel();

        $query = QueryBuilder::for($model)
        ->allowedFields($model::$allowedFields ?? [])
        ->allowedSorts($model::$allowedSorts ?? [])
        ->allowedFilters($model::$allowedFilters ?? [])
        ->paginate(request()->query('per_page'))
        ->appends(request()->query());

        return static::getApiTransformer()::collection($query);
    }
}

I really need your help

rupadana commented 5 months ago

Mate, please try to remove this method in your User model

public function tokens(): HasMany { return $this->hasMany(Token::class); }