Migrate backend to Porto architecture and fix minor bugs
- Fully transitioned the backend to the Porto architectural pattern - Improved code organization and maintainability - Fixed minor bugs and inconsistencies
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Article\Actions;
|
||||
|
||||
use App\Containers\Article\Tasks\BuildFiltersTask;
|
||||
use App\Containers\Article\Tasks\GetCategoriesTask;
|
||||
use App\Containers\Article\Tasks\GetPostsTask;
|
||||
use App\Containers\Article\Tasks\GetTagsTask;
|
||||
use App\Containers\Article\UI\WEB\Transformers\PostListResource;
|
||||
use App\Ship\Contracts\SeoServiceInterface;
|
||||
|
||||
class ListPostsAction
|
||||
{
|
||||
public function __construct(
|
||||
private readonly GetPostsTask $getPostsTask,
|
||||
private readonly GetCategoriesTask $getCategoriesTask,
|
||||
private readonly GetTagsTask $getTagsTask,
|
||||
private readonly BuildFiltersTask $buildFiltersTask,
|
||||
private readonly SeoServiceInterface $seoPageProvider,
|
||||
) {}
|
||||
|
||||
public function run(array $filters): array
|
||||
{
|
||||
$posts = $this->getPostsTask->run($filters);
|
||||
$categories = $this->getCategoriesTask->run();
|
||||
$tags = $this->getTagsTask->run();
|
||||
$filtersData = $this->buildFiltersTask->run($filters);
|
||||
$seo = $this->seoPageProvider->getSeoForCurrentPage();
|
||||
|
||||
return [
|
||||
'posts' => inertia()->deepMerge(fn() => PostListResource::collection($posts->items())),
|
||||
'posts_pagination' => $posts->toArray(),
|
||||
'filters' => $filtersData,
|
||||
'categories' => $categories,
|
||||
'tags' => $tags,
|
||||
'seo' => $seo,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Article\Actions;
|
||||
|
||||
class ViewPostAction
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Article\Enums;
|
||||
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
use Filament\Support\Contracts\HasColor;
|
||||
|
||||
enum PostStatus: string implements HasLabel, HasColor
|
||||
{
|
||||
|
||||
case VERIFICATION = 'verification';
|
||||
case PUBLISHED = 'published';
|
||||
case REJECTED = 'rejected';
|
||||
|
||||
public function getLabel(): ?string
|
||||
{
|
||||
return match ($this) {
|
||||
self::VERIFICATION => 'На рассмотрении',
|
||||
self::PUBLISHED => 'Опубликовано',
|
||||
self::REJECTED => 'Отклонено',
|
||||
};
|
||||
}
|
||||
|
||||
public function getColor(): string|array|null
|
||||
{
|
||||
return match ($this) {
|
||||
self::VERIFICATION => 'warning',
|
||||
self::PUBLISHED => 'success',
|
||||
self::REJECTED => 'gray',
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Article\Loaders;
|
||||
|
||||
class AliasesLoader
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public array $aliases = [];
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Article\Loaders;
|
||||
|
||||
class MiddlewareLoader
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public array $middleware = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public array $middlewareGroups = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public array $routeMiddleware = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public array $middlewarePriority = [];
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Article\Loaders;
|
||||
|
||||
class ProvidersLoader
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public array $providers = [];
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Article\Models;
|
||||
|
||||
use App\Ship\Models\Model;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Category extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $guarded = false;
|
||||
|
||||
public function posts() : HasMany
|
||||
{
|
||||
return $this->hasMany(Post::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Article\Models;
|
||||
|
||||
use App\Containers\Article\Enums\PostStatus;
|
||||
use App\Containers\User\Models\User;
|
||||
use App\Containers\Widget\Models\Slide;
|
||||
use App\Ship\Traits\HasSeo;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphOne;
|
||||
use Spatie\Tags\HasTags;
|
||||
|
||||
class Post extends Model
|
||||
{
|
||||
use HasFactory, HasTags, HasSeo;
|
||||
|
||||
protected $guarded = false;
|
||||
|
||||
public function category() : BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Category::class);
|
||||
}
|
||||
|
||||
public function author() : BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
|
||||
public function slide(): MorphOne
|
||||
{
|
||||
return $this->morphOne(Slide::class, 'slidable');
|
||||
}
|
||||
|
||||
protected $casts = [
|
||||
'content' => 'array',
|
||||
'authors' => 'array',
|
||||
'status' => PostStatus::class,
|
||||
'images' => 'array'
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Article\Models;
|
||||
|
||||
use App\Ship\Models\Model;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
||||
class Tag extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Article\Policies;
|
||||
|
||||
use App\Containers\User\Models\User;
|
||||
use App\Containers\Article\Models\Category;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class CategoryPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
/**
|
||||
* Determine whether the user can view any models.
|
||||
*/
|
||||
public function viewAny(User $user): bool
|
||||
{
|
||||
return $user->can('view_any_category');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can view the model.
|
||||
*/
|
||||
public function view(User $user, Category $category): bool
|
||||
{
|
||||
return $user->can('view_category');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can create models.
|
||||
*/
|
||||
public function create(User $user): bool
|
||||
{
|
||||
return $user->can('create_category');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can update the model.
|
||||
*/
|
||||
public function update(User $user, Category $category): bool
|
||||
{
|
||||
return $user->can('update_category');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can delete the model.
|
||||
*/
|
||||
public function delete(User $user, Category $category): bool
|
||||
{
|
||||
return $user->can('delete_category');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can bulk delete.
|
||||
*/
|
||||
public function deleteAny(User $user): bool
|
||||
{
|
||||
return $user->can('delete_any_category');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can permanently delete.
|
||||
*/
|
||||
public function forceDelete(User $user, Category $category): bool
|
||||
{
|
||||
return $user->can('force_delete_category');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can permanently bulk delete.
|
||||
*/
|
||||
public function forceDeleteAny(User $user): bool
|
||||
{
|
||||
return $user->can('force_delete_any_category');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can restore.
|
||||
*/
|
||||
public function restore(User $user, Category $category): bool
|
||||
{
|
||||
return $user->can('restore_category');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can bulk restore.
|
||||
*/
|
||||
public function restoreAny(User $user): bool
|
||||
{
|
||||
return $user->can('restore_any_category');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can replicate.
|
||||
*/
|
||||
public function replicate(User $user, Category $category): bool
|
||||
{
|
||||
return $user->can('replicate_category');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can reorder.
|
||||
*/
|
||||
public function reorder(User $user): bool
|
||||
{
|
||||
return $user->can('reorder_category');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Article\Policies;
|
||||
|
||||
use App\Containers\User\Models\User;
|
||||
use App\Containers\Article\Models\Post;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class PostPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
/**
|
||||
* Determine whether the user can view any models.
|
||||
*/
|
||||
public function viewAny(User $user): bool
|
||||
{
|
||||
return $user->can('view_any_post');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can view the model.
|
||||
*/
|
||||
public function view(User $user, Post $post): bool
|
||||
{
|
||||
return $user->can('view_post');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can create models.
|
||||
*/
|
||||
public function create(User $user): bool
|
||||
{
|
||||
return $user->can('create_post');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can update the model.
|
||||
*/
|
||||
public function update(User $user, Post $post): bool
|
||||
{
|
||||
return $user->can('update_post');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can delete the model.
|
||||
*/
|
||||
public function delete(User $user, Post $post): bool
|
||||
{
|
||||
return $user->can('delete_post');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can bulk delete.
|
||||
*/
|
||||
public function deleteAny(User $user): bool
|
||||
{
|
||||
return $user->can('delete_any_post');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can permanently delete.
|
||||
*/
|
||||
public function forceDelete(User $user, Post $post): bool
|
||||
{
|
||||
return $user->can('{{ ForceDelete }}');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can permanently bulk delete.
|
||||
*/
|
||||
public function forceDeleteAny(User $user): bool
|
||||
{
|
||||
return $user->can('{{ ForceDeleteAny }}');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can restore.
|
||||
*/
|
||||
public function restore(User $user, Post $post): bool
|
||||
{
|
||||
return $user->can('{{ Restore }}');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can bulk restore.
|
||||
*/
|
||||
public function restoreAny(User $user): bool
|
||||
{
|
||||
return $user->can('{{ RestoreAny }}');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can replicate.
|
||||
*/
|
||||
public function replicate(User $user, Post $post): bool
|
||||
{
|
||||
return $user->can('{{ Replicate }}');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can reorder.
|
||||
*/
|
||||
public function reorder(User $user): bool
|
||||
{
|
||||
return $user->can('{{ Reorder }}');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Article\Policies;
|
||||
|
||||
use App\Containers\User\Models\User;
|
||||
use App\Containers\Article\Models\Tag;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class TagPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
/**
|
||||
* Determine whether the user can view any models.
|
||||
*/
|
||||
public function viewAny(User $user): bool
|
||||
{
|
||||
return $user->can('view_any_tag');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can view the model.
|
||||
*/
|
||||
public function view(User $user, Tag $tag): bool
|
||||
{
|
||||
return $user->can('view_tag');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can create models.
|
||||
*/
|
||||
public function create(User $user): bool
|
||||
{
|
||||
return $user->can('create_tag');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can update the model.
|
||||
*/
|
||||
public function update(User $user, Tag $tag): bool
|
||||
{
|
||||
return $user->can('update_tag');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can delete the model.
|
||||
*/
|
||||
public function delete(User $user, Tag $tag): bool
|
||||
{
|
||||
return $user->can('delete_tag');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can bulk delete.
|
||||
*/
|
||||
public function deleteAny(User $user): bool
|
||||
{
|
||||
return $user->can('delete_any_tag');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can permanently delete.
|
||||
*/
|
||||
public function forceDelete(User $user, Tag $tag): bool
|
||||
{
|
||||
return $user->can('force_delete_tag');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can permanently bulk delete.
|
||||
*/
|
||||
public function forceDeleteAny(User $user): bool
|
||||
{
|
||||
return $user->can('force_delete_any_tag');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can restore.
|
||||
*/
|
||||
public function restore(User $user, Tag $tag): bool
|
||||
{
|
||||
return $user->can('restore_tag');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can bulk restore.
|
||||
*/
|
||||
public function restoreAny(User $user): bool
|
||||
{
|
||||
return $user->can('restore_any_tag');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can replicate.
|
||||
*/
|
||||
public function replicate(User $user, Tag $tag): bool
|
||||
{
|
||||
return $user->can('replicate_tag');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can reorder.
|
||||
*/
|
||||
public function reorder(User $user): bool
|
||||
{
|
||||
return $user->can('reorder_tag');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Article\Tasks;
|
||||
|
||||
use App\Containers\Article\Models\Category;
|
||||
use App\Containers\Article\UI\WEB\Transformers\CategoryResource;
|
||||
use App\Containers\Article\UI\WEB\Transformers\TagResource;
|
||||
use App\Ship\Builders\FilterBuilder;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class BuildFiltersTask
|
||||
{
|
||||
public function __construct(
|
||||
private readonly FilterBuilder $filterBuilder
|
||||
) {}
|
||||
|
||||
public function run(array $filters): array
|
||||
{
|
||||
// Сброс фильтров перед новым запуском
|
||||
$this->filterBuilder->reset();
|
||||
|
||||
// 1. Поисковый фильтр
|
||||
$this->filterBuilder->add(
|
||||
key: 'search_filter',
|
||||
type: 'search',
|
||||
value: $filters['search'] ?? null,
|
||||
param: 'search'
|
||||
);
|
||||
|
||||
|
||||
// 2. Категории
|
||||
|
||||
$categoriesContent = [];
|
||||
if (!empty($filters['category'])) {
|
||||
$categoriesSlugs = Arr::wrap($filters['category']);
|
||||
foreach ($categoriesSlugs as $item) {
|
||||
$cacheKey = 'category_content_' . $item;
|
||||
$categoriesContent[$item] = Cache::remember($cacheKey, now()->addHours(1), function () use ($item) {
|
||||
return new CategoryResource(Category::where('slug', $item)->first());
|
||||
});
|
||||
}
|
||||
}
|
||||
$this->filterBuilder->add(
|
||||
key: 'category_filter',
|
||||
type: 'category',
|
||||
value: $categoriesSlugs ?? null,
|
||||
param: 'category',
|
||||
content: $categoriesContent
|
||||
);
|
||||
|
||||
|
||||
// 3. Теги
|
||||
|
||||
$tagsContent = [];
|
||||
if (!empty($filters['tag'])) {
|
||||
$tagsSlugs = Arr::wrap($filters['tag']);
|
||||
foreach ($tagsSlugs as $item) {
|
||||
$cacheKey = 'tag_content_' . $item;
|
||||
$tagsContent[$item] = Cache::remember($cacheKey, now()->addHours(1), function () use ($item) {
|
||||
return new TagResource(DB::table('tags')
|
||||
->where(DB::raw("JSON_UNQUOTE(JSON_EXTRACT(slug, '$.ru'))"), $item)
|
||||
->first());
|
||||
});
|
||||
}
|
||||
}
|
||||
$this->filterBuilder->add(
|
||||
key: 'tag_filter',
|
||||
type: 'tag',
|
||||
value: $tagsSlugs ?? null,
|
||||
param: 'tag',
|
||||
content: $tagsContent
|
||||
);
|
||||
|
||||
|
||||
// 4. Сортировка
|
||||
$this->filterBuilder->add(
|
||||
key: 'sortingBy_filter',
|
||||
type: 'sort',
|
||||
value: $filters['sort'] ?? null,
|
||||
param: 'sort'
|
||||
);
|
||||
|
||||
|
||||
return $this->filterBuilder->get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Article\Tasks;
|
||||
|
||||
use App\Containers\Article\Models\Category;
|
||||
use App\Containers\Article\UI\WEB\Transformers\CategoryResource;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class GetCategoriesTask
|
||||
{
|
||||
public function run(): AnonymousResourceCollection
|
||||
{
|
||||
return Cache::remember('categories', now()->addHours(48), function () {
|
||||
return CategoryResource::collection(Category::has('posts')->get());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Article\Tasks;
|
||||
|
||||
use App\Containers\Article\Models\Post;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class GetPostsTask
|
||||
{
|
||||
public function run(array $filters): LengthAwarePaginator
|
||||
{
|
||||
$cacheKey = 'posts_' . md5(serialize($filters));
|
||||
|
||||
return Cache::remember($cacheKey, now()->addHours(1), function () use ($filters) {
|
||||
$query = Post::query()
|
||||
->with('category')
|
||||
->select('title', 'slug', 'authors', 'category_id', 'preview', 'search_data', 'publish_at')
|
||||
->where('status', 'published')
|
||||
->where('publish_at', '<', Carbon::now())
|
||||
->when(!empty($filters['tag']), function ($query) use ($filters) {
|
||||
if (is_array($filters['tag'])) {
|
||||
return $query->withAnyTags($filters['tag']);
|
||||
}
|
||||
|
||||
$slugsArray = explode(',', $filters['tag']);
|
||||
return $query->withAnyTags($slugsArray);
|
||||
})
|
||||
->when(!empty($filters['category']), function ($query) use ($filters) {
|
||||
if (is_array($filters['category'])) {
|
||||
$query->whereHas('category', function ($query) use ($filters) {
|
||||
$query->whereIn('slug', $filters['category']);
|
||||
});
|
||||
}
|
||||
})
|
||||
->when(!empty($filters['search']), function ($query) use ($filters) {
|
||||
$query->whereRaw('LOWER(title) like ?', ["%".strtolower($filters['search'])."%"]);
|
||||
});
|
||||
|
||||
$sort = $filters['sort'] ?? 'desc';
|
||||
return $query->orderBy('publish_at', $sort)->paginate(9)->withQueryString();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Article\Tasks;
|
||||
|
||||
use App\Containers\Article\Models\Post;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Spatie\Tags\Tag;
|
||||
|
||||
class GetTagsTask
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
return Cache::remember('tags', now()->addHours(1), function () {
|
||||
$tagIds = DB::table('taggables')
|
||||
->distinct()
|
||||
->select('tag_id')
|
||||
->where('taggable_type', Post::class)
|
||||
->get()
|
||||
->pluck('tag_id');
|
||||
|
||||
return Tag::whereIn('id', $tagIds)->get();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Article\UI\WEB\Controllers;
|
||||
|
||||
use App\Containers\Article\Actions\ListPostsAction;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class IndexPostController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ListPostsAction $listPostsAction,
|
||||
) {}
|
||||
|
||||
public function __invoke(Request $request)
|
||||
{
|
||||
$filters = $request->only(['search', 'category', 'tag', 'sort', 'page']);
|
||||
|
||||
$data = $this->listPostsAction->run($filters);
|
||||
|
||||
return inertia()->render('Client/Posts/Index', $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Article\UI\WEB\Controllers;
|
||||
|
||||
use App\Containers\Article\Models\Post;
|
||||
use App\Containers\Article\UI\WEB\Transformers\PostItemResource;
|
||||
use App\Ship\Enums\CacheKeys;
|
||||
use App\Ship\Contracts\SeoServiceInterface;
|
||||
use App\Ship\Requests\Request;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Inertia\Inertia;
|
||||
|
||||
|
||||
class ShowPostController extends \App\Ship\Controllers\Controller
|
||||
{
|
||||
public function __construct(private readonly SeoServiceInterface $seoPageProvider){}
|
||||
|
||||
public function __invoke(Request $request, $slug): \Inertia\Response
|
||||
{
|
||||
$postData = Cache::remember(
|
||||
CacheKeys::POST_PREFIX->value . $slug,
|
||||
now()->addHours(1),
|
||||
fn() => Post::where('slug', $slug)
|
||||
->where('publish_at', '<', Carbon::now())
|
||||
->firstOrFail()
|
||||
);
|
||||
|
||||
$seo = Cache::remember(
|
||||
CacheKeys::POST_PREFIX->value . 'seo_' . $slug,
|
||||
now()->addHours(1),
|
||||
fn() => $this->seoPageProvider->getSeoForModel($postData)
|
||||
);
|
||||
|
||||
return inertia()->render('Client/Posts/Show', [
|
||||
'post' => new PostItemResource($postData),
|
||||
'seo' => $seo,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
use App\Containers\Article\UI\WEB\Controllers\IndexPostController;
|
||||
use App\Containers\Article\UI\WEB\Controllers\ShowPostController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
|
||||
Route::middleware('access-check')->group(function () {
|
||||
Route::get('/news', IndexPostController::class)->name('client.post.index');
|
||||
Route::get('/news/{slug}', ShowPostController::class)->name('client.post.show');
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Article\UI\WEB\Transformers;
|
||||
|
||||
|
||||
|
||||
use App\Ship\Requests\Request;
|
||||
use App\Ship\Resources\JsonResource;
|
||||
|
||||
class CategoryResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @param \App\Ship\Requests\Request $request
|
||||
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
|
||||
*/
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'title' => $this->title,
|
||||
'slug' => $this->slug,
|
||||
'created_at' => $this->created_at->diffforhumans(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Article\UI\WEB\Transformers;
|
||||
|
||||
use App\Ship\Resources\JsonResource;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class PostItemResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @param \App\Ship\Requests\Request $request
|
||||
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
|
||||
*/
|
||||
public function toArray($request)
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'title' => $this->title,
|
||||
'slug' => $this->slug,
|
||||
'content' => $this->content,
|
||||
'is_published' => $this->is_published,
|
||||
'category' => $this->category,
|
||||
'tags' => TagResource::collection($this->tags()->get()),
|
||||
'authors' => $this->authors,
|
||||
'gallery' => $this->images,
|
||||
'reading_time' => $this->reading_time,
|
||||
'created_post' => Carbon::parse($this->publish_at)->diffforhumans(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Article\UI\WEB\Transformers;
|
||||
|
||||
use App\Ship\Resources\JsonResource;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class PostListResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @param \App\Ship\Requests\Request $request
|
||||
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
|
||||
*/
|
||||
public function toArray($request)
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'title' => $this->title,
|
||||
'slug' => $this->slug,
|
||||
'preview_text' => $this->preview_text,
|
||||
'content' => $this->content,
|
||||
'category' => $this->category,
|
||||
'authors' => $this->authors,
|
||||
'preview' => $this->preview,
|
||||
'reading_time' => $this->reading_time,
|
||||
'created_post' => Carbon::parse($this->publish_at)->diffforhumans(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Article\UI\WEB\Transformers;
|
||||
|
||||
|
||||
use App\Ship\Requests\Request;
|
||||
use App\Ship\Resources\JsonResource;
|
||||
|
||||
class TagResource extends JsonResource
|
||||
{
|
||||
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @param \App\Ship\Requests\Request $request
|
||||
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
|
||||
*/
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'slug' => $this->slug,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user