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,140 @@
<?php
namespace App\Http\Controllers\Api\Admin;
use App\Actions\Announcement\CreateAnnouncementAction;
use App\Actions\Announcement\DeleteAnnouncementAction;
use App\Actions\Announcement\UpdateAnnouncementAction;
use App\DTOs\AnnouncementData;
use App\Http\Controllers\Controller;
use App\Http\Requests\Announcement\StoreAnnouncementRequest;
use App\Http\Requests\Announcement\UpdateAnnouncementRequest;
use App\Http\Resources\AnnouncementResource;
use App\Models\Announcement;
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/admin/announcements',
summary: 'Duyuruları listele (Admin)',
tags: ['Admin - Announcements'],
security: [['sanctum' => []]],
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\Post(
path: '/api/admin/announcements',
summary: 'Yeni duyuru oluştur',
tags: ['Admin - Announcements'],
security: [['sanctum' => []]],
requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(
required: ['title', 'slug', 'category', 'content'],
properties: [
new OA\Property(property: 'title', type: 'string'),
new OA\Property(property: 'slug', type: 'string'),
new OA\Property(property: 'category', type: 'string'),
new OA\Property(property: 'content', type: 'string'),
new OA\Property(property: 'excerpt', type: 'string'),
new OA\Property(property: 'image', type: 'string'),
new OA\Property(property: 'is_featured', type: 'boolean'),
new OA\Property(property: 'is_active', type: 'boolean'),
new OA\Property(property: 'published_at', type: 'string', format: 'date-time'),
new OA\Property(property: 'meta_title', type: 'string'),
new OA\Property(property: 'meta_description', type: 'string'),
],
)),
responses: [
new OA\Response(response: 201, description: 'Duyuru oluşturuldu'),
new OA\Response(response: 422, description: 'Validasyon hatası'),
],
)]
public function store(StoreAnnouncementRequest $request, CreateAnnouncementAction $action): JsonResponse
{
$dto = AnnouncementData::fromArray($request->validated());
$announcement = $action->execute($dto);
return response()->json(new AnnouncementResource($announcement), 201);
}
#[OA\Get(
path: '/api/admin/announcements/{announcement}',
summary: 'Duyuru detayı (Admin)',
tags: ['Admin - Announcements'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'announcement', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
responses: [new OA\Response(response: 200, description: 'Duyuru detayı')],
)]
public function show(Announcement $announcement): JsonResponse
{
return response()->json(new AnnouncementResource($announcement));
}
#[OA\Put(
path: '/api/admin/announcements/{announcement}',
summary: 'Duyuru güncelle',
tags: ['Admin - Announcements'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'announcement', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(
properties: [
new OA\Property(property: 'title', type: 'string'),
new OA\Property(property: 'slug', type: 'string'),
new OA\Property(property: 'category', type: 'string'),
new OA\Property(property: 'content', type: 'string'),
new OA\Property(property: 'excerpt', type: 'string'),
new OA\Property(property: 'image', type: 'string'),
new OA\Property(property: 'is_featured', type: 'boolean'),
new OA\Property(property: 'is_active', type: 'boolean'),
],
)),
responses: [
new OA\Response(response: 200, description: 'Duyuru güncellendi'),
new OA\Response(response: 422, description: 'Validasyon hatası'),
],
)]
public function update(UpdateAnnouncementRequest $request, Announcement $announcement, UpdateAnnouncementAction $action): JsonResponse
{
$dto = AnnouncementData::fromArray(array_merge($announcement->toArray(), $request->validated()));
$announcement = $action->execute($announcement, $dto);
return response()->json(new AnnouncementResource($announcement));
}
#[OA\Delete(
path: '/api/admin/announcements/{announcement}',
summary: 'Duyuru sil',
tags: ['Admin - Announcements'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'announcement', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
responses: [new OA\Response(response: 200, description: 'Duyuru silindi')],
)]
public function destroy(Announcement $announcement, DeleteAnnouncementAction $action): JsonResponse
{
$action->execute($announcement);
return response()->json(['message' => 'Duyuru silindi.']);
}
}

View File

@@ -0,0 +1,118 @@
<?php
namespace App\Http\Controllers\Api\Admin;
use App\Http\Controllers\Controller;
use App\Http\Requests\Auth\LoginRequest;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use OpenApi\Attributes as OA;
class AuthController extends Controller
{
#[OA\Post(
path: '/api/admin/login',
summary: 'Admin girişi',
description: 'E-posta ve şifre ile giriş yaparak Sanctum token alır.',
tags: ['Auth'],
requestBody: new OA\RequestBody(
required: true,
content: new OA\JsonContent(
required: ['email', 'password'],
properties: [
new OA\Property(property: 'email', type: 'string', format: 'email', example: 'admin@bogazicidenizcilik.com.tr'),
new OA\Property(property: 'password', type: 'string', format: 'password', example: 'password'),
],
),
),
responses: [
new OA\Response(response: 200, description: 'Başarılı giriş', content: new OA\JsonContent(
properties: [
new OA\Property(property: 'data', type: 'object', properties: [
new OA\Property(property: 'token', type: 'string'),
new OA\Property(property: 'user', type: 'object'),
]),
],
)),
new OA\Response(response: 401, description: 'Geçersiz kimlik bilgileri'),
new OA\Response(response: 422, description: 'Validasyon hatası'),
],
)]
public function login(LoginRequest $request): JsonResponse
{
if (! Auth::attempt($request->only('email', 'password'))) {
return response()->json([
'message' => 'Geçersiz e-posta veya şifre.',
], 401);
}
/** @var User $user */
$user = Auth::user();
$token = $user->createToken('admin-token')->plainTextToken;
return response()->json([
'data' => [
'token' => $token,
'user' => [
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
'roles' => $user->getRoleNames(),
'permissions' => $user->getAllPermissions()->pluck('name'),
],
],
]);
}
#[OA\Get(
path: '/api/admin/me',
summary: 'Mevcut kullanıcı bilgileri',
description: 'Oturum açmış kullanıcının bilgilerini, rollerini ve izinlerini döndürür.',
security: [['sanctum' => []]],
tags: ['Auth'],
responses: [
new OA\Response(response: 200, description: 'Kullanıcı bilgileri'),
new OA\Response(response: 401, description: 'Yetkisiz erişim'),
],
)]
public function me(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
return response()->json([
'data' => [
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
'roles' => $user->getRoleNames(),
'permissions' => $user->getAllPermissions()->pluck('name'),
],
]);
}
#[OA\Post(
path: '/api/admin/logout',
summary: ıkış yap',
description: 'Mevcut token\'ı iptal eder.',
security: [['sanctum' => []]],
tags: ['Auth'],
responses: [
new OA\Response(response: 200, description: 'Başarıyla çıkış yapıldı'),
new OA\Response(response: 401, description: 'Yetkisiz erişim'),
],
)]
public function logout(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
$user->currentAccessToken()->delete();
return response()->json([
'message' => 'Başarıyla çıkış yapıldı.',
]);
}
}

View File

@@ -0,0 +1,172 @@
<?php
namespace App\Http\Controllers\Api\Admin;
use App\Http\Controllers\Controller;
use App\Http\Resources\PageBlockResource;
use App\Models\Page;
use App\Models\PageBlock;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use OpenApi\Attributes as OA;
class BlockController extends Controller
{
#[OA\Get(
path: '/api/admin/pages/{page}/blocks',
summary: 'Sayfa bloklarını listele',
tags: ['Admin - Page Blocks'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'page', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
responses: [new OA\Response(response: 200, description: 'Blok listesi')],
)]
public function index(Page $page): AnonymousResourceCollection
{
return PageBlockResource::collection(
$page->blocks()->orderBy('order_index')->get()
);
}
#[OA\Post(
path: '/api/admin/pages/{page}/blocks',
summary: 'Yeni blok oluştur',
tags: ['Admin - Page Blocks'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'page', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(
required: ['type', 'content'],
properties: [
new OA\Property(property: 'type', type: 'string'),
new OA\Property(property: 'content', type: 'object'),
new OA\Property(property: 'order_index', type: 'integer'),
new OA\Property(property: 'is_active', type: 'boolean'),
],
)),
responses: [
new OA\Response(response: 201, description: 'Blok oluşturuldu'),
new OA\Response(response: 422, description: 'Validasyon hatası'),
],
)]
public function store(Request $request, Page $page): JsonResponse
{
$validated = $request->validate([
'type' => ['required', 'string', 'max:50'],
'content' => ['present', 'array'],
'order_index' => ['sometimes', 'integer', 'min:0'],
'is_active' => ['sometimes', 'boolean'],
]);
$validated['order_index'] ??= $page->blocks()->max('order_index') + 1;
$block = $page->blocks()->create($validated);
return response()->json(new PageBlockResource($block), 201);
}
#[OA\Get(
path: '/api/admin/pages/{page}/blocks/{block}',
summary: 'Blok detayı',
tags: ['Admin - Page Blocks'],
security: [['sanctum' => []]],
parameters: [
new OA\Parameter(name: 'page', in: 'path', required: true, schema: new OA\Schema(type: 'integer')),
new OA\Parameter(name: 'block', in: 'path', required: true, schema: new OA\Schema(type: 'integer')),
],
responses: [new OA\Response(response: 200, description: 'Blok detayı')],
)]
public function show(Page $page, PageBlock $block): JsonResponse
{
return response()->json(new PageBlockResource($block));
}
#[OA\Put(
path: '/api/admin/pages/{page}/blocks/{block}',
summary: 'Blok güncelle',
tags: ['Admin - Page Blocks'],
security: [['sanctum' => []]],
parameters: [
new OA\Parameter(name: 'page', in: 'path', required: true, schema: new OA\Schema(type: 'integer')),
new OA\Parameter(name: 'block', in: 'path', required: true, schema: new OA\Schema(type: 'integer')),
],
requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(
properties: [
new OA\Property(property: 'type', type: 'string'),
new OA\Property(property: 'content', type: 'object'),
new OA\Property(property: 'order_index', type: 'integer'),
new OA\Property(property: 'is_active', type: 'boolean'),
],
)),
responses: [
new OA\Response(response: 200, description: 'Blok güncellendi'),
new OA\Response(response: 422, description: 'Validasyon hatası'),
],
)]
public function update(Request $request, Page $page, PageBlock $block): JsonResponse
{
$validated = $request->validate([
'type' => ['sometimes', 'string', 'max:50'],
'content' => ['sometimes', 'array'],
'order_index' => ['sometimes', 'integer', 'min:0'],
'is_active' => ['sometimes', 'boolean'],
]);
$block->update($validated);
return response()->json(new PageBlockResource($block->fresh()));
}
#[OA\Delete(
path: '/api/admin/pages/{page}/blocks/{block}',
summary: 'Blok sil',
tags: ['Admin - Page Blocks'],
security: [['sanctum' => []]],
parameters: [
new OA\Parameter(name: 'page', in: 'path', required: true, schema: new OA\Schema(type: 'integer')),
new OA\Parameter(name: 'block', in: 'path', required: true, schema: new OA\Schema(type: 'integer')),
],
responses: [new OA\Response(response: 200, description: 'Blok silindi')],
)]
public function destroy(Page $page, PageBlock $block): JsonResponse
{
$block->delete();
return response()->json(['message' => 'Blok silindi.']);
}
#[OA\Post(
path: '/api/admin/pages/{page}/blocks/reorder',
summary: 'Blok sıralamasını güncelle',
tags: ['Admin - Page Blocks'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'page', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(
required: ['items'],
properties: [
new OA\Property(property: 'items', type: 'array', items: new OA\Items(
properties: [
new OA\Property(property: 'id', type: 'integer'),
new OA\Property(property: 'order_index', type: 'integer'),
],
)),
],
)),
responses: [new OA\Response(response: 200, description: 'Sıralama güncellendi')],
)]
public function reorder(Request $request, Page $page): JsonResponse
{
$validated = $request->validate([
'items' => ['required', 'array', 'min:1'],
'items.*.id' => ['required', 'integer', 'exists:page_blocks,id'],
'items.*.order_index' => ['required', 'integer', 'min:0'],
]);
foreach ($validated['items'] as $item) {
$page->blocks()
->where('id', $item['id'])
->update(['order_index' => $item['order_index']]);
}
return response()->json(['message' => 'Blok sıralaması güncellendi.']);
}
}

View File

@@ -0,0 +1,147 @@
<?php
namespace App\Http\Controllers\Api\Admin;
use App\Actions\Category\CreateCategoryAction;
use App\Actions\Category\DeleteCategoryAction;
use App\Actions\Category\UpdateCategoryAction;
use App\DTOs\CategoryData;
use App\Http\Controllers\Controller;
use App\Http\Requests\Category\StoreCategoryRequest;
use App\Http\Requests\Category\UpdateCategoryRequest;
use App\Http\Resources\CategoryResource;
use App\Models\Category;
use App\Repositories\Contracts\CategoryRepositoryInterface;
use Illuminate\Http\JsonResponse;
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/admin/categories',
summary: 'Kategorileri listele (Admin)',
tags: ['Admin - Categories'],
security: [['sanctum' => []]],
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\Post(
path: '/api/admin/categories',
summary: 'Yeni kategori oluştur',
tags: ['Admin - Categories'],
security: [['sanctum' => []]],
requestBody: new OA\RequestBody(
required: true,
content: new OA\JsonContent(
required: ['name'],
properties: [
new OA\Property(property: 'name', type: 'string'),
new OA\Property(property: 'slug', type: 'string'),
new OA\Property(property: 'description', type: 'string'),
new OA\Property(property: 'image', type: 'string'),
new OA\Property(property: 'icon', type: 'string'),
new OA\Property(property: 'is_active', type: 'boolean'),
new OA\Property(property: 'sort_order', type: 'integer'),
new OA\Property(property: 'meta_title', type: 'string'),
new OA\Property(property: 'meta_description', type: 'string'),
],
),
),
responses: [
new OA\Response(response: 201, description: 'Kategori oluşturuldu'),
new OA\Response(response: 422, description: 'Validasyon hatası'),
],
)]
public function store(StoreCategoryRequest $request, CreateCategoryAction $action): JsonResponse
{
$dto = CategoryData::fromArray($request->validated());
$category = $action->execute($dto);
return (new CategoryResource($category))
->response()
->setStatusCode(201);
}
#[OA\Get(
path: '/api/admin/categories/{category}',
summary: 'Kategori detayı (Admin)',
tags: ['Admin - Categories'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'category', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
responses: [
new OA\Response(response: 200, description: 'Kategori detayı'),
new OA\Response(response: 404, description: 'Bulunamadı'),
],
)]
public function show(Category $category): CategoryResource
{
return new CategoryResource($category);
}
#[OA\Put(
path: '/api/admin/categories/{category}',
summary: 'Kategori güncelle',
tags: ['Admin - Categories'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'category', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(
properties: [
new OA\Property(property: 'name', type: 'string'),
new OA\Property(property: 'slug', type: 'string'),
new OA\Property(property: 'description', type: 'string'),
new OA\Property(property: 'image', type: 'string'),
new OA\Property(property: 'icon', type: 'string'),
new OA\Property(property: 'is_active', type: 'boolean'),
new OA\Property(property: 'sort_order', type: 'integer'),
new OA\Property(property: 'meta_title', type: 'string'),
new OA\Property(property: 'meta_description', type: 'string'),
],
)),
responses: [
new OA\Response(response: 200, description: 'Kategori güncellendi'),
new OA\Response(response: 422, description: 'Validasyon hatası'),
],
)]
public function update(UpdateCategoryRequest $request, Category $category, UpdateCategoryAction $action): CategoryResource
{
$dto = CategoryData::fromArray($request->validated());
$category = $action->execute($category, $dto);
return new CategoryResource($category);
}
#[OA\Delete(
path: '/api/admin/categories/{category}',
summary: 'Kategori sil',
tags: ['Admin - Categories'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'category', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
responses: [
new OA\Response(response: 200, description: 'Kategori silindi'),
new OA\Response(response: 404, description: 'Bulunamadı'),
],
)]
public function destroy(Category $category, DeleteCategoryAction $action): JsonResponse
{
$action->execute($category);
return response()->json(['message' => 'Kategori başarıyla silindi.']);
}
}

View File

@@ -0,0 +1,94 @@
<?php
namespace App\Http\Controllers\Api\Admin;
use App\Actions\Comment\DeleteCommentAction;
use App\Actions\Comment\UpdateCommentAction;
use App\Http\Controllers\Controller;
use App\Http\Requests\Comment\UpdateCommentRequest;
use App\Http\Resources\CommentResource;
use App\Models\Comment;
use App\Repositories\Contracts\CommentRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use OpenApi\Attributes as OA;
class CommentController extends Controller
{
public function __construct(private CommentRepositoryInterface $repository) {}
#[OA\Get(
path: '/api/admin/comments',
summary: 'Yorumları listele (Admin)',
tags: ['Admin - Comments'],
security: [['sanctum' => []]],
parameters: [
new OA\Parameter(name: 'is_approved', in: 'query', required: false, schema: new OA\Schema(type: 'boolean')),
new OA\Parameter(name: 'commentable_type', in: 'query', required: false, schema: new OA\Schema(type: 'string')),
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: 'Yorum listesi')],
)]
public function index(Request $request): AnonymousResourceCollection
{
$comments = $this->repository->paginate(
$request->only(['is_approved', 'commentable_type', 'search']),
$request->integer('per_page', 15),
);
return CommentResource::collection($comments);
}
#[OA\Get(
path: '/api/admin/comments/{comment}',
summary: 'Yorum detayı',
tags: ['Admin - Comments'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'comment', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
responses: [new OA\Response(response: 200, description: 'Yorum detayı')],
)]
public function show(Comment $comment): JsonResponse
{
return response()->json(new CommentResource($comment));
}
#[OA\Put(
path: '/api/admin/comments/{comment}',
summary: 'Yorum güncelle (onayla/reddet)',
tags: ['Admin - Comments'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'comment', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(
properties: [
new OA\Property(property: 'is_approved', type: 'boolean'),
],
)),
responses: [
new OA\Response(response: 200, description: 'Yorum güncellendi'),
new OA\Response(response: 422, description: 'Validasyon hatası'),
],
)]
public function update(UpdateCommentRequest $request, Comment $comment, UpdateCommentAction $action): JsonResponse
{
$comment = $action->execute($comment, $request->validated());
return response()->json(new CommentResource($comment));
}
#[OA\Delete(
path: '/api/admin/comments/{comment}',
summary: 'Yorum sil',
tags: ['Admin - Comments'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'comment', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
responses: [new OA\Response(response: 200, description: 'Yorum silindi')],
)]
public function destroy(Comment $comment, DeleteCommentAction $action): JsonResponse
{
$action->execute($comment);
return response()->json(['message' => 'Yorum silindi.']);
}
}

View File

@@ -0,0 +1,172 @@
<?php
namespace App\Http\Controllers\Api\Admin;
use App\Http\Controllers\Controller;
use App\Http\Resources\CourseBlockResource;
use App\Models\Course;
use App\Models\CourseBlock;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use OpenApi\Attributes as OA;
class CourseBlockController extends Controller
{
#[OA\Get(
path: '/api/admin/courses/{course}/blocks',
summary: 'Eğitim bloklarını listele',
tags: ['Admin - Course Blocks'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'course', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
responses: [new OA\Response(response: 200, description: 'Blok listesi')],
)]
public function index(Course $course): AnonymousResourceCollection
{
return CourseBlockResource::collection(
$course->blocks()->orderBy('order_index')->get()
);
}
#[OA\Post(
path: '/api/admin/courses/{course}/blocks',
summary: 'Yeni eğitim bloğu oluştur',
tags: ['Admin - Course Blocks'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'course', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(
required: ['type', 'content'],
properties: [
new OA\Property(property: 'type', type: 'string'),
new OA\Property(property: 'content', type: 'object'),
new OA\Property(property: 'order_index', type: 'integer'),
new OA\Property(property: 'is_active', type: 'boolean'),
],
)),
responses: [
new OA\Response(response: 201, description: 'Blok oluşturuldu'),
new OA\Response(response: 422, description: 'Validasyon hatası'),
],
)]
public function store(Request $request, Course $course): JsonResponse
{
$validated = $request->validate([
'type' => ['required', 'string', 'max:50'],
'content' => ['present', 'array'],
'order_index' => ['sometimes', 'integer', 'min:0'],
'is_active' => ['sometimes', 'boolean'],
]);
$validated['order_index'] ??= $course->blocks()->max('order_index') + 1;
$block = $course->blocks()->create($validated);
return response()->json(new CourseBlockResource($block), 201);
}
#[OA\Get(
path: '/api/admin/courses/{course}/blocks/{block}',
summary: 'Eğitim blok detayı',
tags: ['Admin - Course Blocks'],
security: [['sanctum' => []]],
parameters: [
new OA\Parameter(name: 'course', in: 'path', required: true, schema: new OA\Schema(type: 'integer')),
new OA\Parameter(name: 'block', in: 'path', required: true, schema: new OA\Schema(type: 'integer')),
],
responses: [new OA\Response(response: 200, description: 'Blok detayı')],
)]
public function show(Course $course, CourseBlock $block): JsonResponse
{
return response()->json(new CourseBlockResource($block));
}
#[OA\Put(
path: '/api/admin/courses/{course}/blocks/{block}',
summary: 'Eğitim bloğu güncelle',
tags: ['Admin - Course Blocks'],
security: [['sanctum' => []]],
parameters: [
new OA\Parameter(name: 'course', in: 'path', required: true, schema: new OA\Schema(type: 'integer')),
new OA\Parameter(name: 'block', in: 'path', required: true, schema: new OA\Schema(type: 'integer')),
],
requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(
properties: [
new OA\Property(property: 'type', type: 'string'),
new OA\Property(property: 'content', type: 'object'),
new OA\Property(property: 'order_index', type: 'integer'),
new OA\Property(property: 'is_active', type: 'boolean'),
],
)),
responses: [
new OA\Response(response: 200, description: 'Blok güncellendi'),
new OA\Response(response: 422, description: 'Validasyon hatası'),
],
)]
public function update(Request $request, Course $course, CourseBlock $block): JsonResponse
{
$validated = $request->validate([
'type' => ['sometimes', 'string', 'max:50'],
'content' => ['sometimes', 'array'],
'order_index' => ['sometimes', 'integer', 'min:0'],
'is_active' => ['sometimes', 'boolean'],
]);
$block->update($validated);
return response()->json(new CourseBlockResource($block->fresh()));
}
#[OA\Delete(
path: '/api/admin/courses/{course}/blocks/{block}',
summary: 'Eğitim bloğu sil',
tags: ['Admin - Course Blocks'],
security: [['sanctum' => []]],
parameters: [
new OA\Parameter(name: 'course', in: 'path', required: true, schema: new OA\Schema(type: 'integer')),
new OA\Parameter(name: 'block', in: 'path', required: true, schema: new OA\Schema(type: 'integer')),
],
responses: [new OA\Response(response: 200, description: 'Blok silindi')],
)]
public function destroy(Course $course, CourseBlock $block): JsonResponse
{
$block->delete();
return response()->json(['message' => 'Blok silindi.']);
}
#[OA\Post(
path: '/api/admin/courses/{course}/blocks/reorder',
summary: 'Eğitim blok sıralamasını güncelle',
tags: ['Admin - Course Blocks'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'course', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(
required: ['items'],
properties: [
new OA\Property(property: 'items', type: 'array', items: new OA\Items(
properties: [
new OA\Property(property: 'id', type: 'integer'),
new OA\Property(property: 'order_index', type: 'integer'),
],
)),
],
)),
responses: [new OA\Response(response: 200, description: 'Sıralama güncellendi')],
)]
public function reorder(Request $request, Course $course): JsonResponse
{
$validated = $request->validate([
'items' => ['required', 'array', 'min:1'],
'items.*.id' => ['required', 'integer', 'exists:course_blocks,id'],
'items.*.order_index' => ['required', 'integer', 'min:0'],
]);
foreach ($validated['items'] as $item) {
$course->blocks()
->where('id', $item['id'])
->update(['order_index' => $item['order_index']]);
}
return response()->json(['message' => 'Blok sıralaması güncellendi.']);
}
}

View File

@@ -0,0 +1,163 @@
<?php
namespace App\Http\Controllers\Api\Admin;
use App\Actions\Course\CreateCourseAction;
use App\Actions\Course\DeleteCourseAction;
use App\Actions\Course\UpdateCourseAction;
use App\DTOs\CourseData;
use App\Http\Controllers\Controller;
use App\Http\Requests\Course\StoreCourseRequest;
use App\Http\Requests\Course\UpdateCourseRequest;
use App\Http\Resources\CourseResource;
use App\Models\Course;
use App\Repositories\Contracts\CourseRepositoryInterface;
use Illuminate\Http\JsonResponse;
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/admin/courses',
summary: 'Eğitimleri listele (Admin)',
tags: ['Admin - Courses'],
security: [['sanctum' => []]],
parameters: [
new OA\Parameter(name: 'category', in: 'query', required: false, schema: new OA\Schema(type: 'string')),
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: '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\Post(
path: '/api/admin/courses',
summary: 'Yeni eğitim oluştur',
tags: ['Admin - Courses'],
security: [['sanctum' => []]],
requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(
required: ['category_id', 'title', 'slug', 'desc', 'long_desc', 'duration'],
properties: [
new OA\Property(property: 'category_id', type: 'integer', description: 'Kategori ID'),
new OA\Property(property: 'slug', type: 'string', description: 'URL slug (unique)'),
new OA\Property(property: 'title', type: 'string', description: 'Eğitim başlığı'),
new OA\Property(property: 'sub', type: 'string', nullable: true, description: 'Alt başlık. Örn: STCW II/1'),
new OA\Property(property: 'desc', type: 'string', description: 'Kısa açıklama'),
new OA\Property(property: 'long_desc', type: 'string', description: 'Detaylııklama'),
new OA\Property(property: 'duration', type: 'string', description: 'Süre. Örn: 5 Gün'),
new OA\Property(property: 'students', type: 'integer', description: 'Öğrenci sayısı'),
new OA\Property(property: 'rating', type: 'number', format: 'float', description: 'Puan (0-5)'),
new OA\Property(property: 'badge', type: 'string', nullable: true, description: 'Rozet. Örn: Simülatör'),
new OA\Property(property: 'image', type: 'string', nullable: true, description: 'Görsel path'),
new OA\Property(property: 'price', type: 'string', nullable: true, description: 'Fiyat. Örn: 5.000 TL'),
new OA\Property(property: 'includes', type: 'array', items: new OA\Items(type: 'string'), nullable: true, description: 'Fiyata dahil olanlar'),
new OA\Property(property: 'requirements', type: 'array', items: new OA\Items(type: 'string'), nullable: true, description: 'Katılım koşulları'),
new OA\Property(property: 'meta_title', type: 'string', nullable: true, description: 'SEO Title'),
new OA\Property(property: 'meta_description', type: 'string', nullable: true, description: 'SEO Description'),
new OA\Property(property: 'scope', type: 'array', items: new OA\Items(type: 'string'), nullable: true, description: 'Eğitim kapsamı konu başlıkları'),
new OA\Property(property: 'standard', type: 'string', nullable: true, description: 'Uyum standardı. Örn: STCW / IMO Uyumlu'),
new OA\Property(property: 'language', type: 'string', nullable: true, description: 'Eğitim dili. Varsayılan: Türkçe'),
new OA\Property(property: 'location', type: 'string', nullable: true, description: 'Varsayılan lokasyon. Örn: Kadıköy, İstanbul'),
],
)),
responses: [
new OA\Response(response: 201, description: 'Eğitim oluşturuldu'),
new OA\Response(response: 422, description: 'Validasyon hatası'),
],
)]
public function store(StoreCourseRequest $request, CreateCourseAction $action): JsonResponse
{
$dto = CourseData::fromArray($request->validated());
$course = $action->execute($dto);
return (new CourseResource($course->load('category')))
->response()
->setStatusCode(201);
}
#[OA\Get(
path: '/api/admin/courses/{course}',
summary: 'Eğitim detayı (Admin)',
tags: ['Admin - Courses'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'course', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
responses: [new OA\Response(response: 200, description: 'Eğitim detayı')],
)]
public function show(Course $course): CourseResource
{
return new CourseResource($course->load(['category', 'schedules', 'blocks']));
}
#[OA\Put(
path: '/api/admin/courses/{course}',
summary: 'Eğitim güncelle',
tags: ['Admin - Courses'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'course', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(
required: ['category_id', 'title', 'slug', 'desc', 'long_desc', 'duration'],
properties: [
new OA\Property(property: 'category_id', type: 'integer', description: 'Kategori ID'),
new OA\Property(property: 'slug', type: 'string', description: 'URL slug (unique)'),
new OA\Property(property: 'title', type: 'string', description: 'Eğitim başlığı'),
new OA\Property(property: 'sub', type: 'string', nullable: true, description: 'Alt başlık'),
new OA\Property(property: 'desc', type: 'string', description: 'Kısa açıklama'),
new OA\Property(property: 'long_desc', type: 'string', description: 'Detaylııklama'),
new OA\Property(property: 'duration', type: 'string', description: 'Süre. Örn: 5 Gün'),
new OA\Property(property: 'students', type: 'integer', description: 'Öğrenci sayısı'),
new OA\Property(property: 'rating', type: 'number', format: 'float', description: 'Puan (0-5)'),
new OA\Property(property: 'badge', type: 'string', nullable: true, description: 'Rozet'),
new OA\Property(property: 'image', type: 'string', nullable: true, description: 'Görsel path'),
new OA\Property(property: 'price', type: 'string', nullable: true, description: 'Fiyat'),
new OA\Property(property: 'includes', type: 'array', items: new OA\Items(type: 'string'), nullable: true, description: 'Fiyata dahil olanlar'),
new OA\Property(property: 'requirements', type: 'array', items: new OA\Items(type: 'string'), nullable: true, description: 'Katılım koşulları'),
new OA\Property(property: 'meta_title', type: 'string', nullable: true, description: 'SEO Title'),
new OA\Property(property: 'meta_description', type: 'string', nullable: true, description: 'SEO Description'),
new OA\Property(property: 'scope', type: 'array', items: new OA\Items(type: 'string'), nullable: true, description: 'Eğitim kapsamı konu başlıkları'),
new OA\Property(property: 'standard', type: 'string', nullable: true, description: 'Uyum standardı'),
new OA\Property(property: 'language', type: 'string', nullable: true, description: 'Eğitim dili'),
new OA\Property(property: 'location', type: 'string', nullable: true, description: 'Varsayılan lokasyon'),
],
)),
responses: [
new OA\Response(response: 200, description: 'Eğitim güncellendi'),
new OA\Response(response: 422, description: 'Validasyon hatası'),
],
)]
public function update(UpdateCourseRequest $request, Course $course, UpdateCourseAction $action): CourseResource
{
$dto = CourseData::fromArray($request->validated());
$course = $action->execute($course, $dto);
return new CourseResource($course->load('category'));
}
#[OA\Delete(
path: '/api/admin/courses/{course}',
summary: 'Eğitim sil',
tags: ['Admin - Courses'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'course', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
responses: [new OA\Response(response: 200, description: 'Eğitim silindi')],
)]
public function destroy(Course $course, DeleteCourseAction $action): JsonResponse
{
$action->execute($course);
return response()->json(['message' => 'Eğitim başarıyla silindi.']);
}
}

View File

@@ -0,0 +1,128 @@
<?php
namespace App\Http\Controllers\Api\Admin;
use App\Actions\Faq\CreateFaqAction;
use App\Actions\Faq\DeleteFaqAction;
use App\Actions\Faq\UpdateFaqAction;
use App\DTOs\FaqData;
use App\Http\Controllers\Controller;
use App\Http\Requests\Faq\StoreFaqRequest;
use App\Http\Requests\Faq\UpdateFaqRequest;
use App\Http\Resources\FaqResource;
use App\Models\Faq;
use App\Repositories\Contracts\FaqRepositoryInterface;
use Illuminate\Http\JsonResponse;
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/admin/faqs',
summary: 'SSS listele (Admin)',
tags: ['Admin - FAQs'],
security: [['sanctum' => []]],
parameters: [
new OA\Parameter(name: 'category', 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: 50)),
],
responses: [new OA\Response(response: 200, description: 'SSS listesi')],
)]
public function index(Request $request): AnonymousResourceCollection
{
$faqs = $this->repository->paginate(
$request->only(['category']),
$request->integer('per_page', 50),
);
return FaqResource::collection($faqs);
}
#[OA\Post(
path: '/api/admin/faqs',
summary: 'Yeni SSS oluştur',
tags: ['Admin - FAQs'],
security: [['sanctum' => []]],
requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(
required: ['question', 'answer', 'category'],
properties: [
new OA\Property(property: 'question', type: 'string'),
new OA\Property(property: 'answer', type: 'string'),
new OA\Property(property: 'category', type: 'string'),
new OA\Property(property: 'is_active', type: 'boolean'),
new OA\Property(property: 'sort_order', type: 'integer'),
],
)),
responses: [
new OA\Response(response: 201, description: 'SSS oluşturuldu'),
new OA\Response(response: 422, description: 'Validasyon hatası'),
],
)]
public function store(StoreFaqRequest $request, CreateFaqAction $action): JsonResponse
{
$dto = FaqData::fromArray($request->validated());
$faq = $action->execute($dto);
return response()->json(new FaqResource($faq), 201);
}
#[OA\Get(
path: '/api/admin/faqs/{faq}',
summary: 'SSS detayı',
tags: ['Admin - FAQs'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'faq', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
responses: [new OA\Response(response: 200, description: 'SSS detayı')],
)]
public function show(Faq $faq): JsonResponse
{
return response()->json(new FaqResource($faq));
}
#[OA\Put(
path: '/api/admin/faqs/{faq}',
summary: 'SSS güncelle',
tags: ['Admin - FAQs'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'faq', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(
properties: [
new OA\Property(property: 'question', type: 'string'),
new OA\Property(property: 'answer', type: 'string'),
new OA\Property(property: 'category', type: 'string'),
new OA\Property(property: 'is_active', type: 'boolean'),
new OA\Property(property: 'sort_order', type: 'integer'),
],
)),
responses: [
new OA\Response(response: 200, description: 'SSS güncellendi'),
new OA\Response(response: 422, description: 'Validasyon hatası'),
],
)]
public function update(UpdateFaqRequest $request, Faq $faq, UpdateFaqAction $action): JsonResponse
{
$dto = FaqData::fromArray(array_merge($faq->toArray(), $request->validated()));
$faq = $action->execute($faq, $dto);
return response()->json(new FaqResource($faq));
}
#[OA\Delete(
path: '/api/admin/faqs/{faq}',
summary: 'SSS sil',
tags: ['Admin - FAQs'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'faq', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
responses: [new OA\Response(response: 200, description: 'SSS silindi')],
)]
public function destroy(Faq $faq, DeleteFaqAction $action): JsonResponse
{
$action->execute($faq);
return response()->json(['message' => 'SSS silindi.']);
}
}

View File

@@ -0,0 +1,127 @@
<?php
namespace App\Http\Controllers\Api\Admin;
use App\Actions\GuideCard\CreateGuideCardAction;
use App\Actions\GuideCard\DeleteGuideCardAction;
use App\Actions\GuideCard\UpdateGuideCardAction;
use App\DTOs\GuideCardData;
use App\Http\Controllers\Controller;
use App\Http\Requests\GuideCard\StoreGuideCardRequest;
use App\Http\Requests\GuideCard\UpdateGuideCardRequest;
use App\Http\Resources\GuideCardResource;
use App\Models\GuideCard;
use App\Repositories\Contracts\GuideCardRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
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/admin/guide-cards',
summary: 'Rehber kartları listele (Admin)',
tags: ['Admin - Guide Cards'],
security: [['sanctum' => []]],
parameters: [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: 'Kart listesi')],
)]
public function index(Request $request): AnonymousResourceCollection
{
$cards = $this->repository->paginate(
$request->only([]),
$request->integer('per_page', 15),
);
return GuideCardResource::collection($cards);
}
#[OA\Post(
path: '/api/admin/guide-cards',
summary: 'Yeni rehber kartı oluştur',
tags: ['Admin - Guide Cards'],
security: [['sanctum' => []]],
requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(
required: ['title', 'description', 'icon'],
properties: [
new OA\Property(property: 'title', type: 'string'),
new OA\Property(property: 'description', type: 'string'),
new OA\Property(property: 'icon', type: 'string'),
new OA\Property(property: 'url', type: 'string'),
new OA\Property(property: 'is_active', type: 'boolean'),
new OA\Property(property: 'sort_order', type: 'integer'),
],
)),
responses: [
new OA\Response(response: 201, description: 'Kart oluşturuldu'),
new OA\Response(response: 422, description: 'Validasyon hatası'),
],
)]
public function store(StoreGuideCardRequest $request, CreateGuideCardAction $action): JsonResponse
{
$dto = GuideCardData::fromArray($request->validated());
$card = $action->execute($dto);
return response()->json(new GuideCardResource($card), 201);
}
#[OA\Get(
path: '/api/admin/guide-cards/{guideCard}',
summary: 'Rehber kart detayı',
tags: ['Admin - Guide Cards'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'guideCard', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
responses: [new OA\Response(response: 200, description: 'Kart detayı')],
)]
public function show(GuideCard $guideCard): JsonResponse
{
return response()->json(new GuideCardResource($guideCard));
}
#[OA\Put(
path: '/api/admin/guide-cards/{guideCard}',
summary: 'Rehber kart güncelle',
tags: ['Admin - Guide Cards'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'guideCard', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(
properties: [
new OA\Property(property: 'title', type: 'string'),
new OA\Property(property: 'description', type: 'string'),
new OA\Property(property: 'icon', type: 'string'),
new OA\Property(property: 'url', type: 'string'),
new OA\Property(property: 'is_active', type: 'boolean'),
new OA\Property(property: 'sort_order', type: 'integer'),
],
)),
responses: [
new OA\Response(response: 200, description: 'Kart güncellendi'),
new OA\Response(response: 422, description: 'Validasyon hatası'),
],
)]
public function update(UpdateGuideCardRequest $request, GuideCard $guideCard, UpdateGuideCardAction $action): JsonResponse
{
$dto = GuideCardData::fromArray(array_merge($guideCard->toArray(), $request->validated()));
$guideCard = $action->execute($guideCard, $dto);
return response()->json(new GuideCardResource($guideCard));
}
#[OA\Delete(
path: '/api/admin/guide-cards/{guideCard}',
summary: 'Rehber kart sil',
tags: ['Admin - Guide Cards'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'guideCard', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
responses: [new OA\Response(response: 200, description: 'Kart silindi')],
)]
public function destroy(GuideCard $guideCard, DeleteGuideCardAction $action): JsonResponse
{
$action->execute($guideCard);
return response()->json(['message' => 'Rehber kartı silindi.']);
}
}

View File

@@ -0,0 +1,130 @@
<?php
namespace App\Http\Controllers\Api\Admin;
use App\Actions\HeroSlide\CreateHeroSlideAction;
use App\Actions\HeroSlide\DeleteHeroSlideAction;
use App\Actions\HeroSlide\UpdateHeroSlideAction;
use App\DTOs\HeroSlideData;
use App\Http\Controllers\Controller;
use App\Http\Requests\HeroSlide\StoreHeroSlideRequest;
use App\Http\Requests\HeroSlide\UpdateHeroSlideRequest;
use App\Http\Resources\HeroSlideResource;
use App\Models\HeroSlide;
use App\Repositories\Contracts\HeroSlideRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
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/admin/hero-slides',
summary: 'Hero slide listele (Admin)',
tags: ['Admin - Hero Slides'],
security: [['sanctum' => []]],
parameters: [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: 'Slide listesi')],
)]
public function index(Request $request): AnonymousResourceCollection
{
$slides = $this->repository->paginate(
$request->only([]),
$request->integer('per_page', 15),
);
return HeroSlideResource::collection($slides);
}
#[OA\Post(
path: '/api/admin/hero-slides',
summary: 'Yeni hero slide oluştur',
tags: ['Admin - Hero Slides'],
security: [['sanctum' => []]],
requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(
required: ['title', 'image'],
properties: [
new OA\Property(property: 'title', type: 'string'),
new OA\Property(property: 'subtitle', type: 'string'),
new OA\Property(property: 'image', type: 'string'),
new OA\Property(property: 'mobile_image', type: 'string'),
new OA\Property(property: 'button_text', type: 'string'),
new OA\Property(property: 'button_url', type: 'string'),
new OA\Property(property: 'is_active', type: 'boolean'),
new OA\Property(property: 'sort_order', type: 'integer'),
],
)),
responses: [
new OA\Response(response: 201, description: 'Slide oluşturuldu'),
new OA\Response(response: 422, description: 'Validasyon hatası'),
],
)]
public function store(StoreHeroSlideRequest $request, CreateHeroSlideAction $action): JsonResponse
{
$dto = HeroSlideData::fromArray($request->validated());
$slide = $action->execute($dto);
return response()->json(new HeroSlideResource($slide), 201);
}
#[OA\Get(
path: '/api/admin/hero-slides/{heroSlide}',
summary: 'Hero slide detayı',
tags: ['Admin - Hero Slides'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'heroSlide', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
responses: [new OA\Response(response: 200, description: 'Slide detayı')],
)]
public function show(HeroSlide $heroSlide): JsonResponse
{
return response()->json(new HeroSlideResource($heroSlide));
}
#[OA\Put(
path: '/api/admin/hero-slides/{heroSlide}',
summary: 'Hero slide güncelle',
tags: ['Admin - Hero Slides'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'heroSlide', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(
properties: [
new OA\Property(property: 'title', type: 'string'),
new OA\Property(property: 'subtitle', type: 'string'),
new OA\Property(property: 'image', type: 'string'),
new OA\Property(property: 'button_text', type: 'string'),
new OA\Property(property: 'button_url', type: 'string'),
new OA\Property(property: 'is_active', type: 'boolean'),
new OA\Property(property: 'sort_order', type: 'integer'),
],
)),
responses: [
new OA\Response(response: 200, description: 'Slide güncellendi'),
new OA\Response(response: 422, description: 'Validasyon hatası'),
],
)]
public function update(UpdateHeroSlideRequest $request, HeroSlide $heroSlide, UpdateHeroSlideAction $action): JsonResponse
{
$dto = HeroSlideData::fromArray(array_merge($heroSlide->toArray(), $request->validated()));
$heroSlide = $action->execute($heroSlide, $dto);
return response()->json(new HeroSlideResource($heroSlide));
}
#[OA\Delete(
path: '/api/admin/hero-slides/{heroSlide}',
summary: 'Hero slide sil',
tags: ['Admin - Hero Slides'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'heroSlide', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
responses: [new OA\Response(response: 200, description: 'Slide silindi')],
)]
public function destroy(HeroSlide $heroSlide, DeleteHeroSlideAction $action): JsonResponse
{
$action->execute($heroSlide);
return response()->json(['message' => 'Hero slide silindi.']);
}
}

View File

@@ -0,0 +1,103 @@
<?php
namespace App\Http\Controllers\Api\Admin;
use App\Actions\Lead\DeleteLeadAction;
use App\Actions\Lead\UpdateLeadAction;
use App\DTOs\LeadData;
use App\Http\Controllers\Controller;
use App\Http\Requests\Lead\UpdateLeadRequest;
use App\Http\Resources\LeadResource;
use App\Models\Lead;
use App\Repositories\Contracts\LeadRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use OpenApi\Attributes as OA;
class LeadController extends Controller
{
public function __construct(private LeadRepositoryInterface $repository) {}
#[OA\Get(
path: '/api/admin/leads',
summary: 'Başvuruları listele (Admin)',
tags: ['Admin - Leads'],
security: [['sanctum' => []]],
parameters: [
new OA\Parameter(name: 'status', in: 'query', required: false, schema: new OA\Schema(type: 'string')),
new OA\Parameter(name: 'source', in: 'query', required: false, schema: new OA\Schema(type: 'string')),
new OA\Parameter(name: 'is_read', 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: 'per_page', in: 'query', required: false, schema: new OA\Schema(type: 'integer', default: 15)),
],
responses: [new OA\Response(response: 200, description: 'Başvuru listesi')],
)]
public function index(Request $request): AnonymousResourceCollection
{
$leads = $this->repository->paginate(
$request->only(['status', 'source', 'is_read', 'search']),
$request->integer('per_page', 15),
);
return LeadResource::collection($leads);
}
#[OA\Get(
path: '/api/admin/leads/{lead}',
summary: 'Başvuru detayı',
tags: ['Admin - Leads'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'lead', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
responses: [new OA\Response(response: 200, description: 'Başvuru detayı')],
)]
public function show(Lead $lead): JsonResponse
{
if (! $lead->is_read) {
$lead->update(['is_read' => true]);
}
return response()->json(new LeadResource($lead));
}
#[OA\Put(
path: '/api/admin/leads/{lead}',
summary: 'Başvuru güncelle',
tags: ['Admin - Leads'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'lead', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(
properties: [
new OA\Property(property: 'status', type: 'string'),
new OA\Property(property: 'is_read', type: 'boolean'),
new OA\Property(property: 'admin_notes', type: 'string'),
],
)),
responses: [
new OA\Response(response: 200, description: 'Başvuru güncellendi'),
new OA\Response(response: 422, description: 'Validasyon hatası'),
],
)]
public function update(UpdateLeadRequest $request, Lead $lead, UpdateLeadAction $action): JsonResponse
{
$dto = LeadData::fromArray(array_merge($lead->toArray(), $request->validated()));
$lead = $action->execute($lead, $dto);
return response()->json(new LeadResource($lead));
}
#[OA\Delete(
path: '/api/admin/leads/{lead}',
summary: 'Başvuru sil',
tags: ['Admin - Leads'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'lead', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
responses: [new OA\Response(response: 200, description: 'Başvuru silindi')],
)]
public function destroy(Lead $lead, DeleteLeadAction $action): JsonResponse
{
$action->execute($lead);
return response()->json(['message' => 'Talep silindi.']);
}
}

View File

@@ -0,0 +1,159 @@
<?php
namespace App\Http\Controllers\Api\Admin;
use App\Actions\Menu\CreateMenuAction;
use App\Actions\Menu\DeleteMenuAction;
use App\Actions\Menu\UpdateMenuAction;
use App\DTOs\MenuData;
use App\Http\Controllers\Controller;
use App\Http\Requests\Menu\ReorderMenuRequest;
use App\Http\Requests\Menu\StoreMenuRequest;
use App\Http\Requests\Menu\UpdateMenuRequest;
use App\Http\Resources\MenuResource;
use App\Models\Menu;
use App\Repositories\Contracts\MenuRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
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/admin/menus',
summary: 'Menü öğelerini listele (Admin)',
tags: ['Admin - Menus'],
security: [['sanctum' => []]],
parameters: [
new OA\Parameter(name: 'location', 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: 50)),
],
responses: [new OA\Response(response: 200, description: 'Menü listesi')],
)]
public function index(Request $request): AnonymousResourceCollection
{
$menus = $this->repository->paginate(
$request->only(['location']),
$request->integer('per_page', 50),
);
return MenuResource::collection($menus);
}
#[OA\Post(
path: '/api/admin/menus',
summary: 'Yeni menü öğesi oluştur',
tags: ['Admin - Menus'],
security: [['sanctum' => []]],
requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(
required: ['title', 'url', 'location'],
properties: [
new OA\Property(property: 'title', type: 'string'),
new OA\Property(property: 'url', type: 'string'),
new OA\Property(property: 'location', type: 'string'),
new OA\Property(property: 'parent_id', type: 'integer'),
new OA\Property(property: 'target', type: 'string'),
new OA\Property(property: 'icon', type: 'string'),
new OA\Property(property: 'is_active', type: 'boolean'),
new OA\Property(property: 'sort_order', type: 'integer'),
],
)),
responses: [
new OA\Response(response: 201, description: 'Menü oluşturuldu'),
new OA\Response(response: 422, description: 'Validasyon hatası'),
],
)]
public function store(StoreMenuRequest $request, CreateMenuAction $action): JsonResponse
{
$dto = MenuData::fromArray($request->validated());
$menu = $action->execute($dto);
return response()->json(new MenuResource($menu), 201);
}
#[OA\Get(
path: '/api/admin/menus/{menu}',
summary: 'Menü detayı',
tags: ['Admin - Menus'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'menu', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
responses: [new OA\Response(response: 200, description: 'Menü detayı')],
)]
public function show(Menu $menu): JsonResponse
{
return response()->json(new MenuResource($menu->load('children')));
}
#[OA\Put(
path: '/api/admin/menus/{menu}',
summary: 'Menü güncelle',
tags: ['Admin - Menus'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'menu', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(
properties: [
new OA\Property(property: 'title', type: 'string'),
new OA\Property(property: 'url', type: 'string'),
new OA\Property(property: 'location', type: 'string'),
new OA\Property(property: 'parent_id', type: 'integer'),
new OA\Property(property: 'is_active', type: 'boolean'),
new OA\Property(property: 'sort_order', type: 'integer'),
],
)),
responses: [
new OA\Response(response: 200, description: 'Menü güncellendi'),
new OA\Response(response: 422, description: 'Validasyon hatası'),
],
)]
public function update(UpdateMenuRequest $request, Menu $menu, UpdateMenuAction $action): JsonResponse
{
$dto = MenuData::fromArray(array_merge($menu->toArray(), $request->validated()));
$menu = $action->execute($menu, $dto);
return response()->json(new MenuResource($menu->load('children')));
}
#[OA\Delete(
path: '/api/admin/menus/{menu}',
summary: 'Menü sil',
tags: ['Admin - Menus'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'menu', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
responses: [new OA\Response(response: 200, description: 'Menü silindi')],
)]
public function destroy(Menu $menu, DeleteMenuAction $action): JsonResponse
{
$action->execute($menu);
return response()->json(['message' => 'Menü silindi.']);
}
#[OA\Post(
path: '/api/admin/menus/reorder',
summary: 'Menü sıralamasını güncelle',
tags: ['Admin - Menus'],
security: [['sanctum' => []]],
requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(
required: ['items'],
properties: [
new OA\Property(property: 'items', type: 'array', items: new OA\Items(
properties: [
new OA\Property(property: 'id', type: 'integer'),
new OA\Property(property: 'order_index', type: 'integer'),
new OA\Property(property: 'parent_id', type: 'integer', nullable: true),
],
)),
],
)),
responses: [new OA\Response(response: 200, description: 'Sıralama güncellendi')],
)]
public function reorder(ReorderMenuRequest $request): JsonResponse
{
$this->repository->reorder($request->validated('items'));
return response()->json(['message' => 'Menü sıralaması güncellendi.']);
}
}

View File

@@ -0,0 +1,179 @@
<?php
namespace App\Http\Controllers\Api\Admin;
use App\Actions\Page\CreatePageAction;
use App\Actions\Page\DeletePageAction;
use App\Actions\Page\UpdatePageAction;
use App\DTOs\PageData;
use App\Http\Controllers\Controller;
use App\Http\Requests\Page\StorePageRequest;
use App\Http\Requests\Page\UpdatePageRequest;
use App\Http\Resources\PageResource;
use App\Models\Page;
use App\Repositories\Contracts\PageRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Illuminate\Support\Facades\DB;
use OpenApi\Attributes as OA;
class PageController extends Controller
{
public function __construct(private PageRepositoryInterface $repository) {}
#[OA\Get(
path: '/api/admin/pages',
summary: 'Sayfaları listele (Admin)',
tags: ['Admin - Pages'],
security: [['sanctum' => []]],
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: 'Sayfa listesi')],
)]
public function index(Request $request): AnonymousResourceCollection
{
$pages = $this->repository->paginate(
$request->only(['search']),
$request->integer('per_page', 15),
);
return PageResource::collection($pages);
}
#[OA\Post(
path: '/api/admin/pages',
summary: 'Yeni sayfa oluştur',
tags: ['Admin - Pages'],
security: [['sanctum' => []]],
requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(
required: ['title', 'slug'],
properties: [
new OA\Property(property: 'title', type: 'string'),
new OA\Property(property: 'slug', type: 'string'),
new OA\Property(property: 'content', type: 'string'),
new OA\Property(property: 'template', type: 'string'),
new OA\Property(property: 'is_active', type: 'boolean'),
new OA\Property(property: 'meta_title', type: 'string'),
new OA\Property(property: 'meta_description', type: 'string'),
new OA\Property(property: 'blocks', type: 'array', items: new OA\Items(
properties: [
new OA\Property(property: 'type', type: 'string'),
new OA\Property(property: 'content', type: 'object'),
new OA\Property(property: 'order_index', type: 'integer'),
],
)),
],
)),
responses: [
new OA\Response(response: 201, description: 'Sayfa oluşturuldu'),
new OA\Response(response: 422, description: 'Validasyon hatası'),
],
)]
public function store(StorePageRequest $request, CreatePageAction $action): JsonResponse
{
return DB::transaction(function () use ($request, $action) {
$validated = $request->validated();
$blocks = $validated['blocks'] ?? [];
unset($validated['blocks']);
$dto = PageData::fromArray($validated);
$page = $action->execute($dto);
foreach ($blocks as $index => $block) {
$page->blocks()->create([
'type' => $block['type'],
'content' => $block['content'],
'order_index' => $block['order_index'] ?? $index,
]);
}
return response()->json(new PageResource($page->load('blocks')), 201);
});
}
#[OA\Get(
path: '/api/admin/pages/{page}',
summary: 'Sayfa detayı (Admin)',
tags: ['Admin - Pages'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'page', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
responses: [new OA\Response(response: 200, description: 'Sayfa detayı')],
)]
public function show(Page $page): JsonResponse
{
return response()->json(new PageResource($page->load('blocks')));
}
#[OA\Put(
path: '/api/admin/pages/{page}',
summary: 'Sayfa güncelle',
tags: ['Admin - Pages'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'page', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(
properties: [
new OA\Property(property: 'title', type: 'string'),
new OA\Property(property: 'slug', type: 'string'),
new OA\Property(property: 'content', type: 'string'),
new OA\Property(property: 'template', type: 'string'),
new OA\Property(property: 'is_active', type: 'boolean'),
new OA\Property(property: 'meta_title', type: 'string'),
new OA\Property(property: 'meta_description', type: 'string'),
new OA\Property(property: 'blocks', type: 'array', items: new OA\Items(
properties: [
new OA\Property(property: 'type', type: 'string'),
new OA\Property(property: 'content', type: 'object'),
new OA\Property(property: 'order_index', type: 'integer'),
],
)),
],
)),
responses: [
new OA\Response(response: 200, description: 'Sayfa güncellendi'),
new OA\Response(response: 422, description: 'Validasyon hatası'),
],
)]
public function update(UpdatePageRequest $request, Page $page, UpdatePageAction $action): JsonResponse
{
return DB::transaction(function () use ($request, $page, $action) {
$validated = $request->validated();
$blocks = $validated['blocks'] ?? null;
unset($validated['blocks']);
$dto = PageData::fromArray(array_merge($page->toArray(), $validated));
$page = $action->execute($page, $dto);
if ($blocks !== null) {
$page->blocks()->delete();
foreach ($blocks as $index => $block) {
$page->blocks()->create([
'type' => $block['type'],
'content' => $block['content'],
'order_index' => $block['order_index'] ?? $index,
]);
}
}
return response()->json(new PageResource($page->load('blocks')));
});
}
#[OA\Delete(
path: '/api/admin/pages/{page}',
summary: 'Sayfa sil',
tags: ['Admin - Pages'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'page', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
responses: [new OA\Response(response: 200, description: 'Sayfa silindi')],
)]
public function destroy(Page $page, DeletePageAction $action): JsonResponse
{
$page->blocks()->delete();
$action->execute($page);
return response()->json(['message' => 'Sayfa silindi.']);
}
}

View File

@@ -0,0 +1,92 @@
<?php
namespace App\Http\Controllers\Api\Admin;
use App\Http\Controllers\Controller;
use App\Http\Requests\Preview\StorePreviewRequest;
use App\Models\Page;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
use OpenApi\Attributes as OA;
class PreviewController extends Controller
{
private const CACHE_TTL = 600; // 10 minutes
#[OA\Post(
path: '/api/admin/preview',
summary: 'Önizleme oluştur',
tags: ['Admin - Preview'],
security: [['sanctum' => []]],
requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(
required: ['page_id', 'blocks'],
properties: [
new OA\Property(property: 'page_id', type: 'integer'),
new OA\Property(property: 'blocks', type: 'array', items: new OA\Items(
properties: [
new OA\Property(property: 'type', type: 'string'),
new OA\Property(property: 'content', type: 'object'),
new OA\Property(property: 'order_index', type: 'integer'),
],
)),
],
)),
responses: [
new OA\Response(response: 201, description: 'Önizleme oluşturuldu'),
new OA\Response(response: 422, description: 'Validasyon hatası'),
],
)]
public function store(StorePreviewRequest $request): JsonResponse
{
$validated = $request->validated();
$page = Page::findOrFail($validated['page_id']);
$token = (string) Str::uuid();
$blocks = collect($validated['blocks'])
->sortBy('order_index')
->values()
->map(fn (array $block, int $index) => [
'id' => $index + 1,
'type' => $block['type'],
'content' => $block['content'],
'order_index' => $block['order_index'],
])
->all();
Cache::put("preview_{$token}", [
'id' => $page->id,
'slug' => $page->slug,
'title' => $page->title,
'meta_title' => $page->meta_title,
'meta_description' => $page->meta_description,
'is_active' => $page->is_active,
'blocks' => $blocks,
'created_at' => $page->created_at?->toISOString(),
'updated_at' => now()->toISOString(),
], self::CACHE_TTL);
$previewUrl = config('app.frontend_url')."/api/preview?token={$token}&slug={$page->slug}";
return response()->json([
'token' => $token,
'preview_url' => $previewUrl,
'expires_in' => self::CACHE_TTL,
], 201);
}
#[OA\Delete(
path: '/api/admin/preview/{token}',
summary: 'Önizlemeyi sil',
tags: ['Admin - Preview'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'token', in: 'path', required: true, schema: new OA\Schema(type: 'string'))],
responses: [new OA\Response(response: 200, description: 'Önizleme silindi')],
)]
public function destroy(string $token): JsonResponse
{
Cache::forget("preview_{$token}");
return response()->json(['message' => 'Önizleme silindi.']);
}
}

View File

@@ -0,0 +1,180 @@
<?php
namespace App\Http\Controllers\Api\Admin;
use App\Http\Controllers\Controller;
use App\Http\Requests\Role\StoreRoleRequest;
use App\Http\Requests\Role\UpdateRoleRequest;
use App\Http\Resources\RoleResource;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use OpenApi\Attributes as OA;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
class RoleController extends Controller
{
#[OA\Get(
path: '/api/admin/roles',
summary: 'Rolleri listele',
tags: ['Admin - Roles'],
security: [['sanctum' => []]],
responses: [new OA\Response(response: 200, description: 'Rol listesi')],
)]
public function index(): AnonymousResourceCollection
{
$roles = Role::query()
->with('permissions')
->get()
->loadCount('users');
return RoleResource::collection($roles);
}
#[OA\Post(
path: '/api/admin/roles',
summary: 'Yeni rol oluştur',
tags: ['Admin - Roles'],
security: [['sanctum' => []]],
requestBody: new OA\RequestBody(
required: true,
content: new OA\JsonContent(
required: ['name', 'permissions'],
properties: [
new OA\Property(property: 'name', type: 'string', example: 'moderator'),
new OA\Property(property: 'permissions', type: 'array', items: new OA\Items(type: 'string'), example: ['view-category', 'view-course']),
],
),
),
responses: [
new OA\Response(response: 201, description: 'Rol oluşturuldu'),
new OA\Response(response: 422, description: 'Validasyon hatası'),
],
)]
public function store(StoreRoleRequest $request): JsonResponse
{
$role = Role::create([
'name' => $request->validated('name'),
'guard_name' => 'web',
]);
$role->syncPermissions($request->validated('permissions'));
$role->load('permissions');
return (new RoleResource($role))
->response()
->setStatusCode(201);
}
#[OA\Get(
path: '/api/admin/roles/{role}',
summary: 'Rol detayı',
tags: ['Admin - Roles'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'role', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
responses: [
new OA\Response(response: 200, description: 'Rol detayı'),
new OA\Response(response: 404, description: 'Bulunamadı'),
],
)]
public function show(Role $role): RoleResource
{
$role->load('permissions');
$role->loadCount('users');
return new RoleResource($role);
}
#[OA\Put(
path: '/api/admin/roles/{role}',
summary: 'Rol güncelle',
tags: ['Admin - Roles'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'role', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(
properties: [
new OA\Property(property: 'name', type: 'string', example: 'moderator'),
new OA\Property(property: 'permissions', type: 'array', items: new OA\Items(type: 'string')),
],
)),
responses: [
new OA\Response(response: 200, description: 'Rol güncellendi'),
new OA\Response(response: 422, description: 'Validasyon hatası'),
],
)]
public function update(UpdateRoleRequest $request, Role $role): RoleResource
{
$validated = $request->validated();
if (isset($validated['name'])) {
$role->update(['name' => $validated['name']]);
}
if (isset($validated['permissions'])) {
$role->syncPermissions($validated['permissions']);
}
$role->load('permissions');
return new RoleResource($role);
}
#[OA\Delete(
path: '/api/admin/roles/{role}',
summary: 'Rol sil',
tags: ['Admin - Roles'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'role', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
responses: [
new OA\Response(response: 200, description: 'Rol silindi'),
new OA\Response(response: 403, description: 'Varsayılan roller silinemez'),
new OA\Response(response: 404, description: 'Bulunamadı'),
],
)]
public function destroy(Role $role): JsonResponse
{
if (in_array($role->name, ['super-admin', 'editor'])) {
return response()->json(['message' => 'Varsayılan roller silinemez.'], 403);
}
if ($role->users()->count() > 0) {
return response()->json(['message' => 'Bu role atanmış kullanıcılar var. Önce kullanıcıların rollerini değiştirin.'], 422);
}
$role->delete();
return response()->json(['message' => 'Rol başarıyla silindi.']);
}
#[OA\Get(
path: '/api/admin/permissions',
summary: 'Tüm yetkileri listele',
description: 'Rol oluştururken/düzenlerken kullanılacak tüm mevcut yetkileri modül bazlı gruplandırarak döner.',
tags: ['Admin - Roles'],
security: [['sanctum' => []]],
responses: [new OA\Response(response: 200, description: 'Yetki listesi')],
)]
public function permissions(): JsonResponse
{
$permissions = Permission::query()
->where('guard_name', 'web')
->orderBy('name')
->pluck('name');
// Modül bazlı gruplandırma
$grouped = [];
foreach ($permissions as $permission) {
$parts = explode('-', $permission, 2);
if (count($parts) === 2) {
$grouped[$parts[1]][] = $permission;
}
}
return response()->json([
'data' => [
'all' => $permissions,
'grouped' => $grouped,
],
]);
}
}

View File

@@ -0,0 +1,135 @@
<?php
namespace App\Http\Controllers\Api\Admin;
use App\Actions\Schedule\CreateScheduleAction;
use App\Actions\Schedule\DeleteScheduleAction;
use App\Actions\Schedule\UpdateScheduleAction;
use App\DTOs\ScheduleData;
use App\Http\Controllers\Controller;
use App\Http\Requests\Schedule\StoreScheduleRequest;
use App\Http\Requests\Schedule\UpdateScheduleRequest;
use App\Http\Resources\CourseScheduleResource;
use App\Models\CourseSchedule;
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/admin/schedules',
summary: 'Takvimleri listele (Admin)',
tags: ['Admin - Schedules'],
security: [['sanctum' => []]],
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\Post(
path: '/api/admin/schedules',
summary: 'Yeni takvim oluştur',
tags: ['Admin - Schedules'],
security: [['sanctum' => []]],
requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(
required: ['course_id', 'start_date', 'location', 'quota'],
properties: [
new OA\Property(property: 'course_id', type: 'integer'),
new OA\Property(property: 'start_date', type: 'string', format: 'date'),
new OA\Property(property: 'end_date', type: 'string', format: 'date'),
new OA\Property(property: 'location', type: 'string'),
new OA\Property(property: 'instructor', type: 'string'),
new OA\Property(property: 'quota', type: 'integer'),
new OA\Property(property: 'enrolled_count', type: 'integer'),
new OA\Property(property: 'price_override', type: 'number'),
new OA\Property(property: 'status', type: 'string'),
new OA\Property(property: 'notes', type: 'string'),
],
)),
responses: [
new OA\Response(response: 201, description: 'Takvim oluşturuldu'),
new OA\Response(response: 422, description: 'Validasyon hatası'),
],
)]
public function store(StoreScheduleRequest $request, CreateScheduleAction $action): JsonResponse
{
$dto = ScheduleData::fromArray($request->validated());
$schedule = $action->execute($dto);
return response()->json(new CourseScheduleResource($schedule->load('course')), 201);
}
#[OA\Get(
path: '/api/admin/schedules/{schedule}',
summary: 'Takvim detayı (Admin)',
tags: ['Admin - Schedules'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'schedule', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
responses: [new OA\Response(response: 200, description: 'Takvim detayı')],
)]
public function show(CourseSchedule $schedule): JsonResponse
{
return response()->json(new CourseScheduleResource($schedule->load('course')));
}
#[OA\Put(
path: '/api/admin/schedules/{schedule}',
summary: 'Takvim güncelle',
tags: ['Admin - Schedules'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'schedule', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(
properties: [
new OA\Property(property: 'course_id', type: 'integer'),
new OA\Property(property: 'start_date', type: 'string', format: 'date'),
new OA\Property(property: 'end_date', type: 'string', format: 'date'),
new OA\Property(property: 'location', type: 'string'),
new OA\Property(property: 'instructor', type: 'string'),
new OA\Property(property: 'quota', type: 'integer'),
new OA\Property(property: 'status', type: 'string'),
],
)),
responses: [
new OA\Response(response: 200, description: 'Takvim güncellendi'),
new OA\Response(response: 422, description: 'Validasyon hatası'),
],
)]
public function update(UpdateScheduleRequest $request, CourseSchedule $schedule, UpdateScheduleAction $action): JsonResponse
{
$dto = ScheduleData::fromArray(array_merge($schedule->toArray(), $request->validated()));
$schedule = $action->execute($schedule, $dto);
return response()->json(new CourseScheduleResource($schedule->load('course')));
}
#[OA\Delete(
path: '/api/admin/schedules/{schedule}',
summary: 'Takvim sil',
tags: ['Admin - Schedules'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'schedule', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
responses: [new OA\Response(response: 200, description: 'Takvim silindi')],
)]
public function destroy(CourseSchedule $schedule, DeleteScheduleAction $action): JsonResponse
{
$action->execute($schedule);
return response()->json(['message' => 'Takvim silindi.']);
}
}

View File

@@ -0,0 +1,89 @@
<?php
namespace App\Http\Controllers\Api\Admin;
use App\Actions\Setting\UpdateSettingsAction;
use App\Enums\SettingGroup;
use App\Http\Controllers\Controller;
use App\Http\Requests\Setting\UpdateSettingsRequest;
use App\Http\Resources\SettingResource;
use App\Repositories\Contracts\SettingRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use OpenApi\Attributes as OA;
class SettingController extends Controller
{
public function __construct(private SettingRepositoryInterface $repository) {}
#[OA\Get(
path: '/api/admin/settings',
summary: 'Tüm ayarları listele (Admin)',
tags: ['Admin - Settings'],
security: [['sanctum' => []]],
responses: [new OA\Response(response: 200, description: 'Ayar listesi')],
)]
public function index(): AnonymousResourceCollection
{
return SettingResource::collection($this->repository->all());
}
#[OA\Get(
path: '/api/admin/settings/group/{group}',
summary: 'Gruba göre ayarları getir',
tags: ['Admin - Settings'],
security: [['sanctum' => []]],
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 group(string $group): AnonymousResourceCollection
{
$settingGroup = SettingGroup::tryFrom($group);
if (! $settingGroup) {
abort(404, 'Ayar grubu bulunamadı.');
}
return SettingResource::collection($this->repository->getByGroup($settingGroup));
}
#[OA\Put(
path: '/api/admin/settings',
summary: 'Ayarları toplu güncelle (dot notation: general.site_name)',
tags: ['Admin - Settings'],
security: [['sanctum' => []]],
requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(
required: ['settings'],
properties: [
new OA\Property(property: 'settings', type: 'object', example: '{"general.site_name": "Yeni Ad", "contact.phone_primary": "+90 ..."}'),
],
)),
responses: [
new OA\Response(response: 200, description: 'Ayarlar güncellendi'),
new OA\Response(response: 422, description: 'Validasyon hatası'),
],
)]
public function update(UpdateSettingsRequest $request, UpdateSettingsAction $action): JsonResponse
{
$action->execute($request->validated('settings'));
return response()->json(['message' => 'Ayarlar güncellendi.']);
}
#[OA\Post(
path: '/api/admin/settings/clear-cache',
summary: 'Ayar cache temizle',
tags: ['Admin - Settings'],
security: [['sanctum' => []]],
responses: [new OA\Response(response: 200, description: 'Cache temizlendi')],
)]
public function clearCache(): JsonResponse
{
$this->repository->clearCache();
return response()->json(['message' => 'Ayar cache temizlendi.']);
}
}

View File

@@ -0,0 +1,138 @@
<?php
namespace App\Http\Controllers\Api\Admin;
use App\Http\Controllers\Controller;
use App\Http\Resources\StoryResource;
use App\Models\Story;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use OpenApi\Attributes as OA;
class StoryController extends Controller
{
#[OA\Get(
path: '/api/admin/stories',
summary: 'Tüm hikayeleri listele (Admin)',
tags: ['Admin - Stories'],
security: [['sanctum' => []]],
responses: [new OA\Response(response: 200, description: 'Hikaye listesi')],
)]
public function index(): AnonymousResourceCollection
{
return StoryResource::collection(
Story::query()->orderBy('order_index')->get()
);
}
#[OA\Post(
path: '/api/admin/stories',
summary: 'Yeni hikaye oluştur',
tags: ['Admin - Stories'],
security: [['sanctum' => []]],
requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(
required: ['title', 'content'],
properties: [
new OA\Property(property: 'title', type: 'string'),
new OA\Property(property: 'badge', type: 'string', nullable: true),
new OA\Property(property: 'content', type: 'string'),
new OA\Property(property: 'image', type: 'string', nullable: true),
new OA\Property(property: 'cta_text', type: 'string', nullable: true),
new OA\Property(property: 'cta_url', type: 'string', nullable: true),
new OA\Property(property: 'order_index', type: 'integer'),
new OA\Property(property: 'is_active', type: 'boolean'),
],
)),
responses: [
new OA\Response(response: 201, description: 'Hikaye oluşturuldu'),
new OA\Response(response: 422, description: 'Validasyon hatası'),
],
)]
public function store(Request $request): JsonResponse
{
$validated = $request->validate([
'title' => ['required', 'string', 'max:255'],
'badge' => ['nullable', 'string', 'max:100'],
'content' => ['required', 'string'],
'image' => ['nullable', 'string', 'max:255'],
'cta_text' => ['nullable', 'string', 'max:100'],
'cta_url' => ['nullable', 'string', 'max:255'],
'order_index' => ['sometimes', 'integer', 'min:0'],
'is_active' => ['sometimes', 'boolean'],
]);
$story = Story::create($validated);
return response()->json(new StoryResource($story), 201);
}
#[OA\Get(
path: '/api/admin/stories/{story}',
summary: 'Hikaye detayı',
tags: ['Admin - Stories'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'story', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
responses: [new OA\Response(response: 200, description: 'Hikaye detayı')],
)]
public function show(Story $story): JsonResponse
{
return response()->json(new StoryResource($story));
}
#[OA\Put(
path: '/api/admin/stories/{story}',
summary: 'Hikaye güncelle',
tags: ['Admin - Stories'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'story', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(
properties: [
new OA\Property(property: 'title', type: 'string'),
new OA\Property(property: 'badge', type: 'string', nullable: true),
new OA\Property(property: 'content', type: 'string'),
new OA\Property(property: 'image', type: 'string', nullable: true),
new OA\Property(property: 'cta_text', type: 'string', nullable: true),
new OA\Property(property: 'cta_url', type: 'string', nullable: true),
new OA\Property(property: 'order_index', type: 'integer'),
new OA\Property(property: 'is_active', type: 'boolean'),
],
)),
responses: [
new OA\Response(response: 200, description: 'Hikaye güncellendi'),
new OA\Response(response: 422, description: 'Validasyon hatası'),
],
)]
public function update(Request $request, Story $story): JsonResponse
{
$validated = $request->validate([
'title' => ['sometimes', 'string', 'max:255'],
'badge' => ['nullable', 'string', 'max:100'],
'content' => ['sometimes', 'string'],
'image' => ['nullable', 'string', 'max:255'],
'cta_text' => ['nullable', 'string', 'max:100'],
'cta_url' => ['nullable', 'string', 'max:255'],
'order_index' => ['sometimes', 'integer', 'min:0'],
'is_active' => ['sometimes', 'boolean'],
]);
$story->update($validated);
return response()->json(new StoryResource($story->fresh()));
}
#[OA\Delete(
path: '/api/admin/stories/{story}',
summary: 'Hikaye sil',
tags: ['Admin - Stories'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'story', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
responses: [new OA\Response(response: 200, description: 'Hikaye silindi')],
)]
public function destroy(Story $story): JsonResponse
{
$story->delete();
return response()->json(['message' => 'Hikaye silindi.']);
}
}

View File

@@ -0,0 +1,138 @@
<?php
namespace App\Http\Controllers\Api\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use OpenApi\Attributes as OA;
class UploadController extends Controller
{
#[OA\Post(
path: '/api/admin/upload',
summary: 'Dosya yükle',
description: 'Görsel veya video dosyası yükler. Görseller max 5MB, videolar max 100MB. Modül bazlı klasörleme destekler.',
security: [['sanctum' => []]],
tags: ['Upload'],
requestBody: new OA\RequestBody(
required: true,
content: new OA\MediaType(
mediaType: 'multipart/form-data',
schema: new OA\Schema(
required: ['file'],
properties: [
new OA\Property(property: 'file', type: 'string', format: 'binary', description: 'Görsel veya video dosyası'),
new OA\Property(property: 'type', type: 'string', enum: ['image', 'video'], description: 'Dosya tipi'),
new OA\Property(property: 'folder', type: 'string', description: 'Modül klasörü (hero-slides, courses, vb.)'),
],
),
),
),
responses: [
new OA\Response(response: 201, description: 'Dosya yüklendi', content: new OA\JsonContent(
properties: [
new OA\Property(property: 'data', type: 'object', properties: [
new OA\Property(property: 'path', type: 'string'),
new OA\Property(property: 'url', type: 'string'),
]),
],
)),
new OA\Response(response: 422, description: 'Validasyon hatası'),
],
)]
public function store(Request $request): JsonResponse
{
$type = $request->input('type', 'image');
if ($type === 'video') {
return $this->storeVideo($request);
}
return $this->storeImage($request);
}
/**
* @var list<string>
*/
private const ALLOWED_FOLDERS = [
'images',
'videos',
'hero-slides',
'settings',
'pages',
'courses',
'announcements',
'categories',
];
private function resolveFolder(Request $request, string $default): string
{
$folder = $request->input('folder', $default);
if (! in_array($folder, self::ALLOWED_FOLDERS, true)) {
$folder = $default;
}
return $folder;
}
private function storeImage(Request $request): JsonResponse
{
$request->validate([
'file' => ['required', 'file', 'image', 'max:5120'],
'folder' => ['sometimes', 'string'],
], [
'file.required' => 'Dosya zorunludur.',
'file.image' => 'Dosya bir görsel (jpg, png, gif, svg, webp) olmalıdır.',
'file.max' => 'Dosya boyutu en fazla 5MB olabilir.',
]);
$folder = $this->resolveFolder($request, 'images');
$file = $request->file('file');
$filename = Str::uuid().'.'.$file->getClientOriginalExtension();
$directory = public_path('uploads/'.$folder);
$file->move($directory, $filename);
$relativePath = 'uploads/'.$folder.'/'.$filename;
return response()->json([
'data' => [
'path' => $relativePath,
'url' => url($relativePath),
],
], 201);
}
private function storeVideo(Request $request): JsonResponse
{
$request->validate([
'file' => ['required', 'file', 'mimes:mp4,webm,mov,avi,mkv', 'max:102400'],
'folder' => ['sometimes', 'string'],
], [
'file.required' => 'Dosya zorunludur.',
'file.mimes' => 'Dosya bir video (mp4, webm, mov, avi, mkv) olmalıdır.',
'file.max' => 'Video boyutu en fazla 100MB olabilir.',
]);
$folder = $this->resolveFolder($request, 'videos');
$file = $request->file('file');
$filename = Str::uuid().'.'.$file->getClientOriginalExtension();
$directory = public_path('uploads/'.$folder);
$file->move($directory, $filename);
$relativePath = 'uploads/'.$folder.'/'.$filename;
return response()->json([
'data' => [
'path' => $relativePath,
'url' => url($relativePath),
],
], 201);
}
}

View File

@@ -0,0 +1,147 @@
<?php
namespace App\Http\Controllers\Api\Admin;
use App\Actions\User\CreateUserAction;
use App\Actions\User\DeleteUserAction;
use App\Actions\User\UpdateUserAction;
use App\DTOs\UserData;
use App\Http\Controllers\Controller;
use App\Http\Requests\User\StoreUserRequest;
use App\Http\Requests\User\UpdateUserRequest;
use App\Http\Resources\UserResource;
use App\Models\User;
use App\Repositories\Contracts\UserRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use OpenApi\Attributes as OA;
class UserController extends Controller
{
public function __construct(private UserRepositoryInterface $repository) {}
#[OA\Get(
path: '/api/admin/users',
summary: 'Admin kullanıcılarını listele',
tags: ['Admin - Users'],
security: [['sanctum' => []]],
parameters: [
new OA\Parameter(name: 'search', in: 'query', required: false, schema: new OA\Schema(type: 'string')),
new OA\Parameter(name: 'role', 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: 'Kullanıcı listesi')],
)]
public function index(Request $request): AnonymousResourceCollection
{
$users = $this->repository->paginate(
filters: $request->only('search', 'role'),
perPage: $request->integer('per_page', 15),
);
return UserResource::collection($users);
}
#[OA\Post(
path: '/api/admin/users',
summary: 'Yeni admin kullanıcı oluştur',
tags: ['Admin - Users'],
security: [['sanctum' => []]],
requestBody: new OA\RequestBody(
required: true,
content: new OA\JsonContent(
required: ['name', 'email', 'password', 'password_confirmation', 'role'],
properties: [
new OA\Property(property: 'name', type: 'string', example: 'Editör Kullanıcı'),
new OA\Property(property: 'email', type: 'string', format: 'email', example: 'editor@bogazici.com'),
new OA\Property(property: 'password', type: 'string', format: 'password', example: 'password123'),
new OA\Property(property: 'password_confirmation', type: 'string', format: 'password', example: 'password123'),
new OA\Property(property: 'role', type: 'string', example: 'editor'),
],
),
),
responses: [
new OA\Response(response: 201, description: 'Kullanıcı oluşturuldu'),
new OA\Response(response: 422, description: 'Validasyon hatası'),
],
)]
public function store(StoreUserRequest $request, CreateUserAction $action): JsonResponse
{
$dto = UserData::fromArray($request->validated());
$user = $action->execute($dto);
return (new UserResource($user))
->response()
->setStatusCode(201);
}
#[OA\Get(
path: '/api/admin/users/{user}',
summary: 'Kullanıcı detayı',
tags: ['Admin - Users'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'user', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
responses: [
new OA\Response(response: 200, description: 'Kullanıcı detayı'),
new OA\Response(response: 404, description: 'Bulunamadı'),
],
)]
public function show(User $user): UserResource
{
$user->load('roles');
return new UserResource($user);
}
#[OA\Put(
path: '/api/admin/users/{user}',
summary: 'Kullanıcı güncelle',
tags: ['Admin - Users'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'user', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(
properties: [
new OA\Property(property: 'name', type: 'string'),
new OA\Property(property: 'email', type: 'string', format: 'email'),
new OA\Property(property: 'password', type: 'string', format: 'password'),
new OA\Property(property: 'password_confirmation', type: 'string', format: 'password'),
new OA\Property(property: 'role', type: 'string', example: 'editor'),
],
)),
responses: [
new OA\Response(response: 200, description: 'Kullanıcı güncellendi'),
new OA\Response(response: 422, description: 'Validasyon hatası'),
],
)]
public function update(UpdateUserRequest $request, User $user, UpdateUserAction $action): UserResource
{
$dto = UserData::fromArray($request->validated());
$user = $action->execute($user, $dto);
return new UserResource($user);
}
#[OA\Delete(
path: '/api/admin/users/{user}',
summary: 'Kullanıcı sil (soft delete)',
tags: ['Admin - Users'],
security: [['sanctum' => []]],
parameters: [new OA\Parameter(name: 'user', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
responses: [
new OA\Response(response: 200, description: 'Kullanıcı silindi'),
new OA\Response(response: 403, description: 'Kendini silemezsin'),
new OA\Response(response: 404, description: 'Bulunamadı'),
],
)]
public function destroy(User $user, DeleteUserAction $action): JsonResponse
{
if ($user->id === auth()->id()) {
return response()->json(['message' => 'Kendinizi silemezsiniz.'], 403);
}
$action->execute($user);
return response()->json(['message' => 'Kullanıcı başarıyla silindi.']);
}
}

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);
}
}