You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
120 lines
4.1 KiB
120 lines
4.1 KiB
<?php |
|
|
|
namespace App\Http\Controllers\Api\V1; |
|
|
|
use App\Http\Controllers\Api\Concerns\RespondsWithJson; |
|
use App\Http\Controllers\Controller; |
|
use App\Models\Feedback; |
|
use App\Services\ZammadTicketService; |
|
use App\Support\ApiActor; |
|
use Illuminate\Http\JsonResponse; |
|
use Illuminate\Http\Request; |
|
use Illuminate\Validation\Rule; |
|
|
|
class FeedbackController extends Controller |
|
{ |
|
use RespondsWithJson; |
|
|
|
public function __construct( |
|
private readonly ZammadTicketService $zammadTickets, |
|
) { |
|
} |
|
|
|
public function store(Request $request): JsonResponse |
|
{ |
|
/** @var ApiActor $actor */ |
|
$actor = $request->attributes->get('api_actor'); |
|
|
|
$validated = $request->validate([ |
|
'type' => ['required', Rule::in(['bug', 'suggestion', 'other'])], |
|
'title' => ['required', 'string', 'max:120'], |
|
'message' => ['required', 'string', 'max:5000'], |
|
'context' => ['sometimes', 'array'], |
|
'context.page' => ['sometimes', 'string', 'max:500'], |
|
'context.url' => ['sometimes', 'string', 'max:1000'], |
|
'context.user_agent' => ['sometimes', 'string', 'max:500'], |
|
'context.reporter' => ['sometimes', 'string', 'max:100'], |
|
'context.reporter_name' => ['sometimes', 'string', 'max:200'], |
|
'context.reported_at' => ['sometimes', 'string'], |
|
]); |
|
|
|
$ctx = $validated['context'] ?? []; |
|
|
|
$feedback = Feedback::create([ |
|
'type' => $validated['type'], |
|
'title' => $validated['title'], |
|
'message' => $validated['message'], |
|
'status' => 'open', |
|
'reporter' => $ctx['reporter'] ?? ($actor?->identifier ?? null), |
|
'reporter_name' => $ctx['reporter_name'] ?? null, |
|
'page' => $ctx['page'] ?? null, |
|
'url' => $ctx['url'] ?? null, |
|
'user_agent' => $ctx['user_agent'] ?? null, |
|
'reported_at' => isset($ctx['reported_at']) ? now()->parse($ctx['reported_at']) : now(), |
|
]); |
|
|
|
$this->zammadTickets->createTicketFromFeedback($feedback, $actor); |
|
$feedback->refresh(); |
|
|
|
return $this->ok($this->serializeFeedback($feedback), [], 201); |
|
} |
|
|
|
public function index(Request $request): JsonResponse |
|
{ |
|
/** @var ApiActor $actor */ |
|
$actor = $request->attributes->get('api_actor'); |
|
|
|
if (!$actor?->is_admin) { |
|
return $this->fail('Forbidden', 'forbidden', 403); |
|
} |
|
|
|
$query = Feedback::query()->orderByDesc('created_at'); |
|
|
|
if ($request->filled('type')) { |
|
$query->where('type', $request->query('type')); |
|
} |
|
if ($request->filled('status')) { |
|
$query->where('status', $request->query('status')); |
|
} |
|
|
|
$items = $query->paginate(20); |
|
|
|
return $this->ok($items); |
|
} |
|
|
|
public function update(Request $request, int $id): JsonResponse |
|
{ |
|
/** @var ApiActor $actor */ |
|
$actor = $request->attributes->get('api_actor'); |
|
|
|
if (!$actor?->is_admin) { |
|
return $this->fail('Forbidden', 'forbidden', 403); |
|
} |
|
|
|
$feedback = Feedback::findOrFail($id); |
|
|
|
$validated = $request->validate([ |
|
'status' => ['sometimes', Rule::in(['open', 'in_progress', 'resolved', 'closed'])], |
|
'admin_note' => ['sometimes', 'nullable', 'string', 'max:2000'], |
|
]); |
|
|
|
$feedback->update($validated); |
|
|
|
return $this->ok($this->serializeFeedback($feedback)); |
|
} |
|
|
|
/** |
|
* @return array<string, mixed> |
|
*/ |
|
private function serializeFeedback(Feedback $feedback): array |
|
{ |
|
return [ |
|
'id' => $feedback->id, |
|
'status' => $feedback->status, |
|
'zammad_ticket_id' => $feedback->zammad_ticket_id, |
|
'zammad_ticket_number' => $feedback->zammad_ticket_number, |
|
'zammad_ticket_url' => $this->zammadTickets->ticketUrl($feedback), |
|
'zammad_sync_error' => $feedback->zammad_sync_error, |
|
]; |
|
} |
|
}
|
|
|