authorizeProject($request, $project); $query = $project->issues(); // Apply filters if ($status = $request->query('status')) { $query->where('status', $status); } if ($level = $request->query('level')) { $query->where('level', $level); } if ($environment = $request->query('environment')) { $query->whereHas('events', fn($q) => $q->where('environment', $environment)); } // Pagination $perPage = min((int) $request->query('per_page', 25), 100); $issues = $query->orderBy('last_seen_at', 'desc')->paginate($perPage); return response()->json($issues); } /** * Get a specific issue */ public function show(Request $request, Project $project, Issue $issue): JsonResponse { $this->authorizeProject($request, $project); abort_unless($issue->project_id === $project->id, 404); $issue->load('events'); return response()->json($issue); } /** * Update issue status */ public function update(Request $request, Project $project, Issue $issue): JsonResponse { $this->authorizeProject($request, $project); abort_unless($issue->project_id === $project->id, 404); $validated = $request->validate([ 'status' => 'required|in:resolved,unresolved,ignored,muted', 'resolved_in_release' => 'nullable|string', ]); if ($validated['status'] === 'resolved') { $issue->resolve($validated['resolved_in_release'] ?? null); } elseif ($validated['status'] === 'unresolved') { $issue->unresolve(); } elseif ($validated['status'] === 'ignored') { $issue->ignore(); } elseif ($validated['status'] === 'muted') { $issue->mute(); } return response()->json($issue->fresh()); } /** * Delete an issue */ public function destroy(Request $request, Project $project, Issue $issue): JsonResponse { $this->authorizeProject($request, $project); abort_unless($issue->project_id === $project->id, 404); $issue->delete(); return response()->json(['message' => 'Issue deleted successfully']); } private function authorizeProject(Request $request, Project $project): void { $user = $request->user(); abort_unless( $user && $user->projects()->where('projects.id', $project->id)->exists(), 403, 'Unauthorized' ); } }