login update

This commit is contained in:
2026-03-05 11:24:37 +01:00
parent 5a33ca55a1
commit f6772f673b
67 changed files with 10640 additions and 116 deletions

View File

@@ -0,0 +1,188 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Story;
use App\Models\StoryTag;
use App\Models\StoryAuthor;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
/**
* Stories API JSON endpoints for React frontend.
*
* GET /api/stories list published stories (paginated)
* GET /api/stories/{slug} single story detail
* GET /api/stories/tag/{tag} stories by tag
* GET /api/stories/author/{author} stories by author
* GET /api/stories/featured featured stories
*/
final class StoriesApiController extends Controller
{
/**
* List published stories (paginated).
* GET /api/stories?page=1&per_page=12
*/
public function index(Request $request): JsonResponse
{
$perPage = min((int) $request->get('per_page', 12), 50);
$page = (int) $request->get('page', 1);
$cacheKey = "stories:api:list:{$perPage}:{$page}";
$stories = Cache::remember($cacheKey, 300, fn () =>
Story::published()
->with('author', 'tags')
->orderByDesc('published_at')
->paginate($perPage, ['*'], 'page', $page)
);
return response()->json([
'data' => $stories->getCollection()->map(fn (Story $s) => $this->formatCard($s)),
'meta' => [
'current_page' => $stories->currentPage(),
'last_page' => $stories->lastPage(),
'per_page' => $stories->perPage(),
'total' => $stories->total(),
],
]);
}
/**
* Single story detail.
* GET /api/stories/{slug}
*/
public function show(string $slug): JsonResponse
{
$story = Cache::remember('stories:api:' . $slug, 600, fn () =>
Story::published()
->with('author', 'tags')
->where('slug', $slug)
->firstOrFail()
);
return response()->json($this->formatFull($story));
}
/**
* Featured story.
* GET /api/stories/featured
*/
public function featured(): JsonResponse
{
$story = Cache::remember('stories:api:featured', 300, fn () =>
Story::published()->featured()
->with('author', 'tags')
->orderByDesc('published_at')
->first()
);
if (! $story) {
return response()->json(null);
}
return response()->json($this->formatFull($story));
}
/**
* Stories by tag.
* GET /api/stories/tag/{tag}?page=1
*/
public function byTag(Request $request, string $tag): JsonResponse
{
$storyTag = StoryTag::where('slug', $tag)->firstOrFail();
$page = (int) $request->get('page', 1);
$stories = Cache::remember("stories:api:tag:{$tag}:{$page}", 300, fn () =>
Story::published()
->with('author', 'tags')
->whereHas('tags', fn ($q) => $q->where('stories_tags.id', $storyTag->id))
->orderByDesc('published_at')
->paginate(12, ['*'], 'page', $page)
);
return response()->json([
'tag' => ['id' => $storyTag->id, 'slug' => $storyTag->slug, 'name' => $storyTag->name],
'data' => $stories->getCollection()->map(fn (Story $s) => $this->formatCard($s)),
'meta' => [
'current_page' => $stories->currentPage(),
'last_page' => $stories->lastPage(),
'per_page' => $stories->perPage(),
'total' => $stories->total(),
],
]);
}
/**
* Stories by author.
* GET /api/stories/author/{username}?page=1
*/
public function byAuthor(Request $request, string $username): JsonResponse
{
$author = StoryAuthor::whereHas('user', fn ($q) => $q->where('username', $username))->first()
?? StoryAuthor::where('name', $username)->firstOrFail();
$page = (int) $request->get('page', 1);
$stories = Cache::remember("stories:api:author:{$author->id}:{$page}", 300, fn () =>
Story::published()
->with('author', 'tags')
->where('author_id', $author->id)
->orderByDesc('published_at')
->paginate(12, ['*'], 'page', $page)
);
return response()->json([
'author' => $this->formatAuthor($author),
'data' => $stories->getCollection()->map(fn (Story $s) => $this->formatCard($s)),
'meta' => [
'current_page' => $stories->currentPage(),
'last_page' => $stories->lastPage(),
'per_page' => $stories->perPage(),
'total' => $stories->total(),
],
]);
}
// ── Private formatters ────────────────────────────────────────────────
private function formatCard(Story $story): array
{
return [
'id' => $story->id,
'slug' => $story->slug,
'url' => $story->url,
'title' => $story->title,
'excerpt' => $story->excerpt,
'cover_image' => $story->cover_url,
'author' => $story->author ? $this->formatAuthor($story->author) : null,
'tags' => $story->tags->map(fn ($t) => ['id' => $t->id, 'slug' => $t->slug, 'name' => $t->name, 'url' => $t->url]),
'views' => $story->views,
'featured' => $story->featured,
'reading_time' => $story->reading_time,
'published_at' => $story->published_at?->toIso8601String(),
];
}
private function formatFull(Story $story): array
{
return array_merge($this->formatCard($story), [
'content' => $story->content,
]);
}
private function formatAuthor(StoryAuthor $author): array
{
return [
'id' => $author->id,
'name' => $author->name,
'avatar_url' => $author->avatar_url,
'bio' => $author->bio,
'profile_url' => $author->profile_url,
];
}
}