From 61c381e37ab9d09df834ae8a1134df449b18fea1 Mon Sep 17 00:00:00 2001 From: Tuan Duong Date: Tue, 30 Apr 2019 17:29:09 +0700 Subject: [PATCH] Laravue permission feature - Apply package spatie/laravel-permission - Manage FE by permissions - Directive for permissions/roles - UI to manage permission for role/user --- README.md | 4 +- app/Http/Controllers/AuthController.php | 3 +- app/Http/Controllers/LaravueController.php | 5 + app/Http/Controllers/PermissionController.php | 76 ++++ app/Http/Controllers/RoleController.php | 99 +++++ app/Http/Controllers/UserController.php | 137 +++++-- app/Http/Kernel.php | 3 + .../{User.php => PermissionResource.php} | 6 +- app/Http/Resources/RoleResource.php | 23 ++ app/Http/Resources/UserResource.php | 30 ++ app/Laravue/Acl.php | 96 +++++ app/Laravue/Models/Permission.php | 31 ++ app/Laravue/Models/Role.php | 31 ++ app/Laravue/Models/User.php | 87 ++++ app/User.php | 38 +- composer.json | 3 +- composer.lock | 165 +++++--- config/auth.php | 2 +- config/permission.php | 129 ++++++ ..._04_20_125200_create_permission_tables.php | 102 +++++ ...19_04_20_130706_setup_role_permissions.php | 85 ++++ database/seeds/DatabaseSeeder.php | 48 ++- database/seeds/UsersTableSeeder.php | 17 +- resources/js/api/auth.js | 23 ++ resources/js/api/role.js | 17 + resources/js/api/user.js | 38 +- resources/js/components/LangSelect/index.vue | 2 +- .../js/directive/permission/permission.js | 11 +- resources/js/directive/role/index.js | 13 + resources/js/directive/role/role.js | 21 + resources/js/icons/svg/dollar.svg | 1 + resources/js/icons/svg/role.svg | 1 + resources/js/lang/en.js | 19 +- resources/js/lang/vi.js | 16 +- resources/js/permission.js | 20 +- resources/js/router/index.js | 8 +- resources/js/router/modules/admin.js | 31 +- resources/js/router/modules/charts.js | 1 + resources/js/router/modules/components.js | 1 + resources/js/router/modules/element-ui.js | 1 + resources/js/router/modules/excel.js | 1 + resources/js/router/modules/nested.js | 1 + resources/js/router/modules/permission.js | 6 +- resources/js/router/modules/table.js | 3 +- resources/js/store/getters.js | 1 + resources/js/store/modules/permission.js | 41 +- resources/js/store/modules/user.js | 18 +- resources/js/styles/laravue.scss | 8 + resources/js/utils/permission.js | 10 +- resources/js/utils/role.js | 22 ++ resources/js/views/dashboard/editor/index.vue | 2 +- resources/js/views/i18n/index.vue | 2 +- resources/js/views/login/index.vue | 8 +- resources/js/views/permission/Directive.vue | 79 +++- .../permission/components/SwitchRoles.vue | 43 +- resources/js/views/role-permission/List.vue | 208 ++++++++++ resources/js/views/users/Create.vue | 13 - resources/js/views/users/Edit.vue | 373 ------------------ resources/js/views/users/List.vue | 222 ++++++++++- resources/js/views/users/Profile.vue | 46 +++ .../views/users/components/UserActivity.vue | 290 ++++++++++++++ .../js/views/users/components/UserBio.vue | 65 +++ .../js/views/users/components/UserCard.vue | 94 +++++ routes/api.php | 8 +- 64 files changed, 2362 insertions(+), 646 deletions(-) create mode 100644 app/Http/Controllers/PermissionController.php create mode 100644 app/Http/Controllers/RoleController.php rename app/Http/Resources/{User.php => PermissionResource.php} (66%) create mode 100644 app/Http/Resources/RoleResource.php create mode 100644 app/Http/Resources/UserResource.php create mode 100644 app/Laravue/Acl.php create mode 100644 app/Laravue/Models/Permission.php create mode 100644 app/Laravue/Models/Role.php create mode 100644 app/Laravue/Models/User.php create mode 100644 config/permission.php create mode 100644 database/migrations/2019_04_20_125200_create_permission_tables.php create mode 100644 database/migrations/2019_04_20_130706_setup_role_permissions.php create mode 100644 resources/js/api/auth.js create mode 100644 resources/js/api/role.js create mode 100644 resources/js/directive/role/index.js create mode 100644 resources/js/directive/role/role.js create mode 100644 resources/js/icons/svg/dollar.svg create mode 100644 resources/js/icons/svg/role.svg create mode 100644 resources/js/utils/role.js create mode 100644 resources/js/views/role-permission/List.vue delete mode 100644 resources/js/views/users/Create.vue delete mode 100644 resources/js/views/users/Edit.vue create mode 100644 resources/js/views/users/Profile.vue create mode 100644 resources/js/views/users/components/UserActivity.vue create mode 100644 resources/js/views/users/components/UserBio.vue create mode 100644 resources/js/views/users/components/UserCard.vue diff --git a/README.md b/README.md index 1f5db6af2..87425d07c 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@ vue - vue + vue - element-ui + element-ui license diff --git a/app/Http/Controllers/AuthController.php b/app/Http/Controllers/AuthController.php index 957b81df2..4ec471b57 100644 --- a/app/Http/Controllers/AuthController.php +++ b/app/Http/Controllers/AuthController.php @@ -9,11 +9,10 @@ namespace App\Http\Controllers; use App\Laravue\JsonResponse; -use App\User; use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Support\Facades\Auth; -use App\Http\Resources\User as UserResource; +use App\Http\Resources\UserResource as UserResource; /** * Class AuthController diff --git a/app/Http/Controllers/LaravueController.php b/app/Http/Controllers/LaravueController.php index 65abf3b61..60ad2f4cd 100644 --- a/app/Http/Controllers/LaravueController.php +++ b/app/Http/Controllers/LaravueController.php @@ -11,6 +11,11 @@ use Illuminate\Http\Request; +/** + * Class LaravueController + * + * @package App\Http\Controllers + */ class LaravueController extends Controller { /** diff --git a/app/Http/Controllers/PermissionController.php b/app/Http/Controllers/PermissionController.php new file mode 100644 index 000000000..f39d10fa9 --- /dev/null +++ b/app/Http/Controllers/PermissionController.php @@ -0,0 +1,76 @@ + + * @package Laravue + * @version 1.0 + */ +namespace App\Http\Controllers; + +use App\Http\Resources\PermissionResource; +use Illuminate\Http\Request; +use App\Laravue\Models\Permission; + +/** + * Class PermissionController + * + * @package App\Http\Controllers + */ +class PermissionController extends Controller +{ + /** + * Display a listing of the resource. + * + * @return \Illuminate\Http\Response + */ + public function index() + { + return PermissionResource::collection(Permission::all()); + } + + /** + * Store a newly created resource in storage. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function store(Request $request) + { + // + } + + /** + * Display the specified resource. + * + * @param int $id + * @return \Illuminate\Http\Response + */ + public function show($id) + { + // + } + + /** + * Update the specified resource in storage. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\Response + */ + public function update(Request $request, $id) + { + // + } + + /** + * Remove the specified resource from storage. + * + * @param int $id + * @return \Illuminate\Http\Response + */ + public function destroy($id) + { + // + } +} diff --git a/app/Http/Controllers/RoleController.php b/app/Http/Controllers/RoleController.php new file mode 100644 index 000000000..280b4b6f0 --- /dev/null +++ b/app/Http/Controllers/RoleController.php @@ -0,0 +1,99 @@ + + * @package Laravue + * @version 1.0 + */ +namespace App\Http\Controllers; + +use App\Http\Resources\PermissionResource; +use App\Laravue\Models\Permission; +use App\Laravue\Models\User; +use Illuminate\Http\Request; +use App\Laravue\Models\Role; +use App\Http\Resources\RoleResource; +use Illuminate\Support\Facades\Auth; + +/** + * Class RoleController + * + * @package App\Http\Controllers + */ +class RoleController extends Controller +{ + /** + * Display a listing of the resource. + * + * @return \Illuminate\Http\Response + */ + public function index() + { + return RoleResource::collection(Role::all()); + } + + /** + * Store a newly created resource in storage. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function store(Request $request) + { + // + } + + /** + * Display the specified resource. + * + * @param Role + * @return \Illuminate\Http\Response + */ + public function show(Role $role) + { + // + } + + /** + * Update the specified resource in storage. + * + * @param \Illuminate\Http\Request $request + * @param Role $role + * @return \Illuminate\Http\Response + */ + public function update(Request $request, Role $role) + { + if ($role === null || $role->isAdmin()) { + return response()->json(['error' => 'Role not found'], 404); + } + + $permissionIds = $request->get('permissions', []); + $permissions = Permission::allowed()->whereIn('id', $permissionIds)->get(); + $role->syncPermissions($permissions); + $role->save(); + return new RoleResource($role); + } + + /** + * Remove the specified resource from storage. + * + * @param int $id + * @return \Illuminate\Http\Response + */ + public function destroy($id) + { + // + } + + /** + * Get permissions from role + * + * @param Role $role + * @return \Illuminate\Http\Response + */ + public function permissions(Role $role) + { + return PermissionResource::collection($role->permissions); + } +} diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index e5cb3a95a..60958325c 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -9,17 +9,30 @@ namespace App\Http\Controllers; -use App\Http\Resources\User; +use App\Http\Resources\PermissionResource; +use App\Http\Resources\UserResource; +use App\Laravue\Acl; +use App\Laravue\JsonResponse; +use App\Laravue\Models\Permission; +use App\Laravue\Models\Role; +use App\Laravue\Models\User; use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\ResourceCollection; use Illuminate\Support\Arr; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\Hash; use Illuminate\Validation\Rule; use Validator; +/** + * Class UserController + * + * @package App\Http\Controllers + */ class UserController extends Controller { const ITEM_PER_PAGE = 15; + /** * Display a listing of the user resource. * @@ -29,13 +42,13 @@ class UserController extends Controller public function index(Request $request) { $searchParams = $request->all(); - $userQuery = \App\User::query(); + $userQuery = User::query(); $limit = Arr::get($searchParams, 'limit', static::ITEM_PER_PAGE); $role = Arr::get($searchParams, 'role', ''); $keyword = Arr::get($searchParams, 'keyword', ''); if (!empty($role)) { - $userQuery->where('role', $role); + $userQuery->whereHas('roles', function($q) use ($role) { $q->where('name', $role); }); } if (!empty($keyword)) { @@ -43,7 +56,7 @@ public function index(Request $request) $userQuery->where('email', 'LIKE', '%' . $keyword . '%'); } - return User::collection($userQuery->paginate($limit)); + return UserResource::collection($userQuery->paginate($limit)); } /** @@ -70,73 +83,145 @@ public function store(Request $request) return response()->json(['errors' => $validator->errors()], 403); } else { $params = $request->all(); - $user = \App\User::create([ + $user = User::create([ 'name' => $params['name'], 'email' => $params['email'], - 'role' => $params['role'], 'password' => Hash::make($params['password']), ]); + $role = Role::findByName($params['role']); + $user->syncRoles($role); - return new User($user); + return new UserResource($user); } } /** * Display the specified resource. * - * @param int $id - * @return \Illuminate\Http\Response + * @param User $user + * @return UserResource|\Illuminate\Http\JsonResponse */ - public function show($id) + public function show(User $user) { - return new User(\App\User::find($id)); + return new UserResource($user); } /** * Update the specified resource in storage. * - * @param \Illuminate\Http\Request $request - * @param \App\User $user - * @return \Illuminate\Http\Response + * @param Request $request + * @param User $user + * @return UserResource|\Illuminate\Http\JsonResponse */ - public function update(Request $request, \App\User $user) + public function update(Request $request, User $user) { if ($user === null) { return response()->json(['error' => 'User not found'], 404); } + if ($user->isAdmin()) { + return response()->json(['error' => 'Admin can not be modified'], 403); + } - $validator = Validator::make($request->all(), $this->getValidationRules()); + $validator = Validator::make($request->all(), $this->getValidationRules(false)); if ($validator->fails()) { return response()->json(['errors' => $validator->errors()], 403); } else { + $email = $request->get('email'); + $found = User::where('email', $email)->first(); + if ($found && $found->id !== $user->id) { + return response()->json(['error' => 'Email has been taken'], 403); + } + $user->name = $request->get('name'); - $user->email = $request->get('email'); + $user->email = $email; $user->save(); - return new User($user); + return new UserResource($user); } } + /** + * Update the specified resource in storage. + * + * @param Request $request + * @param User $user + * @return UserResource|\Illuminate\Http\JsonResponse + */ + public function updatePermissions(Request $request, User $user) + { + if ($user === null) { + return response()->json(['error' => 'User not found'], 404); + } + + if ($user->isAdmin()) { + return response()->json(['error' => 'Admin can not be modified'], 403); + } + + $permissionIds = $request->get('permissions', []); + $rolePermissionIds = array_map( + function($permission) { + return $permission['id']; + }, + + $user->getPermissionsViaRoles()->toArray() + ); + + $newPermissionIds = array_diff($permissionIds, $rolePermissionIds); + $permissions = Permission::allowed()->whereIn('id', $newPermissionIds)->get(); + $user->syncPermissions($permissions); + return new UserResource($user); + } + /** * Remove the specified resource from storage. * - * @param \App\User $user + * @param User $user * @return \Illuminate\Http\Response */ - public function destroy(\App\User $user) + public function destroy(User $user) { - $user->delete(); + if ($user->isAdmin()) { + response()->json(['error' => 'Ehhh! Can not delete admin user'], 403); + } + + try { + $user->delete(); + } catch (\Exception $ex) { + response()->json(['error' => $ex->getMessage()], 403); + } + return response()->json(null, 204); } - private function getValidationRules() + /** + * Get permissions from role + * + * @param User $user + * @return array|\Illuminate\Http\Resources\Json\AnonymousResourceCollection + */ + public function permissions(User $user) + { + try { + return new JsonResponse([ + 'user' => PermissionResource::collection($user->getDirectPermissions()), + 'role' => PermissionResource::collection($user->getPermissionsViaRoles()), + ]); + } catch (\Exception $ex) { + response()->json(['error' => $ex->getMessage()], 403); + } + } + + /** + * @param bool $isNew + * @return array + */ + private function getValidationRules($isNew = true) { return [ 'name' => 'required', - 'email' => 'required|email', - 'role' => [ + 'email' => $isNew ? 'required|email|unique:users' : 'required|email', + 'roles' => [ 'required', - 'not_in:admin', - Rule::in(['admin', 'editor', 'user', 'guest']), + 'array' ], ]; } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index a3d8c48d5..ff108a4fb 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -60,6 +60,9 @@ class Kernel extends HttpKernel 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, + 'role' => \Spatie\Permission\Middlewares\RoleMiddleware::class, + 'permission' => \Spatie\Permission\Middlewares\PermissionMiddleware::class, + 'role_or_permission' => \Spatie\Permission\Middlewares\RoleOrPermissionMiddleware::class, ]; /** diff --git a/app/Http/Resources/User.php b/app/Http/Resources/PermissionResource.php similarity index 66% rename from app/Http/Resources/User.php rename to app/Http/Resources/PermissionResource.php index b63166708..3e12b1ccb 100644 --- a/app/Http/Resources/User.php +++ b/app/Http/Resources/PermissionResource.php @@ -4,7 +4,7 @@ use Illuminate\Http\Resources\Json\JsonResource; -class User extends JsonResource +class PermissionResource extends JsonResource { /** * Transform the resource into an array. @@ -17,10 +17,6 @@ public function toArray($request) return [ 'id' => $this->id, 'name' => $this->name, - 'email' => $this->email, - 'role' => $this->role, - 'roles' => [$this->role], - 'avatar' => 'http://i.pravatar.cc', ]; } } diff --git a/app/Http/Resources/RoleResource.php b/app/Http/Resources/RoleResource.php new file mode 100644 index 000000000..a19cf7a35 --- /dev/null +++ b/app/Http/Resources/RoleResource.php @@ -0,0 +1,23 @@ + $this->id, + 'name' => $this->name, + 'permissions' => PermissionResource::collection($this->permissions), + ]; + } +} diff --git a/app/Http/Resources/UserResource.php b/app/Http/Resources/UserResource.php new file mode 100644 index 000000000..d22353eda --- /dev/null +++ b/app/Http/Resources/UserResource.php @@ -0,0 +1,30 @@ + $this->id, + 'name' => $this->name, + 'email' => $this->email, + 'roles' => array_map(function ($role) { + return $role['name']; + }, $this->roles->toArray()), + 'permissions' => array_map(function ($permission) { + return $permission['name']; + }, $this->getAllPermissions()->toArray()), + 'avatar' => 'http://i.pravatar.cc', + ]; + } +} diff --git a/app/Laravue/Acl.php b/app/Laravue/Acl.php new file mode 100644 index 000000000..3e838fa2e --- /dev/null +++ b/app/Laravue/Acl.php @@ -0,0 +1,96 @@ + + * @package Laravue + * @version 1.0 + */ +namespace App\Laravue; + +use Illuminate\Support\Arr; +use Illuminate\Support\Str; + +/** + * Class Acl + * + * @package App\Laravue + */ +final class Acl +{ + const ROLE_ADMIN = 'admin'; + const ROLE_MANAGER = 'manager'; + const ROLE_EDITOR = 'editor'; + const ROLE_USER = 'user'; + const ROLE_VISITOR = 'visitor'; + + const PERMISSION_VIEW_MENU_ELEMENT_UI = 'view menu element ui'; + const PERMISSION_VIEW_MENU_PERMISSION = 'view menu permission'; + const PERMISSION_VIEW_MENU_COMPONENTS = 'view menu components'; + const PERMISSION_VIEW_MENU_CHARTS = 'view menu charts'; + const PERMISSION_VIEW_MENU_NESTED_ROUTES = 'view menu nested routes'; + const PERMISSION_VIEW_MENU_TABLE = 'view menu table'; + const PERMISSION_VIEW_MENU_ADMINISTRATOR = 'view menu administrator'; + const PERMISSION_VIEW_MENU_THEME = 'view menu theme'; + const PERMISSION_VIEW_MENU_CLIPBOARD = 'view menu clipboard'; + const PERMISSION_VIEW_MENU_EXCEL = 'view menu excel'; + const PERMISSION_VIEW_MENU_ZIP = 'view menu zip'; + const PERMISSION_VIEW_MENU_PDF = 'view menu pdf'; + const PERMISSION_VIEW_MENU_I18N = 'view menu i18n'; + + const PERMISSION_USER_MANAGE = 'manage user'; + const PERMISSION_ARTICLE_MANAGE = 'manage article'; + const PERMISSION_PERMISSION_MANAGE = 'manage permission'; + + /** + * @param array $exclusives Exclude some permissions from the list + * @return array + */ + public static function permissions(array $exclusives = []): array + { + try { + $class = new \ReflectionClass(__CLASS__); + $constants = $class->getConstants(); + $permissions = Arr::where($constants, function($value, $key) use ($exclusives) { + return !in_array($value, $exclusives) && Str::startsWith($key, 'PERMISSION_'); + }); + + return array_values($permissions); + } catch (\ReflectionException $exception) { + return []; + } + } + + public static function menuPermissions(): array + { + try { + $class = new \ReflectionClass(__CLASS__); + $constants = $class->getConstants(); + $permissions = Arr::where($constants, function($value, $key) { + return Str::startsWith($key, 'PERMISSION_VIEW_MENU_'); + }); + + return array_values($permissions); + } catch (\ReflectionException $exception) { + return []; + } + } + + /** + * @return array + */ + public static function roles(): array + { + try { + $class = new \ReflectionClass(__CLASS__); + $constants = $class->getConstants(); + $roles = Arr::where($constants, function($value, $key) { + return Str::startsWith($key, 'ROLE_'); + }); + + return array_values($roles); + } catch (\ReflectionException $exception) { + return []; + } + } +} diff --git a/app/Laravue/Models/Permission.php b/app/Laravue/Models/Permission.php new file mode 100644 index 000000000..9e6d2274f --- /dev/null +++ b/app/Laravue/Models/Permission.php @@ -0,0 +1,31 @@ + + * @package Laravue + * @version 1.0 + */ + +namespace App\Laravue\Models; +use App\Laravue\Acl; +use Illuminate\Database\Query\Builder; + +/** + * Class Permission + * + * @package App\Laravue\Models + */ +class Permission extends \Spatie\Permission\Models\Permission +{ + /** + * To exclude permission management from the list + * + * @param $query + * @return Builder + */ + public function scopeAllowed($query) + { + return $query->where('name', '!=', Acl::PERMISSION_PERMISSION_MANAGE); + } +} diff --git a/app/Laravue/Models/Role.php b/app/Laravue/Models/Role.php new file mode 100644 index 000000000..b2f85e9e3 --- /dev/null +++ b/app/Laravue/Models/Role.php @@ -0,0 +1,31 @@ + + * @package Laravue + * @version + */ +namespace App\Laravue\Models; + +use App\Laravue\Acl; +use Spatie\Permission\Models\Permission; + +/** + * Class Role + * + * @property Permission[] $permissions + * @property string $name + * @package App\Laravue\Models + */ +class Role extends \Spatie\Permission\Models\Role +{ + /** + * Check whether current role is admin + * @return bool + */ + public function isAdmin(): bool + { + return $this->name === Acl::ROLE_ADMIN; + } +} diff --git a/app/Laravue/Models/User.php b/app/Laravue/Models/User.php new file mode 100644 index 000000000..69ec73d31 --- /dev/null +++ b/app/Laravue/Models/User.php @@ -0,0 +1,87 @@ + 'datetime', + ]; + + /** + * Set permissions guard to API by default + * @var string + */ + protected $guard_name = 'api'; + + /** + * @inheritdoc + */ + public function getJWTIdentifier() + { + return $this->getKey(); + } + + /** + * @inheritdoc + */ + public function getJWTCustomClaims() + { + return []; + } + + /** + * @return bool + */ + public function isAdmin(): bool + { + foreach ($this->roles as $role) { + if ($role->isAdmin()) { + return true; + } + } + + return false; + } +} diff --git a/app/User.php b/app/User.php index 69571bd1e..0990df510 100644 --- a/app/User.php +++ b/app/User.php @@ -1,34 +1,19 @@ 'datetime', ]; - - /** - * @inheritdoc - */ - public function getJWTIdentifier() - { - return $this->getKey(); - } - - /** - * @inheritdoc - */ - public function getJWTCustomClaims() - { - return []; - } } diff --git a/composer.json b/composer.json index 259d500cc..b51ffefbf 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "tuandm/laravue", - "description": "The beautiful dashboard for Laravel buiit by VueJS.", + "description": "The beautiful dashboard for Laravel built by VueJS.", "keywords": [ "laravel", "laravue", @@ -24,6 +24,7 @@ "laravel/framework": "5.8.*", "laravel/tinker": "^1.0", "league/flysystem-aws-s3-v3": "^1.0", + "spatie/laravel-permission": "^2.37", "tymon/jwt-auth": "^1.0" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 42cc40d8b..c2906b7d2 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "06f6d0c08fccd69ab4d4ab58000ec94f", + "content-hash": "c78df90ce5a21206faeecc0e47f074d7", "packages": [ { "name": "aws/aws-sdk-php", - "version": "3.91.4", + "version": "3.92.1", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "41b67dce3c86da61137b47054d9d52c6ef57b5ec" + "reference": "94ecf9ad388d9fd04e8dfe492fd9484f94efb69e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/41b67dce3c86da61137b47054d9d52c6ef57b5ec", - "reference": "41b67dce3c86da61137b47054d9d52c6ef57b5ec", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/94ecf9ad388d9fd04e8dfe492fd9484f94efb69e", + "reference": "94ecf9ad388d9fd04e8dfe492fd9484f94efb69e", "shasum": "" }, "require": { @@ -86,7 +86,7 @@ "s3", "sdk" ], - "time": "2019-04-05T18:10:20+00:00" + "time": "2019-04-19T18:08:54+00:00" }, { "name": "dnoegel/php-xdg-base-dir", @@ -726,16 +726,16 @@ }, { "name": "laravel/framework", - "version": "v5.8.11", + "version": "v5.8.13", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "a2cf7a7983329d63edc6fde43142b232bb61aa0a" + "reference": "837e3823e2274d22b8e281d093391b5d9c23029a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/a2cf7a7983329d63edc6fde43142b232bb61aa0a", - "reference": "a2cf7a7983329d63edc6fde43142b232bb61aa0a", + "url": "https://api.github.com/repos/laravel/framework/zipball/837e3823e2274d22b8e281d093391b5d9c23029a", + "reference": "837e3823e2274d22b8e281d093391b5d9c23029a", "shasum": "" }, "require": { @@ -869,7 +869,7 @@ "framework", "laravel" ], - "time": "2019-04-10T13:05:18+00:00" + "time": "2019-04-18T15:28:45+00:00" }, { "name": "laravel/tinker", @@ -1321,16 +1321,16 @@ }, { "name": "nesbot/carbon", - "version": "2.16.3", + "version": "2.17.0", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "373d9f0d58651af366435148c39beb702c2b7ef4" + "reference": "9b49d637ad009e5e211142bc7492adcb19dbd645" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/373d9f0d58651af366435148c39beb702c2b7ef4", - "reference": "373d9f0d58651af366435148c39beb702c2b7ef4", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/9b49d637ad009e5e211142bc7492adcb19dbd645", + "reference": "9b49d637ad009e5e211142bc7492adcb19dbd645", "shasum": "" }, "require": { @@ -1340,9 +1340,9 @@ }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.14 || ^3.0", - "kylekatarnls/multi-tester": "^0.1", + "kylekatarnls/multi-tester": "^1.1", "phpmd/phpmd": "^2.6", - "phpstan/phpstan": "^0.10.8", + "phpstan/phpstan": "^0.11", "phpunit/phpunit": "^7.5 || ^8.0", "squizlabs/php_codesniffer": "^3.4" }, @@ -1377,7 +1377,7 @@ "datetime", "time" ], - "time": "2019-04-06T17:09:23+00:00" + "time": "2019-04-17T08:51:36+00:00" }, { "name": "nikic/php-parser", @@ -1976,6 +1976,71 @@ ], "time": "2018-07-19T23:38:55+00:00" }, + { + "name": "spatie/laravel-permission", + "version": "2.37.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-permission.git", + "reference": "81dbe9d372d70c255b66a2727a235076509f8d45" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-permission/zipball/81dbe9d372d70c255b66a2727a235076509f8d45", + "reference": "81dbe9d372d70c255b66a2727a235076509f8d45", + "shasum": "" + }, + "require": { + "illuminate/auth": "~5.3.0|~5.4.0|~5.5.0|~5.6.0|~5.7.0|~5.8.0", + "illuminate/container": "~5.3.0|~5.4.0|~5.5.0|~5.6.0|~5.7.0|~5.8.0", + "illuminate/contracts": "~5.3.0|~5.4.0|~5.5.0|~5.6.0|~5.7.0|~5.8.0", + "illuminate/database": "~5.4.0|~5.5.0|~5.6.0|~5.7.0|~5.8.0", + "php": ">=7.0" + }, + "require-dev": { + "orchestra/testbench": "~3.4.2|~3.5.0|~3.6.0|~3.7.0", + "phpunit/phpunit": "^5.7|6.2|^7.0", + "predis/predis": "^1.1" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\Permission\\PermissionServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Spatie\\Permission\\": "src" + }, + "files": [ + "src/helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Permission handling for Laravel 5.4 and up", + "homepage": "https://github.com/spatie/laravel-permission", + "keywords": [ + "acl", + "laravel", + "permission", + "security", + "spatie" + ], + "time": "2019-04-09T12:45:17+00:00" + }, { "name": "swiftmailer/swiftmailer", "version": "v6.2.0", @@ -2040,7 +2105,7 @@ }, { "name": "symfony/console", - "version": "v4.2.6", + "version": "v4.2.7", "source": { "type": "git", "url": "https://github.com/symfony/console.git", @@ -2180,7 +2245,7 @@ }, { "name": "symfony/css-selector", - "version": "v4.2.6", + "version": "v4.2.7", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", @@ -2233,7 +2298,7 @@ }, { "name": "symfony/debug", - "version": "v4.2.6", + "version": "v4.2.7", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", @@ -2289,7 +2354,7 @@ }, { "name": "symfony/event-dispatcher", - "version": "v4.2.6", + "version": "v4.2.7", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", @@ -2353,7 +2418,7 @@ }, { "name": "symfony/finder", - "version": "v4.2.6", + "version": "v4.2.7", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", @@ -2402,16 +2467,16 @@ }, { "name": "symfony/http-foundation", - "version": "v4.2.6", + "version": "v4.2.7", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "5b7ab6beaa5b053b8d3c9b13367ada9b292e12e1" + "reference": "6ebbe61f48069033225c9d3fa7eb5ed116d766d6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/5b7ab6beaa5b053b8d3c9b13367ada9b292e12e1", - "reference": "5b7ab6beaa5b053b8d3c9b13367ada9b292e12e1", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6ebbe61f48069033225c9d3fa7eb5ed116d766d6", + "reference": "6ebbe61f48069033225c9d3fa7eb5ed116d766d6", "shasum": "" }, "require": { @@ -2452,20 +2517,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2019-03-30T15:58:42+00:00" + "time": "2019-04-17T14:56:00+00:00" }, { "name": "symfony/http-kernel", - "version": "v4.2.6", + "version": "v4.2.7", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "72f5f8f9dd6e6fbda0220ded537610ad20fa2ce8" + "reference": "3db83303dbc1da9777e5ff63423b8b7fde423a1b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/72f5f8f9dd6e6fbda0220ded537610ad20fa2ce8", - "reference": "72f5f8f9dd6e6fbda0220ded537610ad20fa2ce8", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/3db83303dbc1da9777e5ff63423b8b7fde423a1b", + "reference": "3db83303dbc1da9777e5ff63423b8b7fde423a1b", "shasum": "" }, "require": { @@ -2541,7 +2606,7 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2019-04-16T07:20:25+00:00" + "time": "2019-04-17T16:17:13+00:00" }, { "name": "symfony/polyfill-ctype", @@ -2946,7 +3011,7 @@ }, { "name": "symfony/process", - "version": "v4.2.6", + "version": "v4.2.7", "source": { "type": "git", "url": "https://github.com/symfony/process.git", @@ -2995,16 +3060,16 @@ }, { "name": "symfony/routing", - "version": "v4.2.6", + "version": "v4.2.7", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "b9f16550d76897ab0a86c198f8008c6578a5068f" + "reference": "0e5719d216017b1a0342fa48e86467cedca1c954" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/b9f16550d76897ab0a86c198f8008c6578a5068f", - "reference": "b9f16550d76897ab0a86c198f8008c6578a5068f", + "url": "https://api.github.com/repos/symfony/routing/zipball/0e5719d216017b1a0342fa48e86467cedca1c954", + "reference": "0e5719d216017b1a0342fa48e86467cedca1c954", "shasum": "" }, "require": { @@ -3067,11 +3132,11 @@ "uri", "url" ], - "time": "2019-04-03T13:26:22+00:00" + "time": "2019-04-14T18:04:59+00:00" }, { "name": "symfony/translation", - "version": "v4.2.6", + "version": "v4.2.7", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", @@ -3146,16 +3211,16 @@ }, { "name": "symfony/var-dumper", - "version": "v4.2.6", + "version": "v4.2.7", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "f42850fa32b8d7a35a75510810f6ef597674be74" + "reference": "e760a38e12b15032325e64be63f7ffc1817af617" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/f42850fa32b8d7a35a75510810f6ef597674be74", - "reference": "f42850fa32b8d7a35a75510810f6ef597674be74", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/e760a38e12b15032325e64be63f7ffc1817af617", + "reference": "e760a38e12b15032325e64be63f7ffc1817af617", "shasum": "" }, "require": { @@ -3218,7 +3283,7 @@ "debug", "dump" ], - "time": "2019-04-11T11:27:41+00:00" + "time": "2019-04-17T14:57:01+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -4420,16 +4485,16 @@ }, { "name": "phpunit/phpunit", - "version": "7.5.8", + "version": "7.5.9", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "c29c0525cf4572c11efe1db49a8b8aee9dfac58a" + "reference": "134669cf0eeac3f79bc7f0c793efbc158bffc160" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c29c0525cf4572c11efe1db49a8b8aee9dfac58a", - "reference": "c29c0525cf4572c11efe1db49a8b8aee9dfac58a", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/134669cf0eeac3f79bc7f0c793efbc158bffc160", + "reference": "134669cf0eeac3f79bc7f0c793efbc158bffc160", "shasum": "" }, "require": { @@ -4500,7 +4565,7 @@ "testing", "xunit" ], - "time": "2019-03-26T13:23:54+00:00" + "time": "2019-04-19T15:50:46+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", diff --git a/config/auth.php b/config/auth.php index f04623496..34f9e3049 100644 --- a/config/auth.php +++ b/config/auth.php @@ -67,7 +67,7 @@ 'providers' => [ 'users' => [ 'driver' => 'eloquent', - 'model' => App\User::class, + 'model' => App\Laravue\Models\User::class, ], // 'users' => [ diff --git a/config/permission.php b/config/permission.php new file mode 100644 index 000000000..200c7e5b4 --- /dev/null +++ b/config/permission.php @@ -0,0 +1,129 @@ + [ + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * Eloquent model should be used to retrieve your permissions. Of course, it + * is often just the "Permission" model but you may use whatever you like. + * + * The model you want to use as a Permission model needs to implement the + * `Spatie\Permission\Contracts\Permission` contract. + */ + + 'permission' => App\Laravue\Models\Permission::class, + + /* + * When using the "HasRoles" trait from this package, we need to know which + * Eloquent model should be used to retrieve your roles. Of course, it + * is often just the "Role" model but you may use whatever you like. + * + * The model you want to use as a Role model needs to implement the + * `Spatie\Permission\Contracts\Role` contract. + */ + + 'role' => App\Laravue\Models\Role::class, + + ], + + 'table_names' => [ + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your roles. We have chosen a basic + * default value but you may easily change it to any table you like. + */ + + 'roles' => 'roles', + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * table should be used to retrieve your permissions. We have chosen a basic + * default value but you may easily change it to any table you like. + */ + + 'permissions' => 'permissions', + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * table should be used to retrieve your models permissions. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'model_has_permissions' => 'model_has_permissions', + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your models roles. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'model_has_roles' => 'model_has_roles', + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your roles permissions. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'role_has_permissions' => 'role_has_permissions', + ], + + 'column_names' => [ + + /* + * Change this if you want to name the related model primary key other than + * `model_id`. + * + * For example, this would be nice if your primary keys are all UUIDs. In + * that case, name this `model_uuid`. + */ + + 'model_morph_key' => 'model_id', + ], + + /* + * When set to true, the required permission/role names are added to the exception + * message. This could be considered an information leak in some contexts, so + * the default setting is false here for optimum safety. + */ + + 'display_permission_in_exception' => true, + + 'cache' => [ + + /* + * By default all permissions are cached for 24 hours to speed up performance. + * When permissions or roles are updated the cache is flushed automatically. + */ + + 'expiration_time' => \DateInterval::createFromDateString('24 hours'), + + /* + * The cache key used to store all permissions. + */ + + 'key' => 'spatie.permission.cache', + + /* + * When checking for a permission against a model by passing a Permission + * instance to the check, this key determines what attribute on the + * Permissions model is used to cache against. + * + * Ideally, this should match your preferred way of checking permissions, eg: + * `$user->can('view-posts')` would be 'name'. + */ + + 'model_key' => 'name', + + /* + * You may optionally indicate a specific cache driver to use for permission and + * role caching using any of the `store` drivers listed in the cache.php config + * file. Using 'default' here means to use the `default` set in cache.php. + */ + + 'store' => 'default', + ], +]; diff --git a/database/migrations/2019_04_20_125200_create_permission_tables.php b/database/migrations/2019_04_20_125200_create_permission_tables.php new file mode 100644 index 000000000..0fe3cac63 --- /dev/null +++ b/database/migrations/2019_04_20_125200_create_permission_tables.php @@ -0,0 +1,102 @@ +increments('id'); + $table->string('name'); + $table->string('guard_name'); + $table->timestamps(); + }); + + Schema::create($tableNames['roles'], function (Blueprint $table) { + $table->increments('id'); + $table->string('name'); + $table->string('guard_name'); + $table->timestamps(); + }); + + Schema::create($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames) { + $table->unsignedInteger('permission_id'); + + $table->string('model_type'); + $table->unsignedBigInteger($columnNames['model_morph_key']); + $table->index([$columnNames['model_morph_key'], 'model_type', ]); + + $table->foreign('permission_id') + ->references('id') + ->on($tableNames['permissions']) + ->onDelete('cascade'); + + $table->primary(['permission_id', $columnNames['model_morph_key'], 'model_type'], + 'model_has_permissions_permission_model_type_primary'); + }); + + Schema::create($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames) { + $table->unsignedInteger('role_id'); + + $table->string('model_type'); + $table->unsignedBigInteger($columnNames['model_morph_key']); + $table->index([$columnNames['model_morph_key'], 'model_type', ]); + + $table->foreign('role_id') + ->references('id') + ->on($tableNames['roles']) + ->onDelete('cascade'); + + $table->primary(['role_id', $columnNames['model_morph_key'], 'model_type'], + 'model_has_roles_role_model_type_primary'); + }); + + Schema::create($tableNames['role_has_permissions'], function (Blueprint $table) use ($tableNames) { + $table->unsignedInteger('permission_id'); + $table->unsignedInteger('role_id'); + + $table->foreign('permission_id') + ->references('id') + ->on($tableNames['permissions']) + ->onDelete('cascade'); + + $table->foreign('role_id') + ->references('id') + ->on($tableNames['roles']) + ->onDelete('cascade'); + + $table->primary(['permission_id', 'role_id']); + }); + + app('cache') + ->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null) + ->forget(config('permission.cache.key')); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + $tableNames = config('permission.table_names'); + + Schema::drop($tableNames['role_has_permissions']); + Schema::drop($tableNames['model_has_roles']); + Schema::drop($tableNames['model_has_permissions']); + Schema::drop($tableNames['roles']); + Schema::drop($tableNames['permissions']); + } +} diff --git a/database/migrations/2019_04_20_130706_setup_role_permissions.php b/database/migrations/2019_04_20_130706_setup_role_permissions.php new file mode 100644 index 000000000..79133f17c --- /dev/null +++ b/database/migrations/2019_04_20_130706_setup_role_permissions.php @@ -0,0 +1,85 @@ +givePermissionTo(Acl::permissions()); + $managerRole->givePermissionTo(Acl::permissions([Acl::PERMISSION_PERMISSION_MANAGE])); + $editorRole->givePermissionTo(Acl::menuPermissions()); + $editorRole->givePermissionTo(Acl::PERMISSION_ARTICLE_MANAGE); + $userRole->givePermissionTo([ + Acl::PERMISSION_VIEW_MENU_ELEMENT_UI, + Acl::PERMISSION_VIEW_MENU_PERMISSION, + ]); + $visitorRole->givePermissionTo([ + Acl::PERMISSION_VIEW_MENU_ELEMENT_UI, + Acl::PERMISSION_VIEW_MENU_PERMISSION, + ]); + + foreach (Acl::roles() as $role) { + /** @var \App\User[] $users */ + $users = \App\Laravue\Models\User::where('role', $role)->get(); + $role = Role::findByName($role); + foreach ($users as $user) { + $user->syncRoles($role); + } + } + + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('role'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + if (!Schema::hasColumn('users', 'role')) { + Schema::table('users', function (Blueprint $table) { + $table->string('role')->default('editor'); + }); + } + + /** @var \App\User[] $users */ + $users = \App\Laravue\Models\User::all(); + foreach ($users as $user) { + $roles = array_reverse(Acl::roles()); + foreach ($roles as $role) { + if ($user->hasRole($role)) { + $user->role = $role; + $user->save(); + } + } + } + } +} diff --git a/database/seeds/DatabaseSeeder.php b/database/seeds/DatabaseSeeder.php index 71efe3871..6a91fe284 100644 --- a/database/seeds/DatabaseSeeder.php +++ b/database/seeds/DatabaseSeeder.php @@ -1,9 +1,9 @@ 'Admin', - 'email' => 'admin@test.com', - 'password' => Hash::make('admin'), - 'role' => 'admin' + 'email' => 'admin@laravue.dev', + 'password' => Hash::make('laravue'), + ]); + $manager = User::create([ + 'name' => 'Manager', + 'email' => 'manager@laravue.dev', + 'password' => Hash::make('laravue'), ]); - User::create([ + $editor = User::create([ 'name' => 'Editor', - 'email' => 'editor@test.com', - 'password' => Hash::make('editor'), - 'role' => 'editor' + 'email' => 'editor@laravue.dev', + 'password' => Hash::make('laravue'), ]); - User::create([ + $user = User::create([ 'name' => 'User', - 'email' => 'user@test.com', - 'password' => Hash::make('user'), - 'role' => 'user' + 'email' => 'user@laravue.dev', + 'password' => Hash::make('laravue'), ]); - // $this->call(UsersTableSeeder::class); + $visitor = User::create([ + 'name' => 'Visitor', + 'email' => 'visitor@laravue.dev', + 'password' => Hash::make('laravue'), + ]); + + $adminRole = Role::findByName(\App\Laravue\Acl::ROLE_ADMIN); + $managerRole = Role::findByName(\App\Laravue\Acl::ROLE_MANAGER); + $editorRole = Role::findByName(\App\Laravue\Acl::ROLE_EDITOR); + $userRole = Role::findByName(\App\Laravue\Acl::ROLE_USER); + $visitorRole = Role::findByName(\App\Laravue\Acl::ROLE_VISITOR); + $admin->syncRoles($adminRole); + $manager->syncRoles($managerRole); + $editor->syncRoles($editorRole); + $user->syncRoles($userRole); + $visitor->syncRoles($visitorRole); + $this->call(UsersTableSeeder::class); } } diff --git a/database/seeds/UsersTableSeeder.php b/database/seeds/UsersTableSeeder.php index 8d7d28a88..9aaf36bee 100644 --- a/database/seeds/UsersTableSeeder.php +++ b/database/seeds/UsersTableSeeder.php @@ -1,6 +1,8 @@ $fullName, 'email' => strtolower($name) . '@laravue.dev', - 'password' => \Illuminate\Support\Facades\Hash::make(strtolower($name)), - 'role' => \App\Laravue\Faker::randomInArray(['editor', 'user']), + 'password' => \Illuminate\Support\Facades\Hash::make('laravue'), ]); + + $role = Role::findByName($roleName); + $user->syncRoles($role); } } } diff --git a/resources/js/api/auth.js b/resources/js/api/auth.js new file mode 100644 index 000000000..ac0d7c446 --- /dev/null +++ b/resources/js/api/auth.js @@ -0,0 +1,23 @@ +import request from '@/utils/request'; + +export function login(data) { + return request({ + url: '/auth/login', + method: 'post', + data: data, + }); +} + +export function getInfo(token) { + return request({ + url: '/auth/user', + method: 'get', + }); +} + +export function logout() { + return request({ + url: '/auth/logout', + method: 'post', + }); +} diff --git a/resources/js/api/role.js b/resources/js/api/role.js new file mode 100644 index 000000000..a8a2045e2 --- /dev/null +++ b/resources/js/api/role.js @@ -0,0 +1,17 @@ +import request from '@/utils/request'; +import Resource from '@/api/resource'; + +class RoleResource extends Resource { + constructor() { + super('roles'); + } + + permissions(id) { + return request({ + url: '/' + this.uri + '/' + id + '/permissions', + method: 'get', + }); + } +} + +export { RoleResource as default }; diff --git a/resources/js/api/user.js b/resources/js/api/user.js index ac0d7c446..5854c50f7 100644 --- a/resources/js/api/user.js +++ b/resources/js/api/user.js @@ -1,23 +1,25 @@ import request from '@/utils/request'; +import Resource from '@/api/resource'; -export function login(data) { - return request({ - url: '/auth/login', - method: 'post', - data: data, - }); -} +class UserResource extends Resource { + constructor() { + super('users'); + } -export function getInfo(token) { - return request({ - url: '/auth/user', - method: 'get', - }); -} + permissions(id) { + return request({ + url: '/' + this.uri + '/' + id + '/permissions', + method: 'get', + }); + } -export function logout() { - return request({ - url: '/auth/logout', - method: 'post', - }); + updatePermission(id, permissions) { + return request({ + url: '/' + this.uri + '/' + id + '/permissions', + method: 'put', + data: permissions, + }); + } } + +export { UserResource as default }; diff --git a/resources/js/components/LangSelect/index.vue b/resources/js/components/LangSelect/index.vue index c2d2737f8..02bddf106 100644 --- a/resources/js/components/LangSelect/index.vue +++ b/resources/js/components/LangSelect/index.vue @@ -21,7 +21,7 @@ export default { methods: { handleSetLanguage(lang) { this.$i18n.locale = lang; - this.$store.dispatch('setLanguage', lang); + this.$store.dispatch('app/setLanguage', lang); this.$message({ message: 'Switch Language Success', type: 'success', diff --git a/resources/js/directive/permission/permission.js b/resources/js/directive/permission/permission.js index f33a50704..10543e589 100644 --- a/resources/js/directive/permission/permission.js +++ b/resources/js/directive/permission/permission.js @@ -1,22 +1,21 @@ - import store from '@/store'; export default { inserted(el, binding, vnode) { const { value } = binding; - const roles = store.getters && store.getters.roles; + const permissions = store.getters && store.getters.permissions; if (value && value instanceof Array && value.length > 0) { - const permissionRoles = value; - const hasPermission = roles.some(role => { - return permissionRoles.includes(role); + const requiredPermissions = value; + const hasPermission = permissions.some(permission => { + return requiredPermissions.includes(permission); }); if (!hasPermission) { el.parentNode && el.parentNode.removeChild(el); } } else { - throw new Error(`Roles are required! Example: v-permission="['admin','editor']"`); + throw new Error(`Permissions are required! Example: v-permission="['manage user','manage permission']"`); } }, }; diff --git a/resources/js/directive/role/index.js b/resources/js/directive/role/index.js new file mode 100644 index 000000000..d6a569016 --- /dev/null +++ b/resources/js/directive/role/index.js @@ -0,0 +1,13 @@ +import role from './role'; + +const install = function(Vue) { + Vue.directive('role', role); +}; + +if (window.Vue) { + window['role'] = role; + Vue.use(install); // eslint-disable-line +} + +role.install = install; +export default role; diff --git a/resources/js/directive/role/role.js b/resources/js/directive/role/role.js new file mode 100644 index 000000000..496c79fef --- /dev/null +++ b/resources/js/directive/role/role.js @@ -0,0 +1,21 @@ +import store from '@/store'; + +export default { + inserted(el, binding, vnode) { + const { value } = binding; + const roles = store.getters && store.getters.roles; + + if (value && value instanceof Array && value.length > 0) { + const requiredRoles = value; + const hasRole = roles.some(role => { + return requiredRoles.includes(role); + }); + + if (!hasRole) { + el.parentNode && el.parentNode.removeChild(el); + } + } else { + throw new Error(`Roles are required! Example: v-role="['admin','editor']"`); + } + }, +}; diff --git a/resources/js/icons/svg/dollar.svg b/resources/js/icons/svg/dollar.svg new file mode 100644 index 000000000..07cd47661 --- /dev/null +++ b/resources/js/icons/svg/dollar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/js/icons/svg/role.svg b/resources/js/icons/svg/role.svg new file mode 100644 index 000000000..b047e5d0c --- /dev/null +++ b/resources/js/icons/svg/role.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/js/lang/en.js b/resources/js/lang/en.js index 093591386..e157d38b4 100644 --- a/resources/js/lang/en.js +++ b/resources/js/lang/en.js @@ -7,7 +7,7 @@ export default { permission: 'Permission', pagePermission: 'Page Permission', rolePermission: 'Role Permission', - directivePermission: 'Directive Permission', + directivePermission: 'Directives', icons: 'Icons', components: 'Components', componentIndex: 'Introduction', @@ -38,7 +38,7 @@ export default { 'menu1-2-2': 'Menu 1-2-2', 'menu1-3': 'Menu 1-3', menu2: 'Menu 2', - Table: 'Table', + table: 'Table', dynamicTable: 'Dynamic Table', dragTable: 'Drag Table', inlineEditTable: 'Inline Edit', @@ -69,7 +69,7 @@ export default { elementUi: 'Element UI', administrator: 'Administrator', users: 'Users', - editUser: 'Edit User', + userProfile: 'User Profile', }, navbar: { logOut: 'Log Out', @@ -98,7 +98,7 @@ export default { editPermission: 'Edit Permission', roles: 'Your roles', switchRoles: 'Switch roles', - tips: 'In some cases it is not suitable to use v-permission, such as element Tab component or el-table-column and other asynchronous rendering dom cases which can only be achieved by manually setting the v-if.', + tips: 'In some cases it is not suitable to use v-role/v-permission, such as element Tab component or el-table-column and other asynchronous rendering dom cases which can only be achieved by manually setting the v-if with checkRole or/and checkPermission.', delete: 'Delete', confirm: 'Confirm', cancel: 'Cancel', @@ -117,10 +117,12 @@ export default { imageUploadTips: 'Since I was using only the vue@1 version, and it is not compatible with mockjs at the moment, I modified it myself, and if you are going to use it, it is better to use official version.', }, table: { + description: 'Description', dynamicTips1: 'Fixed header, sorted by header order', dynamicTips2: 'Not fixed header, sorted by click order', dragTips1: 'The default order', dragTips2: 'The after dragging order', + name: 'Name', title: 'Title', importance: 'Imp', type: 'Type', @@ -186,4 +188,13 @@ export default { 'name': 'Name', 'email': 'Email', }, + roles: { + description: { + admin: 'Super Administrator. Have access and full permission to all pages.', + manager: 'Manager. Have access and permission to most of pages except permission page.', + editor: 'Editor. Have access to most of pages, full permission with articles and related resources.', + user: 'Normal user. Have access to some pages', + visitor: 'Visitor. Have access to static pages, should not have any writable permission', + }, + }, }; diff --git a/resources/js/lang/vi.js b/resources/js/lang/vi.js index 511a02a3e..951de588c 100644 --- a/resources/js/lang/vi.js +++ b/resources/js/lang/vi.js @@ -52,7 +52,7 @@ export default { form: 'Form', createArticle: 'Viết bài', editArticle: 'Chỉnh sửa', - articleList: 'Danh sách', + articleList: 'Tin tức', errorPages: 'Trang lỗi', page401: '401', page404: '404', @@ -70,7 +70,8 @@ export default { elementUi: 'Thành phần giao diện', administrator: 'Quản trị', users: 'Tài khoản', - editUser: 'Chỉnh sửa tài khoản', + userProfile: 'Tài khoản người dùng', + rolePermission: 'Quản lý quyền truy cập', }, navbar: { logOut: 'Đăng xuất', @@ -114,10 +115,12 @@ export default { imageUploadTips: 'Since I was using only the vue@1 version, and it is not compatible with mockjs at the moment, I modified it myself, and if you are going to use it, it is better to use official version.', }, table: { + description: 'Mô tả', dynamicTips1: 'Fixed header, sorted by header order', dynamicTips2: 'Not fixed header, sorted by click order', dragTips1: 'The default order', dragTips2: 'The after dragging order', + name: 'Tên', title: 'Tiêu đề', importance: 'IMP', type: 'Thể loại', @@ -176,4 +179,13 @@ export default { 'name': 'Tên', 'email': 'Địa chỉ email', }, + roles: { + description: { + admin: 'Super Administrator. Have access and full permission to all pages.', + manager: 'Manager. Have access and permission to most of pages except permission page.', + editor: 'Editor. Have access to most of pages, full permission with articles and related resources.', + user: 'Normal user. Have access to some pages', + visitor: 'Visitor. Have access to static pages, should not have any writable permission', + }, + }, }; diff --git a/resources/js/permission.js b/resources/js/permission.js index 20114c05f..a0824e555 100644 --- a/resources/js/permission.js +++ b/resources/js/permission.js @@ -32,17 +32,19 @@ router.beforeEach(async (to, from, next) => { } else { try { // get user info - // note: roles must be a object array! such as: ['admin'] or ,['developer','editor'] - const { roles } = await store.dispatch('user/getInfo'); - // generate accessible routes map based on roles - const accessRoutes = await store.dispatch('permission/generateRoutes', roles); + // note: roles must be a object array! such as: ['admin'] or ,['manager','editor'] + const { roles, permissions } = await store.dispatch('user/getInfo'); - // dynamically add accessible routes - router.addRoutes(accessRoutes); + // generate accessible routes map based on roles + // const accessRoutes = await store.dispatch('permission/generateRoutes', roles, permissions); + store.dispatch('permission/generateRoutes', { roles, permissions }).then(response => { + // dynamically add accessible routes + router.addRoutes(response); - // hack method to ensure that addRoutes is complete - // set the replace: true, so the navigation will not leave a history record - next({ ...to, replace: true }); + // hack method to ensure that addRoutes is complete + // set the replace: true, so the navigation will not leave a history record + next({ ...to, replace: true }); + }); } catch (error) { // remove token and go to login page to re-login await store.dispatch('user/resetToken'); diff --git a/resources/js/router/index.js b/resources/js/router/index.js index 5d15b501f..b1e0dc797 100644 --- a/resources/js/router/index.js +++ b/resources/js/router/index.js @@ -144,12 +144,13 @@ export const asyncRoutes = [ path: '/clipboard', component: Layout, redirect: 'noredirect', + meta: { permissions: ['view menu clipboard'] }, children: [ { path: 'index', component: () => import('@/views/clipboard/index'), name: 'ClipboardDemo', - meta: { title: 'clipboardDemo', icon: 'clipboard' }, + meta: { title: 'clipboardDemo', icon: 'clipboard', roles: ['admin', 'manager', 'editor', 'user'] }, }, ], }, @@ -160,7 +161,7 @@ export const asyncRoutes = [ component: Layout, redirect: '/zip/download', alwaysShow: true, - meta: { title: 'zip', icon: 'zip' }, + meta: { title: 'zip', icon: 'zip', permissions: ['view menu zip'] }, children: [ { path: 'download', @@ -174,7 +175,7 @@ export const asyncRoutes = [ path: '/pdf', component: Layout, redirect: '/pdf/index', - meta: { title: 'pdf', icon: 'pdf' }, + meta: { title: 'pdf', icon: 'pdf', permissions: ['view menu pdf'] }, children: [ { path: 'index', @@ -192,6 +193,7 @@ export const asyncRoutes = [ { path: '/i18n', component: Layout, + meta: { permissions: ['view menu i18n'] }, children: [ { path: 'index', diff --git a/resources/js/router/modules/admin.js b/resources/js/router/modules/admin.js index 6fb3b7295..9051ae72b 100644 --- a/resources/js/router/modules/admin.js +++ b/resources/js/router/modules/admin.js @@ -6,50 +6,53 @@ const adminRoutes = { component: Layout, redirect: '/administrator/users', name: 'Administrator', + alwaysShow: true, meta: { title: 'administrator', icon: 'admin', + permissions: ['view menu administrator'], }, children: [ - { - path: 'users/create', - component: () => import('@/views/users/Create'), - name: 'CreateUser', - meta: { title: 'createUser', icon: 'create-user', noCache: true }, - hidden: true, - }, + /** User managements */ { path: 'users/edit/:id(\\d+)', - component: () => import('@/views/users/Edit'), - name: 'EditUser', - meta: { title: 'editUser', noCache: true }, + component: () => import('@/views/users/Profile'), + name: 'UserProfile', + meta: { title: 'userProfile', noCache: true, permissions: ['manage user'] }, hidden: true, }, { path: 'users', component: () => import('@/views/users/List'), name: 'UserList', - meta: { title: 'users', icon: 'user' }, + meta: { title: 'users', icon: 'user', permissions: ['manage user'] }, + }, + /** Role and permission */ + { + path: 'roles', + component: () => import('@/views/role-permission/List'), + name: 'RoleList', + meta: { title: 'rolePermission', icon: 'role', permissions: ['manage permission'] }, }, { path: 'articles/create', component: () => import('@/views/articles/Create'), name: 'CreateArticle', - meta: { title: 'createArticle', icon: 'edit' }, + meta: { title: 'createArticle', icon: 'edit', permissions: ['manage article'] }, hidden: true, }, { path: 'articles/edit/:id(\\d+)', component: () => import('@/views/articles/Edit'), name: 'EditArticle', - meta: { title: 'editArticle', noCache: true }, + meta: { title: 'editArticle', noCache: true, permissions: ['manage article'] }, hidden: true, }, { path: 'articles', component: () => import('@/views/articles/List'), name: 'ArticleList', - meta: { title: 'articleList', icon: 'list' }, + meta: { title: 'articleList', icon: 'list', permissions: ['manage article'] }, }, ], }; diff --git a/resources/js/router/modules/charts.js b/resources/js/router/modules/charts.js index 8e68436a3..dade890f8 100644 --- a/resources/js/router/modules/charts.js +++ b/resources/js/router/modules/charts.js @@ -9,6 +9,7 @@ const chartsRoutes = { meta: { title: 'charts', icon: 'chart', + permissions: ['view menu charts'], }, children: [ { diff --git a/resources/js/router/modules/components.js b/resources/js/router/modules/components.js index 18527b6bd..76cd1d0bb 100644 --- a/resources/js/router/modules/components.js +++ b/resources/js/router/modules/components.js @@ -10,6 +10,7 @@ const componentRoutes = { meta: { title: 'components', icon: 'component', + permissions: ['view menu components'], }, children: [ { diff --git a/resources/js/router/modules/element-ui.js b/resources/js/router/modules/element-ui.js index d1073a2ad..1488fe7ed 100644 --- a/resources/js/router/modules/element-ui.js +++ b/resources/js/router/modules/element-ui.js @@ -8,6 +8,7 @@ const elementUiRoutes = { meta: { title: 'elementUi', icon: 'layout', + permissions: ['view menu element ui'], }, children: [ { diff --git a/resources/js/router/modules/excel.js b/resources/js/router/modules/excel.js index 9e33fc044..62ff5e601 100644 --- a/resources/js/router/modules/excel.js +++ b/resources/js/router/modules/excel.js @@ -9,6 +9,7 @@ const excelRoutes = { meta: { title: 'excel', icon: 'excel', + permissions: ['view menu excel'], }, children: [ { diff --git a/resources/js/router/modules/nested.js b/resources/js/router/modules/nested.js index d4c3bcd12..fbcf61c1b 100644 --- a/resources/js/router/modules/nested.js +++ b/resources/js/router/modules/nested.js @@ -9,6 +9,7 @@ const nestedRoutes = { meta: { title: 'nested', icon: 'nested', + permissions: ['view menu nested routes'], }, children: [ { diff --git a/resources/js/router/modules/permission.js b/resources/js/router/modules/permission.js index 40f2dc254..3b6112acf 100644 --- a/resources/js/router/modules/permission.js +++ b/resources/js/router/modules/permission.js @@ -8,7 +8,7 @@ const permissionRoutes = { meta: { title: 'permission', icon: 'lock', - roles: ['admin', 'editor'], // you can set roles in root nav + permissions: ['view menu permission'], }, children: [ { @@ -17,7 +17,7 @@ const permissionRoutes = { name: 'PagePermission', meta: { title: 'pagePermission', - roles: ['admin'], // or you can only set roles in sub nav + permissions: ['manage permission'], }, }, { @@ -26,7 +26,7 @@ const permissionRoutes = { name: 'DirectivePermission', meta: { title: 'directivePermission', - // if do not set roles, means: this page does not require permission + // if do not set roles neither permissions, means: this page does not require permission }, }, ], diff --git a/resources/js/router/modules/table.js b/resources/js/router/modules/table.js index 889246d80..5e94a6e97 100644 --- a/resources/js/router/modules/table.js +++ b/resources/js/router/modules/table.js @@ -6,8 +6,9 @@ const tableRoutes = { redirect: '/table/complex-table', name: 'Complex Table', meta: { - title: 'Table', + title: 'table', icon: 'table', + permissions: ['view menu table'], }, children: [ { diff --git a/resources/js/store/getters.js b/resources/js/store/getters.js index 751a746cb..47ef3ddc7 100644 --- a/resources/js/store/getters.js +++ b/resources/js/store/getters.js @@ -10,6 +10,7 @@ const getters = { name: state => state.user.name, introduction: state => state.user.introduction, roles: state => state.user.roles, + permissions: state => state.user.permissions, permission_routes: state => state.permission.routes, addRoutes: state => state.permission.addRoutes, }; diff --git a/resources/js/store/modules/permission.js b/resources/js/store/modules/permission.js index f69d6dad6..6e5438963 100644 --- a/resources/js/store/modules/permission.js +++ b/resources/js/store/modules/permission.js @@ -1,14 +1,32 @@ import { asyncRoutes, constantRoutes } from '@/router'; + /** * Check if it matches the current user right by meta.role - * @param roles + * @param {String[]} roles + * @param {String[]} permissions * @param route */ -function hasPermission(roles, route) { - if (route.meta && route.meta.roles) { - return roles.some(role => route.meta.roles.includes(role)); +function canAccess(roles, permissions, route) { + if (route.meta) { + let hasRole = true; + let hasPermission = true; + if (route.meta.roles || route.meta.permissions) { + // If it has meta.roles or meta.permissions, accessible = hasRole || permission + hasRole = false; + hasPermission = false; + if (route.meta.roles) { + hasRole = roles.some(role => route.meta.roles.includes(role)); + } + + if (route.meta.permissions) { + hasPermission = permissions.some(permission => route.meta.permissions.includes(permission)); + } + } + + return hasRole || hasPermission; } + // If no meta.roles/meta.permissions inputted - the route should be accessible return true; } @@ -17,14 +35,18 @@ function hasPermission(roles, route) { * @param routes asyncRoutes * @param roles */ -function filterAsyncRoutes(routes, roles) { +function filterAsyncRoutes(routes, roles, permissions) { const res = []; routes.forEach(route => { const tmp = { ...route }; - if (hasPermission(roles, tmp)) { + if (canAccess(roles, permissions, tmp)) { if (tmp.children) { - tmp.children = filterAsyncRoutes(tmp.children, roles); + tmp.children = filterAsyncRoutes( + tmp.children, + roles, + permissions + ); } res.push(tmp); } @@ -46,14 +68,15 @@ const mutations = { }; const actions = { - generateRoutes({ commit }, roles) { + generateRoutes({ commit }, { roles, permissions }) { return new Promise(resolve => { let accessedRoutes; if (roles.includes('admin')) { accessedRoutes = asyncRoutes; } else { - accessedRoutes = filterAsyncRoutes(asyncRoutes, roles); + accessedRoutes = filterAsyncRoutes(asyncRoutes, roles, permissions); } + commit('SET_ROUTES', accessedRoutes); resolve(accessedRoutes); }); diff --git a/resources/js/store/modules/user.js b/resources/js/store/modules/user.js index 679536f11..40c8357d8 100644 --- a/resources/js/store/modules/user.js +++ b/resources/js/store/modules/user.js @@ -1,6 +1,7 @@ -import { login, logout, getInfo } from '@/api/user'; +import { login, logout, getInfo } from '@/api/auth'; import { getToken, setToken, removeToken } from '@/utils/auth'; import router, { resetRouter } from '@/router'; +import store from '@/store'; const state = { token: getToken(), @@ -8,6 +9,7 @@ const state = { avatar: '', introduction: '', roles: [], + permissions: [], }; const mutations = { @@ -26,6 +28,9 @@ const mutations = { SET_ROLES: (state, roles) => { state.roles = roles; }, + SET_PERMISSIONS: (state, permissions) => { + state.permissions = permissions; + }, }; const actions = { @@ -56,13 +61,14 @@ const actions = { reject('Verification failed, please Login again.'); } - const { roles, name, avatar, introduction } = data; + const { roles, name, avatar, introduction, permissions } = data; // roles must be a non-empty array if (!roles || roles.length <= 0) { reject('getInfo: roles must be a non-null array!'); } commit('SET_ROLES', roles); + commit('SET_PERMISSIONS', permissions); commit('SET_NAME', name); commit('SET_AVATAR', avatar); commit('SET_INTRODUCTION', introduction); @@ -111,14 +117,14 @@ const actions = { // const { roles } = await dispatch('getInfo'); - const roles = [role]; + const roles = [role.name]; + const permissions = role.permissions.map(permission => permission.name); commit('SET_ROLES', roles); + commit('SET_PERMISSIONS', permissions); resetRouter(); // generate accessible routes map based on roles - const accessRoutes = await dispatch('permission/generateRoutes', roles, { - root: true, - }); + const accessRoutes = await store.dispatch('permission/generateRoutes', { roles, permissions }); // dynamically add accessible routes router.addRoutes(accessRoutes); diff --git a/resources/js/styles/laravue.scss b/resources/js/styles/laravue.scss index f9419dd4b..f19ff6c82 100644 --- a/resources/js/styles/laravue.scss +++ b/resources/js/styles/laravue.scss @@ -7,4 +7,12 @@ } .pull-right { float: right !important; +} + +.permissions-container { + .block { + .el-form-item__label { + padding-left: 24px; + } + } } \ No newline at end of file diff --git a/resources/js/utils/permission.js b/resources/js/utils/permission.js index f03ab07cb..fd51664d3 100644 --- a/resources/js/utils/permission.js +++ b/resources/js/utils/permission.js @@ -7,16 +7,16 @@ import store from '@/store'; */ export default function checkPermission(value) { if (value && value instanceof Array && value.length > 0) { - const roles = store.getters && store.getters.roles; - const permissionRoles = value; + const permissions = store.getters && store.getters.permissions; + const requiredPermissions = value; - const hasPermission = roles.some(role => { - return permissionRoles.includes(role); + const hasPermission = permissions.some(permission => { + return requiredPermissions.includes(permission); }); return hasPermission; } else { - console.error(`need roles! Like v-permission="['admin','editor']"`); + console.error(`Need permissions! Like v-permission="['manage permission','edit article']"`); return false; } } diff --git a/resources/js/utils/role.js b/resources/js/utils/role.js new file mode 100644 index 000000000..33b55f2b8 --- /dev/null +++ b/resources/js/utils/role.js @@ -0,0 +1,22 @@ +import store from '@/store'; + +/** + * @param {Array} value + * @returns {Boolean} + * @example see @/views/permission/Directive.vue + */ +export default function checkRole(value) { + if (value && value instanceof Array && value.length > 0) { + const roles = store.getters && store.getters.roles; + const requiredRoles = value; + + const hasRole = roles.some(role => { + return requiredRoles.includes(role); + }); + + return hasRole; + } else { + console.error(`Need roles! Like v-role="['admin','editor']"`); + return false; + } +} diff --git a/resources/js/views/dashboard/editor/index.vue b/resources/js/views/dashboard/editor/index.vue index 1dd11273d..06aa2a857 100644 --- a/resources/js/views/dashboard/editor/index.vue +++ b/resources/js/views/dashboard/editor/index.vue @@ -7,7 +7,7 @@
{{ name }} - Editor's Dashboard + {{ roles.join('|') }}'s Dashboard
diff --git a/resources/js/views/i18n/index.vue b/resources/js/views/i18n/index.vue index 7a59fc8dd..ee01c6ba7 100644 --- a/resources/js/views/i18n/index.vue +++ b/resources/js/views/i18n/index.vue @@ -89,7 +89,7 @@ export default { }, set(lang) { this.$i18n.locale = lang; - this.$store.dispatch('setLanguage', lang); + this.$store.dispatch('app/setLanguage', lang); }, }, }, diff --git a/resources/js/views/login/index.vue b/resources/js/views/login/index.vue index da8d1ddcf..45d72a313 100644 --- a/resources/js/views/login/index.vue +++ b/resources/js/views/login/index.vue @@ -30,8 +30,8 @@
- email: admin@test.com - password: admin + Email: admin@laravue.dev + Password: laravue
@@ -61,8 +61,8 @@ export default { }; return { loginForm: { - email: 'admin@test.com', - password: 'admin', + email: 'admin@laravue.dev', + password: 'laravue', }, loginRules: { email: [{ required: true, trigger: 'blur', validator: validateEmail }], diff --git a/resources/js/views/permission/Directive.vue b/resources/js/views/permission/Directive.vue index 510ead913..7e00d1a69 100644 --- a/resources/js/views/permission/Directive.vue +++ b/resources/js/views/permission/Directive.vue @@ -1,30 +1,58 @@ - + - + @@ -57,6 +60,36 @@ + +
+
+
+ + + + + +
+
+ + + + + +
+
+
+
+ + {{ $t('permission.cancel') }} + + + {{ $t('permission.confirm') }} + +
+
+
+
@@ -93,15 +126,19 @@ - diff --git a/resources/js/views/users/Profile.vue b/resources/js/views/users/Profile.vue new file mode 100644 index 000000000..2b3739da4 --- /dev/null +++ b/resources/js/views/users/Profile.vue @@ -0,0 +1,46 @@ + + + diff --git a/resources/js/views/users/components/UserActivity.vue b/resources/js/views/users/components/UserActivity.vue new file mode 100644 index 000000000..90bba8976 --- /dev/null +++ b/resources/js/views/users/components/UserActivity.vue @@ -0,0 +1,290 @@ + + + + + diff --git a/resources/js/views/users/components/UserBio.vue b/resources/js/views/users/components/UserBio.vue new file mode 100644 index 000000000..510fa7c99 --- /dev/null +++ b/resources/js/views/users/components/UserBio.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/resources/js/views/users/components/UserCard.vue b/resources/js/views/users/components/UserCard.vue new file mode 100644 index 000000000..483b7e0a8 --- /dev/null +++ b/resources/js/views/users/components/UserCard.vue @@ -0,0 +1,94 @@ + + + + + diff --git a/routes/api.php b/routes/api.php index 42f6adeaf..d9c19eafa 100644 --- a/routes/api.php +++ b/routes/api.php @@ -26,7 +26,13 @@ Route::post('auth/logout', 'AuthController@logout'); }); - Route::apiResource('users', 'UserController'); + Route::apiResource('users', 'UserController')->middleware('permission:' . \App\Laravue\Acl::PERMISSION_USER_MANAGE); + Route::get('users/{user}/permissions', 'UserController@permissions')->middleware('permission:' . \App\Laravue\Acl::PERMISSION_PERMISSION_MANAGE); + Route::put('users/{user}/permissions', 'UserController@updatePermissions')->middleware('permission:' . \App\Laravue\Acl::PERMISSION_PERMISSION_MANAGE); + Route::apiResource('roles', 'RoleController')->middleware('permission:' . \App\Laravue\Acl::PERMISSION_PERMISSION_MANAGE); + Route::get('roles/{role}/permissions', 'RoleController@permissions')->middleware('permission:' . \App\Laravue\Acl::PERMISSION_PERMISSION_MANAGE); + Route::apiResource('permissions', 'PermissionController')->middleware('permission:' . \App\Laravue\Acl::PERMISSION_PERMISSION_MANAGE); + // Fake APIs Route::get('/table/list', function () { $rowsNumber = mt_rand(20, 30);