Laravel

Multitenant Validation

Hi @alex

Thanks for being an amazing tutor, and i'm glad i subscribed to this website.

I have a problem with one of your course which is here https://codecourse.com/watch/laravel-multi-tenancy-basics?part=tenant-registration the problem has to do with validation. I am trying to validate unique email and also unique username for newly registered tenant from central domain.

My code looks like this

return [
            'username' => ['required', 'alpha_dash', 'string', 'max:40', 'without_spaces'],
            'email' => ['required', 'string', 'email', 'max:255', Rule::unique('users', 'email')->where('tenant_id', tenant('id'))],
            // 'email' => $tenant->unique('users'),
            'domain' => ['required', 'string', 'max:255', 'unique:domains'],
            'password' => ['required', Rules\Password::min(8)
                ->mixedCase()
                ->letters()
                ->numbers()
                ->symbols()],

            'username.without_spaces' => 'Whitespace not allowed.'
        ];

and i get this error

SQLSTATE[42S02]: Base table or view not found: 1146 Table 'tenancy.users' doesn't exist

How do i solve this please

henry75958
henry75958
0
7
584
alex
alex
Moderator

Hmm, this should work because you're scoping to the current tenant. Is the controller that you're validating in under the tenant group in your routes? Might be worth posting those too.

henry75958
henry75958

Hi @alex,

Thanks for your response

i have this in my controller

public function store(RegisterTenantRequest $request)
    {
        $tenant = Tenant::create($request->validated());
        $tenant->createDomain(['domain' => $request->domain]);
    }

i have this in my user model

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
use Stancl\Tenancy\Database\Concerns\HasScopedValidationRules;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable, HasScopedValidationRules;

and this in my Tenant model

use Stancl\Tenancy\Database\Models\Tenant as BaseTenant;
use Stancl\Tenancy\Contracts\TenantWithDatabase;
use Stancl\Tenancy\Database\Concerns\HasDatabase;
use Stancl\Tenancy\Database\Concerns\HasDomains;
use Stancl\Tenancy\Database\Concerns\HasScopedValidationRules;

class Tenant extends BaseTenant implements TenantWithDatabase
{
   use HasDatabase, HasDomains, HasScopedValidationRules;

and this in my user schema

public function up(): void
   {
       Schema::create('users', function (Blueprint $table) {
           $table->id();
           $table->unsignedBigInteger('tenant_id');
           $table->string('name')->nullable();
           $table->string('email')->unique();
           $table->string('username')->unique();
           $table->timestamp('email_verified_at')->nullable();
           $table->string('password');
           $table->rememberToken();
           $table->timestamps();

           $table->foreign('tenant_id')->references('id')->on('tenants')->onDelete('cascade');
       });
   }

Looking forward to your response

alex
alex
Moderator

Thanks for the additional code. Are you trying to do something differently to the course? With the initial tenant registration, this should happen in the tenants table of the main central application database.

Let me know if your code is hosted somewhere too, like GitHub, so I can have a really good look.

Tenancy is difficult!

henry75958
henry75958

Hi @alex thanks again for replying.

You know in the video we create a tenant and also register main user for the tenant.

What i am trying to do is validate the email and username of the users so that a user that sign up with the registration form can't sign up again with same email and username but different domain.

Here is my github linkhttps://github.com/oladejihenry/multi

alex
alex
Moderator

Thanks, I'll take a look at it over the next day or so for you!

henry75958
henry75958

Hi @alex

I was able to solve this by adding the HasScopedValidationRules to my Tenant model and did the following below also in the Tenant Model

protected $rules;

    public function __construct(array $attributes = [])
    {
        parent::__construct($attributes);

        $this->rules = [
            'email' => ['required', 'email', Rule::unique('tenants')->whereNull('deleted_at')],
            // Other validation rules for the Tenant model here
        ];
    }

Then i created a validation rules and i have this in it

public function passes($attribute, $value)
    {
        // Check if the email address is unique among tenants (within the JSON data)
        return !DB::table('tenants')->whereJsonContains('data->email', $value)->exists();
    }

    public function message()
    {
        return 'This email address has already been taken by another tenant.';
    }

Then passed that into my form request validation as this 'email' => ['required', 'string', 'email', 'max:255', new UniqueEmailForTenant],

Then it works by checking if an email is already taken by a tenant and pass the validation message to them

What do you think??

whoami (Daryl)
whoami (Daryl)

Cool!