Add public API v1 with API key auth, rate limiting, and docs page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
68d01448d2
commit
a583a13da1
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Game;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class GameApiController extends Controller
|
||||
{
|
||||
private function formatGame(Game $game): array
|
||||
{
|
||||
return [
|
||||
'id' => $game->id,
|
||||
'title' => $game->title,
|
||||
'slug' => $game->slug,
|
||||
'cover_url' => $game->cover_url,
|
||||
'release_year' => $game->release_year,
|
||||
'release_date' => $game->release_date?->toDateString(),
|
||||
'rating_avg' => $game->rating_avg ? (float) $game->rating_avg : null,
|
||||
'rating_count' => $game->rating_count,
|
||||
'library_count' => $game->library_count,
|
||||
'genres' => $game->genres->pluck('name'),
|
||||
'platforms' => $game->platforms->pluck('name'),
|
||||
];
|
||||
}
|
||||
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
$query = Game::with(['genres', 'platforms'])->published();
|
||||
|
||||
if ($request->filled('genre')) {
|
||||
$query->whereHas('genres', fn ($q) => $q->where('genres.id', $request->genre));
|
||||
}
|
||||
|
||||
if ($request->filled('platform')) {
|
||||
$query->whereHas('platforms', fn ($q) => $q->where('platforms.id', $request->platform));
|
||||
}
|
||||
|
||||
if ($request->filled('year')) {
|
||||
$query->where('release_year', $request->year);
|
||||
}
|
||||
|
||||
match ($request->get('sort', 'popular')) {
|
||||
'rating' => $query->orderByDesc('rating_avg'),
|
||||
'release' => $query->orderByDesc('release_date'),
|
||||
'title' => $query->orderBy('title'),
|
||||
default => $query->orderByDesc('library_count'),
|
||||
};
|
||||
|
||||
$paginator = $query->paginate(20);
|
||||
|
||||
return response()->json([
|
||||
'data' => $paginator->map(fn ($game) => $this->formatGame($game))->values(),
|
||||
'meta' => [
|
||||
'current_page' => $paginator->currentPage(),
|
||||
'last_page' => $paginator->lastPage(),
|
||||
'per_page' => $paginator->perPage(),
|
||||
'total' => $paginator->total(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(Game $game): JsonResponse
|
||||
{
|
||||
$game->load(['genres', 'platforms', 'companies', 'screenshots', 'reviews.user']);
|
||||
|
||||
return response()->json([
|
||||
'data' => array_merge($this->formatGame($game), [
|
||||
'description' => $game->summary,
|
||||
'companies' => $game->companies->map(fn ($c) => [
|
||||
'name' => $c->name,
|
||||
'role' => $c->pivot->role,
|
||||
]),
|
||||
'screenshots' => $game->screenshots->pluck('url'),
|
||||
'reviews' => $game->reviews->take(5)->map(fn ($r) => [
|
||||
'user' => $r->user->slug,
|
||||
'body' => $r->body,
|
||||
'rating' => $r->rating,
|
||||
'created_at' => $r->created_at->toIso8601String(),
|
||||
]),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function search(Request $request): JsonResponse
|
||||
{
|
||||
$q = trim((string) $request->get('q', ''));
|
||||
|
||||
if (mb_strlen($q) < 2) {
|
||||
return response()->json(['error' => 'Query must be at least 2 characters.'], 422);
|
||||
}
|
||||
|
||||
$games = Game::with(['genres', 'platforms'])
|
||||
->where('title', 'like', '%' . $q . '%')
|
||||
->published()
|
||||
->orderBy('title')
|
||||
->limit(20)
|
||||
->get();
|
||||
|
||||
return response()->json([
|
||||
'data' => $games->map(fn ($game) => $this->formatGame($game))->values(),
|
||||
'meta' => ['total' => $games->count()],
|
||||
]);
|
||||
}
|
||||
|
||||
public function topRated(Request $request): JsonResponse
|
||||
{
|
||||
$paginator = Game::with(['genres', 'platforms'])
|
||||
->published()
|
||||
->where('rating_count', '>=', 1)
|
||||
->orderByDesc('rating_avg')
|
||||
->orderByDesc('rating_count')
|
||||
->paginate(20);
|
||||
|
||||
return response()->json([
|
||||
'data' => $paginator->map(fn ($game) => $this->formatGame($game))->values(),
|
||||
'meta' => [
|
||||
'current_page' => $paginator->currentPage(),
|
||||
'last_page' => $paginator->lastPage(),
|
||||
'per_page' => $paginator->perPage(),
|
||||
'total' => $paginator->total(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function newReleases(Request $request): JsonResponse
|
||||
{
|
||||
$paginator = Game::with(['genres', 'platforms'])
|
||||
->published()
|
||||
->where('release_date', '<=', now())
|
||||
->orderByDesc('release_date')
|
||||
->paginate(20);
|
||||
|
||||
return response()->json([
|
||||
'data' => $paginator->map(fn ($game) => $this->formatGame($game))->values(),
|
||||
'meta' => [
|
||||
'current_page' => $paginator->currentPage(),
|
||||
'last_page' => $paginator->lastPage(),
|
||||
'per_page' => $paginator->perPage(),
|
||||
'total' => $paginator->total(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Genre;
|
||||
use App\Models\Platform;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class MetaApiController extends Controller
|
||||
{
|
||||
public function genres(): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'data' => Genre::orderBy('name')->get(['id', 'name', 'slug']),
|
||||
]);
|
||||
}
|
||||
|
||||
public function platforms(): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'data' => Platform::orderBy('name')->get(['id', 'name', 'slug']),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class UserApiController extends Controller
|
||||
{
|
||||
public function show(User $user): JsonResponse
|
||||
{
|
||||
$library = $user->library();
|
||||
|
||||
return response()->json([
|
||||
'data' => [
|
||||
'username' => $user->slug,
|
||||
'name' => $user->name,
|
||||
'avatar_url' => $user->avatar,
|
||||
'joined_at' => $user->created_at->toIso8601String(),
|
||||
'stats' => [
|
||||
'played' => (clone $library)->where('status', 'played')->count(),
|
||||
'playing' => (clone $library)->where('status', 'playing')->count(),
|
||||
'want_to_play' => (clone $library)->where('status', 'want_to_play')->count(),
|
||||
'total' => $user->library()->count(),
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function library(User $user, Request $request): JsonResponse
|
||||
{
|
||||
$paginator = $user->library()
|
||||
->with('game')
|
||||
->orderByDesc('updated_at')
|
||||
->paginate(20);
|
||||
|
||||
return response()->json([
|
||||
'data' => $paginator->map(fn ($ug) => [
|
||||
'game' => [
|
||||
'title' => $ug->game->title,
|
||||
'slug' => $ug->game->slug,
|
||||
'cover_url' => $ug->game->cover_url,
|
||||
],
|
||||
'status' => $ug->status,
|
||||
'rating' => $ug->rating,
|
||||
'updated_at' => $ug->updated_at->toIso8601String(),
|
||||
])->values(),
|
||||
'meta' => [
|
||||
'current_page' => $paginator->currentPage(),
|
||||
'last_page' => $paginator->lastPage(),
|
||||
'per_page' => $paginator->perPage(),
|
||||
'total' => $paginator->total(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\ApiKey;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
class ApiKeyController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$keys = auth()->user()->apiKeys()->latest()->get();
|
||||
return view('settings.api-keys', compact('keys'));
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$request->validate(['name' => 'required|string|max:50']);
|
||||
|
||||
$apiKey = ApiKey::generate(auth()->user(), $request->name);
|
||||
|
||||
return redirect()->route('settings.api-keys')->with('new_api_key', $apiKey->key);
|
||||
}
|
||||
|
||||
public function destroy(ApiKey $apiKey)
|
||||
{
|
||||
Gate::authorize('delete', $apiKey);
|
||||
$apiKey->delete();
|
||||
return redirect()->route('settings.api-keys')->with('success', 'API key deleted.');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user