añadir funicionalidades de permisos y grupos
This commit is contained in:
@@ -2,7 +2,11 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
use AuthorizesRequests, ValidatesRequests; // <-- Traits esenciales
|
||||
}
|
||||
|
||||
@@ -4,6 +4,10 @@ namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Folder;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use App\Rules\UniqueFolderName;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
class FolderController extends Controller
|
||||
{
|
||||
@@ -50,9 +54,51 @@ class FolderController extends Controller
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, Folder $folder)
|
||||
public function update(Folder $folder, Request $request)
|
||||
{
|
||||
//
|
||||
try {
|
||||
// Verificar permisos
|
||||
if (!Gate::allows('update', $folder)) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'No tienes permisos para modificar esta carpeta'
|
||||
], Response::HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
// Validación
|
||||
$validator = Validator::make($request->all(), [
|
||||
'name' => [
|
||||
'required',
|
||||
'max:255',
|
||||
new UniqueFolderName(
|
||||
$folder->project_id,
|
||||
$folder->parent_id
|
||||
)
|
||||
]
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'errors' => $validator->errors()
|
||||
], Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
// Actualizar nombre
|
||||
$folder->update(['name' => $request->name]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Carpeta actualizada',
|
||||
'folder' => $folder
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Error al actualizar carpeta: ' . $e->getMessage()
|
||||
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,6 +106,91 @@ class FolderController extends Controller
|
||||
*/
|
||||
public function destroy(Folder $folder)
|
||||
{
|
||||
//
|
||||
try {
|
||||
// Verificar permisos
|
||||
if (!Gate::allows('delete', $folder)) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'No tienes permisos para eliminar esta carpeta'
|
||||
], Response::HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
// Validar que esté vacía
|
||||
if ($folder->documents()->exists() || $folder->children()->exists()) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'No puedes eliminar carpetas con contenido'
|
||||
], Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
// Eliminar
|
||||
$folder->delete();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Carpeta eliminada'
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Error al eliminar carpeta: ' . $e->getMessage()
|
||||
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the specified folder to a new location.
|
||||
*/
|
||||
public function move(Folder $folder, Request $request)
|
||||
{
|
||||
try {
|
||||
// Verificar permisos
|
||||
if (!Gate::allows('move', $folder)) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'No tienes permisos para esta acción'
|
||||
], Response::HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
// Validación
|
||||
$validator = Validator::make($request->all(), [
|
||||
'parent_id' => 'nullable|exists:folders,id',
|
||||
'project_id' => 'required|exists:projects,id'
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'errors' => $validator->errors()
|
||||
], Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
// Prevenir movimiento a sí mismo o descendientes
|
||||
if ($request->parent_id && $folder->isDescendantOf($request->parent_id)) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'No puedes mover una carpeta a su propia jerarquía'
|
||||
], Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
// Actualizar ubicación
|
||||
$folder->update([
|
||||
'parent_id' => $request->parent_id,
|
||||
'project_id' => $request->project_id
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Carpeta movida exitosamente',
|
||||
'folder' => $folder->fresh()
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Error al mover la carpeta: ' . $e->getMessage()
|
||||
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
65
app/Http/Controllers/GroupController.php
Normal file
65
app/Http/Controllers/GroupController.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Group;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class GroupController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(Group $group)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit(Group $group)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, Group $group)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(Group $group)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller; // <-- Asegúrate de tener esta línea
|
||||
use Illuminate\Http\Request;
|
||||
use Spatie\Permission\Models\Role;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
@@ -12,24 +13,36 @@ class RoleController extends Controller
|
||||
public function index()
|
||||
{
|
||||
$this->authorize('viewAny', Role::class);
|
||||
|
||||
$roles = Role::withCount('users')->paginate(10);
|
||||
return view('roles.index', compact('roles'));
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
$this->authorize('create', Role::class);
|
||||
$permissions = Permission::all()->groupBy('group');
|
||||
$this->authorize('create roles');
|
||||
$permissions = Permission::all(['id', 'name']);
|
||||
return view('roles.create', compact('permissions'));
|
||||
}
|
||||
|
||||
public function store(StoreRoleRequest $request)
|
||||
public function store(Request $request)
|
||||
{
|
||||
$role = Role::create($request->only('name'));
|
||||
/*$role = Role::create($request->only('name'));
|
||||
$role->syncPermissions($request->permissions);
|
||||
|
||||
return redirect()->route('roles.index')
|
||||
->with('success', 'Rol creado exitosamente');
|
||||
->with('success', 'Rol creado exitosamente');*/
|
||||
|
||||
$this->authorize('create', Role::class);
|
||||
|
||||
$request->validate([
|
||||
'name' => 'required|unique:roles',
|
||||
'description' => 'required'
|
||||
]);
|
||||
|
||||
Role::create($request->all());
|
||||
|
||||
return redirect()->route('roles.index');
|
||||
}
|
||||
|
||||
public function edit(Role $role)
|
||||
@@ -41,7 +54,7 @@ class RoleController extends Controller
|
||||
return view('roles.edit', compact('role', 'permissions', 'rolePermissions'));
|
||||
}
|
||||
|
||||
public function update(StoreRoleRequest $request, Role $role)
|
||||
public function update(Request $request, Role $role)
|
||||
{
|
||||
$role->update($request->only('name'));
|
||||
$role->syncPermissions($request->permissions);
|
||||
|
||||
27
app/Http/Middleware/CheckResourcePermissions.php
Normal file
27
app/Http/Middleware/CheckResourcePermissions.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class CheckResourcePermissions
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle($request, $next, $permission)
|
||||
{
|
||||
$resource = $request->route()->parameter('project')
|
||||
?? $request->route()->parameter('folder');
|
||||
|
||||
if (!$request->user()->hasPermissionToResource($resource, $permission)) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
82
app/Livewire/FileUpload.php
Normal file
82
app/Livewire/FileUpload.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\Document;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithFileUploads;
|
||||
use App\Models\Project;
|
||||
use App\Models\Folder;
|
||||
|
||||
class FileUpload extends Component
|
||||
{
|
||||
|
||||
use WithFileUploads;
|
||||
|
||||
public Project $project;
|
||||
public Folder|null $folder;
|
||||
public $files = [];
|
||||
public $previews = [];
|
||||
public $maxSize = 50; // MB
|
||||
public $folderId;
|
||||
|
||||
protected $listeners = ['folderChanged' => 'updateFolder'];
|
||||
|
||||
public function updateFolder($folderId)
|
||||
{
|
||||
$this->folder = Folder::find($folderId);
|
||||
}
|
||||
|
||||
public function updatedFiles()
|
||||
{
|
||||
$this->validate([
|
||||
'files.*' => "max:{$this->maxSize}1024|mimes:pdf,docx,xlsx,jpg,png,svg",
|
||||
'folderId' => 'required|exists:folders,id'
|
||||
]);
|
||||
|
||||
$this->previews = [];
|
||||
foreach ($this->files as $file) {
|
||||
$this->previews[] = [
|
||||
'name' => $file->getClientOriginalName(),
|
||||
'type' => $file->getMimeType(),
|
||||
'size' => $file->getSize(),
|
||||
'preview' => str_starts_with($file->getMimeType(), 'image/')
|
||||
? $file->temporaryUrl()
|
||||
: null
|
||||
];
|
||||
|
||||
Document::create([
|
||||
'name' => $file->getClientOriginalName(),
|
||||
'file_path' => $file->store("projects/{$this->folderId}"),
|
||||
'folder_id' => $this->folderId
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function save()
|
||||
{
|
||||
$this->validate([
|
||||
'files.*' => "required|max:{$this->maxSize}1024|mimes:pdf,docx,xlsx,jpg,png,svg"
|
||||
]);
|
||||
|
||||
foreach ($this->files as $file) {
|
||||
$path = $file->store("projects/{$this->project->id}/documents", 'public');
|
||||
|
||||
$this->project->documents()->create([
|
||||
'name' => $file->getClientOriginalName(),
|
||||
'file_path' => $path,
|
||||
'folder_id' => $this->folder?->id,
|
||||
'size' => $file->getSize(),
|
||||
'type' => $file->getMimeType()
|
||||
]);
|
||||
}
|
||||
|
||||
$this->reset(['files', 'previews']);
|
||||
$this->emit('documentsUpdated');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.file-upload');
|
||||
}
|
||||
}
|
||||
13
app/Livewire/GroupSearch.php
Normal file
13
app/Livewire/GroupSearch.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use Livewire\Component;
|
||||
|
||||
class GroupSearch extends Component
|
||||
{
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.group-search');
|
||||
}
|
||||
}
|
||||
101
app/Livewire/PermissionManager.php
Normal file
101
app/Livewire/PermissionManager.php
Normal file
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use Livewire\Component;
|
||||
use App\Models\Project;
|
||||
use App\Models\Folder;
|
||||
use App\Models\User;
|
||||
|
||||
class PermissionManager extends Component
|
||||
{
|
||||
public $selectedProject;
|
||||
public $selectedFolder;
|
||||
public $assignTo = 'user';
|
||||
public $selectedPermissions = [];
|
||||
public $selectedUser;
|
||||
public $selectedGroup;
|
||||
|
||||
protected $listeners = ['userSelected', 'groupSelected'];
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'selectedProject' => 'required|exists:projects,id',
|
||||
'selectedFolder' => 'nullable|exists:folders,id',
|
||||
'selectedPermissions' => 'required|array|min:1',
|
||||
'selectedUser' => 'required_if:assignTo,user',
|
||||
'selectedGroup' => 'required_if:assignTo,group',
|
||||
];
|
||||
}
|
||||
|
||||
public function getSelectedResourceIdProperty()
|
||||
{
|
||||
return $this->selectedFolder ?: $this->selectedProject;
|
||||
}
|
||||
|
||||
public function getCanSaveProperty()
|
||||
{
|
||||
return $this->selectedProject &&
|
||||
($this->assignTo === 'group' ? $this->selectedGroup : $this->selectedUser) &&
|
||||
count($this->selectedPermissions) > 0;
|
||||
}
|
||||
|
||||
public function getProjectsProperty()
|
||||
{
|
||||
return Project::accessibleBy(auth()->user())->get();
|
||||
}
|
||||
|
||||
public function getFoldersProperty()
|
||||
{
|
||||
if (!$this->selectedProject) return collect();
|
||||
|
||||
return Folder::where('project_id', $this->selectedProject)
|
||||
->withDepth()
|
||||
->get()
|
||||
->toTree();
|
||||
}
|
||||
|
||||
public function getPermissionsProperty()
|
||||
{
|
||||
$type = $this->selectedFolder ? 'folder' : 'project';
|
||||
return config("permissions.types.$type", []);
|
||||
}
|
||||
|
||||
public function userSelected($userId)
|
||||
{
|
||||
$this->selectedUser = $userId;
|
||||
}
|
||||
|
||||
public function groupSelected($groupId)
|
||||
{
|
||||
$this->selectedGroup = $groupId;
|
||||
}
|
||||
|
||||
public function savePermissions()
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
$model = $this->assignTo === 'user'
|
||||
? User::find($this->selectedUser)
|
||||
: Group::find($this->selectedGroup);
|
||||
|
||||
$resource = $this->selectedFolder
|
||||
? Folder::find($this->selectedFolder)
|
||||
: Project::find($this->selectedProject);
|
||||
|
||||
// Asignar permisos usando Spatie
|
||||
$model->givePermissionTo(
|
||||
$resource->permissions()->whereIn('name', $this->selectedPermissions)->get()
|
||||
);
|
||||
|
||||
$this->reset(['selectedPermissions']);
|
||||
$this->dispatch('permissionsUpdated');
|
||||
session()->flash('message', 'Permisos actualizados correctamente.');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.permission-manager');
|
||||
}
|
||||
}
|
||||
59
app/Livewire/PermissionsList.php
Normal file
59
app/Livewire/PermissionsList.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use Livewire\Component;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
use App\Models\{User, Group};
|
||||
|
||||
class PermissionsList extends Component
|
||||
{
|
||||
public $resourceId;
|
||||
public $resourceType;
|
||||
|
||||
public function mount($resourceId)
|
||||
{
|
||||
$this->resourceId = $resourceId;
|
||||
$this->determineResourceType();
|
||||
}
|
||||
|
||||
protected function determineResourceType()
|
||||
{
|
||||
if (Project::find($this->resourceId)) {
|
||||
$this->resourceType = 'project';
|
||||
} else {
|
||||
$this->resourceType = 'folder';
|
||||
}
|
||||
}
|
||||
|
||||
public function getPermissions()
|
||||
{
|
||||
return Permission::where('name', 'like', "{$this->resourceType}-{$this->resourceId}-%")
|
||||
->get()
|
||||
->groupBy(function ($permission) {
|
||||
return explode('-', $permission->name)[2]; // Obtener tipo de permiso
|
||||
});
|
||||
}
|
||||
|
||||
public function revokePermission($permissionId, $modelType, $modelId)
|
||||
{
|
||||
$permission = Permission::findOrFail($permissionId);
|
||||
|
||||
$model = $modelType === 'user'
|
||||
? User::find($modelId)
|
||||
: Group::find($modelId);
|
||||
|
||||
$model->revokePermissionTo($permission);
|
||||
|
||||
$this->dispatch('permissionsUpdated');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.permissions-list', [
|
||||
'permissions' => $this->getPermissions(),
|
||||
'users' => User::withPermissionsForResource($this->resourceId, $this->resourceType)->get(),
|
||||
'groups' => Group::withPermissionsForResource($this->resourceId, $this->resourceType)->get()
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -3,28 +3,40 @@
|
||||
namespace App\Livewire;
|
||||
|
||||
use Livewire\Component;
|
||||
use Livewire\Attributes\Title;
|
||||
use Livewire\WithFileUploads;
|
||||
use App\Models\Project;
|
||||
use App\Models\Folder;
|
||||
use App\Models\Document;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class ProjectShow extends Component
|
||||
{
|
||||
|
||||
use WithFileUploads;
|
||||
|
||||
public Project $project;
|
||||
public $selectedFolderId = null;
|
||||
public ?Folder $currentFolder = null;
|
||||
public $expandedFolders = [];
|
||||
public $files = [];
|
||||
public $folderName = '';
|
||||
|
||||
public $selectedFolderId = null;
|
||||
|
||||
|
||||
public function mount(Project $project)
|
||||
{
|
||||
$this->project = $project->load('rootFolders');
|
||||
$this->currentFolder = $this->project->rootFolders->first() ?? null;
|
||||
}
|
||||
|
||||
public function selectFolder($folderId)
|
||||
{
|
||||
$this->selectedFolderId = $folderId;
|
||||
$this->currentFolder = Folder::with('children')->find($folderId);
|
||||
}
|
||||
|
||||
public function toggleFolder($folderId)
|
||||
public function toggleFolder($folderId): void
|
||||
{
|
||||
if (in_array($folderId, $this->expandedFolders)) {
|
||||
$this->expandedFolders = array_diff($this->expandedFolders, [$folderId]);
|
||||
@@ -33,18 +45,82 @@ class ProjectShow extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function createFolder(): void
|
||||
{
|
||||
$this->validate([
|
||||
'folderName' => [
|
||||
'required',
|
||||
'max:255',
|
||||
Rule::unique('folders', 'name')->where(function ($query) {
|
||||
return $query->where('project_id', $this->project->id)
|
||||
->where('parent_id', $this->currentFolder?->id);
|
||||
})
|
||||
]
|
||||
]);
|
||||
|
||||
Folder::create([
|
||||
'name' => $this->folderName,
|
||||
'project_id' => $this->project->id,
|
||||
'parent_id' => $this->currentFolder?->id
|
||||
]);
|
||||
|
||||
$this->reset('folderName');
|
||||
$this->project->load('rootFolders'); // Recargar carpetas raíz
|
||||
if ($this->currentFolder) {
|
||||
$this->currentFolder->load('children'); // Recargar hijos si está en una subcarpeta
|
||||
}
|
||||
$this->project->refresh();
|
||||
}
|
||||
|
||||
public function uploadFiles(): void
|
||||
{
|
||||
$this->validate([
|
||||
'files.*' => 'file|max:10240|mimes:pdf,docx,xlsx,jpg,png'
|
||||
]);
|
||||
|
||||
foreach ($this->files as $file) {
|
||||
Document::create([
|
||||
'name' => $file->getClientOriginalName(),
|
||||
'file_path' => $file->store("projects/{$this->project->id}/documents"),
|
||||
'project_id' => $this->project->id,
|
||||
'folder_id' => $this->currentFolder?->id
|
||||
]);
|
||||
}
|
||||
|
||||
$this->reset('files');
|
||||
if ($this->currentFolder) {
|
||||
$this->currentFolder->refresh(); // Recargar documentos
|
||||
}
|
||||
$this->reset('files');
|
||||
}
|
||||
|
||||
public function getDocumentsProperty()
|
||||
{
|
||||
return Document::where('folder_id', $this->selectedFolderId)
|
||||
->where('project_id', $this->project->id)
|
||||
->with('versions')
|
||||
->get();
|
||||
return $this->currentFolder
|
||||
? $this->currentFolder->documents()->with('versions')->get()
|
||||
: Document::whereNull('folder_id')->where('project_id', $this->project->id)->with('versions')->get();
|
||||
}
|
||||
|
||||
public function getBreadcrumbsProperty()
|
||||
{
|
||||
if (!$this->currentFolder) return collect();
|
||||
|
||||
$breadcrumbs = collect();
|
||||
$folder = $this->currentFolder;
|
||||
|
||||
while ($folder) {
|
||||
$breadcrumbs->prepend($folder);
|
||||
$folder = $folder->parent;
|
||||
}
|
||||
|
||||
return $breadcrumbs;
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project-show', [
|
||||
'rootFolders' => $this->project->rootFolders
|
||||
]);
|
||||
return view('livewire.project-show')
|
||||
->layout('layouts.livewire-app', [
|
||||
'title' => $this->project->name
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
25
app/Livewire/Toolbar.php
Normal file
25
app/Livewire/Toolbar.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use Livewire\Component;
|
||||
use App\Models\Project;
|
||||
use App\Models\Folder;
|
||||
|
||||
class Toolbar extends Component
|
||||
{
|
||||
|
||||
public Project $project;
|
||||
public ?Folder $currentFolder;
|
||||
|
||||
public function mount(Project $project, Folder $currentFolder = null)
|
||||
{
|
||||
$this->project = $project;
|
||||
$this->currentFolder = $currentFolder;
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.toolbar');
|
||||
}
|
||||
}
|
||||
28
app/Livewire/UserSearch.php
Normal file
28
app/Livewire/UserSearch.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\User;
|
||||
use Livewire\Component;
|
||||
|
||||
class UserSearch extends Component
|
||||
{
|
||||
public $search = '';
|
||||
public $selectedUser;
|
||||
|
||||
public function selectUser($userId)
|
||||
{
|
||||
$this->selectedUser = $userId;
|
||||
$this->emitUp('userSelected', $userId);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$users = User::query()
|
||||
->when($this->search, fn($q) => $q->where('name', 'like', "%{$this->search}%"))
|
||||
->limit(5)
|
||||
->get();
|
||||
|
||||
return view('livewire.user-search', compact('users'));
|
||||
}
|
||||
}
|
||||
176
app/Models/Group.php
Normal file
176
app/Models/Group.php
Normal file
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Spatie\Permission\Traits\HasPermissions;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class Group extends Model
|
||||
{
|
||||
use SoftDeletes, HasPermissions;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description'
|
||||
];
|
||||
|
||||
/**
|
||||
* The accessors to append to the model's array form.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $appends = ['permission_names'];
|
||||
|
||||
/**
|
||||
* Relationship: Users belonging to this group
|
||||
*/
|
||||
public function users(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(User::class)
|
||||
->withTimestamps()
|
||||
->using(GroupUser::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship: Permissions assigned to this group
|
||||
*/
|
||||
public function permissions(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(
|
||||
config('permission.models.permission'),
|
||||
config('permission.table_names.group_has_permissions'),
|
||||
'group_id',
|
||||
'permission_id'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope: Groups with specific permission
|
||||
*/
|
||||
public function scopeWithPermission(Builder $query, $permission): Builder
|
||||
{
|
||||
return $query->whereHas('permissions', function ($q) use ($permission) {
|
||||
$q->where('name', $permission);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope: Groups with permissions on specific resource
|
||||
*/
|
||||
public function scopeWithResourcePermissions(Builder $query, $resourceId, $resourceType): Builder
|
||||
{
|
||||
return $query->whereHas('permissions', function ($q) use ($resourceId, $resourceType) {
|
||||
$q->where('name', 'like', "{$resourceType}-{$resourceId}-%");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign permission to group
|
||||
*/
|
||||
public function assignPermission($permission): self
|
||||
{
|
||||
if (is_string($permission)) {
|
||||
$permission = Permission::findByName($permission);
|
||||
}
|
||||
|
||||
$this->permissions()->syncWithoutDetaching($permission);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke permission from group
|
||||
*/
|
||||
public function revokePermission($permission): self
|
||||
{
|
||||
if (is_string($permission)) {
|
||||
$permission = Permission::findByName($permission);
|
||||
}
|
||||
|
||||
$this->permissions()->detach($permission);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync all permissions
|
||||
*/
|
||||
public function syncPermissions($permissions): self
|
||||
{
|
||||
$permissionIds = collect($permissions)->map(function ($perm) {
|
||||
return is_string($perm) ? Permission::findByName($perm)->id : $perm->id;
|
||||
});
|
||||
|
||||
$this->permissions()->sync($permissionIds);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if group has permission
|
||||
*/
|
||||
public function hasPermission($permission): bool
|
||||
{
|
||||
if (is_string($permission)) {
|
||||
return $this->permissions->contains('name', $permission);
|
||||
}
|
||||
|
||||
return $this->permissions->contains('id', $permission->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all users with permissions through this group
|
||||
*/
|
||||
public function getUsersWithPermissionsAttribute()
|
||||
{
|
||||
return User::whereHas('groups', function ($query) {
|
||||
$query->where('groups.id', $this->id);
|
||||
})->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get permission names attribute
|
||||
*/
|
||||
public function getPermissionNamesAttribute()
|
||||
{
|
||||
return $this->permissions->pluck('name');
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation rules
|
||||
*/
|
||||
public static function validationRules($groupId = null): array
|
||||
{
|
||||
return [
|
||||
'name' => 'required|string|max:255|unique:groups,name,'.$groupId,
|
||||
'description' => 'nullable|string|max:500',
|
||||
'permissions' => 'array',
|
||||
'permissions.*' => 'exists:permissions,id',
|
||||
'users' => 'array',
|
||||
'users.*' => 'exists:users,id'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* The "booting" method of the model
|
||||
*/
|
||||
protected static function boot()
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
static::deleting(function ($group) {
|
||||
if ($group->isForceDeleting()) {
|
||||
$group->users()->detach();
|
||||
$group->permissions()->detach();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,11 @@ class Project extends Model
|
||||
return $this->hasMany(Folder::class);
|
||||
}
|
||||
|
||||
public function currentFolder()
|
||||
{
|
||||
return $this->belongsTo(Folder::class);
|
||||
}
|
||||
|
||||
public function documents() {
|
||||
return $this->hasMany(Document::class);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Str;
|
||||
@@ -12,8 +13,7 @@ use Spatie\Permission\Traits\HasRoles;
|
||||
class User extends Authenticatable
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\UserFactory> */
|
||||
use HasFactory, Notifiable;
|
||||
use HasRoles;
|
||||
use HasFactory, Notifiable, HasRoles, SoftDeletes;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
@@ -60,4 +60,34 @@ class User extends Authenticatable
|
||||
->map(fn (string $name) => Str::of($name)->substr(0, 1))
|
||||
->implode('');
|
||||
}
|
||||
|
||||
public function groups()
|
||||
{
|
||||
return $this->belongsToMany(Group::class)
|
||||
->withTimestamps()
|
||||
->using(GroupUser::class);
|
||||
}
|
||||
|
||||
public function hasPermissionThroughGroup($permission)
|
||||
{
|
||||
return $this->groups->flatMap(function ($group) {
|
||||
return $group->permissions;
|
||||
})->contains('name', $permission);
|
||||
}
|
||||
|
||||
public function getAllPermissionsAttribute()
|
||||
{
|
||||
return $this->getAllPermissions()
|
||||
->merge($this->groups->flatMap->permissions)
|
||||
->unique('id');
|
||||
}
|
||||
|
||||
public function hasAnyPermission($permissions): bool
|
||||
{
|
||||
return $this->hasPermissionTo($permissions) ||
|
||||
$this->groups->contains(function ($group) use ($permissions) {
|
||||
return $group->hasAnyPermission($permissions);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,10 +4,13 @@ namespace App\Policies;
|
||||
|
||||
use App\Models\Document;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
use Illuminate\Auth\Access\Response;
|
||||
|
||||
class DocumentPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
/**
|
||||
* Determine whether the user can view any models.
|
||||
*/
|
||||
@@ -22,7 +25,8 @@ class DocumentPolicy
|
||||
public function view(User $user, Document $document)
|
||||
{
|
||||
return $user->hasPermissionTo('view documents')
|
||||
&& $user->hasProjectAccess($document->project_id);
|
||||
&& $user->hasProjectAccess($document->project_id)
|
||||
&& $user->hasPermissionToResource($document->resource(), 'view');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -38,7 +42,7 @@ class DocumentPolicy
|
||||
*/
|
||||
public function update(User $user, Document $document): bool
|
||||
{
|
||||
return false;
|
||||
return $user->hasPermissionToResource($document->resource(), 'edit');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -46,7 +50,7 @@ class DocumentPolicy
|
||||
*/
|
||||
public function delete(User $user, Document $document): bool
|
||||
{
|
||||
return false;
|
||||
return $user->hasPermissionTo('delete documents');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
39
app/Policies/FolderPolicy.php
Normal file
39
app/Policies/FolderPolicy.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\Folder;
|
||||
|
||||
class FolderPolicy
|
||||
{
|
||||
/**
|
||||
* Create a new policy instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function create(User $user, Folder $folder = null)
|
||||
{
|
||||
if ($folder) {
|
||||
return $user->can('manage-projects') &&
|
||||
$user->projects->contains($folder->project_id);
|
||||
}
|
||||
|
||||
return $user->can('manage-projects');
|
||||
}
|
||||
|
||||
public function move(User $user, Folder $folder)
|
||||
{
|
||||
return $user->can('manage-projects') &&
|
||||
$user->projects->contains($folder->project_id);
|
||||
}
|
||||
|
||||
public function delete(User $user, Folder $folder)
|
||||
{
|
||||
return $user->can('delete-projects') &&
|
||||
$user->projects->contains($folder->project_id);
|
||||
}
|
||||
}
|
||||
@@ -74,5 +74,10 @@ class ProjectPolicy
|
||||
$project->managers->contains($user->id) ||
|
||||
$project->users->contains($user->id);
|
||||
}
|
||||
|
||||
public function managePermissions(User $user, Project $project)
|
||||
{
|
||||
return $user->hasPermissionToResource($project, 'manage_permissions');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\Role;
|
||||
use Spatie\Permission\Models\Role;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Access\Response;
|
||||
|
||||
@@ -13,7 +13,7 @@ class RolePolicy
|
||||
*/
|
||||
public function viewAny(User $user): bool
|
||||
{
|
||||
return $user->hasPermissionTo('manage roles');
|
||||
return $user->hasPermissionTo('view roles');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -29,7 +29,7 @@ class RolePolicy
|
||||
*/
|
||||
public function create(User $user): bool
|
||||
{
|
||||
return $user->hasPermissionTo('manage roles');
|
||||
return $user->hasPermissionTo('create roles');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,7 +37,7 @@ class RolePolicy
|
||||
*/
|
||||
public function update(User $user, Role $role): bool
|
||||
{
|
||||
return $user->hasPermissionTo('manage roles') && !$role->is_protected;
|
||||
return $user->hasPermissionTo('edit roles') && !$role->is_protected;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -45,7 +45,7 @@ class RolePolicy
|
||||
*/
|
||||
public function delete(User $user, Role $role): bool
|
||||
{
|
||||
return $user->hasPermissionTo('manage roles') && !$role->is_protected;
|
||||
return $user->hasPermissionTo('delete roles') && !$role->is_protected;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,33 +2,14 @@
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Models\Document;
|
||||
use App\Models\Project;
|
||||
use App\Models\User;
|
||||
use App\Policies\DocumentPolicy;
|
||||
use App\Policies\PermissionPolicy;
|
||||
use App\Policies\ProfilePolicy;
|
||||
use App\Policies\ProjectPolicy;
|
||||
use App\Policies\RolePolicy;
|
||||
use App\Policies\UserPolicy;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Livewire\Livewire;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
||||
protected $policies = [
|
||||
User::class => UserPolicy::class,
|
||||
User::class => ProfilePolicy::class,
|
||||
Role::class => RolePolicy::class,
|
||||
Permission::class => PermissionPolicy::class,
|
||||
Document::class => DocumentPolicy::class,
|
||||
Project::class => ProjectPolicy::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
@@ -42,10 +23,23 @@ class AppServiceProvider extends ServiceProvider
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
// Configuración de componentes Blade
|
||||
Blade::componentNamespace('App\\View\\Components', 'icons');
|
||||
Blade::component('multiselect', \App\View\Components\Multiselect::class);
|
||||
|
||||
// Registro de componentes Livewire
|
||||
Livewire::component('project-show', \App\Http\Livewire\ProjectShow::class);
|
||||
Livewire::component('file-upload', \App\Http\Livewire\FileUpload::class);
|
||||
Livewire::component('toolbar', \App\Http\Livewire\Toolbar::class);
|
||||
|
||||
// Validación personalizada
|
||||
Validator::extend('max_upload_size', function ($attribute, $value, $parameters, $validator) {
|
||||
$maxSize = env('MAX_UPLOAD_SIZE', 51200); // 50MB por defecto
|
||||
$totalSize = array_reduce($value, function($sum, $file) {
|
||||
return $sum + $file->getSize();
|
||||
}, 0);
|
||||
|
||||
return $totalSize <= ($maxSize * 1024);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
66
app/Providers/AppServiceProvider.php.back
Normal file
66
app/Providers/AppServiceProvider.php.back
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Models\Document;
|
||||
use App\Models\Folder;
|
||||
use App\Models\Project;
|
||||
use App\Models\User;
|
||||
use App\Policies\DocumentPolicy;
|
||||
use App\Policies\FolderPolicy;
|
||||
use App\Policies\PermissionPolicy;
|
||||
use App\Policies\ProfilePolicy;
|
||||
use App\Policies\ProjectPolicy;
|
||||
use App\Policies\RolePolicy;
|
||||
use App\Policies\UserPolicy;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Livewire\Livewire;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
||||
protected $policies = [
|
||||
User::class => UserPolicy::class,
|
||||
User::class => ProfilePolicy::class,
|
||||
Role::class => RolePolicy::class,
|
||||
Permission::class => PermissionPolicy::class,
|
||||
Document::class => DocumentPolicy::class,
|
||||
Project::class => ProjectPolicy::class,
|
||||
Folder::class => FolderPolicy::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
Blade::componentNamespace('App\\View\\Components', 'icons');
|
||||
Blade::component('multiselect', \App\View\Components\Multiselect::class);
|
||||
|
||||
Livewire::component('project-show', \App\Http\Livewire\ProjectShow::class);
|
||||
Livewire::component('project-show', \App\Http\Livewire\FileUpload::class);
|
||||
Livewire::component('toolbar', \App\Http\Livewire\Toolbar::class);
|
||||
|
||||
Validator::extend('max_upload_size', function ($attribute, $value, $parameters, $validator) {
|
||||
$maxSize = env('MAX_UPLOAD_SIZE', 51200); // Default 50MB
|
||||
$totalSize = array_reduce($value, function($sum, $file) {
|
||||
return $sum + $file->getSize();
|
||||
}, 0);
|
||||
|
||||
return $totalSize <= ($maxSize * 1024);
|
||||
});
|
||||
}
|
||||
}
|
||||
62
app/Providers/AuthServiceProvider.php
Normal file
62
app/Providers/AuthServiceProvider.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
//use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use App\Models\User;
|
||||
use App\Models\Document;
|
||||
use App\Models\Project;
|
||||
use App\Models\Folder;
|
||||
use Spatie\Permission\Models\Role;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
use App\Policies\UserPolicy;
|
||||
use App\Policies\DocumentPolicy;
|
||||
use App\Policies\ProjectPolicy;
|
||||
use App\Policies\FolderPolicy;
|
||||
use App\Policies\RolePolicy;
|
||||
use App\Policies\PermissionPolicy;
|
||||
|
||||
class AuthServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
||||
/**
|
||||
* The policy mappings for the application.
|
||||
*
|
||||
* @var array<class-string, class-string>
|
||||
*/
|
||||
protected $policies = [
|
||||
User::class => UserPolicy::class,
|
||||
|
||||
Project::class => ProjectPolicy::class,
|
||||
Folder::class => FolderPolicy::class,
|
||||
Document::class => DocumentPolicy::class,
|
||||
|
||||
Role::class => RolePolicy::class,
|
||||
Permission::class => PermissionPolicy::class,
|
||||
];
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Register services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
$this->registerPolicies();
|
||||
|
||||
// Configuración adicional de gates aquí si es necesario
|
||||
Gate::before(function ($user, $ability) {
|
||||
return $user->hasRole('admin') ? true : null;
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user