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:
F4ilji
2025-05-12 08:47:43 +05:00
parent 76deaf75f3
commit c01016a154
620 changed files with 16218 additions and 6073 deletions
@@ -0,0 +1,29 @@
<?php
namespace App\Containers\Widget\UI\API\Controllers;
use App\Containers\AdditionalEducation\Models\AdditionalEducation;
use App\Containers\Search\UI\API\Transformers\AdditionalEducationSearchResource;
use App\Ship\Controllers\Controller;
use App\Ship\Enums\CacheKeys;
use Illuminate\Support\Facades\Cache;
class ClientWidgetAdditionalEducationalProgramController extends Controller
{
public function index()
{
return Cache::remember(
CacheKeys::ADDITIONAL_EDUCATIONAL_PROGRAMS_PREFIX->value . 'search_list',
now()->addDay(), // Кешируем на 1 день
function () {
return AdditionalEducationSearchResource::collection(
AdditionalEducation::query()
->where('is_active', true)
->orderBy('title', 'desc')
->get()
);
}
);
}
}
@@ -0,0 +1,27 @@
<?php
namespace App\Containers\Widget\UI\API\Controllers;
use App\Containers\Widget\Models\ContactWidget;
use App\Containers\Widget\UI\API\Transformers\ContactWidgetResource;
use App\Ship\Controllers\Controller;
use App\Ship\Enums\CacheKeys;
use Illuminate\Support\Facades\Cache;
class ClientWidgetContactController extends Controller
{
public function show(string $slug)
{
return Cache::remember(
CacheKeys::CONTACT_WIDGET_PREFIX->value . $slug,
now()->addHours(12), // Кешируем на 12 часов
function () use ($slug) {
return new ContactWidgetResource(
ContactWidget::query()
->where('slug', $slug)
->firstOrFail()
);
}
);
}
}
@@ -0,0 +1,29 @@
<?php
namespace App\Containers\Widget\UI\API\Controllers;
use App\Containers\Education\Models\EducationalProgram;
use App\Containers\Search\UI\API\Transformers\EducationalProgramSearchResource;
use App\Ship\Controllers\Controller;
use App\Ship\Enums\CacheKeys;
use App\Ship\Enums\Education\EducationalProgramStatus;
use Illuminate\Support\Facades\Cache;
class ClientWidgetEducationalProgramController extends Controller
{
public function index()
{
return Cache::remember(
CacheKeys::EDUCATION_PROGRAMS_PREFIX->value . 'search_list',
now()->addDay(), // Кешируем на 1 день
function () {
return EducationalProgramSearchResource::collection(
EducationalProgram::query()
->where('status', EducationalProgramStatus::PUBLISHED)
->orderBy('name', 'desc')
->get()
);
}
);
}
}
@@ -0,0 +1,122 @@
<?php
namespace App\Containers\Widget\UI\API\Controllers;
use App\Containers\Widget\Data\Rules\UniqueJsonField;
use App\Containers\Widget\Enums\CustomFormStatus;
use App\Containers\Widget\Models\CustomForm;
use App\Containers\Widget\Models\CustomFormResponse;
use App\Containers\Widget\UI\API\Transformers\FormResource;
use App\Ship\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class ClientWidgetFormController extends Controller
{
public function single(string $id)
{
return new FormResource(CustomForm::query()->where('status', CustomFormStatus::PUBLISHED)->where('form_id', $id)->firstOrFail());
}
public function submit(int $id, Request $request)
{
$data = CustomForm::findOrFail($id);
$rules = $this->generateValidationRules($data['columns'], $data['id']);
$messages = $this->generateValidationMessages($data['columns']);
$validateData = Validator::make($request->all(), $rules, $messages);
if ($validateData->fails()) {
return response()->json($validateData->errors(), 422);
}
$this->storeResponse($id, $validateData->validated());
return response()->json([
'message' => $data['send_message'],
'status' => 'ok'
]);
}
private function generateValidationRules(array $columns, int $id): array
{
$rules = [];
foreach ($columns as $column) {
// Извлекаем информацию о поле
$name = $column['data']['name_field'];
$rulesForField = [];
// Проверяем, является ли поле массивом (множественный выбор)
if ($column['type'] === 'multiple_choice') {
$rulesForField[] = 'required'; // Обязательно для множественного выбора
$rulesForField[] = 'array'; // Указывает, что это массив
$rulesForField[] = 'min:1'; // Минимум один элемент
$rules[$name] = $rulesForField; // Правила для каждого элемента массива
} else {
// Обработка обычных полей
if (!empty($column['data']['rules']['required'])) {
$rulesForField[] = 'required';
}
if (!empty($column['data']['rules']['min'])) {
$rulesForField[] = 'min:' . $column['data']['rules']['min'];
}
if (!empty($column['data']['rules']['max'])) {
$rulesForField[] = 'max:' . $column['data']['rules']['max'];
}
if (!empty($column['data']['rules']['unique']) && $column['data']['rules']['unique'] === true) {
$rulesForField[] = new UniqueJsonField($id, $name);
}
$rules[$name] = $rulesForField; // Добавляем правила для обычного поля
}
}
return $rules;
}
private function generateValidationMessages(array $columns): array
{
$messages = [];
foreach ($columns as $column) {
$name = $column['data']['name_field'];
if (!empty($column['data']['rules']['required'])) {
$messages[$name . '.required'] = 'Поле обязательно для заполнения.';
}
if (!empty($column['data']['rules']['min'])) {
$messages[$name . '.min'] = 'Минимальная длина поля должна быть ' . $column['data']['rules']['min'] . ' символов.';
}
if (!empty($column['data']['rules']['max'])) {
$messages[$name . '.max'] = 'Максимальная длина поля должна быть ' . $column['data']['rules']['max'] . ' символов.';
}
if (!empty($column['data']['rules']['unique'])) {
$messages[$name . '.max'] = 'Поле должно быть уникальным';
}
}
return $messages;
}
private function storeResponse(int $id, array $validateData): void
{
CustomFormResponse::create([
'custom_form_id' => $id,
'answers' => $validateData,
]);
}
}
@@ -0,0 +1,39 @@
<?php
namespace App\Containers\Widget\UI\API\Controllers;
use App\Containers\AppStructure\Models\Page;
use App\Containers\Widget\UI\API\Transformers\PageNavigateResource;
use App\Ship\Controllers\Controller;
use Illuminate\Support\Facades\Cache;
class ClientWidgetPageController extends Controller
{
public function single(int $id): \Illuminate\Http\JsonResponse
{
$cacheKey = 'page_' . md5($id);
// Пытаемся получить данные из кеша
$page = Cache::remember($cacheKey, now()->addHours(1), function () use ($id) {
return Page::where('id', '=', $id)
->with('section.pages.section', 'section.mainSection')
->firstOrFail();
});
if (isset($page->section)) {
$breadcrumbs = [
'mainSection' => $page->section->mainSection->title,
'subSection' => $page->section->title,
'page' => $page->title,
];
} else {
$breadcrumbs = null;
}
return response()->json([
'data' => [
'page' => new PageNavigateResource($page),
'breadcrumbs' => $breadcrumbs
],
], 200);
}
}
@@ -0,0 +1,27 @@
<?php
namespace App\Containers\Widget\UI\API\Controllers;
use App\Containers\Widget\Models\PageReferenceList;
use App\Containers\Widget\UI\API\Transformers\PageReferenceListResource;
use App\Ship\Controllers\Controller;
use App\Ship\Enums\CacheKeys;
use Illuminate\Support\Facades\Cache;
class ClientWidgetPageReferenceListController extends Controller
{
public function show(string $slug)
{
return Cache::remember(
CacheKeys::PAGE_REFERENCE_LIST_PREFIX->value . $slug,
now()->addWeek(), // Кешируем на неделю, так как справочники меняются редко
function () use ($slug) {
return new PageReferenceListResource(
PageReferenceList::query()
->where('slug', $slug)
->firstOrFail()
);
}
);
}
}
@@ -0,0 +1,45 @@
<?php
namespace App\Containers\Widget\UI\API\Controllers;
use App\Containers\Article\Enums\PostStatus;
use App\Containers\Article\Models\Post;
use App\Containers\Widget\UI\API\Transformers\PostThumbnailResource;
use App\Ship\Controllers\Controller;
use Illuminate\Support\Facades\Cache;
class ClientWidgetPostController extends Controller
{
public function index()
{
$category_id = request()->input('category');
$count = request()->input('count', 5);
$cacheKey = 'posts_' . $category_id . '_' . $count;
return Cache::remember($cacheKey, now()->addMinutes(10), function () use ($category_id, $count) {
return PostThumbnailResource::collection(
Post::query()
->where('status', PostStatus::PUBLISHED)
->when($category_id, function ($query, $category_id) {
$query->where('category_id', $category_id);
})
->with('category')
->orderBy('publish_at', 'desc')
->take($count)
->get()
);
});
}
public function single(int $id)
{
$cacheKey = 'post_' . $id;
return Cache::remember($cacheKey, now()->addMinutes(10), function () use ($id) {
return new PostThumbnailResource(
Post::query()->with('category')->find($id)->firstOrFail()
);
});
}
}
@@ -0,0 +1,27 @@
<?php
namespace App\Containers\Widget\UI\API\Controllers;
use App\Containers\Widget\Models\Slider;
use App\Ship\Controllers\Controller;
use Illuminate\Support\Facades\Cache;
class ClientWidgetSliderController extends Controller
{
public function show(string $slug): ?object
{
$cacheKey = 'slider_' . $slug;
return Cache::remember($cacheKey, now()->addHour(), function () use ($slug) {
$slider = Slider::query()
->where('slug', $slug)
->where('is_active', true)
->with(['slides' => function($query) {
$query->where('is_active', true);
}])
->firstOrFail();
return $slider ?: null;
});
}
}