phase 5
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Issue;
|
||||
use App\Models\Project;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ApiIssueController extends Controller
|
||||
{
|
||||
/**
|
||||
* List issues for a project
|
||||
*/
|
||||
public function index(Request $request, Project $project): JsonResponse
|
||||
{
|
||||
$this->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'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,12 @@ class IssueController extends Controller
|
||||
});
|
||||
}
|
||||
|
||||
if ($environment = $request->query('environment')) {
|
||||
$query->whereHas('events', function ($q) use ($environment) {
|
||||
$q->where('environment', $environment);
|
||||
});
|
||||
}
|
||||
|
||||
$sortField = $request->query('sort', 'last_seen_at');
|
||||
$sortDir = $request->query('dir', 'desc');
|
||||
$allowedSorts = ['last_seen_at', 'first_seen_at', 'times_seen', 'level'];
|
||||
@@ -42,12 +48,22 @@ class IssueController extends Controller
|
||||
|
||||
$issues = $query->cursorPaginate(25)->withQueryString();
|
||||
|
||||
// Get distinct environments from events for this project
|
||||
$environments = $project->events()
|
||||
->whereNotNull('environment')
|
||||
->distinct()
|
||||
->pluck('environment')
|
||||
->sort()
|
||||
->values();
|
||||
|
||||
return view('issues.index', [
|
||||
'project' => $project,
|
||||
'issues' => $issues,
|
||||
'currentStatus' => $status ?: 'unresolved',
|
||||
'currentLevel' => $level,
|
||||
'currentEnvironment' => $environment,
|
||||
'search' => $search,
|
||||
'environments' => $environments,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -56,18 +72,36 @@ class IssueController extends Controller
|
||||
$this->authorizeProject($request, $project);
|
||||
abort_unless($issue->project_id === $project->id, 404);
|
||||
|
||||
$latestEvent = $issue->events()->latest('timestamp')->first();
|
||||
$environment = $request->query('environment');
|
||||
|
||||
$events = $issue->events()
|
||||
$eventsQuery = $issue->events();
|
||||
|
||||
if ($environment) {
|
||||
$eventsQuery->where('environment', $environment);
|
||||
}
|
||||
|
||||
$latestEvent = (clone $eventsQuery)->latest('timestamp')->first();
|
||||
|
||||
$events = $eventsQuery
|
||||
->latest('timestamp')
|
||||
->cursorPaginate(10)
|
||||
->withQueryString();
|
||||
|
||||
// Get distinct environments for this issue
|
||||
$environments = $issue->events()
|
||||
->whereNotNull('environment')
|
||||
->distinct()
|
||||
->pluck('environment')
|
||||
->sort()
|
||||
->values();
|
||||
|
||||
return view('issues.show', [
|
||||
'project' => $project,
|
||||
'issue' => $issue,
|
||||
'latestEvent' => $latestEvent,
|
||||
'events' => $events,
|
||||
'environments' => $environments,
|
||||
'currentEnvironment' => $environment,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Models\PersonalAccessToken;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class AuthenticateApiToken
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param Closure(Request): (Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$token = $request->bearerToken();
|
||||
|
||||
if (!$token) {
|
||||
return response()->json(['error' => 'Unauthenticated'], 401);
|
||||
}
|
||||
|
||||
$hashedToken = hash('sha256', $token);
|
||||
$accessToken = PersonalAccessToken::where('token', $hashedToken)->first();
|
||||
|
||||
if (!$accessToken) {
|
||||
return response()->json(['error' => 'Invalid token'], 401);
|
||||
}
|
||||
|
||||
if ($accessToken->isExpired()) {
|
||||
return response()->json(['error' => 'Token expired'], 401);
|
||||
}
|
||||
|
||||
// Set the authenticated user
|
||||
$request->setUserResolver(fn() => $accessToken->tokenable);
|
||||
|
||||
// Mark token as used
|
||||
$accessToken->markAsUsed();
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user