team members and invitations
This commit is contained in:
@@ -2,7 +2,11 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
|
||||
abstract class Controller extends BaseController
|
||||
{
|
||||
//
|
||||
use AuthorizesRequests, ValidatesRequests;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ class IssueController extends Controller
|
||||
{
|
||||
public function index(Request $request, Project $project): View
|
||||
{
|
||||
$this->authorizeProject($request, $project);
|
||||
$this->authorize('view', $project);
|
||||
|
||||
$query = $project->issues();
|
||||
|
||||
@@ -69,7 +69,7 @@ class IssueController extends Controller
|
||||
|
||||
public function show(Request $request, Project $project, Issue $issue): View
|
||||
{
|
||||
$this->authorizeProject($request, $project);
|
||||
$this->authorize('view', $project);
|
||||
abort_unless($issue->project_id === $project->id, 404);
|
||||
|
||||
$environment = $request->query('environment');
|
||||
@@ -107,7 +107,7 @@ class IssueController extends Controller
|
||||
|
||||
public function updateStatus(Request $request, Project $project, Issue $issue): RedirectResponse
|
||||
{
|
||||
$this->authorizeProject($request, $project);
|
||||
$this->authorize('view', $project);
|
||||
abort_unless($issue->project_id === $project->id, 404);
|
||||
|
||||
$action = $request->input('action');
|
||||
@@ -125,7 +125,7 @@ class IssueController extends Controller
|
||||
|
||||
public function bulkAction(Request $request, Project $project): RedirectResponse
|
||||
{
|
||||
$this->authorizeProject($request, $project);
|
||||
$this->authorize('view', $project);
|
||||
|
||||
$validated = $request->validate([
|
||||
'action' => 'required|in:resolve,ignore,mute,delete',
|
||||
@@ -147,12 +147,4 @@ class IssueController extends Controller
|
||||
|
||||
return back()->with('status', "{$count} issue(s) {$validated['action']}d.");
|
||||
}
|
||||
|
||||
private function authorizeProject(Request $request, Project $project): void
|
||||
{
|
||||
abort_unless(
|
||||
$request->user()->projects()->where('projects.id', $project->id)->exists(),
|
||||
403,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,14 +43,14 @@ class ProjectController extends Controller
|
||||
|
||||
public function settings(Request $request, Project $project): View
|
||||
{
|
||||
$this->authorizeProject($request, $project);
|
||||
$this->authorize('view', $project);
|
||||
|
||||
return view('projects.settings', compact('project'));
|
||||
}
|
||||
|
||||
public function update(Request $request, Project $project): RedirectResponse
|
||||
{
|
||||
$this->authorizeProject($request, $project);
|
||||
$this->authorize('update', $project);
|
||||
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
@@ -64,7 +64,7 @@ class ProjectController extends Controller
|
||||
|
||||
public function regenerateKeys(Request $request, Project $project): RedirectResponse
|
||||
{
|
||||
$this->authorizeProject($request, $project);
|
||||
$this->authorize('manageSettings', $project);
|
||||
|
||||
$project->regenerateKeys();
|
||||
|
||||
@@ -73,18 +73,10 @@ class ProjectController extends Controller
|
||||
|
||||
public function destroy(Request $request, Project $project): RedirectResponse
|
||||
{
|
||||
$this->authorizeProject($request, $project);
|
||||
$this->authorize('delete', $project);
|
||||
|
||||
$project->delete();
|
||||
|
||||
return redirect()->route('projects.index')->with('status', 'Project deleted.');
|
||||
}
|
||||
|
||||
private function authorizeProject(Request $request, Project $project): void
|
||||
{
|
||||
abort_unless(
|
||||
$request->user()->projects()->where('projects.id', $project->id)->exists(),
|
||||
403,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Mail\ProjectInvitationMail;
|
||||
use App\Models\Project;
|
||||
use App\Models\ProjectInvitation;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class ProjectMemberController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display team members for a project
|
||||
*/
|
||||
public function index(Project $project): View
|
||||
{
|
||||
$this->authorize('view', $project);
|
||||
|
||||
$members = $project->users()
|
||||
->withPivot('role', 'created_at')
|
||||
->orderBy('project_user.created_at', 'desc')
|
||||
->get();
|
||||
|
||||
$pendingInvitations = $project->invitations()
|
||||
->whereNull('accepted_at')
|
||||
->where('expires_at', '>', now())
|
||||
->with('inviter')
|
||||
->orderBy('created_at', 'desc')
|
||||
->get();
|
||||
|
||||
return view('projects.members.index', [
|
||||
'project' => $project,
|
||||
'members' => $members,
|
||||
'pendingInvitations' => $pendingInvitations,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invite a new member to the project
|
||||
*/
|
||||
public function invite(Request $request, Project $project): RedirectResponse
|
||||
{
|
||||
$this->authorize('manageMembers', $project);
|
||||
|
||||
$validated = $request->validate([
|
||||
'email' => 'required|email',
|
||||
'role' => 'required|in:member,admin',
|
||||
]);
|
||||
|
||||
// Check if user is already a member
|
||||
$existingUser = User::where('email', $validated['email'])->first();
|
||||
if ($existingUser && $project->users()->where('users.id', $existingUser->id)->exists()) {
|
||||
return back()->withErrors(['email' => 'This user is already a member of the project.']);
|
||||
}
|
||||
|
||||
// Check if there's already a pending invitation
|
||||
$existingInvitation = $project->invitations()
|
||||
->where('email', $validated['email'])
|
||||
->whereNull('accepted_at')
|
||||
->where('expires_at', '>', now())
|
||||
->first();
|
||||
|
||||
if ($existingInvitation) {
|
||||
return back()->withErrors(['email' => 'An invitation has already been sent to this email.']);
|
||||
}
|
||||
|
||||
// Create invitation
|
||||
$invitation = $project->invitations()->create([
|
||||
'email' => $validated['email'],
|
||||
'role' => $validated['role'],
|
||||
'invited_by' => auth()->id(),
|
||||
]);
|
||||
|
||||
// Send invitation email
|
||||
Mail::to($invitation->email)->send(new ProjectInvitationMail($invitation));
|
||||
|
||||
return back()->with('success', 'Invitation sent successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept an invitation
|
||||
*/
|
||||
public function acceptInvitation(string $token): RedirectResponse
|
||||
{
|
||||
$invitation = ProjectInvitation::where('token', $token)->firstOrFail();
|
||||
|
||||
if ($invitation->isExpired()) {
|
||||
return redirect()->route('dashboard')->withErrors(['error' => 'This invitation has expired.']);
|
||||
}
|
||||
|
||||
if ($invitation->accepted_at) {
|
||||
return redirect()->route('dashboard')->withErrors(['error' => 'This invitation has already been accepted.']);
|
||||
}
|
||||
|
||||
// Check if the logged-in user's email matches
|
||||
if (auth()->user()->email !== $invitation->email) {
|
||||
return redirect()->route('dashboard')->withErrors(['error' => 'This invitation was sent to a different email address.']);
|
||||
}
|
||||
|
||||
$invitation->accept(auth()->user());
|
||||
|
||||
return redirect()->route('projects.issues.index', $invitation->project)
|
||||
->with('success', 'You have joined the project successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a member's role
|
||||
*/
|
||||
public function updateRole(Request $request, Project $project, User $user): RedirectResponse
|
||||
{
|
||||
$this->authorize('manageMembers', $project);
|
||||
|
||||
$validated = $request->validate([
|
||||
'role' => 'required|in:member,admin,owner',
|
||||
]);
|
||||
|
||||
// Don't allow changing your own role
|
||||
if ($user->id === auth()->id()) {
|
||||
return back()->withErrors(['error' => 'You cannot change your own role.']);
|
||||
}
|
||||
|
||||
// Check if user is a member of the project
|
||||
if (!$project->users()->where('users.id', $user->id)->exists()) {
|
||||
return back()->withErrors(['error' => 'User is not a member of this project.']);
|
||||
}
|
||||
|
||||
$project->users()->updateExistingPivot($user->id, ['role' => $validated['role']]);
|
||||
|
||||
return back()->with('success', 'Member role updated successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a member from the project
|
||||
*/
|
||||
public function destroy(Project $project, User $user): RedirectResponse
|
||||
{
|
||||
$this->authorize('manageMembers', $project);
|
||||
|
||||
// Don't allow removing yourself
|
||||
if ($user->id === auth()->id()) {
|
||||
return back()->withErrors(['error' => 'You cannot remove yourself from the project.']);
|
||||
}
|
||||
|
||||
// Check if user is a member
|
||||
if (!$project->users()->where('users.id', $user->id)->exists()) {
|
||||
return back()->withErrors(['error' => 'User is not a member of this project.']);
|
||||
}
|
||||
|
||||
$project->users()->detach($user->id);
|
||||
|
||||
return back()->with('success', 'Member removed from project successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel a pending invitation
|
||||
*/
|
||||
public function cancelInvitation(Project $project, ProjectInvitation $invitation): RedirectResponse
|
||||
{
|
||||
$this->authorize('manageMembers', $project);
|
||||
|
||||
if ($invitation->project_id !== $project->id) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
$invitation->delete();
|
||||
|
||||
return back()->with('success', 'Invitation cancelled successfully.');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user