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,23 @@
<?php
namespace App\Containers\AdditionalEducation\Data\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class AdditionalEducationCategoryPreviewResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'title' => $this->title,
'slug' => $this->slug,
];
}
}
@@ -0,0 +1,23 @@
<?php
namespace App\Containers\AdditionalEducation\Data\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class AdditionalEducationCategoryResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'title' => $this->title,
'additionalEducations' => $this->additionalEducations
];
}
}
@@ -0,0 +1,30 @@
<?php
namespace App\Containers\AdditionalEducation\Data\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class AdditionalEducationResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'title' => $this->title,
'content' => $this->content,
'category' => $this->category->title,
'directionAdditionalEducation' => new DirectionAdditionalEducationResource($this->category->direction),
'target_group' => $this->target_group,
'price' => $this->price,
'qualification' => $this->qualification,
'learning_time' => $this->learning_time,
'form_education' => $this->form_education->getLabel(),
];
}
}
@@ -0,0 +1,23 @@
<?php
namespace App\Containers\AdditionalEducation\Data\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class DirectionAdditionalEducationResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'title' => $this->title,
'slug' => $this->slug,
];
}
}
@@ -0,0 +1,11 @@
<?php
namespace App\Containers\AdditionalEducation\Loaders;
class AliasesLoader
{
/**
* @var array
*/
public array $aliases = [];
}
@@ -0,0 +1,26 @@
<?php
namespace App\Containers\AdditionalEducation\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\AdditionalEducation\Loaders;
class ProvidersLoader
{
/**
* @var array
*/
public array $providers = [];
}
@@ -0,0 +1,32 @@
<?php
namespace App\Containers\AdditionalEducation\Models;
use App\Ship\Enums\Education\FormEducation;
use App\Ship\Models\Model;
use App\Ship\Models\Seo;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphOne;
class AdditionalEducation extends Model
{
use HasFactory;
protected $guarded = false;
protected $casts = [
'content' => 'array',
'form_education' => FormEducation::class,
];
public function category(): BelongsTo
{
return $this->belongsTo(AdditionalEducationCategory::class, 'category_id', 'id');
}
public function seo(): MorphOne
{
return $this->morphOne(Seo::class, 'seoable');
}
}
@@ -0,0 +1,33 @@
<?php
namespace App\Containers\AdditionalEducation\Models;
use App\Ship\Models\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class AdditionalEducationCategory extends Model
{
use HasFactory;
protected $guarded = false;
public function scopeWithActivePrograms(Builder $query): Builder
{
return $query->with(['additionalEducations' => function ($query) {
$query->where('is_active', true);
}]);
}
public function additionalEducations(): HasMany
{
return $this->hasMany(AdditionalEducation::class, 'category_id', 'id');
}
public function direction(): BelongsTo
{
return $this->belongsTo(DirectionAdditionalEducation::class, 'dir_addit_educat_id', 'id');
}
}
@@ -0,0 +1,20 @@
<?php
namespace App\Containers\AdditionalEducation\Models;
use App\Ship\Models\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
class DirectionAdditionalEducation extends Model
{
use HasFactory;
protected $guarded = false;
public function additionalEducationCategories(): HasMany
{
return $this->hasMany(AdditionalEducationCategory::class, 'dir_addit_educat_id', 'id');
}
}
@@ -0,0 +1,108 @@
<?php
namespace App\Containers\AdditionalEducation\Policies;
use App\Containers\User\Models\User;
use App\Containers\AdditionalEducation\Models\AdditionalEducationCategory;
use Illuminate\Auth\Access\HandlesAuthorization;
class AdditionalEducationCategoryPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->can('view_any_additional::education::category');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, AdditionalEducationCategory $additionalEducationCategory): bool
{
return $user->can('view_additional::education::category');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->can('create_additional::education::category');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, AdditionalEducationCategory $additionalEducationCategory): bool
{
return $user->can('update_additional::education::category');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, AdditionalEducationCategory $additionalEducationCategory): bool
{
return $user->can('delete_additional::education::category');
}
/**
* Determine whether the user can bulk delete.
*/
public function deleteAny(User $user): bool
{
return $user->can('delete_any_additional::education::category');
}
/**
* Determine whether the user can permanently delete.
*/
public function forceDelete(User $user, AdditionalEducationCategory $additionalEducationCategory): bool
{
return $user->can('force_delete_additional::education::category');
}
/**
* Determine whether the user can permanently bulk delete.
*/
public function forceDeleteAny(User $user): bool
{
return $user->can('force_delete_any_additional::education::category');
}
/**
* Determine whether the user can restore.
*/
public function restore(User $user, AdditionalEducationCategory $additionalEducationCategory): bool
{
return $user->can('restore_additional::education::category');
}
/**
* Determine whether the user can bulk restore.
*/
public function restoreAny(User $user): bool
{
return $user->can('restore_any_additional::education::category');
}
/**
* Determine whether the user can replicate.
*/
public function replicate(User $user, AdditionalEducationCategory $additionalEducationCategory): bool
{
return $user->can('replicate_additional::education::category');
}
/**
* Determine whether the user can reorder.
*/
public function reorder(User $user): bool
{
return $user->can('reorder_additional::education::category');
}
}
@@ -0,0 +1,108 @@
<?php
namespace App\Containers\AdditionalEducation\Policies;
use App\Containers\User\Models\User;
use App\Containers\AdditionalEducation\Models\AdditionalEducation;
use Illuminate\Auth\Access\HandlesAuthorization;
class AdditionalEducationPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->can('view_any_additional::education');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, AdditionalEducation $additionalEducation): bool
{
return $user->can('view_additional::education');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->can('create_additional::education');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, AdditionalEducation $additionalEducation): bool
{
return $user->can('update_additional::education');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, AdditionalEducation $additionalEducation): bool
{
return $user->can('delete_additional::education');
}
/**
* Determine whether the user can bulk delete.
*/
public function deleteAny(User $user): bool
{
return $user->can('delete_any_additional::education');
}
/**
* Determine whether the user can permanently delete.
*/
public function forceDelete(User $user, AdditionalEducation $additionalEducation): bool
{
return $user->can('force_delete_additional::education');
}
/**
* Determine whether the user can permanently bulk delete.
*/
public function forceDeleteAny(User $user): bool
{
return $user->can('force_delete_any_additional::education');
}
/**
* Determine whether the user can restore.
*/
public function restore(User $user, AdditionalEducation $additionalEducation): bool
{
return $user->can('restore_additional::education');
}
/**
* Determine whether the user can bulk restore.
*/
public function restoreAny(User $user): bool
{
return $user->can('restore_any_additional::education');
}
/**
* Determine whether the user can replicate.
*/
public function replicate(User $user, AdditionalEducation $additionalEducation): bool
{
return $user->can('replicate_additional::education');
}
/**
* Determine whether the user can reorder.
*/
public function reorder(User $user): bool
{
return $user->can('reorder_additional::education');
}
}
@@ -0,0 +1,108 @@
<?php
namespace App\Containers\AdditionalEducation\Policies;
use App\Containers\User\Models\User;
use App\Containers\AdditionalEducation\Models\DirectionAdditionalEducation;
use Illuminate\Auth\Access\HandlesAuthorization;
class DirectionAdditionalEducationPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->can('view_any_direction::additional::education');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, DirectionAdditionalEducation $directionAdditionalEducation): bool
{
return $user->can('view_direction::additional::education');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->can('create_direction::additional::education');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, DirectionAdditionalEducation $directionAdditionalEducation): bool
{
return $user->can('update_direction::additional::education');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, DirectionAdditionalEducation $directionAdditionalEducation): bool
{
return $user->can('delete_direction::additional::education');
}
/**
* Determine whether the user can bulk delete.
*/
public function deleteAny(User $user): bool
{
return $user->can('delete_any_direction::additional::education');
}
/**
* Determine whether the user can permanently delete.
*/
public function forceDelete(User $user, DirectionAdditionalEducation $directionAdditionalEducation): bool
{
return $user->can('force_delete_direction::additional::education');
}
/**
* Determine whether the user can permanently bulk delete.
*/
public function forceDeleteAny(User $user): bool
{
return $user->can('force_delete_any_direction::additional::education');
}
/**
* Determine whether the user can restore.
*/
public function restore(User $user, DirectionAdditionalEducation $directionAdditionalEducation): bool
{
return $user->can('restore_direction::additional::education');
}
/**
* Determine whether the user can bulk restore.
*/
public function restoreAny(User $user): bool
{
return $user->can('restore_any_direction::additional::education');
}
/**
* Determine whether the user can replicate.
*/
public function replicate(User $user, DirectionAdditionalEducation $directionAdditionalEducation): bool
{
return $user->can('replicate_direction::additional::education');
}
/**
* Determine whether the user can reorder.
*/
public function reorder(User $user): bool
{
return $user->can('reorder_direction::additional::education');
}
}
@@ -0,0 +1,161 @@
<?php
namespace App\Containers\AdditionalEducation\UI\WEB\Controllers;
use App\Containers\AdditionalEducation\Data\Resources\AdditionalEducationCategoryPreviewResource;
use App\Containers\AdditionalEducation\Data\Resources\AdditionalEducationCategoryResource;
use App\Containers\AdditionalEducation\Data\Resources\AdditionalEducationResource;
use App\Containers\AdditionalEducation\Data\Resources\DirectionAdditionalEducationResource;
use App\Containers\AdditionalEducation\Models\AdditionalEducation;
use App\Containers\AdditionalEducation\Models\AdditionalEducationCategory;
use App\Containers\AdditionalEducation\Models\DirectionAdditionalEducation;
use App\Ship\Contracts\SeoServiceInterface;
use App\Ship\Controllers\Controller;
use App\Ship\Enums\CacheKeys;
use App\Ship\Enums\Education\FormEducation;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Inertia\Inertia;
class ClientAdditionalEducationController extends Controller
{
public function __construct(readonly SeoServiceInterface $seoPageProvider){}
public function index(Request $request): \Inertia\Response
{
$cacheKey = md5(serialize($request->all()));
// Основные данные (кешируются)
$directionAdditionalEducations = Cache::remember(
CacheKeys::ADDITIONAL_EDUCATIONAL_PROGRAMS_PREFIX->value . 'directions_' . $cacheKey,
now()->addDay(),
function () {
return DirectionAdditionalEducationResource::collection(
DirectionAdditionalEducation::query()
->where('is_active', true)
->whereHas('additionalEducationCategories', fn ($q) => $q->whereHas('additionalEducations'))
->get()
);
}
);
$additionalEducations = Cache::remember(
CacheKeys::ADDITIONAL_EDUCATIONAL_PROGRAMS_PREFIX->value . $cacheKey,
now()->addDay(),
function () use ($request) {
$query = AdditionalEducationCategory::query()
->has('additionalEducations')
->withActivePrograms()
->where('is_active', true);
if ($request->has('form')) {
$formValue = FormEducation::fromName($request->form)->value;
$query->whereHas('additionalEducations', fn ($q) => $q->where('form_education', $formValue))
->with(['additionalEducations' => fn ($q) => $q->where('form_education', $formValue)]);
}
if ($request->has('category')) {
$slugs = is_array($request->category) ? $request->category : [$request->category];
$query->whereIn('slug', $slugs);
}
if ($request->has('direction')) {
$query->whereHas('direction', fn($q) => $q->where('slug', $request->direction));
}
return AdditionalEducationCategoryResource::collection($query->get());
}
);
$categories = Cache::remember(
CacheKeys::ADDITIONAL_EDUCATIONAL_PROGRAMS_PREFIX->value . 'categories',
now()->addWeek(),
function () {
return AdditionalEducationCategoryPreviewResource::collection(
AdditionalEducationCategory::query()
->where('is_active', true)
->has('additionalEducations')
->get()
);
}
);
// Динамические данные (не кешируются)
$categoriesContent = [];
if ($request->category) {
foreach ((array)$request->category as $item) {
$categoriesContent[$item] = new AdditionalEducationCategoryResource(
AdditionalEducationCategory::where('slug', $item)->first()
);
}
}
$forms_education = array_reduce(
FormEducation::cases(),
fn ($acc, $case) => $acc + [$case->name => $case->getLabel()],
[]
);
$filters = [
'direction_filter' => [
'type' => 'direction',
'value' => $request->input('direction'),
'param' => 'direction'
],
'form_education_filter' => [
'type' => 'form',
'value' => $request->input('form'),
'param' => 'form'
],
'category_filter' => [
'type' => 'category',
'value' => $request->input('category'),
'param' => 'category',
'content' => $categoriesContent,
],
];
$seo = $this->seoPageProvider->getSeoForCurrentPage();
return Inertia::render('Client/Additional-educations/Index', compact(
'directionAdditionalEducations',
'additionalEducations',
'filters',
'forms_education',
'categories',
'seo'
));
}
public function show(string $slug): \Inertia\Response
{
$additionalEducationModel = Cache::remember(
CacheKeys::ADDITIONAL_EDUCATIONAL_PROGRAM_PREFIX->value . $slug,
now()->addDay(),
fn() => AdditionalEducation::with('category.direction')->where('slug', $slug)->firstOrFail()
);
$seo = Cache::remember(
CacheKeys::ADDITIONAL_EDUCATIONAL_PROGRAM_PREFIX->value . 'seo_' . $slug,
now()->addDay(),
fn() => $this->seoPageProvider->getSeoForModel($additionalEducationModel)
);
$settingsPage = request()->attributes->get('settings_page') ?? [];
if (array_key_exists('custom_form', $settingsPage)) {
$form = $settingsPage['custom_form'];
} else {
$form = null;
}
$additionalEducation = new AdditionalEducationResource($additionalEducationModel);
return Inertia::render('Client/Additional-educations/Show', compact(
'additionalEducation',
'seo',
'form',
));
}}
@@ -0,0 +1,12 @@
<?php
use App\Containers\AdditionalEducation\UI\WEB\Controllers\ClientAdditionalEducationController;
use Illuminate\Support\Facades\Route;
Route::middleware('access-check')->group(function () {
Route::get('/additional-education/{slug}', [ClientAdditionalEducationController::class, 'show'])->name('client.additionalEducation.show');
Route::get('/additional-education/', [ClientAdditionalEducationController::class, 'index'])->name('client.additionalEducation.index');
});
@@ -0,0 +1,11 @@
<?php
namespace App\Containers\AppStructure\Loaders;
class AliasesLoader
{
/**
* @var array
*/
public array $aliases = [];
}
@@ -0,0 +1,26 @@
<?php
namespace App\Containers\AppStructure\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\AppStructure\Loaders;
class ProvidersLoader
{
/**
* @var array
*/
public array $providers = [];
}
@@ -0,0 +1,19 @@
<?php
namespace App\Containers\AppStructure\Models;
use App\Ship\Models\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
class MainSection extends Model
{
use HasFactory;
protected $guarded = false;
public function subSections(): HasMany
{
return $this->hasMany(SubSection::class);
}
}
@@ -0,0 +1,31 @@
<?php
namespace App\Containers\AppStructure\Models;
use App\Ship\Models\Model;
use App\Ship\Models\Seo;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphOne;
class Page extends Model
{
use HasFactory;
protected $guarded = false;
public function section() : BelongsTo
{
return $this->belongsTo(SubSection::class, 'sub_section_id');
}
public function seo(): MorphOne
{
return $this->morphOne(Seo::class, 'seoable');
}
protected $casts = [
'content' => 'array',
'settings' => 'array',
];
}
@@ -0,0 +1,25 @@
<?php
namespace App\Containers\AppStructure\Models;
use App\Ship\Models\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class SubSection extends Model
{
use HasFactory;
protected $guarded = false;
public function mainSection() : BelongsTo
{
return $this->belongsTo(MainSection::class);
}
public function pages() : HasMany
{
return $this->hasMany(Page::class);
}
}
@@ -0,0 +1,108 @@
<?php
namespace App\Containers\AppStructure\Policies;
use App\Containers\User\Models\User;
use App\Containers\AppStructure\Models\MainSection;
use Illuminate\Auth\Access\HandlesAuthorization;
class MainSectionPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->can('view_any_main::section');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, MainSection $mainSection): bool
{
return $user->can('view_main::section');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->can('create_main::section');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, MainSection $mainSection): bool
{
return $user->can('update_main::section');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, MainSection $mainSection): bool
{
return $user->can('delete_main::section');
}
/**
* Determine whether the user can bulk delete.
*/
public function deleteAny(User $user): bool
{
return $user->can('delete_any_main::section');
}
/**
* Determine whether the user can permanently delete.
*/
public function forceDelete(User $user, MainSection $mainSection): bool
{
return $user->can('force_delete_main::section');
}
/**
* Determine whether the user can permanently bulk delete.
*/
public function forceDeleteAny(User $user): bool
{
return $user->can('force_delete_any_main::section');
}
/**
* Determine whether the user can restore.
*/
public function restore(User $user, MainSection $mainSection): bool
{
return $user->can('restore_main::section');
}
/**
* Determine whether the user can bulk restore.
*/
public function restoreAny(User $user): bool
{
return $user->can('restore_any_main::section');
}
/**
* Determine whether the user can replicate.
*/
public function replicate(User $user, MainSection $mainSection): bool
{
return $user->can('replicate_main::section');
}
/**
* Determine whether the user can reorder.
*/
public function reorder(User $user): bool
{
return $user->can('reorder_main::section');
}
}
@@ -0,0 +1,108 @@
<?php
namespace App\Containers\AppStructure\Policies;
use App\Containers\User\Models\User;
use App\Containers\AppStructure\Models\Page;
use Illuminate\Auth\Access\HandlesAuthorization;
class PagePolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->can('view_any_url::link');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Page $page): bool
{
return $user->can('view_url::link');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->can('create_url::link');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Page $page): bool
{
return $user->can('update_url::link');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Page $page): bool
{
return $user->can('delete_url::link');
}
/**
* Determine whether the user can bulk delete.
*/
public function deleteAny(User $user): bool
{
return $user->can('delete_any_url::link');
}
/**
* Determine whether the user can permanently delete.
*/
public function forceDelete(User $user, Page $page): bool
{
return $user->can('force_delete_url::link');
}
/**
* Determine whether the user can permanently bulk delete.
*/
public function forceDeleteAny(User $user): bool
{
return $user->can('force_delete_any_url::link');
}
/**
* Determine whether the user can restore.
*/
public function restore(User $user, Page $page): bool
{
return $user->can('restore_url::link');
}
/**
* Determine whether the user can bulk restore.
*/
public function restoreAny(User $user): bool
{
return $user->can('restore_any_url::link');
}
/**
* Determine whether the user can replicate.
*/
public function replicate(User $user, Page $page): bool
{
return $user->can('replicate_url::link');
}
/**
* Determine whether the user can reorder.
*/
public function reorder(User $user): bool
{
return $user->can('reorder_url::link');
}
}
@@ -0,0 +1,108 @@
<?php
namespace App\Containers\AppStructure\Policies;
use App\Containers\User\Models\User;
use App\Containers\AppStructure\Models\SubSection;
use Illuminate\Auth\Access\HandlesAuthorization;
class SubSectionPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->can('view_any_sub::section');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, SubSection $subSection): bool
{
return $user->can('view_sub::section');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->can('create_sub::section');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, SubSection $subSection): bool
{
return $user->can('update_sub::section');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, SubSection $subSection): bool
{
return $user->can('delete_sub::section');
}
/**
* Determine whether the user can bulk delete.
*/
public function deleteAny(User $user): bool
{
return $user->can('delete_any_sub::section');
}
/**
* Determine whether the user can permanently delete.
*/
public function forceDelete(User $user, SubSection $subSection): bool
{
return $user->can('force_delete_sub::section');
}
/**
* Determine whether the user can permanently bulk delete.
*/
public function forceDeleteAny(User $user): bool
{
return $user->can('force_delete_any_sub::section');
}
/**
* Determine whether the user can restore.
*/
public function restore(User $user, SubSection $subSection): bool
{
return $user->can('restore_sub::section');
}
/**
* Determine whether the user can bulk restore.
*/
public function restoreAny(User $user): bool
{
return $user->can('restore_any_sub::section');
}
/**
* Determine whether the user can replicate.
*/
public function replicate(User $user, SubSection $subSection): bool
{
return $user->can('replicate_sub::section');
}
/**
* Determine whether the user can reorder.
*/
public function reorder(User $user): bool
{
return $user->can('reorder_sub::section');
}
}
@@ -0,0 +1,15 @@
<?php
namespace App\Containers\AppStructure\UI\API\Controllers;
use App\Containers\AppStructure\Models\MainSection;
use App\Containers\AppStructure\UI\API\Transformers\NavigationResource;
use App\Ship\Controllers\Controller;
class NavigateController extends Controller
{
public function index()
{
return NavigationResource::collection(MainSection::with('subSections.pages')->orderBy('sort', 'asc')->get());
}
}
@@ -0,0 +1,11 @@
<?php
use App\Containers\AppStructure\UI\API\Controllers\NavigateController;
use Illuminate\Support\Facades\Route;
Route::middleware('ensure.browser')->group(function () {
Route::get('/getNavigation', [NavigateController::class, 'index'])->name('client.main.navigate');
});
@@ -0,0 +1,23 @@
<?php
namespace App\Containers\AppStructure\UI\API\Transformers;
use App\Ship\Resources\JsonResource;
class NavigationResource 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,
'subSections' => SubSectionNavigateResource::collection($this->whenLoaded('subSections')->sortBy('sort')),
];
}
}
@@ -0,0 +1,26 @@
<?php
namespace App\Containers\AppStructure\UI\API\Transformers;
use App\Ship\Resources\JsonResource;
class PageNavigateResource 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,
'path' => $this->path,
'is_url' => $this->is_url,
'icon' => $this->icon
];
}
}
@@ -0,0 +1,25 @@
<?php
namespace App\Containers\AppStructure\UI\API\Transformers;
use App\Ship\Resources\JsonResource;
class SubSectionNavigateResource 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,
'pages' => PageNavigateResource::collection($this->whenLoaded('pages')),
];
}
}
@@ -0,0 +1,223 @@
<?php
namespace App\Containers\AppStructure\UI\CLI\Commands;
use App\Ship\Abstracts\Commands\ConsoleCommand as AbstractConsoleCommand;
use App\Containers\AdditionalEducation\Models\AdditionalEducation;
use App\Containers\AppStructure\Models\Page;
use App\Containers\Article\Enums\PostStatus;
use App\Containers\Article\Models\Post;
use App\Containers\Education\Models\EducationalProgram;
use App\Containers\Event\Models\Event;
use App\Containers\InstituteStructure\Models\Department;
use App\Containers\InstituteStructure\Models\Division;
use App\Containers\InstituteStructure\Models\Faculty;
use App\Containers\User\Models\User;
use App\Ship\Enums\Education\EducationalProgramStatus;
use Spatie\Sitemap\Sitemap;
use Spatie\Sitemap\Tags\Url;
class GenerateSitemap extends AbstractConsoleCommand
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'sitemap:generate';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Генерирует карту сайта';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$sitemap = Sitemap::create();
// Генерация карты сайта для всех моделей
$this->generatePages($sitemap);
$this->generatePosts($sitemap);
$this->generateDivisions($sitemap);
$this->generateEducationPrograms($sitemap); // Добавляем генерацию для EducationProgram
$this->generateEvents($sitemap); // Добавляем генерацию для Event
$this->generateAdditionalEducations($sitemap); // Добавляем генерацию для AdditionalEducation
$this->generateFaculties($sitemap); // Добавляем генерацию для Faculty
$this->generateDepartments($sitemap); // Добавляем генерацию для Department
$this->generateUsers($sitemap); // Добавляем генерацию для User
// Сохраняем карту сайта в файл
$sitemap->writeToFile(public_path('sitemap.xml'));
}
protected function generatePages(Sitemap $sitemap)
{
$pages = Page::query()
->where('is_visible', true)
->where('code', 200)
->where('path', '!=', null)
->where('is_url', false)
->where('title', '!=', null)
->where('searchable', true)
->get();
$this->addUrlsToSitemap($sitemap, $pages, function($page) {
return [
'route' => 'page.view',
'params' => ['path' => $page->path],
'lastModificationDate' => $page->updated_at,
'priority' => 0.5,
];
});
}
protected function generatePosts(Sitemap $sitemap)
{
$posts = Post::query()
->where('status', PostStatus::PUBLISHED)
->get();
$this->addUrlsToSitemap($sitemap, $posts, function($post) {
return [
'route' => 'client.post.show',
'params' => ['slug' => $post->slug],
'lastModificationDate' => $post->updated_at,
'priority' => 0.5,
];
});
}
protected function generateDivisions(Sitemap $sitemap)
{
$divisions = Division::query()
->where('is_active', true)
->get();
$this->addUrlsToSitemap($sitemap, $divisions, function($division) {
return [
'route' => 'client.division.show',
'params' => ['slug' => $division->slug],
'lastModificationDate' => $division->updated_at,
'priority' => 0.5,
];
});
}
protected function generateEducationPrograms(Sitemap $sitemap)
{
$programs = EducationalProgram::query()
->where('status', EducationalProgramStatus::PUBLISHED) // Пример условия для активных программ
->get();
$this->addUrlsToSitemap($sitemap, $programs, function($program) {
return [
'route' => 'client.program.show',
'params' => ['slug' => $program->slug],
'lastModificationDate' => $program->updated_at,
'priority' => 0.5,
];
});
}
protected function generateEvents(Sitemap $sitemap)
{
$now = now()->toDateString(); // Текущая дата
$events = Event::query()
->where('event_date_end', '>=', $now) // Только актуальные события
->get();
$this->addUrlsToSitemap($sitemap, $events, function($event) {
return [
'route' => 'client.event.show',
'params' => ['slug' => $event->slug],
'lastModificationDate' => $event->updated_at,
'priority' => 0.5,
];
});
}
protected function generateAdditionalEducations(Sitemap $sitemap)
{
$educations = AdditionalEducation::query()
->where('is_active', true) // Пример условия для активных программ
->get();
$this->addUrlsToSitemap($sitemap, $educations, function($education) {
return [
'route' => 'client.additionalEducation.show',
'params' => ['slug' => $education->slug],
'lastModificationDate' => $education->updated_at,
'priority' => 0.5,
];
});
}
protected function generateFaculties(Sitemap $sitemap)
{
$faculties = Faculty::query()
->where('is_active', true) // Пример условия для активных факультетов
->get();
$this->addUrlsToSitemap($sitemap, $faculties, function($faculty) {
return [
'route' => 'client.faculty.show',
'params' => ['slug' => $faculty->slug],
'lastModificationDate' => $faculty->updated_at,
'priority' => 0.5,
];
});
}
protected function generateDepartments(Sitemap $sitemap)
{
$departments = Department::query()
->where('is_active', true) // Пример условия для активных кафедр
->get();
$this->addUrlsToSitemap($sitemap, $departments, function($department) {
return [
'route' => 'client.department.show',
'params' => ['facultySlug' => $department->faculty->slug, 'departmentSlug' => $department->slug],
'lastModificationDate' => $department->updated_at,
'priority' => 0.5,
];
});
}
protected function generateUsers(Sitemap $sitemap)
{
$users = User::query()
->whereHas('userDetail', function ($q) {
$q->where('is_only_worker', false);
})
->get();
$this->addUrlsToSitemap($sitemap, $users, function($user) {
return [
'route' => 'client.person.show',
'params' => ['slug' => $user->slug],
'lastModificationDate' => $user->updated_at,
'priority' => 0.5,
];
});
}
protected function addUrlsToSitemap(Sitemap $sitemap, $items, callable $callback)
{
foreach ($items as $item) {
$urlData = $callback($item);
$url = route($urlData['route'], $urlData['params']);
$sitemap->add(Url::create($url)
->setLastModificationDate($urlData['lastModificationDate'])
->setPriority($urlData['priority']));
}
}
}
@@ -0,0 +1,53 @@
<?php
namespace App\Containers\AppStructure\UI\CLI\Commands;
use App\Ship\Abstracts\Commands\ConsoleCommand as AbstractConsoleCommand;
use App\Containers\AppStructure\Models\Page;
use Illuminate\Support\Facades\Route;
class RegisterRoutes extends AbstractConsoleCommand
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'routes:register';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Register application routes in the database';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$routes = Route::getRoutes();
foreach ($routes as $route) {
// Проверяем, существует ли маршрут в базе данных
if (!Page::where('path', '=', $route->uri)->where('is_registered', '=', true)->exists()) {
// Если не существует, создаем новую запись
Page::create([
'path' => $route->uri,
'is_registered' => true,
'is_url' => false,
'searchable' => false,
'code' => 200,
]);
$this->info("Маршрут зарегистрирован: " . $route->uri);
} else {
$this->info("Маршрут уже существует: " . $route->uri);
}
}
$this->info('Все маршруты успешно проверены.');
}
}
@@ -0,0 +1,49 @@
<?php
namespace App\Containers\AppStructure\UI\WEB\Controllers;
use App\Containers\AppStructure\Models\Page;
use App\Containers\AppStructure\UI\WEB\Transformers\PageResource;
use App\Ship\Contracts\SeoServiceInterface;
use App\Ship\Controllers\Controller;
use Illuminate\Support\Facades\Cache;
use Inertia\Inertia;
class PageController extends Controller
{
public function __construct(readonly SeoServiceInterface $seoPageProvider){}
public function render(string $path): \Inertia\Response
{
// Генерируем уникальный ключ для кеширования
$cacheKey = 'page_' . md5($path);
// Пытаемся получить данные из кеша
$page = Cache::remember($cacheKey, now()->addHours(48), function () use ($path) {
return Page::where('path', '=', $path)
->with('section.pages.section', 'section.mainSection')
->first();
});
if ($page === null) {
abort(404);
}
$subSectionPages = $page->section ? PageResource::collection($page->section->pages) : null;
$seo = $this->seoPageProvider->getSeoForModel($page);
$page = new PageResource($page);
if ($page->code != 200) {
abort($page->code);
}
return inertia()->render('Page', [
'page' => $page,
'subSectionPages' => $subSectionPages,
'seo' => $seo,
]);
}
}
@@ -0,0 +1,31 @@
<?php
namespace App\Containers\AppStructure\UI\WEB\Transformers;
use App\Ship\Resources\JsonResource;
class PageResource 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,
'content' => $this->content,
'slug' => $this->slug,
'code' => $this->code,
'path' => $this->path,
'is_url' => $this->is_url,
'settings' => $this->settings,
'icon' => $this->icon,
'section' => $this->section ? $this->section->title : null,
'created_at' => $this->created_at->diffforhumans()
];
}
}
@@ -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);
}
}
+42
View File
@@ -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'
];
}
+11
View File
@@ -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,
];
}
}
@@ -0,0 +1,45 @@
<?php
namespace App\Containers\Education\Enums;
use Filament\Support\Contracts\HasLabel;
use Filament\Support\Contracts\HasColor;
enum TypeExam: int implements HasLabel, HasColor
{
case EGE = 1;
case INTERNAL_EXAM = 2;
case AVG_SCORE = 3;
case ACCREDITATION = 4;
public function getLabel(): ?string
{
return match ($this) {
self::EGE => 'ЕГЭ',
self::INTERNAL_EXAM => 'ВИ, проводимое организацией самостоятельно',
self::AVG_SCORE => 'Ср. балл документа об образовании',
self::ACCREDITATION => 'Аккредитация',
};
}
public function getColor(): string|array|null
{
return match ($this) {
self::EGE => 'primary',
self::INTERNAL_EXAM => 'info',
self::AVG_SCORE => 'success',
self::ACCREDITATION => 'warning',
};
}
public static function fromName(string $name): ?self
{
return match ($name) {
'EGE' => self::EGE,
'INTERNAL_EXAM' => self::INTERNAL_EXAM,
'AVG_SCORE' => self::AVG_SCORE,
'ACCREDITATION' => self::ACCREDITATION,
default => null,
};
}
}
@@ -0,0 +1,11 @@
<?php
namespace App\Containers\Education\Loaders;
class AliasesLoader
{
/**
* @var array
*/
public array $aliases = [];
}
@@ -0,0 +1,26 @@
<?php
namespace App\Containers\Education\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\Education\Loaders;
class ProvidersLoader
{
/**
* @var array
*/
public array $providers = [];
}
@@ -0,0 +1,36 @@
<?php
namespace App\Containers\Education\Models;
use App\Ship\Models\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
class AdmissionCampaign extends Model
{
use HasFactory;
protected $guarded = false;
protected $casts = [
'info' => 'array'
];
public function admission_plans(): HasMany
{
return $this->hasMany(AdmissionPlan::class, 'admission_campaigns_id', 'id');
}
public function educationalPrograms(): HasManyThrough
{
return $this->hasManyThrough(
EducationalProgram::class,
AdmissionPlan::class,
'admission_campaigns_id', // внешний ключ в AdmissionPlan
'id', // внешний ключ в EducationalPrograms
'id', // локальный ключ в AdmissionCampaign
'educational_programs_id' // локальный ключ в AdmissionPlan
);
}
}
@@ -0,0 +1,29 @@
<?php
namespace App\Containers\Education\Models;
use App\Ship\Models\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class AdmissionPlan extends Model
{
use HasFactory;
protected $guarded = false;
protected $casts = [
'exams' => 'array',
'contests' => 'array'
];
public function educationalProgram(): BelongsTo
{
return $this->belongsTo(EducationalProgram::class, 'educational_programs_id', 'id');
}
public function admissionCampaign(): BelongsTo
{
return $this->belongsTo(AdmissionCampaign::class, 'admission_campaigns_id', 'id');
}
}
@@ -0,0 +1,64 @@
<?php
namespace App\Containers\Education\Models;
use App\Ship\Enums\Education\LevelEducational;
use App\Ship\Models\Model;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
class DirectionStudy extends Model
{
use HasFactory;
protected $guarded = false;
protected $casts = [
'lvl_edu' => LevelEducational::class,
];
public function programs(): HasMany
{
return $this->hasMany(EducationalProgram::class);
}
public function scopeWithActivePrograms(Builder $query): Builder
{
return $query->with(['programs' => function ($query) {
$query->whereHas('admission_plans.admissionCampaign', function ($q) {
$q->where('status', 1);
});
}]);
}
public function scopeWithActiveAdmissionCampaign(Builder $query): Builder
{
return $query->whereHas('programs.admission_plans.admissionCampaign', function ($q) {
$q->where('status', 1);
});
}
public function scopeWithAdmissionCampaignByYear(Builder $query, string $year): Builder
{
return $query->whereHas('programs.admission_plans.admissionCampaign', function ($q) use ($year) {
$q->where('status', 1)->where('academic_year', $year);
});
}
public function scopeForBachelorLevel(Builder $query): Builder
{
return $query->where('lvl_edu', LevelEducational::BACHELOR);
}
public function scopeForMiddleLevel(Builder $query): Builder
{
return $query->whereIn('lvl_edu', [LevelEducational::MIDDLE_LEVEL_SPECIALIST_TRAINING, LevelEducational::PREPARATION_OF_QUALIFIED_WORKERS]);
}
public function scopeForMasterLevel(Builder $query): Builder
{
return $query->where('lvl_edu', LevelEducational::MASTER);
}
}
@@ -0,0 +1,43 @@
<?php
namespace App\Containers\Education\Models;
use App\Containers\InstituteStructure\Models\Department;
use App\Ship\Enums\Education\LevelEducational;
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;
use Illuminate\Database\Eloquent\Relations\HasMany;
class EducationalProgram extends Model
{
use HasFactory, HasSeo;
protected $guarded = false;
protected $casts = [
'program_features' => 'array',
'about_program' => 'array',
'learning_forms' => 'array',
'lvl_edu' => LevelEducational::class,
];
public function directionStudy(): BelongsTo
{
return $this->belongsTo(DirectionStudy::class);
}
public function departments(): BelongsToMany
{
return $this->belongsToMany(Department::class, 'program_department');
}
public function admission_plans(): HasMany
{
return $this->hasMany(AdmissionPlan::class, 'educational_programs_id', 'id');
}
}
@@ -0,0 +1,108 @@
<?php
namespace App\Containers\Education\Policies;
use App\Containers\User\Models\User;
use App\Containers\Education\Models\AdmissionCampaign;
use Illuminate\Auth\Access\HandlesAuthorization;
class AdmissionCampaignPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->can('view_any_admission::campaign');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, AdmissionCampaign $admissionCampaign): bool
{
return $user->can('view_admission::campaign');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->can('create_admission::campaign');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, AdmissionCampaign $admissionCampaign): bool
{
return $user->can('update_admission::campaign');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, AdmissionCampaign $admissionCampaign): bool
{
return $user->can('delete_admission::campaign');
}
/**
* Determine whether the user can bulk delete.
*/
public function deleteAny(User $user): bool
{
return $user->can('delete_any_admission::campaign');
}
/**
* Determine whether the user can permanently delete.
*/
public function forceDelete(User $user, AdmissionCampaign $admissionCampaign): bool
{
return $user->can('force_delete_admission::campaign');
}
/**
* Determine whether the user can permanently bulk delete.
*/
public function forceDeleteAny(User $user): bool
{
return $user->can('force_delete_any_admission::campaign');
}
/**
* Determine whether the user can restore.
*/
public function restore(User $user, AdmissionCampaign $admissionCampaign): bool
{
return $user->can('restore_admission::campaign');
}
/**
* Determine whether the user can bulk restore.
*/
public function restoreAny(User $user): bool
{
return $user->can('restore_any_admission::campaign');
}
/**
* Determine whether the user can replicate.
*/
public function replicate(User $user, AdmissionCampaign $admissionCampaign): bool
{
return $user->can('replicate_admission::campaign');
}
/**
* Determine whether the user can reorder.
*/
public function reorder(User $user): bool
{
return $user->can('reorder_admission::campaign');
}
}
@@ -0,0 +1,108 @@
<?php
namespace App\Containers\Education\Policies;
use App\Containers\User\Models\User;
use App\Containers\Education\Models\AdmissionPlan;
use Illuminate\Auth\Access\HandlesAuthorization;
class AdmissionPlanPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->can('view_any_admission::plan');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, AdmissionPlan $admissionPlan): bool
{
return $user->can('view_admission::plan');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->can('create_admission::plan');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, AdmissionPlan $admissionPlan): bool
{
return $user->can('update_admission::plan');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, AdmissionPlan $admissionPlan): bool
{
return $user->can('delete_admission::plan');
}
/**
* Determine whether the user can bulk delete.
*/
public function deleteAny(User $user): bool
{
return $user->can('delete_any_admission::plan');
}
/**
* Determine whether the user can permanently delete.
*/
public function forceDelete(User $user, AdmissionPlan $admissionPlan): bool
{
return $user->can('force_delete_admission::plan');
}
/**
* Determine whether the user can permanently bulk delete.
*/
public function forceDeleteAny(User $user): bool
{
return $user->can('force_delete_any_admission::plan');
}
/**
* Determine whether the user can restore.
*/
public function restore(User $user, AdmissionPlan $admissionPlan): bool
{
return $user->can('restore_admission::plan');
}
/**
* Determine whether the user can bulk restore.
*/
public function restoreAny(User $user): bool
{
return $user->can('restore_any_admission::plan');
}
/**
* Determine whether the user can replicate.
*/
public function replicate(User $user, AdmissionPlan $admissionPlan): bool
{
return $user->can('replicate_admission::plan');
}
/**
* Determine whether the user can reorder.
*/
public function reorder(User $user): bool
{
return $user->can('reorder_admission::plan');
}
}
@@ -0,0 +1,108 @@
<?php
namespace App\Containers\Education\Policies;
use App\Containers\User\Models\User;
use App\Containers\Education\Models\DirectionStudy;
use Illuminate\Auth\Access\HandlesAuthorization;
class DirectionStudyPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->can('view_any_direction::study');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, DirectionStudy $directionStudy): bool
{
return $user->can('view_direction::study');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->can('create_direction::study');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, DirectionStudy $directionStudy): bool
{
return $user->can('update_direction::study');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, DirectionStudy $directionStudy): bool
{
return $user->can('delete_direction::study');
}
/**
* Determine whether the user can bulk delete.
*/
public function deleteAny(User $user): bool
{
return $user->can('delete_any_direction::study');
}
/**
* Determine whether the user can permanently delete.
*/
public function forceDelete(User $user, DirectionStudy $directionStudy): bool
{
return $user->can('force_delete_direction::study');
}
/**
* Determine whether the user can permanently bulk delete.
*/
public function forceDeleteAny(User $user): bool
{
return $user->can('force_delete_any_direction::study');
}
/**
* Determine whether the user can restore.
*/
public function restore(User $user, DirectionStudy $directionStudy): bool
{
return $user->can('restore_direction::study');
}
/**
* Determine whether the user can bulk restore.
*/
public function restoreAny(User $user): bool
{
return $user->can('restore_any_direction::study');
}
/**
* Determine whether the user can replicate.
*/
public function replicate(User $user, DirectionStudy $directionStudy): bool
{
return $user->can('replicate_direction::study');
}
/**
* Determine whether the user can reorder.
*/
public function reorder(User $user): bool
{
return $user->can('reorder_direction::study');
}
}
@@ -0,0 +1,108 @@
<?php
namespace App\Containers\Education\Policies;
use App\Containers\User\Models\User;
use App\Containers\Education\Models\EducationalProgram;
use Illuminate\Auth\Access\HandlesAuthorization;
class EducationalProgramPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->can('view_any_educational::program');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, EducationalProgram $educationalProgram): bool
{
return $user->can('view_educational::program');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->can('create_educational::program');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, EducationalProgram $educationalProgram): bool
{
return $user->can('update_educational::program');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, EducationalProgram $educationalProgram): bool
{
return $user->can('delete_educational::program');
}
/**
* Determine whether the user can bulk delete.
*/
public function deleteAny(User $user): bool
{
return $user->can('delete_any_educational::program');
}
/**
* Determine whether the user can permanently delete.
*/
public function forceDelete(User $user, EducationalProgram $educationalProgram): bool
{
return $user->can('force_delete_educational::program');
}
/**
* Determine whether the user can permanently bulk delete.
*/
public function forceDeleteAny(User $user): bool
{
return $user->can('force_delete_any_educational::program');
}
/**
* Determine whether the user can restore.
*/
public function restore(User $user, EducationalProgram $educationalProgram): bool
{
return $user->can('restore_educational::program');
}
/**
* Determine whether the user can bulk restore.
*/
public function restoreAny(User $user): bool
{
return $user->can('restore_any_educational::program');
}
/**
* Determine whether the user can replicate.
*/
public function replicate(User $user, EducationalProgram $educationalProgram): bool
{
return $user->can('replicate_educational::program');
}
/**
* Determine whether the user can reorder.
*/
public function reorder(User $user): bool
{
return $user->can('reorder_educational::program');
}
}
@@ -0,0 +1,15 @@
<?php
namespace App\Containers\Education\UI\API\Controllers;
use App\Containers\Education\Models\AdmissionCampaign;
use App\Ship\Controllers\Controller;
class AcademicYearController extends Controller
{
public function __invoke()
{
$activeCampaign = AdmissionCampaign::query()->where('status', 1)->firstOrFail();
return $activeCampaign->academic_year;
}
}
@@ -0,0 +1,24 @@
<?php
namespace App\Containers\Education\UI\API\Controllers;
use App\Http\Controllers\Controller;
use App\Jobs\CreateAdmissionPlan;
use App\Jobs\CreateDirectionStudy;
use App\Jobs\CreateEducationalProgram;
use App\Services\Vicon\EducationalProgram\EducationalProgramService;
class UpdateAdmissionPlansDataApiController extends Controller
{
public function index()
{
$this->clearAndCreateAdmissionPlans();
return redirect()->route('index');
}
private function clearAndCreateAdmissionPlans() : void
{
dispatch(new CreateAdmissionPlan());
}
}
@@ -0,0 +1,28 @@
<?php
namespace App\Containers\Education\UI\API\Controllers;
use App\Http\Controllers\Controller;
use App\Jobs\CreateDirectionStudy;
use App\Jobs\CreateEducationalProgram;
use App\Services\Vicon\EducationalProgram\EducationalProgramService;
class UpdateEduDataApiController extends Controller
{
public function index()
{
$this->updateOrCreateDirectionStudy();
$this->updateOrCreateEducationProgram();
return redirect()->route('index');
}
private function updateOrCreateDirectionStudy() : void
{
dispatch(new CreateDirectionStudy());
}
private function updateOrCreateEducationProgram() : void
{
dispatch(new CreateEducationalProgram());
}
}
@@ -0,0 +1,13 @@
<?php
use App\Containers\Education\UI\API\Controllers\AcademicYearController;
use App\Containers\Education\UI\API\Controllers\UpdateAdmissionPlansDataApiController;
use App\Containers\Education\UI\API\Controllers\UpdateEduDataApiController;
use Illuminate\Support\Facades\Route;
Route::get('/getAcademicYear', AcademicYearController::class)->name('academic.year');
Route::middleware(['auth', 'superadmin'])->group(function () {
Route::get('/get-edu-program-data', [UpdateEduDataApiController::class, 'index']);
Route::get('/get-admission-plans-data', [UpdateAdmissionPlansDataApiController::class, 'index']);
});
@@ -0,0 +1,176 @@
<?php
namespace App\Containers\Education\UI\WEB\Controllers;
use App\Containers\Education\Models\AdmissionCampaign;
use App\Containers\Education\Models\AdmissionPlan;
use App\Containers\Education\Models\DirectionStudy;
use App\Containers\Education\Models\EducationalProgram;
use App\Containers\Education\UI\WEB\Transformers\DirectionStudyResource;
use App\Containers\Education\UI\WEB\Transformers\EducationalProgramResource;
use App\Ship\Contracts\SeoServiceInterface;
use App\Ship\Controllers\Controller;
use App\Ship\Enums\CacheKeys;
use App\Ship\Enums\Education\BudgetEducation;
use App\Ship\Enums\Education\FormEducation;
use App\Ship\Enums\Education\LevelEducational;
use App\Ship\Requests\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
use Inertia\Inertia;
class ClientProgramController extends Controller
{
public function __construct(readonly SeoServiceInterface $seoPageProvider){}
public function index(Request $request): \Inertia\Response
{
$cacheKey = CacheKeys::EDUCATION_PROGRAMS_PREFIX->value . md5(serialize($request->all()));
$cacheKeyLevels = 'education_levels_list';
$cacheKeyForms = 'education_forms_list';
$cacheKeyBudgets = 'education_budgets_list';
$cacheKeySeo = 'education_programs_seo';
$activeCampaign = Cache::remember(CacheKeys::ADMISSION_CAMPAIGNS_PREFIX->value, now()->addDay(), function () {
return AdmissionCampaign::where('status', 1)->first();
});
$levelsEducational = Cache::remember($cacheKeyLevels, now()->addDay(), function () {
return EducationalProgram::distinct()->pluck('lvl_edu')
->mapWithKeys(fn($level) => [$level->name => $level->getLabel()]);
});
$formsEdu = Cache::remember($cacheKeyForms, now()->addDay(), function () {
return AdmissionPlan::distinct()
->pluck('contests')
->flatten(1)
->filter(fn ($item) => isset($item['form_education']))
->pluck('form_education')
->unique()
->map(fn ($item) => FormEducation::tryFrom((int)$item))
->filter()
->mapWithKeys(fn ($form) => [$form->name => $form->getLabel()]);
});
$budgetEdu = Cache::remember($cacheKeyBudgets, now()->addDay(), function () {
return AdmissionPlan::distinct()
->pluck('contests')
->flatten(1)
->filter(fn ($item) => isset($item['places']['form_budget']))
->pluck('places.form_budget')
->unique()
->map(fn ($item) => BudgetEducation::tryFrom($item))
->filter()
->mapWithKeys(fn ($form) => [$form->name => $form->getLabel()]);
});
$seo = Cache::remember($cacheKeySeo, now()->addDay(), function () {
return $this->seoPageProvider->getSeoForCurrentPage();
});
$naprs = Cache::remember($cacheKey, now()->addHours(), function () use ($request, $activeCampaign) {
return DirectionStudyResource::collection(
DirectionStudy::query()
->withAdmissionCampaignByYear($activeCampaign->academic_year)
->withActivePrograms()
// ->with('programs.admission_plans')
->when($request->input('level'), fn($q, $level) =>
$q->where('lvl_edu', LevelEducational::fromName($level)->value))
->when($request->input('form'), fn($q, $form) =>
$this->applyFormFilter($q, $form))
->when($request->input('budget'), fn($q, $budget) =>
$this->applyBudgetFilter($q, $budget))
->when($request->input('direction'), fn($q, $slugs) =>
is_array($slugs) ? $q->whereIn('slug', $slugs) : $q)
->get()
);
});
$data = [
'naprs' => $naprs,
'campaignName' => $this->getAdmissionCampaignName(),
'levelsEducational' => $levelsEducational,
'filters' => [
'level_filter' => ['type' => 'level', 'value' => $request->input('level'), 'param' => 'level'],
'budget_filter' => ['type' => 'budget', 'value' => $request->input('budget'), 'param' => 'budget'],
'formEdu_filter' => ['type' => 'form', 'value' => $request->input('form'), 'param' => 'form'],
'direction_filter' => ['type' => 'direction', 'value' => $request->input('direction'), 'param' => 'direction'],
],
'formsEdu' => $formsEdu,
'budgetEdu' => $budgetEdu,
'direction_studies' => DirectionStudy::query()
->withAdmissionCampaignByYear($activeCampaign->academic_year)
->withActivePrograms()
->get(),
'seo' => $seo
];
return Inertia::render('Client/Programs/Index', $data);
}
public function show(string $slug): \Inertia\Response
{
$cacheKeyProgram = CacheKeys::EDUCATION_PROGRAM_PREFIX->value . md5($slug);
$cacheKeySeo = CacheKeys::EDUCATION_PROGRAM_PREFIX->value . 'seo_' . md5($slug);
$cacheKeyForms = 'education_forms_list';
$programModel = Cache::remember($cacheKeyProgram, now()->addHours(1), function () use ($slug) {
return EducationalProgram::query()
->where('slug', $slug)
->with(['admission_plans', 'directionStudy', 'seo'])
->firstOrFail();
});
$formsEdu = Cache::remember($cacheKeyForms, now()->addDay(), function () {
return collect(BudgetEducation::cases())
->mapWithKeys(fn($form) => [$form->value => $form->getLabel()]);
});
$seo = Cache::remember($cacheKeySeo, now()->addHours(1), function () use ($programModel) {
return $this->seoPageProvider->getSeoForModel($programModel);
});
$program = new EducationalProgramResource($programModel);
return Inertia::render('Client/Programs/Show', compact('program', 'formsEdu', 'seo'));
}
private function getAdmissionCampaignName(): string
{
$cacheKey = CacheKeys::EDUCATION_PROGRAM_PREFIX->value . 'active_campaign_name';
return Cache::remember($cacheKey, now()->addHours(1), function () {
$campaign = AdmissionCampaign::query()->where('status', 1)->first();
return $campaign->name;
});
}
private function applyFormFilter($query, $form)
{
$formValue = Str::of(FormEducation::fromName($form)->value)->toString();
$query->whereHas('programs.admission_plans', function ($query) use ($formValue) {
$query->whereJsonContains('contests', ['form_education' => $formValue]);
})
->with(['programs' => function ($query) use ($formValue) {
$query->whereHas('admission_plans', function ($q) use ($formValue) {
$q->whereJsonContains('contests', ['form_education' => $formValue]);
});
}]);
}
private function applyBudgetFilter($query, $budget): void
{
$budgetValue = Str::of(BudgetEducation::fromName($budget)->value)->toString();
$query->whereHas('programs.admission_plans', function ($query) use ($budgetValue) {
$query->where('contests', 'like', '%"form_budget":"'.$budgetValue.'"%');
})
->with(['programs' => function ($query) use ($budgetValue) {
$query->whereHas('admission_plans', function ($query) use ($budgetValue) {
$query->where('contests', 'like', '%"form_budget":"'.$budgetValue.'"%');
});
}]);
}
}
@@ -0,0 +1,12 @@
<?php
use App\Containers\Education\UI\WEB\Controllers\ClientProgramController;
use Illuminate\Support\Facades\Route;
Route::middleware('access-check')->group(function () {
Route::get('/programs/', [ClientProgramController::class, 'index'])->name('client.program.index');
Route::get('/program/{slug}', [ClientProgramController::class, 'show'])->name('client.program.show');
});
@@ -0,0 +1,18 @@
<?php
namespace App\Containers\Education\UI\WEB\Transformers;
use App\Ship\Resources\JsonResource;
class DirectionStudyResource extends JsonResource
{
public function toArray($request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'code' => $this->code,
'programs' => EducationalProgramBasicResource::collection($this->programs),
];
}
}
@@ -0,0 +1,23 @@
<?php
namespace App\Containers\Education\UI\WEB\Transformers;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class EducationalProgramBasicResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'slug' => $this->slug
];
}
}
@@ -0,0 +1,40 @@
<?php
namespace App\Containers\Education\UI\WEB\Transformers;
use App\Ship\Enums\Education\FormEducation;
use App\Ship\Resources\JsonResource;
class EducationalProgramResource extends JsonResource
{
public function toArray($request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'about_program' => $this->about_program,
'program_features' => $this->program_features,
'inner_code' => $this->inner_code,
'lvl_edu' => $this->lvl_edu->getLabel(),
'status' => $this->status,
// 'lang_stud' => $this->lang_stud,
'learning_forms' => $this->transformLearningForms($this->learning_forms),
'directionStudy' => $this->directionStudy,
'admissionPlans' => $this->admission_plans,
];
}
private function transformLearningForms(array $learningForms): array
{
return array_map(function ($form) {
$formId = $form['form_id'];
$formEnum = FormEducation::from($formId); // Получаем enum по form_id
return [
'form_edu' => $formEnum->getLabel(), // Значение из enum
'period_data' => $form['period_data'],
];
}, $learningForms);
}
}
@@ -0,0 +1,11 @@
<?php
namespace App\Containers\Event\Loaders;
class AliasesLoader
{
/**
* @var array
*/
public array $aliases = [];
}
@@ -0,0 +1,26 @@
<?php
namespace App\Containers\Event\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\Event\Loaders;
class ProvidersLoader
{
/**
* @var array
*/
public array $providers = [];
}
+26
View File
@@ -0,0 +1,26 @@
<?php
namespace App\Containers\Event\Models;
use App\Ship\Models\Model;
use App\Ship\Traits\HasSeo;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Spatie\Tags\HasTags;
class Event extends Model
{
use HasFactory, HasTags, HasSeo;
protected $guarded = false;
protected $casts = [
'content' => 'array',
];
public function category() : BelongsTo
{
return $this->belongsTo(EventCategory::class);
}
}
@@ -0,0 +1,19 @@
<?php
namespace App\Containers\Event\Models;
use App\Ship\Models\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
class EventCategory extends Model
{
use HasFactory;
protected $guarded = false;
public function events() : HasMany
{
return $this->hasMany(Event::class, 'category_id', 'id');
}
}
@@ -0,0 +1,108 @@
<?php
namespace App\Containers\Event\Policies;
use App\Containers\User\Models\User;
use App\Containers\Event\Models\EventCategory;
use Illuminate\Auth\Access\HandlesAuthorization;
class EventCategoryPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->can('view_any_event::category');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, EventCategory $eventCategory): bool
{
return $user->can('view_event::category');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->can('create_event::category');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, EventCategory $eventCategory): bool
{
return $user->can('update_event::category');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, EventCategory $eventCategory): bool
{
return $user->can('delete_event::category');
}
/**
* Determine whether the user can bulk delete.
*/
public function deleteAny(User $user): bool
{
return $user->can('delete_any_event::category');
}
/**
* Determine whether the user can permanently delete.
*/
public function forceDelete(User $user, EventCategory $eventCategory): bool
{
return $user->can('force_delete_event::category');
}
/**
* Determine whether the user can permanently bulk delete.
*/
public function forceDeleteAny(User $user): bool
{
return $user->can('force_delete_any_event::category');
}
/**
* Determine whether the user can restore.
*/
public function restore(User $user, EventCategory $eventCategory): bool
{
return $user->can('restore_event::category');
}
/**
* Determine whether the user can bulk restore.
*/
public function restoreAny(User $user): bool
{
return $user->can('restore_any_event::category');
}
/**
* Determine whether the user can replicate.
*/
public function replicate(User $user, EventCategory $eventCategory): bool
{
return $user->can('replicate_event::category');
}
/**
* Determine whether the user can reorder.
*/
public function reorder(User $user): bool
{
return $user->can('reorder_event::category');
}
}
@@ -0,0 +1,108 @@
<?php
namespace App\Containers\Event\Policies;
use App\Containers\User\Models\User;
use App\Containers\Event\Models\Event;
use Illuminate\Auth\Access\HandlesAuthorization;
class EventPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->can('view_any_event');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Event $event): bool
{
return $user->can('view_event');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->can('create_event');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Event $event): bool
{
return $user->can('update_event');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Event $event): bool
{
return $user->can('delete_event');
}
/**
* Determine whether the user can bulk delete.
*/
public function deleteAny(User $user): bool
{
return $user->can('delete_any_event');
}
/**
* Determine whether the user can permanently delete.
*/
public function forceDelete(User $user, Event $event): bool
{
return $user->can('force_delete_event');
}
/**
* Determine whether the user can permanently bulk delete.
*/
public function forceDeleteAny(User $user): bool
{
return $user->can('force_delete_any_event');
}
/**
* Determine whether the user can restore.
*/
public function restore(User $user, Event $event): bool
{
return $user->can('restore_event');
}
/**
* Determine whether the user can bulk restore.
*/
public function restoreAny(User $user): bool
{
return $user->can('restore_any_event');
}
/**
* Determine whether the user can replicate.
*/
public function replicate(User $user, Event $event): bool
{
return $user->can('replicate_event');
}
/**
* Determine whether the user can reorder.
*/
public function reorder(User $user): bool
{
return $user->can('reorder_event');
}
}
@@ -0,0 +1,320 @@
<?php
namespace App\Containers\Event\UI\WEB\Controllers;
use App\Containers\Event\Models\Event;
use App\Containers\Event\Models\EventCategory;
use App\Containers\Event\UI\WEB\Transformers\EventCategoryResource;
use App\Containers\Event\UI\WEB\Transformers\EventPreviewResource;
use App\Containers\Event\UI\WEB\Transformers\EventResource;
use App\Ship\Contracts\SeoServiceInterface;
use App\Ship\Controllers\Controller;
use App\Ship\Enums\CacheKeys;
use App\Ship\Requests\Request;
use Carbon\Carbon;
use DateTime;
use Illuminate\Support\Facades\Cache;
use Inertia\Inertia;
class ClientEventController extends Controller
{
public function __construct(readonly SeoServiceInterface $seoPageProvider){}
public function index(Request $request): \Inertia\Response
{
$currentDate = $this->getCurrentDate($request);
$cacheKey = md5(serialize([$currentDate, $request->all()]));
$events = Cache::remember(
CacheKeys::EVENTS_PREFIX->value . $cacheKey,
now()->addHours(12),
fn() => $this->getEvents($currentDate)
);
$eventDates = Cache::remember(
CacheKeys::EVENTS_PREFIX->value . 'dates_' . $cacheKey,
now()->addHours(12),
fn() => $this->getEventDates($this->getFilters())
);
$categories = Cache::remember(
CacheKeys::EVENTS_PREFIX->value . 'categories',
now()->addDay(),
fn() => EventCategoryResource::collection(EventCategory::has('events')->get())
);
$filters = $this->getFilters();
$seo = $this->seoPageProvider->getSeoForCurrentPage();
return Inertia::render('Client/Events/Index', compact(
'eventDates',
'events',
'currentDate',
'filters',
'categories',
'seo'
));
}
public function show(string $slug): \Inertia\Response
{
$eventModel = Cache::remember(
CacheKeys::EVENT_PREFIX->value . $slug,
now()->addDay(),
function () use ($slug) {
return Event::where('slug', $slug)
->with(['category', 'seo'])
->firstOrFail();
}
);
$seo = Cache::remember(
CacheKeys::EVENT_PREFIX->value . 'seo_' . $slug,
now()->addDay(),
function () use ($eventModel) {
return $this->seoPageProvider->getSeoForModel($eventModel);
}
);
$event = new EventResource($eventModel);
return Inertia::render('Client/Events/Show', compact(
'event',
'seo'
));
}
public function archive(Request $request): \Inertia\Response
{
$cacheKey = md5(serialize($request->all()));
$events = Cache::remember(
CacheKeys::EVENTS_PREFIX->value . 'archive_' . $cacheKey,
now()->addDay(),
fn() => $this->getEventsArchive()
);
$categories = Cache::remember(
CacheKeys::EVENTS_PREFIX->value . 'categories',
now()->addDay(),
fn() => EventCategoryResource::collection(EventCategory::has('events')->get())
);
$filters = $this->getFilters();
$seo = $this->seoPageProvider->getSeoForCurrentPage();
return Inertia::render('Client/Events/Archive', compact(
'events',
'filters',
'categories',
'seo'
));
}
private function getCurrentDate(Request $request): array
{
$dateInput = $request->input('date');
// Если дата введена, создаем объект Carbon, иначе получаем ближайшую дату события
if ($dateInput) {
$date = new Carbon($dateInput);
} else {
$nearestEvent = Event::whereDate('event_date_start', '>=', now())
->orderBy('event_date_start', 'asc')
->first();
// Проверяем, найдено ли событие
if ($nearestEvent) {
$date = new Carbon($nearestEvent->event_date_start);
} else {
// Если событий нет, устанавливаем текущую дату
$date = Carbon::now();
}
}
return [
'fullDate' => $date->format('Y-m-j'),
'day' => $date->format('j'),
'month' => $date->getTranslatedMonthName('Do MMMM'),
];
}
private function getEvents(array $currentDate)
{
return EventPreviewResource::collection(Event::select('title', 'slug', 'event_date_start', 'event_time_start', 'address', 'is_online', 'category_id')
->whereDate('event_date_start', '=', $currentDate['fullDate'])
->with('category')
->when(request()->input('is_online'), function ($query, $value) {
$this->applyOnlineFilter($query, $value);
})
->when(request()->input('search'), function ($query, $search) {
$query->whereRaw('LOWER(title) like ?', ["%".strtolower($search)."%"]);
})
->when(request()->input('category'), function ($query) {
$slugs = request()->input('category');
if (is_array($slugs)) {
$query->whereHas('category', function ($query) use ($slugs) {
$query->whereIn('slug', $slugs);
});
}
})
->orderBy('event_time_start', 'asc')
->get());
}
private function getEventsArchive()
{
return EventPreviewResource::collection(Event::select('title', 'slug', 'event_date_start', 'event_time_start', 'address', 'is_online', 'category_id')
->whereDate('event_date_start', '<', now())
->with('category')
->when(request()->input('is_online'), function ($query, $value) {
$this->applyOnlineFilter($query, $value);
})
->when(request()->input('search'), function ($query, $search) {
$query->whereRaw('LOWER(title) like ?', ["%".strtolower($search)."%"]);
})
->when(request()->input('category'), function ($query) {
$slugs = request()->input('category');
if (is_array($slugs)) {
$query->whereHas('category', function ($query) use ($slugs) {
$query->whereIn('slug', $slugs);
});
}
})
->when(request()->input('sort', 'desc'), function ($query, $sort) {
$query->orderBy('event_date_start', $sort);
})
->orderBy('event_time_start', 'asc')
->paginate(6)
->withQueryString());
}
private function getEventDates(array $filters): \Illuminate\Support\Collection
{
// Получаем события с учетом фильтров
$events = Event::select('event_date_start')
->distinct()
->where('event_date_start', '>=', date('Y-m-d'))
->when($filters['is_online_filter']['value'], function ($query, $value) {
if ($value === 'online') {
$query->where('is_online', true);
} elseif ($value === 'offline') {
$query->where('is_online', false);
}
})
->when($filters['category_filter']['value'], function ($query) {
$slugs = request()->input('category');
if (is_array($slugs)) {
$query->whereHas('category', function ($query) use ($slugs) {
$query->whereIn('slug', $slugs);
});
}
})
->orderBy('event_date_start')
->get();
// Получаем массив без ключей
// Извлекаем уникальные даты из событий
return $events->map(function ($event) {
$date = new DateTime($event->event_date_start);
return [
'day' => $date->format('j'),
'dayOfWeek' => $this->getDayOfWeekRussian($date->format('l')),
'date' => $date->format('Y-m-j')
];
})
->groupBy(function ($item) {
return (new DateTime($item['date']))->format('m');
})
->map(function ($group, $month) {
return [
"month" => $this->getMonthNameRussian((int)$month),
"events" => $group->toArray()
];
})
->sortKeys() // Сортируем ключи по возрастанию
->values();
}
private function getFilters(): array
{
$categoriesContent = [];
if (request()->input('category')) {
foreach (request()->input('category') as $item) {
$categoriesContent[$item] = new EventCategoryResource(EventCategory::where('slug', $item)->first());
}
}
return [
'search_filter' => [
'type' => 'search',
'value' => request()->input('search'),
'param' => 'search'
],
'category_filter' => [
'type' => 'category',
'value' => request()->input('category'),
'param' => 'category',
'content' => $categoriesContent,
],
'sortingBy_filter' => [
'type' => 'sort',
'value' => request()->input('sort'),
'param' => 'sort',
],
'is_online_filter' => [
'type' => 'is_online',
'value' => request()->input('is_online'),
'param' => 'is_online',
],
];
}
private function getDayOfWeekRussian(string $dayOfWeek): string
{
$russianDays = [
'Monday' => 'пн',
'Tuesday' => 'вт',
'Wednesday' => 'ср',
'Thursday' => 'чт',
'Friday' => 'пт',
'Saturday' => 'сб',
'Sunday' => 'вс'
];
return $russianDays[$dayOfWeek] ?? '';
}
private function getMonthNameRussian(int $month): string
{
$russianMonths = [
1 => 'Январь',
2 => 'Февраль',
3 => 'Март',
4 => 'Апрель',
5 => 'Май',
6 => 'Июнь',
7 => 'Июль',
8 => 'Август',
9 => 'Сентябрь',
10 => 'Октябрь',
11 => 'Ноябрь',
12 => 'Декабрь'
];
return $russianMonths[$month] ?? '';
}
private function applyOnlineFilter($query, $isOnline): void
{
if ($isOnline === 'online') {
$query->where('is_online', true);
} elseif ($isOnline === 'offline') {
$query->where('is_online', false);
}
}
}
@@ -0,0 +1,13 @@
<?php
use App\Containers\Event\UI\WEB\Controllers\ClientEventController;
use Illuminate\Support\Facades\Route;
Route::middleware('access-check')->group(function () {
Route::get('/events', [ClientEventController::class, 'index'])->name('client.event.index');
Route::get('/events/archive', [ClientEventController::class, 'archive'])->name('client.event.archive'); // Доделать builder
Route::get('/events/{slug}', [ClientEventController::class, 'show'])->name('client.event.show');
});
@@ -0,0 +1,19 @@
<?php
namespace App\Containers\Event\UI\WEB\Transformers;
use App\Ship\Resources\JsonResource;
class EventCategoryResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray($request): array
{
return parent::toArray($request);
}
}
@@ -0,0 +1,28 @@
<?php
namespace App\Containers\Event\UI\WEB\Transformers;
use App\Ship\Resources\JsonResource;
use Illuminate\Support\Carbon;
class EventPreviewResource 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,
'event_date_start' => $this->event_date_start,
'event_time_start' => Carbon::parse($this->event_time_start)->format('H:i'),
'address' => $this->address,
'is_online' => $this->is_online,
'category' => $this->category ?? null,
];
}
}
@@ -0,0 +1,30 @@
<?php
namespace App\Containers\Event\UI\WEB\Transformers;
use App\Ship\Resources\JsonResource;
use Illuminate\Support\Carbon;
class EventResource 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,
'content' => $this->content,
'event_date_start' => $this->event_date_start,
'event_time_start' => Carbon::parse($this->event_time_start)->format('H:i'),
'address' => $this->address,
'is_online' => $this->is_online,
'category' => $this->category ?? null,
];
}
}
@@ -0,0 +1,30 @@
<?php
namespace App\Containers\Event\UI\WEB\Transformers;
use App\Ship\Resources\JsonResource;
use Carbon\Carbon;
class EventThumbnailResource 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,
'is_online' => $this->is_online,
'address' => $this->address,
'event_date_start' => [
'day' => Carbon::parse($this->event_date_start)->format('d'),
'month' => Carbon::parse($this->event_date_start)->getTranslatedMonthName('Do MMMM'),
'time' => Carbon::parse($this->event_time_start)->format('H:i'),
],
];
}
}
@@ -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; // Используем имя направления как ключ
});
}
}

Some files were not shown because too many files have changed in this diff Show More