add: full multi-tenancy control

This commit is contained in:
Cauê Faleiros
2026-02-02 15:31:15 -03:00
commit c6ec92802b
1711 changed files with 258106 additions and 0 deletions

1
packages/Webkul/User/.gitignore vendored Executable file
View File

@@ -0,0 +1 @@
/node_modules

View File

@@ -0,0 +1,27 @@
{
"name": "krayin/laravel-user",
"license": "MIT",
"authors": [
{
"name": "Jitendra Singh",
"email": "jitendra@webkul.com"
}
],
"require": {
"krayin/laravel-core": "^1.0"
},
"autoload": {
"psr-4": {
"Webkul\\User\\": "src/"
}
},
"extra": {
"laravel": {
"providers": [
"Webkul\\User\\Providers\\UserServiceProvider"
],
"aliases": {}
}
},
"minimum-stability": "dev"
}

View File

@@ -0,0 +1,5 @@
<?php
namespace Webkul\User\Contracts;
interface Group {}

View File

@@ -0,0 +1,5 @@
<?php
namespace Webkul\User\Contracts;
interface Role {}

View File

@@ -0,0 +1,5 @@
<?php
namespace Webkul\User\Contracts;
interface User {}

View File

@@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('groups', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('description')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('groups');
}
};

View File

@@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('roles', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('description')->nullable();
$table->string('permission_type');
$table->json('permissions')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('roles');
}
};

View File

@@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('email')->unique();
$table->string('password')->nullable();
$table->boolean('status')->default(0);
$table->integer('role_id')->unsigned();
$table->foreign('role_id')->references('id')->on('roles')->onDelete('cascade');
$table->rememberToken();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('users');
}
};

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('user_groups', function (Blueprint $table) {
$table->integer('group_id')->unsigned();
$table->foreign('group_id')->references('id')->on('groups')->onDelete('cascade');
$table->integer('user_id')->unsigned();
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('user_groups');
}
};

View File

@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('user_password_resets', function (Blueprint $table) {
$table->string('email')->index();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('user_password_resets');
}
};

View File

@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('groups', function (Blueprint $table) {
$table->unique('name');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('groups', function (Blueprint $table) {
$table->dropUnique('groups_name_unique');
});
}
};

View File

@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('image')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('image');
});
}
};

View File

@@ -0,0 +1,27 @@
<?php
namespace Webkul\User\Models;
use Illuminate\Database\Eloquent\Model;
use Webkul\User\Contracts\Group as GroupContract;
class Group extends Model implements GroupContract
{
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'description',
];
/**
* The users that belong to the group.
*/
public function users()
{
return $this->belongsToMany(UserProxy::modelClass(), 'user_groups');
}
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Webkul\User\Models;
use Konekt\Concord\Proxies\ModelProxy;
class GroupProxy extends ModelProxy {}

View File

@@ -0,0 +1,33 @@
<?php
namespace Webkul\User\Models;
use Illuminate\Database\Eloquent\Model;
use Webkul\User\Contracts\Role as RoleContract;
class Role extends Model implements RoleContract
{
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'description',
'permission_type',
'permissions',
];
protected $casts = [
'permissions' => 'array',
];
/**
* Get the users.
*/
public function users()
{
return $this->hasMany(UserProxy::modelClass());
}
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Webkul\User\Models;
use Konekt\Concord\Proxies\ModelProxy;
class RoleProxy extends ModelProxy {}

View File

@@ -0,0 +1,104 @@
<?php
namespace Webkul\User\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Storage;
use Laravel\Sanctum\HasApiTokens;
use Webkul\User\Contracts\User as UserContract;
class User extends Authenticatable implements UserContract
{
use HasApiTokens, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'email',
'image',
'password',
'api_token',
'role_id',
'status',
'view_permission',
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password',
'api_token',
'remember_token',
];
/**
* Get image url for the product image.
*/
public function image_url()
{
if (! $this->image) {
return;
}
return Storage::url($this->image);
}
/**
* Get image url for the product image.
*/
public function getImageUrlAttribute()
{
return $this->image_url();
}
/**
* @return array
*/
public function toArray()
{
$array = parent::toArray();
$array['image_url'] = $this->image_url;
return $array;
}
/**
* Get the role that owns the user.
*/
public function role()
{
return $this->belongsTo(RoleProxy::modelClass());
}
/**
* The groups that belong to the user.
*/
public function groups()
{
return $this->belongsToMany(GroupProxy::modelClass(), 'user_groups');
}
/**
* Checks if user has permission to perform certain action.
*
* @param string $permission
* @return bool
*/
public function hasPermission($permission)
{
if ($this->role->permission_type == 'custom' && ! $this->role->permissions) {
return false;
}
return in_array($permission, $this->role->permissions);
}
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Webkul\User\Models;
use Konekt\Concord\Proxies\ModelProxy;
class UserProxy extends ModelProxy {}

View File

@@ -0,0 +1,14 @@
<?php
namespace Webkul\User\Providers;
use Webkul\Core\Providers\BaseModuleServiceProvider;
class ModuleServiceProvider extends BaseModuleServiceProvider
{
protected $models = [
\Webkul\User\Models\Group::class,
\Webkul\User\Models\Role::class,
\Webkul\User\Models\User::class,
];
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Webkul\User\Providers;
use Illuminate\Routing\Router;
use Illuminate\Support\ServiceProvider;
class UserServiceProvider extends ServiceProvider
{
/**
* Bootstrap services.
*
* @return void
*/
public function boot(Router $router)
{
$this->loadMigrationsFrom(__DIR__.'/../Database/Migrations');
}
/**
* Register services.
*
* @return void
*/
public function register() {}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace Webkul\User\Repositories;
use Webkul\Core\Eloquent\Repository;
class GroupRepository extends Repository
{
/**
* Specify Model class name
*
* @return mixed
*/
public function model()
{
return 'Webkul\User\Contracts\Group';
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace Webkul\User\Repositories;
use Webkul\Core\Eloquent\Repository;
class RoleRepository extends Repository
{
/**
* Specify Model class name
*
* @return mixed
*/
public function model()
{
return 'Webkul\User\Contracts\Role';
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace Webkul\User\Repositories;
use Webkul\Core\Eloquent\Repository;
class UserRepository extends Repository
{
/**
* Searchable fields
*/
protected $fieldSearchable = [
'name',
'email',
'status',
'view_permission',
'role_id',
];
/**
* Specify Model class name
*
* @return mixed
*/
public function model()
{
return 'Webkul\User\Contracts\User';
}
/**
* This function will return user ids of current user's groups
*
* @return array
*/
public function getCurrentUserGroupsUserIds()
{
$userIds = $this->scopeQuery(function ($query) {
return $query->select('users.*')
->leftJoin('user_groups', 'users.id', '=', 'user_groups.user_id')
->leftJoin('groups', 'user_groups.group_id', 'groups.id')
->whereIn('groups.id', auth()->guard('user')->user()->groups()->pluck('id'));
})->get()->pluck('id')->toArray();
return $userIds;
}
}