refactor: split tickets, use route model binding, form requests and mass assignment

This commit is contained in:
Roardom
2023-07-02 08:49:45 +00:00
parent 0f1965260f
commit 18a6e7ae6c
18 changed files with 153 additions and 145 deletions
@@ -0,0 +1,45 @@
<?php
/**
* NOTICE OF LICENSE.
*
* UNIT3D Community Edition is open-sourced software licensed under the GNU Affero General Public License v3.0
* The details is bundled with this project in the file LICENSE.txt.
*
* @project UNIT3D Community Edition
*
* @author HDVinnie <hdinnovations@protonmail.com>
* @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
*/
namespace App\Http\Controllers;
use App\Models\Ticket;
use Illuminate\Http\Request;
class TicketAssigneeController extends Controller
{
final public function store(Request $request, Ticket $ticket): \Illuminate\Http\RedirectResponse
{
abort_unless($request->user()->group->is_modo, 403);
$ticket->update([
'staff_id' => $request->user()->id,
'staff_read' => 0,
]);
return to_route('tickets.show', ['ticket' => $ticket])
->withSuccess(trans('ticket.assigned-success'));
}
final public function destroy(Request $request, Ticket $ticket): \Illuminate\Http\RedirectResponse
{
abort_unless($request->user()->group->is_modo, 403);
$ticket->update([
'staff_id' => null,
]);
return to_route('tickets.show', ['ticket' => $ticket])
->withSuccess(trans('ticket.unassigned-success'));
}
}
@@ -13,15 +13,19 @@
namespace App\Http\Controllers;
use App\Models\Ticket;
use App\Models\TicketAttachment;
use Illuminate\Http\Request;
class TicketAttachmentController extends Controller
{
/**
* Download a ticket attachment from storage.
*/
final public function download(TicketAttachment $attachment): \Symfony\Component\HttpFoundation\BinaryFileResponse
final public function download(Request $request, Ticket $ticket, TicketAttachment $attachment): \Symfony\Component\HttpFoundation\BinaryFileResponse
{
abort_unless($request->user()->group->is_modo || $request->user()->id === $ticket->user_id, 403);
return response()->download(getcwd().'/files/attachments/attachments/'.$attachment->file_name)->deleteFileAfterSend(false);
}
}
+19 -118
View File
@@ -13,8 +13,8 @@
namespace App\Http\Controllers;
use App\Http\Requests\StoreTicketRequest;
use App\Models\Ticket;
use App\Models\TicketAttachment;
use App\Models\TicketCategory;
use App\Models\TicketPriority;
use Illuminate\Http\Request;
@@ -43,159 +43,60 @@ class TicketController extends Controller
/**
* Store a newly created resource in storage.
*/
final public function store(Request $request): \Illuminate\Http\RedirectResponse
final public function store(StoreTicketRequest $request): \Illuminate\Http\RedirectResponse
{
$user = $request->user();
$ticket = Ticket::create(['user_id' => $request->user()->id] + $request->validated());
$ticket = new Ticket();
$ticket->user_id = $user->id;
$ticket->category_id = $request->input('category');
$ticket->priority_id = $request->input('priority');
$ticket->subject = $request->input('subject');
$ticket->body = $request->input('body');
$v = validator($ticket->toArray(), [
'user_id' => 'required|exists:users,id',
'category_id' => 'required|exists:ticket_categories,id',
'priority_id' => 'required|exists:ticket_priorities,id',
'subject' => 'required',
'body' => 'required',
]);
if ($v->fails()) {
return to_route('tickets.create')
->withInput()
->withErrors($v->errors());
}
$ticket->save();
return to_route('tickets.show', ['id' => $ticket->id])
return to_route('tickets.show', ['ticket' => $ticket])
->withSuccess(trans('ticket.created-success'));
}
/**
* Display the specified resource.
*/
final public function show(Request $request, int $id): \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
final public function show(Request $request, Ticket $ticket): \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
{
$user = $request->user();
$ticket = Ticket::with(['comments'])->findOrFail($id);
abort_unless($user->group->is_modo || $user->id == $ticket->user_id, 403);
abort_unless($request->user()->group->is_modo || $request->user()->id === $ticket->user_id, 403);
if ($user->id == $ticket->user_id) {
if ($request->user()->id === $ticket->user_id) {
$ticket->user_read = 1;
$ticket->save();
}
if ($user->id == $ticket->staff_id) {
if ($request->user()->id === $ticket->staff_id) {
$ticket->staff_read = 1;
$ticket->save();
}
return view('ticket.show', [
'user' => $user,
'ticket' => $ticket,
]);
}
/**
* Update the specified resource in storage.
*/
final public function update(Request $request, int $id): \Illuminate\Http\RedirectResponse
{
$ticket = Ticket::findOrFail($id);
$user = $request->user();
abort_unless($user->group->is_modo || $user->id == $ticket->user_id, 403);
$ticket->category_id = $request->input('category');
$ticket->priority_id = $request->input('priority');
$ticket->subject = $request->input('subject');
$ticket->body = $request->input('body');
$v = validator($ticket->toArray(), [
'user_id' => 'required|exists:users,id',
'category_id' => 'required|exists:ticket_categories,id',
'priority_id' => 'required|exists:ticket_priorities,id',
'subject' => 'required',
'body' => 'required',
]);
if ($v->fails()) {
return to_route('tickets.create')
->withInput()
->withErrors($v->errors());
}
$ticket->save();
return to_route('tickets.show', ['id' => $ticket->id])
->withSuccess(trans('ticket.updated-success'));
return view('ticket.show', [
'user' => $request->user(),
'ticket' => $ticket->load('comments'),
]);
}
/**
* Remove the specified resource from storage.
*/
final public function destroy(Request $request, int $id): \Illuminate\Http\RedirectResponse
final public function destroy(Request $request, Ticket $ticket): \Illuminate\Http\RedirectResponse
{
$ticket = Ticket::findOrFail($id);
$user = $request->user();
abort_unless($user->group->is_modo, 403);
abort_unless($request->user()->group->is_modo, 403);
$ticket->comments()->delete();
TicketAttachment::where('ticket_id', '=', $id)->delete();
$ticket->attachments()->delete();
$ticket->delete();
return to_route('tickets.index')
->withSuccess(trans('ticket.deleted-success'));
}
final public function assign(Request $request, int $id): \Illuminate\Http\RedirectResponse
final public function close(Request $request, Ticket $ticket): \Illuminate\Http\RedirectResponse
{
$ticket = Ticket::findOrFail($id);
$user = $request->user();
abort_unless($user->group->is_modo, 403);
abort_unless($request->user()->group->is_modo || $request->user()->id === $ticket->user_id, 403);
$ticket->staff_id = $request->input('user_id');
$ticket->staff_read = 0;
$v = validator($ticket->toArray(), [
'user_id' => 'required|exists:users,id',
$ticket->update([
'closed_at' => now(),
]);
if ($v->fails()) {
return to_route('tickets.show', ['id' => $ticket->id])
->withErrors($v->errors());
}
$ticket->save();
return to_route('tickets.show', ['id' => $ticket->id])
->withSuccess(trans('ticket.assigned-success'));
}
final public function unassign(Request $request, int $id): \Illuminate\Http\RedirectResponse
{
$ticket = Ticket::findOrFail($id);
$user = $request->user();
abort_unless($user->group->is_modo, 403);
$ticket->staff_id = null;
$ticket->save();
return to_route('tickets.show', ['id' => $ticket->id])
->withSuccess(trans('ticket.unassigned-success'));
}
final public function close(Request $request, int $id): \Illuminate\Http\RedirectResponse
{
$ticket = Ticket::findOrFail($id);
$user = $request->user();
abort_unless($user->group->is_modo || $user->id == $ticket->user_id, 403);
$ticket->closed_at = now();
$ticket->save();
return to_route('tickets.index')
->withSuccess(trans('ticket.closed-success'));
}
+57
View File
@@ -0,0 +1,57 @@
<?php
/**
* NOTICE OF LICENSE.
*
* UNIT3D Community Edition is open-sourced software licensed under the GNU Affero General Public License v3.0
* The details is bundled with this project in the file LICENSE.txt.
*
* @project UNIT3D Community Edition
*
* @author Roardom <roardom@protonmail.com>
* @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
*/
namespace App\Http\Requests;
use App\Models\User;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
class StoreTicketRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*/
public function rules(Request $request): array
{
return [
'category_id' => [
'required',
'integer',
Rule::exists('ticket_categories', 'id'),
],
'priority_id' => [
'required',
'integer',
Rule::exists('ticket_priorities', 'id'),
],
'subject' => [
'required',
'max:255',
],
'body' => [
'required',
'max:65535',
],
];
}
}
+2
View File
@@ -16,6 +16,8 @@ class Ticket extends Model
'reminded_at' => 'datetime',
];
protected $guarded = [];
public function scopeStatus($query, $status)
{
if ($status === 'all') {
+1 -1
View File
@@ -45,7 +45,7 @@ class StaffCommentCreated extends Notification
return (new MailMessage())
->subject('A comment was added (Staff)')
->line('A comment was added')
->action('View Ticket', route('tickets.show', ['id' => $this->comment->ticket->id]));
->action('View Ticket', route('tickets.show', ['ticket' => $this->comment->ticket]));
}
/**
+1 -1
View File
@@ -45,7 +45,7 @@ class StaffTicketAssigned extends Notification
return (new MailMessage())
->subject('A ticket was assigned (Ticket # '.$this->ticket->id.')')
->line('A ticket was assigned to '.$this->ticket->staff->username)
->action('View Ticket', route('tickets.show', ['id' => $this->ticket->id]));
->action('View Ticket', route('tickets.show', ['ticket' => $this->ticket]));
}
/**
+1 -1
View File
@@ -45,7 +45,7 @@ class StaffTicketClosed extends Notification
return (new MailMessage())
->subject('A ticket was closed (Staff)')
->line('A ticket was closed')
->action('View Ticket', route('tickets.show', ['id' => $this->ticket->id]));
->action('View Ticket', route('tickets.show', ['ticket' => $this->ticket]));
}
/**
+1 -1
View File
@@ -45,7 +45,7 @@ class StaffTicketCreated extends Notification
return (new MailMessage())
->subject('A ticket was created (Ticket # '.$this->ticket->id.')')
->line('A ticket was created.')
->action('View Ticket', route('tickets.show', ['id' => $this->ticket->id]));
->action('View Ticket', route('tickets.show', ['ticket' => $this->ticket]));
}
/**
+1 -1
View File
@@ -45,7 +45,7 @@ class UserCommentCreated extends Notification
return (new MailMessage())
->subject('A comment was added (User)')
->line('A comment was added')
->action('View Ticket', route('tickets.show', ['id' => $this->comment->ticket->id]));
->action('View Ticket', route('tickets.show', ['ticket' => $this->comment->ticket]));
}
/**
+1 -1
View File
@@ -45,7 +45,7 @@ class UserTicketAssigned extends Notification
return (new MailMessage())
->subject('Your ticket was assigned (Ticket # '.$this->ticket->id.')')
->line('Your ticket was assigned to '.$this->ticket->user->username)
->action('View Ticket', route('tickets.show', ['id' => $this->ticket->id]));
->action('View Ticket', route('tickets.show', ['ticket' => $this->ticket]));
}
/**
+1 -1
View File
@@ -45,7 +45,7 @@ class UserTicketClosed extends Notification
return (new MailMessage())
->subject('Your ticket was closed (User)')
->line('Your ticket was closed')
->action('View Ticket', route('tickets.show', ['id' => $this->ticket->id]));
->action('View Ticket', route('tickets.show', ['ticket' => $this->ticket]));
}
/**
+1 -1
View File
@@ -45,7 +45,7 @@ class UserTicketCreated extends Notification
return (new MailMessage())
->subject('Your ticket was created (Ticket # '.$this->ticket->id.')')
->line('Your ticket was created.')
->action('View Ticket', route('tickets.show', ['id' => $this->ticket->id]));
->action('View Ticket', route('tickets.show', ['ticket' => $this->ticket]));
}
/**
+1 -1
View File
@@ -46,7 +46,7 @@ class UserTicketStale extends Notification
->cc($this->ticket->staff->email)
->subject('Your ticket is still open')
->line('This is a reminder that your ticket is still open')
->action('View Ticket', route('tickets.show', ['id' => $this->ticket->id]));
->action('View Ticket', route('tickets.show', ['ticket' => $this->ticket]));
}
/**
@@ -35,7 +35,7 @@
<menu class="data-table__actions">
<li class="data-table__action">
<form
action="{{ route('tickets.attachment.download', $attachment) }}"
action="{{ route('tickets.attachment.download', ['ticket' => $ticket, 'attachment' => $attachment]) }}"
method="POST"
>
@csrf
@@ -93,7 +93,7 @@
{{ $ticket->id }}
</td>
<td>
<a href="{{ route('tickets.show', ['id' => $ticket->id]) }}">{{ $ticket->subject }}</a>
<a href="{{ route('tickets.show', ['ticket' => $ticket]) }}">{{ $ticket->subject }}</a>
@if (auth()->user()->group->is_modo)
@php
$myTicketUnread = DB::table('tickets')
@@ -164,7 +164,7 @@
<td>
<menu class="data-table__actions">
<li class="data-table__action">
<form method="POST" action="{{ route('tickets.close', ['id' => $ticket->id]) }}">
<form method="POST" action="{{ route('tickets.close', ['ticket' => $ticket]) }}">
@csrf
<button class="form__button form__button--text">
{{ __('ticket.close') }}
+5 -4
View File
@@ -66,7 +66,7 @@
@if($user->group->is_modo)
<form
class="form form--horizontal"
action="{{ route('tickets.assign', ['id' => $ticket->id]) }}"
action="{{ route('tickets.assignee.store', ['ticket' => $ticket]) }}"
method="POST"
x-data
>
@@ -85,10 +85,11 @@
</form>
@if(! empty($ticket->staff_id))
<form
action="{{ route('tickets.unassign', ['id' => $ticket->id]) }}"
action="{{ route('tickets.assignee.destroy', ['ticket' => $ticket]) }}"
method="POST"
>
@csrf
@method('DELETE')
<p class="form__group form__group--horizontal">
<button class="form__button form__button--filled form__button--centered">
{{ __('ticket.unassign') }}
@@ -97,7 +98,7 @@
</form>
@endif
<form
action="{{ route('tickets.destroy', ['id' => $ticket->id]) }}"
action="{{ route('tickets.destroy', ['ticket' => $ticket]) }}"
method="POST"
>
@csrf
@@ -111,7 +112,7 @@
@endif
@if(empty($ticket->closed_at))
<form
action="{{ route('tickets.close', ['id' => $ticket->id]) }}"
action="{{ route('tickets.close', ['ticket' => $ticket]) }}"
method="POST"
>
<p class="form__group form__group--horizontal">
+8 -10
View File
@@ -272,16 +272,14 @@ Route::middleware('language')->group(function (): void {
Route::name('tickets.')->group(function (): void {
Route::get('/', [App\Http\Controllers\TicketController::class, 'index'])->name('index');
Route::get('/create', [App\Http\Controllers\TicketController::class, 'create'])->name('create');
Route::post('/store', [App\Http\Controllers\TicketController::class, 'store'])->name('store');
Route::get('/{id}', [App\Http\Controllers\TicketController::class, 'show'])->where('id', '[0-9]+')->name('show');
Route::get('/{id}/edit', [App\Http\Controllers\TicketController::class, 'edit'])->name('edit');
Route::patch('/{id}/update', [App\Http\Controllers\TicketController::class, 'update'])->name('update');
Route::delete('/{id}/destroy', [App\Http\Controllers\TicketController::class, 'destroy'])->name('destroy');
Route::post('/{id}/assign', [App\Http\Controllers\TicketController::class, 'assign'])->name('assign');
Route::post('/{id}/unassign', [App\Http\Controllers\TicketController::class, 'unassign'])->name('unassign');
Route::post('/{id}/close', [App\Http\Controllers\TicketController::class, 'close'])->name('close');
Route::post('/attachments/{attachment}/download', [App\Http\Controllers\TicketAttachmentController::class, 'download'])->name('attachment.download');
});
Route::post('/', [App\Http\Controllers\TicketController::class, 'store'])->name('store');
Route::get('/{ticket}', [App\Http\Controllers\TicketController::class, 'show'])->name('show');
Route::delete('/{ticket}', [App\Http\Controllers\TicketController::class, 'destroy'])->name('destroy');
Route::post('/{ticket}/assignee', [App\Http\Controllers\TicketAssigneeController::class, 'store'])->name('assignee.store');
Route::delete('/{ticket}/assignee', [App\Http\Controllers\TicketAssigneeController::class, 'destroy'])->name('assignee.destroy');
Route::post('/{ticket}/close', [App\Http\Controllers\TicketController::class, 'close'])->name('close');
Route::post('/{ticket}/attachments/{attachment}/download', [App\Http\Controllers\TicketAttachmentController::class, 'download'])->name('attachment.download');
})->scopeBindings();
});
// Missing System