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,11 @@
<?php
namespace App\Containers\InstituteStructure\Loaders;
class AliasesLoader
{
/**
* @var array
*/
public array $aliases = [];
}
@@ -0,0 +1,26 @@
<?php
namespace App\Containers\InstituteStructure\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\InstituteStructure\Loaders;
class ProvidersLoader
{
/**
* @var array
*/
public array $providers = [];
}
@@ -0,0 +1,42 @@
<?php
namespace App\Containers\InstituteStructure\Models;
use App\Containers\Education\Models\EducationalProgram;
use App\Containers\User\Models\User;
use App\Ship\Models\Model;
use App\Ship\Traits\HasSeo;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Department extends Model
{
use HasFactory, HasSeo;
protected $guarded = false;
protected $casts = [
'content' => 'array',
];
public function faculty(): BelongsTo
{
return $this->belongsTo(Faculty::class);
}
public function workers(): BelongsToMany
{
return $this->belongsToMany(User::class, 'workers_departments')->withPivot(['position', 'sort', 'service_email', 'service_phone', 'cabinet']);
}
public function programs(): BelongsToMany
{
return $this->belongsToMany(EducationalProgram::class, 'program_department');
}
public function teachers(): BelongsToMany
{
return $this->belongsToMany(User::class, 'teachers_departments')->withPivot(['teaching_position', 'sort', 'service_email', 'service_phone', 'cabinet']);
}
}
@@ -0,0 +1,30 @@
<?php
namespace App\Containers\InstituteStructure\Models;
use App\Containers\User\Models\User;
use App\Ship\Contracts\SeoDescriptionInterface;
use App\Ship\Models\Model;
use App\Ship\Traits\HasSeo;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Division extends Model implements SeoDescriptionInterface
{
use HasFactory, HasSeo;
protected $guarded = false;
protected $casts = [
'description' => 'array',
];
public function workers()
{
return $this->belongsToMany(User::class, 'division_user')->withPivot(['administrativePosition', 'sort', 'service_email', 'service_phone', 'cabinet']);
}
public function getSeoDescription(): array
{
return $this->description;
}
}
@@ -0,0 +1,31 @@
<?php
namespace App\Containers\InstituteStructure\Models;
use App\Containers\User\Models\User;
use App\Ship\Models\Model;
use App\Ship\Traits\HasSeo;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Faculty extends Model
{
use HasFactory, HasSeo;
protected $guarded = false;
protected $casts = [
'content' => 'array',
];
public function departments(): HasMany
{
return $this->hasMany(Department::class);
}
public function workers(): BelongsToMany
{
return $this->belongsToMany(User::class, 'workers_faculties')->withPivot(['position', 'sort', 'service_email', 'service_phone', 'cabinet'])->whereHas('userDetail');
}
}
@@ -0,0 +1,108 @@
<?php
namespace App\Containers\InstituteStructure\Policies;
use App\Containers\User\Models\User;
use App\Containers\InstituteStructure\Models\Department;
use Illuminate\Auth\Access\HandlesAuthorization;
class DepartmentPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->can('view_any_department');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Department $department): bool
{
return $user->can('view_department');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->can('create_department');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Department $department): bool
{
return $user->can('update_department');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Department $department): bool
{
return $user->can('delete_department');
}
/**
* Determine whether the user can bulk delete.
*/
public function deleteAny(User $user): bool
{
return $user->can('delete_any_department');
}
/**
* Determine whether the user can permanently delete.
*/
public function forceDelete(User $user, Department $department): bool
{
return $user->can('force_delete_department');
}
/**
* Determine whether the user can permanently bulk delete.
*/
public function forceDeleteAny(User $user): bool
{
return $user->can('force_delete_any_department');
}
/**
* Determine whether the user can restore.
*/
public function restore(User $user, Department $department): bool
{
return $user->can('restore_department');
}
/**
* Determine whether the user can bulk restore.
*/
public function restoreAny(User $user): bool
{
return $user->can('restore_any_department');
}
/**
* Determine whether the user can replicate.
*/
public function replicate(User $user, Department $department): bool
{
return $user->can('replicate_department');
}
/**
* Determine whether the user can reorder.
*/
public function reorder(User $user): bool
{
return $user->can('reorder_department');
}
}
@@ -0,0 +1,108 @@
<?php
namespace App\Containers\InstituteStructure\Policies;
use App\Containers\User\Models\User;
use App\Containers\InstituteStructure\Models\Division;
use Illuminate\Auth\Access\HandlesAuthorization;
class DivisionPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->can('view_any_division');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Division $division): bool
{
return $user->can('view_division');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->can('create_division');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Division $division): bool
{
return $user->can('update_division');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Division $division): bool
{
return $user->can('delete_division');
}
/**
* Determine whether the user can bulk delete.
*/
public function deleteAny(User $user): bool
{
return $user->can('delete_any_division');
}
/**
* Determine whether the user can permanently delete.
*/
public function forceDelete(User $user, Division $division): bool
{
return $user->can('force_delete_division');
}
/**
* Determine whether the user can permanently bulk delete.
*/
public function forceDeleteAny(User $user): bool
{
return $user->can('force_delete_any_division');
}
/**
* Determine whether the user can restore.
*/
public function restore(User $user, Division $division): bool
{
return $user->can('restore_division');
}
/**
* Determine whether the user can bulk restore.
*/
public function restoreAny(User $user): bool
{
return $user->can('restore_any_division');
}
/**
* Determine whether the user can replicate.
*/
public function replicate(User $user, Division $division): bool
{
return $user->can('replicate_division');
}
/**
* Determine whether the user can reorder.
*/
public function reorder(User $user): bool
{
return $user->can('reorder_division');
}
}
@@ -0,0 +1,108 @@
<?php
namespace App\Containers\InstituteStructure\Policies;
use App\Containers\User\Models\User;
use App\Containers\InstituteStructure\Models\Faculty;
use Illuminate\Auth\Access\HandlesAuthorization;
class FacultyPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->can('view_any_faculty');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Faculty $faculty): bool
{
return $user->can('view_faculty');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->can('create_faculty');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Faculty $faculty): bool
{
return $user->can('update_faculty');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Faculty $faculty): bool
{
return $user->can('delete_faculty');
}
/**
* Determine whether the user can bulk delete.
*/
public function deleteAny(User $user): bool
{
return $user->can('delete_any_faculty');
}
/**
* Determine whether the user can permanently delete.
*/
public function forceDelete(User $user, Faculty $faculty): bool
{
return $user->can('force_delete_faculty');
}
/**
* Determine whether the user can permanently bulk delete.
*/
public function forceDeleteAny(User $user): bool
{
return $user->can('force_delete_any_faculty');
}
/**
* Determine whether the user can restore.
*/
public function restore(User $user, Faculty $faculty): bool
{
return $user->can('restore_faculty');
}
/**
* Determine whether the user can bulk restore.
*/
public function restoreAny(User $user): bool
{
return $user->can('restore_any_faculty');
}
/**
* Determine whether the user can replicate.
*/
public function replicate(User $user, Faculty $faculty): bool
{
return $user->can('replicate_faculty');
}
/**
* Determine whether the user can reorder.
*/
public function reorder(User $user): bool
{
return $user->can('reorder_faculty');
}
}
@@ -0,0 +1,105 @@
<?php
namespace App\Containers\InstituteStructure\UI\WEB\Controllers;
use App\Containers\InstituteStructure\Models\Department;
use App\Containers\InstituteStructure\Models\Faculty;
use App\Containers\InstituteStructure\UI\WEB\Transformers\DepartmentPreviewResource;
use App\Containers\InstituteStructure\UI\WEB\Transformers\DepartmentResource;
use App\Ship\Contracts\SeoServiceInterface;
use App\Ship\Controllers\Controller;
use App\Ship\Enums\CacheKeys;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Cache;
use Inertia\Inertia;
class ClientDepartmentController extends Controller
{
public function __construct(readonly SeoServiceInterface $seoPageProvider){}
public function show(string $facultySlug, string $departmentSlug)
{
// Ключ для кеширования
$cacheKey = "{$facultySlug}_{$departmentSlug}";
// Кешируем факультет
$faculty = Cache::remember(
CacheKeys::FACULTY_PREFIX->value . $facultySlug,
now()->addDay(),
function () use ($facultySlug) {
return Faculty::query()
->where('slug', $facultySlug)
->first();
}
);
// Кешируем список активных кафедр факультета
$departments = Cache::remember(
CacheKeys::DEPARTMENTS_PREFIX->value . 'active_' . $faculty->id,
now()->addDay(),
function () use ($faculty) {
return DepartmentPreviewResource::collection(
Department::query()
->where('is_active', true)
->where('faculty_id', $faculty->id)
->get()
);
}
);
$departmentModel = Cache::remember(
CacheKeys::DEPARTMENT_PREFIX->value . $cacheKey,
now()->addDay(),
function () use ($departmentSlug) {
return Department::query()
->where('slug', $departmentSlug)
->where('is_active', true)
->with([
'faculty',
'workers.userDetail',
'teachers.userDetail',
'programs.directionStudy',
'seo'
])
->firstOrFail();
}
);
$seo = Cache::remember(
CacheKeys::DEPARTMENT_PREFIX->value . 'seo_' . $cacheKey,
now()->addDay(),
function () use ($departmentModel) {
return $this->seoPageProvider->getSeoForModel($departmentModel);
}
);
$department = new DepartmentResource($departmentModel);
$directions = Cache::remember(
CacheKeys::DEPARTMENT_PREFIX->value . 'directions_' . $cacheKey,
now()->addDay(),
function () use ($department) {
return $this->groupProgramsByDirection($department->programs);
}
);
return Inertia::render('Client/Departments/Show', compact(
'department',
'departments',
'directions',
'seo',
));
}
private function groupProgramsByDirection(Collection $programs): Collection
{
// Группируем программы по имени направления
return $programs->groupBy(function ($program) {
return $program->directionStudy->code . " " . $program->directionStudy->name; // Используем имя направления как ключ
});
}
}
@@ -0,0 +1,51 @@
<?php
namespace App\Containers\InstituteStructure\UI\WEB\Controllers;
use App\Containers\InstituteStructure\Models\Division;
use App\Containers\InstituteStructure\UI\WEB\Transformers\DivisionResource;
use App\Ship\Contracts\SeoServiceInterface;
use App\Ship\Controllers\Controller;
use App\Ship\Enums\CacheKeys;
use Illuminate\Support\Facades\Cache;
use Inertia\Inertia;
class ClientDivisionController extends Controller
{
public function __construct(readonly SeoServiceInterface $seoPageProvider){}
public function index()
{
$divisions = Cache::remember(CacheKeys::DIVISIONS_PREFIX->value . 'list', now()->addDay(), function () {
return DivisionResource::collection(
Division::query()->where('is_active', true)->get()
);
});
$seo = Cache::remember(CacheKeys::DIVISIONS_PREFIX->value . 'seo', now()->addDay(), function () {
return $this->seoPageProvider->getSeoForCurrentPage();
});
return Inertia::render('Client/Divisions/Index', compact('divisions', 'seo'));
}
public function show(string $slug): \Inertia\Response
{
$divisions = Cache::remember(CacheKeys::DIVISIONS_PREFIX->value . 'list', now()->addDay(), function () {
return DivisionResource::collection(
Division::query()->where('is_active', true)->get()
);
});
$divisionData = Cache::remember(CacheKeys::DIVISION_PREFIX->value . $slug, now()->addDay(), function () use ($slug) {
return Division::with(['workers.userDetail', 'seo'])->where('is_active', true)->where('slug', $slug)->firstOrFail();
});
$seo = $this->seoPageProvider->getSeoForModel($divisionData);
$division = new DivisionResource($divisionData);
return Inertia::render('Client/Divisions/Show', compact('divisions', 'division', 'seo'));
}
}
@@ -0,0 +1,83 @@
<?php
namespace App\Containers\InstituteStructure\UI\WEB\Controllers;
use App\Containers\InstituteStructure\Models\Faculty;
use App\Containers\InstituteStructure\UI\WEB\Transformers\FacultyPreviewResource;
use App\Containers\InstituteStructure\UI\WEB\Transformers\FacultyResource;
use App\Ship\Contracts\SeoServiceInterface;
use App\Ship\Controllers\Controller;
use App\Ship\Enums\CacheKeys;
use App\Ship\Requests\Request;
use Illuminate\Support\Facades\Cache;
use Inertia\Inertia;
class ClientFacultyController extends Controller
{
public function __construct(readonly SeoServiceInterface $seoPageProvider){}
public function index(Request $request)
{
$faculties = Cache::remember(
CacheKeys::FACULTIES_PREFIX->value . 'active_list',
now()->addDay(), // Кешируем на 1 день
function () {
return FacultyPreviewResource::collection(
Faculty::query()
->where('is_active', true)
->get()
);
}
);
$seo = $this->seoPageProvider->getSeoForCurrentPage();
return Inertia::render('Client/Faculties/Index', compact('faculties', 'seo'));
}
public function show(string $slug)
{
// Кешируем список факультетов
$faculties = Cache::remember(
CacheKeys::FACULTIES_PREFIX->value . 'active_list',
now()->addDay(),
function () {
return FacultyPreviewResource::collection(
Faculty::query()
->where('is_active', true)
->get()
);
}
);
// Кешируем данные конкретного факультета
$faculty = Cache::remember(
CacheKeys::FACULTY_PREFIX->value . $slug,
now()->addDay(),
function () use ($slug) {
return Faculty::where('slug', $slug)
->where('is_active', true)
->with(['departments.faculty', 'workers', 'seo'])
->firstOrFail();
}
);
$seo = Cache::remember(
CacheKeys::FACULTY_PREFIX->value . "SEO_" . $slug,
now()->addDay(),
function () use ($faculty) {
return $this->seoPageProvider->getSeoForModel($faculty);
}
);
$faculty = new FacultyResource($faculty);
return Inertia::render('Client/Faculties/Show', compact('faculty', 'faculties', 'seo'));
}
}
@@ -0,0 +1,21 @@
<?php
use App\Containers\InstituteStructure\UI\WEB\Controllers\ClientDepartmentController;
use App\Containers\InstituteStructure\UI\WEB\Controllers\ClientDivisionController;
use App\Containers\InstituteStructure\UI\WEB\Controllers\ClientFacultyController;
use Illuminate\Support\Facades\Route;
Route::middleware('access-check')->group(function () {
// Факультеты и кафедры
Route::get('/faculties', [ClientFacultyController::class, 'index'])->name('client.faculty.index');
Route::get('/faculties/{slug}', [ClientFacultyController::class, 'show'])->name('client.faculty.show');
Route::get('/faculties/{facultySlug}/{departmentSlug}', [ClientDepartmentController::class, 'show'])->name('client.department.show');
// Подразделения института
Route::get('/divisions', [ClientDivisionController::class, 'index'])->name('client.division.index');
Route::get('/divisions/{slug}', [ClientDivisionController::class, 'show'])->name('client.division.show');
});
@@ -0,0 +1,23 @@
<?php
namespace App\Containers\InstituteStructure\UI\WEB\Transformers;
use App\Ship\Resources\JsonResource;
class DepartmentPreviewResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray($request): array
{
return [
'id' => $this->id,
'title' => $this->title,
'slug' => $this->slug,
];
}
}
@@ -0,0 +1,29 @@
<?php
namespace App\Containers\InstituteStructure\UI\WEB\Transformers;
use App\Containers\User\UI\WEB\Transformers\PersonDepartmentPreviewResource;
use App\Containers\User\UI\WEB\Transformers\PersonDepartmentTeachPreviewResource;
use App\Ship\Resources\JsonResource;
class DepartmentResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray($request): array
{
return [
'id' => $this->id,
'title' => $this->title,
'content' => $this->content,
'faculty' => $this->faculty,
'slug' => $this->slug,
'workers' => PersonDepartmentPreviewResource::collection($this->workers),
'teachers' => PersonDepartmentTeachPreviewResource::collection($this->teachers),
];
}
}
@@ -0,0 +1,25 @@
<?php
namespace App\Containers\InstituteStructure\UI\WEB\Transformers;
use App\Containers\User\UI\WEB\Transformers\PersonDivisionPreviewResource;
use App\Ship\Resources\JsonResource;
class DivisionResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray($request): array
{
return [
'id' => $this->id,
'title' => $this->title,
'slug' => $this->slug,
'workers' => PersonDivisionPreviewResource::collection($this->whenLoaded('workers')),
'description' => $this->description,
];
}
}
@@ -0,0 +1,24 @@
<?php
namespace App\Containers\InstituteStructure\UI\WEB\Transformers;
use App\Ship\Resources\JsonResource;
class FacultyPreviewResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray($request): array
{
return [
'id' => $this->id,
'title' => $this->title,
'shortTitle' => $this->abbreviation,
'slug' => $this->slug,
];
}
}
@@ -0,0 +1,28 @@
<?php
namespace App\Containers\InstituteStructure\UI\WEB\Transformers;
use App\Containers\User\UI\WEB\Transformers\PersonFacultyPreviewResource;
use App\Ship\Resources\JsonResource;
class FacultyResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray($request): array
{
return [
'id' => $this->id,
'title' => $this->title,
'content' => $this->content,
'shortTitle' => $this->abbreviation,
'slug' => $this->slug,
'workers' => PersonFacultyPreviewResource::collection($this->workers),
'departments' => DepartmentPreviewResource::collection($this->departments)
];
}
}