update deploy

This commit is contained in:
bulut
2026-03-27 10:41:54 +03:00
parent 69d19c0176
commit 6f6448aa06
422 changed files with 37956 additions and 0 deletions

View File

@@ -0,0 +1,62 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Http\Resources\AnnouncementResource;
use App\Repositories\Contracts\AnnouncementRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use OpenApi\Attributes as OA;
class AnnouncementController extends Controller
{
public function __construct(private AnnouncementRepositoryInterface $repository) {}
#[OA\Get(
path: '/api/v1/announcements',
summary: 'Duyuruları listele',
tags: ['Announcements'],
parameters: [
new OA\Parameter(name: 'category', in: 'query', required: false, schema: new OA\Schema(type: 'string')),
new OA\Parameter(name: 'featured', in: 'query', required: false, schema: new OA\Schema(type: 'boolean')),
new OA\Parameter(name: 'search', in: 'query', required: false, schema: new OA\Schema(type: 'string')),
new OA\Parameter(name: 'sort', in: 'query', required: false, schema: new OA\Schema(type: 'string')),
new OA\Parameter(name: 'per_page', in: 'query', required: false, schema: new OA\Schema(type: 'integer', default: 15)),
],
responses: [new OA\Response(response: 200, description: 'Duyuru listesi')],
)]
public function index(Request $request): AnonymousResourceCollection
{
$announcements = $this->repository->paginate(
$request->only(['category', 'featured', 'search', 'sort']),
$request->integer('per_page', 15),
);
return AnnouncementResource::collection($announcements);
}
#[OA\Get(
path: '/api/v1/announcements/{slug}',
summary: 'Duyuru detayı',
tags: ['Announcements'],
parameters: [new OA\Parameter(name: 'slug', in: 'path', required: true, schema: new OA\Schema(type: 'string'))],
responses: [
new OA\Response(response: 200, description: 'Duyuru detayı'),
new OA\Response(response: 404, description: 'Duyuru bulunamadı'),
],
)]
public function show(string $slug): JsonResponse
{
$announcement = $this->repository->findBySlug($slug);
if (! $announcement) {
return response()->json(['message' => 'Duyuru bulunamadı.'], 404);
}
$announcement->load(['comments' => fn ($q) => $q->where('is_approved', true)->latest()]);
return response()->json(new AnnouncementResource($announcement));
}
}

View File

@@ -0,0 +1,60 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Http\Resources\CategoryResource;
use App\Repositories\Contracts\CategoryRepositoryInterface;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use OpenApi\Attributes as OA;
class CategoryController extends Controller
{
public function __construct(private CategoryRepositoryInterface $repository) {}
#[OA\Get(
path: '/api/v1/categories',
summary: 'Kategorileri listele',
description: 'Tüm aktif kategorileri sayfalanmış olarak döndürür.',
tags: ['Categories'],
parameters: [
new OA\Parameter(name: 'search', in: 'query', required: false, schema: new OA\Schema(type: 'string')),
new OA\Parameter(name: 'per_page', in: 'query', required: false, schema: new OA\Schema(type: 'integer', default: 15)),
],
responses: [
new OA\Response(response: 200, description: 'Kategori listesi'),
],
)]
public function index(Request $request): AnonymousResourceCollection
{
$categories = $this->repository->paginate(
filters: $request->only('search'),
perPage: $request->integer('per_page', 15),
);
return CategoryResource::collection($categories);
}
#[OA\Get(
path: '/api/v1/categories/{slug}',
summary: 'Kategori detayı',
description: 'Slug ile kategori detayını döndürür.',
tags: ['Categories'],
parameters: [
new OA\Parameter(name: 'slug', in: 'path', required: true, schema: new OA\Schema(type: 'string')),
],
responses: [
new OA\Response(response: 200, description: 'Kategori detayı'),
new OA\Response(response: 404, description: 'Kategori bulunamadı'),
],
)]
public function show(string $slug): CategoryResource
{
$category = $this->repository->findBySlug($slug);
abort_if(! $category, 404, 'Kategori bulunamadı.');
return new CategoryResource($category);
}
}

View File

@@ -0,0 +1,92 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Actions\Comment\CreateCommentAction;
use App\DTOs\CommentData;
use App\Http\Controllers\Controller;
use App\Http\Requests\Comment\StoreCommentRequest;
use App\Http\Resources\CommentResource;
use App\Models\Announcement;
use App\Models\Category;
use App\Models\Course;
use App\Repositories\Contracts\CommentRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use OpenApi\Attributes as OA;
class CommentController extends Controller
{
/**
* Morph map for commentable types from the request.
*
* @var array<string, string>
*/
private const COMMENTABLE_MAP = [
'course' => Course::class,
'category' => Category::class,
'announcement' => Announcement::class,
];
public function __construct(private CommentRepositoryInterface $repository) {}
#[OA\Get(
path: '/api/v1/comments/{type}/{id}',
summary: 'Onaylı yorumları getir',
tags: ['Comments'],
parameters: [
new OA\Parameter(name: 'type', in: 'path', required: true, schema: new OA\Schema(type: 'string', enum: ['course', 'category', 'announcement'])),
new OA\Parameter(name: 'id', in: 'path', required: true, schema: new OA\Schema(type: 'integer')),
],
responses: [
new OA\Response(response: 200, description: 'Onaylı yorum listesi'),
new OA\Response(response: 404, description: 'Geçersiz yorum tipi'),
],
)]
public function index(string $type, int $id): AnonymousResourceCollection|JsonResponse
{
$commentableType = self::COMMENTABLE_MAP[$type] ?? null;
if ($commentableType === null) {
return response()->json(['message' => 'Geçersiz yorum tipi.'], 404);
}
$comments = $this->repository->getApprovedByCommentable($commentableType, $id);
return CommentResource::collection($comments);
}
#[OA\Post(
path: '/api/v1/comments',
summary: 'Yorum gönder',
tags: ['Comments'],
requestBody: new OA\RequestBody(
required: true,
content: new OA\JsonContent(
required: ['body', 'author_name', 'author_email', 'commentable_type', 'commentable_id'],
properties: [
new OA\Property(property: 'body', type: 'string'),
new OA\Property(property: 'author_name', type: 'string'),
new OA\Property(property: 'author_email', type: 'string', format: 'email'),
new OA\Property(property: 'commentable_type', type: 'string', enum: ['course', 'category', 'announcement']),
new OA\Property(property: 'commentable_id', type: 'integer'),
new OA\Property(property: 'rating', type: 'integer', minimum: 1, maximum: 5),
],
),
),
responses: [
new OA\Response(response: 201, description: 'Yorum gönderildi'),
new OA\Response(response: 422, description: 'Validasyon hatası'),
],
)]
public function store(StoreCommentRequest $request, CreateCommentAction $action): JsonResponse
{
$validated = $request->validated();
$validated['commentable_type'] = self::COMMENTABLE_MAP[$validated['commentable_type']] ?? $validated['commentable_type'];
$dto = CommentData::fromArray($validated);
$action->execute($dto);
return response()->json(['message' => 'Yorumunuz incelenmek üzere gönderildi.'], 201);
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Http\Resources\CourseResource;
use App\Repositories\Contracts\CourseRepositoryInterface;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use OpenApi\Attributes as OA;
class CourseController extends Controller
{
public function __construct(private CourseRepositoryInterface $repository) {}
#[OA\Get(
path: '/api/v1/courses',
summary: 'Eğitimleri listele',
description: 'Tüm eğitimleri sayfalanmış olarak döndürür. Kategori, arama ve sıralama filtresi destekler.',
tags: ['Courses'],
parameters: [
new OA\Parameter(name: 'category', in: 'query', required: false, schema: new OA\Schema(type: 'string'), description: 'Kategori slug filtresi'),
new OA\Parameter(name: 'search', in: 'query', required: false, schema: new OA\Schema(type: 'string')),
new OA\Parameter(name: 'sort', in: 'query', required: false, schema: new OA\Schema(type: 'string'), description: 'Örn: -created_at, title'),
new OA\Parameter(name: 'per_page', in: 'query', required: false, schema: new OA\Schema(type: 'integer', default: 15)),
],
responses: [
new OA\Response(response: 200, description: 'Eğitim listesi'),
],
)]
public function index(Request $request): AnonymousResourceCollection
{
$courses = $this->repository->paginate(
filters: $request->only('category', 'search', 'sort'),
perPage: $request->integer('per_page', 15),
);
return CourseResource::collection($courses);
}
#[OA\Get(
path: '/api/v1/courses/{slug}',
summary: 'Eğitim detayı',
description: 'Slug ile eğitim detayını döndürür.',
tags: ['Courses'],
parameters: [
new OA\Parameter(name: 'slug', in: 'path', required: true, schema: new OA\Schema(type: 'string')),
],
responses: [
new OA\Response(response: 200, description: 'Eğitim detayı'),
new OA\Response(response: 404, description: 'Eğitim bulunamadı'),
],
)]
public function show(string $slug): CourseResource
{
$course = $this->repository->findBySlug($slug);
abort_if(! $course, 404, 'Eğitim bulunamadı.');
return new CourseResource($course);
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Enums\FaqCategory;
use App\Http\Controllers\Controller;
use App\Http\Resources\FaqResource;
use App\Repositories\Contracts\FaqRepositoryInterface;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use OpenApi\Attributes as OA;
class FaqController extends Controller
{
public function __construct(private FaqRepositoryInterface $repository) {}
#[OA\Get(
path: '/api/v1/faqs/{category?}',
summary: 'SSS listesi',
description: 'Tüm veya kategoriye göre filtrelenmiş SSS listesi döndürür. ?limit=6 ile anasayfa için sınırlanabilir.',
tags: ['FAQs'],
parameters: [
new OA\Parameter(name: 'category', in: 'path', required: false, schema: new OA\Schema(type: 'string'), description: 'FAQ kategori filtresi'),
new OA\Parameter(name: 'limit', in: 'query', required: false, schema: new OA\Schema(type: 'integer'), description: 'Dönen SSS sayısını sınırla (anasayfa için)'),
],
responses: [
new OA\Response(response: 200, description: 'SSS listesi'),
new OA\Response(response: 404, description: 'Kategori bulunamadı'),
],
)]
public function index(Request $request, ?string $category = null): AnonymousResourceCollection
{
$limit = $request->integer('limit', 0);
if ($category) {
$faqCategory = FaqCategory::tryFrom($category);
if (! $faqCategory) {
abort(404, 'FAQ kategorisi bulunamadı.');
}
$faqs = $this->repository->getByCategory($faqCategory);
if ($limit > 0) {
$faqs = $faqs->take($limit);
}
return FaqResource::collection($faqs);
}
$perPage = $limit > 0 ? $limit : 100;
return FaqResource::collection($this->repository->paginate([], $perPage));
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Http\Resources\GuideCardResource;
use App\Repositories\Contracts\GuideCardRepositoryInterface;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use OpenApi\Attributes as OA;
class GuideCardController extends Controller
{
public function __construct(private GuideCardRepositoryInterface $repository) {}
#[OA\Get(
path: '/api/v1/guide-cards',
summary: 'Rehber kartlarını listele',
tags: ['Guide Cards'],
responses: [new OA\Response(response: 200, description: 'Rehber kart listesi')],
)]
public function index(): AnonymousResourceCollection
{
return GuideCardResource::collection($this->repository->active());
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Http\Resources\HeroSlideResource;
use App\Repositories\Contracts\HeroSlideRepositoryInterface;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use OpenApi\Attributes as OA;
class HeroSlideController extends Controller
{
public function __construct(private HeroSlideRepositoryInterface $repository) {}
#[OA\Get(
path: '/api/v1/hero-slides',
summary: 'Aktif hero slide listesi',
tags: ['Hero Slides'],
responses: [new OA\Response(response: 200, description: 'Aktif hero slide listesi')],
)]
public function index(): AnonymousResourceCollection
{
return HeroSlideResource::collection($this->repository->active());
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Actions\Lead\CreateLeadAction;
use App\DTOs\LeadData;
use App\Http\Controllers\Controller;
use App\Http\Requests\Lead\StoreLeadRequest;
use Illuminate\Http\JsonResponse;
use OpenApi\Attributes as OA;
class LeadController extends Controller
{
#[OA\Post(
path: '/api/v1/leads',
summary: 'Yeni başvuru/talep oluştur',
tags: ['Leads'],
requestBody: new OA\RequestBody(
required: true,
content: new OA\JsonContent(
required: ['name', 'phone', 'source', 'kvkk_consent'],
properties: [
new OA\Property(property: 'name', type: 'string', description: 'Ad Soyad'),
new OA\Property(property: 'phone', type: 'string', description: 'Telefon'),
new OA\Property(property: 'email', type: 'string', format: 'email', nullable: true),
new OA\Property(property: 'source', type: 'string', description: 'kurs_kayit, danismanlik, duyuru, iletisim'),
new OA\Property(property: 'target_course', type: 'string', nullable: true, description: 'Kurs slug'),
new OA\Property(property: 'education_level', type: 'string', nullable: true),
new OA\Property(property: 'subject', type: 'string', nullable: true),
new OA\Property(property: 'message', type: 'string', nullable: true),
new OA\Property(property: 'kvkk_consent', type: 'boolean', description: 'KVKK onayı (zorunlu, true olmalı)'),
new OA\Property(property: 'marketing_consent', type: 'boolean', nullable: true),
new OA\Property(property: 'utm_source', type: 'string', nullable: true),
new OA\Property(property: 'utm_medium', type: 'string', nullable: true),
new OA\Property(property: 'utm_campaign', type: 'string', nullable: true),
],
),
),
responses: [
new OA\Response(response: 201, description: 'Başvuru alındı'),
new OA\Response(response: 422, description: 'Validasyon hatası'),
],
)]
public function store(StoreLeadRequest $request, CreateLeadAction $action): JsonResponse
{
$dto = LeadData::fromArray($request->validated());
$action->execute($dto);
return response()->json([
'success' => true,
'message' => 'Talebiniz alınmıştır. En kısa sürede sizinle iletişime geçeceğiz.',
], 201);
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Enums\MenuLocation;
use App\Http\Controllers\Controller;
use App\Http\Resources\MenuResource;
use App\Repositories\Contracts\MenuRepositoryInterface;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use OpenApi\Attributes as OA;
class MenuController extends Controller
{
public function __construct(private MenuRepositoryInterface $repository) {}
#[OA\Get(
path: '/api/v1/menus/{location}',
summary: 'Konuma göre menü öğelerini getir',
tags: ['Menus'],
parameters: [new OA\Parameter(name: 'location', in: 'path', required: true, schema: new OA\Schema(type: 'string'), description: 'header, footer, mobile')],
responses: [
new OA\Response(response: 200, description: 'Menü listesi'),
new OA\Response(response: 404, description: 'Menü konumu bulunamadı'),
],
)]
public function index(string $location): AnonymousResourceCollection
{
$menuLocation = MenuLocation::tryFrom($location);
if (! $menuLocation) {
abort(404, 'Menü konumu bulunamadı.');
}
return MenuResource::collection($this->repository->getByLocation($menuLocation));
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Http\Resources\PageResource;
use App\Repositories\Contracts\PageRepositoryInterface;
use Illuminate\Http\JsonResponse;
use OpenApi\Attributes as OA;
class PageController extends Controller
{
public function __construct(private PageRepositoryInterface $repository) {}
#[OA\Get(
path: '/api/v1/pages/{slug}',
summary: 'Sayfa detayı',
tags: ['Pages'],
parameters: [new OA\Parameter(name: 'slug', in: 'path', required: true, schema: new OA\Schema(type: 'string'))],
responses: [
new OA\Response(response: 200, description: 'Sayfa detayı'),
new OA\Response(response: 404, description: 'Sayfa bulunamadı'),
],
)]
public function show(string $slug): JsonResponse
{
$page = $this->repository->findBySlug($slug);
if (! $page || ! $page->is_active) {
return response()->json(['message' => 'Sayfa bulunamadı.'], 404);
}
return response()->json(new PageResource($page));
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Cache;
use OpenApi\Attributes as OA;
class PreviewController extends Controller
{
#[OA\Get(
path: '/api/v1/preview/{token}',
summary: 'Önizleme verisini getir (public)',
tags: ['Preview'],
parameters: [new OA\Parameter(name: 'token', in: 'path', required: true, schema: new OA\Schema(type: 'string'))],
responses: [
new OA\Response(response: 200, description: 'Önizleme verisi'),
new OA\Response(response: 404, description: 'Önizleme bulunamadı veya süresi dolmuş'),
],
)]
public function show(string $token): JsonResponse
{
$data = Cache::get("preview_{$token}");
if (! $data) {
return response()->json(['message' => 'Önizleme bulunamadı veya süresi dolmuş.'], 404);
}
return response()->json(['data' => $data]);
}
}

View File

@@ -0,0 +1,72 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Http\Resources\CourseScheduleResource;
use App\Repositories\Contracts\ScheduleRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use OpenApi\Attributes as OA;
class ScheduleController extends Controller
{
public function __construct(private ScheduleRepositoryInterface $repository) {}
#[OA\Get(
path: '/api/v1/schedules',
summary: 'Eğitim takvimini listele',
tags: ['Schedules'],
parameters: [
new OA\Parameter(name: 'course_id', in: 'query', required: false, schema: new OA\Schema(type: 'integer')),
new OA\Parameter(name: 'per_page', in: 'query', required: false, schema: new OA\Schema(type: 'integer', default: 15)),
],
responses: [new OA\Response(response: 200, description: 'Takvim listesi')],
)]
public function index(Request $request): AnonymousResourceCollection
{
$schedules = $this->repository->paginate(
$request->only(['course_id']),
$request->integer('per_page', 15),
);
return CourseScheduleResource::collection($schedules);
}
#[OA\Get(
path: '/api/v1/schedules/upcoming',
summary: 'Yaklaşan eğitimleri listele',
tags: ['Schedules'],
responses: [new OA\Response(response: 200, description: 'Yaklaşan eğitimler')],
)]
public function upcoming(): AnonymousResourceCollection
{
$schedules = $this->repository->upcoming(20);
return CourseScheduleResource::collection($schedules);
}
#[OA\Get(
path: '/api/v1/schedules/{id}',
summary: 'Takvim detayı',
tags: ['Schedules'],
parameters: [new OA\Parameter(name: 'id', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
responses: [
new OA\Response(response: 200, description: 'Takvim detayı'),
new OA\Response(response: 404, description: 'Takvim bulunamadı'),
],
)]
public function show(int $id): JsonResponse
{
$schedule = $this->repository->findById($id);
if (! $schedule) {
return response()->json(['message' => 'Takvim bulunamadı.'], 404);
}
$schedule->load('course.category');
return response()->json(new CourseScheduleResource($schedule));
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Enums\SettingGroup;
use App\Http\Controllers\Controller;
use App\Repositories\Contracts\SettingRepositoryInterface;
use Illuminate\Http\JsonResponse;
use OpenApi\Attributes as OA;
class SettingController extends Controller
{
public function __construct(private SettingRepositoryInterface $repository) {}
/**
* Return all public settings grouped by group name.
*/
#[OA\Get(
path: '/api/v1/settings',
summary: 'Tüm site ayarlarını getir (group bazlı nested)',
tags: ['Settings'],
responses: [new OA\Response(response: 200, description: 'Ayarlar listesi')],
)]
public function index(): JsonResponse
{
return response()->json($this->repository->publicGrouped());
}
/**
* Return public settings for a single group.
*/
#[OA\Get(
path: '/api/v1/settings/{group}',
summary: 'Tek grup ayarlarını getir',
tags: ['Settings'],
parameters: [new OA\Parameter(name: 'group', in: 'path', required: true, schema: new OA\Schema(type: 'string'))],
responses: [
new OA\Response(response: 200, description: 'Grup ayarları'),
new OA\Response(response: 404, description: 'Grup bulunamadı'),
],
)]
public function show(string $group): JsonResponse
{
$settingGroup = SettingGroup::tryFrom($group);
if (! $settingGroup) {
abort(404, 'Ayar grubu bulunamadı.');
}
return response()->json($this->repository->publicByGroup($settingGroup));
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Models\Announcement;
use App\Models\Course;
use App\Models\Page;
use Illuminate\Http\JsonResponse;
use OpenApi\Attributes as OA;
class SitemapController extends Controller
{
#[OA\Get(
path: '/api/v1/sitemap-data',
summary: 'SEO sitemap verisi',
description: 'Yayındaki tüm kurs, duyuru ve sayfa slug+updated_at bilgilerini döner.',
tags: ['Sitemap'],
responses: [new OA\Response(response: 200, description: 'Sitemap verisi')],
)]
public function index(): JsonResponse
{
$courses = Course::query()
->select('slug', 'updated_at')
->get()
->map(fn (Course $c) => [
'loc' => '/courses/'.$c->slug,
'lastmod' => $c->updated_at?->toISOString(),
]);
$announcements = Announcement::query()
->select('slug', 'updated_at')
->get()
->map(fn (Announcement $a) => [
'loc' => '/announcements/'.$a->slug,
'lastmod' => $a->updated_at?->toISOString(),
]);
$pages = Page::query()
->where('is_active', true)
->select('slug', 'updated_at')
->get()
->map(fn (Page $p) => [
'loc' => '/'.$p->slug,
'lastmod' => $p->updated_at?->toISOString(),
]);
return response()->json([
'courses' => $courses,
'announcements' => $announcements,
'pages' => $pages,
]);
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Http\Resources\StoryResource;
use App\Models\Story;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use OpenApi\Attributes as OA;
class StoryController extends Controller
{
#[OA\Get(
path: '/api/v1/stories',
summary: 'Aktif hikayeleri listele',
tags: ['Stories'],
responses: [new OA\Response(response: 200, description: 'Hikaye listesi')],
)]
public function index(): AnonymousResourceCollection
{
$stories = Story::query()
->where('is_active', true)
->orderBy('order_index')
->get();
return StoryResource::collection($stories);
}
}