diff --git a/app/Containers/AppStructure/Models/Page.php b/app/Containers/AppStructure/Models/Page.php index bf78c2a..60d9cb3 100755 --- a/app/Containers/AppStructure/Models/Page.php +++ b/app/Containers/AppStructure/Models/Page.php @@ -14,7 +14,24 @@ class Page extends Model protected $guarded = false; - public function section() : BelongsTo + protected static function boot(): void + { + parent::boot(); + + // Auto-generate path when page is created + static::creating(function (Page $page) { + $page->generatePath(); + }); + + // Only regenerate path if slug or parent section changed + static::updating(function (Page $page) { + if ($page->isDirty(['slug', 'sub_section_id'])) { + $page->generatePath(); + } + }); + } + + public function section(): BelongsTo { return $this->belongsTo(SubSection::class, 'sub_section_id'); } @@ -28,4 +45,45 @@ class Page extends Model 'content' => 'array', 'settings' => 'array', ]; + + /** + * Generate path for the page based on slug and parent section + */ + protected function generatePath(): void + { + // Don't regenerate path for registered pages (system routes) + if ($this->exists && $this->is_registered) { + return; + } + + // Only generate path if slug is present + if (empty($this->slug)) { + return; + } + + // Get sub_section_id from model attributes + $subSectionId = $this->sub_section_id; + + if ($subSectionId === null) { + $this->path = $this->slug; + return; + } + + // Fetch subSection with mainSection relationship + $subSection = SubSection::with('mainSection')->find($subSectionId); + + if ($subSection === null) { + $this->path = $this->slug; + return; + } + + $mainSection = $subSection->mainSection; + + if ($mainSection === null) { + $this->path = $subSection->slug . '/' . $this->slug; + return; + } + + $this->path = $mainSection->slug . '/' . $subSection->slug . '/' . $this->slug; + } } diff --git a/app/Containers/Dashboard/Actions/Pages/CreatePageAction.php b/app/Containers/Dashboard/Actions/Pages/CreatePageAction.php index 76cd325..9ad9019 100644 --- a/app/Containers/Dashboard/Actions/Pages/CreatePageAction.php +++ b/app/Containers/Dashboard/Actions/Pages/CreatePageAction.php @@ -8,7 +8,6 @@ use App\Containers\Dashboard\Tasks\Content\GenerateSearchDataTask; class CreatePageAction { public function __construct( - private readonly GeneratePagePathAction $generatePagePathAction, private readonly GenerateSearchDataTask $generateSearchDataTask, ) {} @@ -20,19 +19,11 @@ class CreatePageAction */ public function run(array $data): Page { - $subSectionId = $data['sub_section_id'] ?? null; - - // Генерируем path на основе subSection - $data['path'] = $this->generatePagePathAction->run($data['slug'], $subSectionId); - // Генерируем search_data из контента if (!empty($data['content'])) { $data['search_data'] = $this->generateSearchDataTask->run($data['content']); } - // Удаляем sub_section_id — он не является полем модели Page - unset($data['sub_section_id']); - return Page::create($data); } } diff --git a/app/Containers/Dashboard/Actions/Pages/GeneratePagePathAction.php b/app/Containers/Dashboard/Actions/Pages/GeneratePagePathAction.php deleted file mode 100644 index 8165392..0000000 --- a/app/Containers/Dashboard/Actions/Pages/GeneratePagePathAction.php +++ /dev/null @@ -1,27 +0,0 @@ -find($subSectionId); - - if ($subSection === null) { - return $slug; - } - - if ($subSection->mainSection === null) { - return $subSection->slug . '/' . $slug; - } - - return $subSection->mainSection->slug . '/' . $subSection->slug . '/' . $slug; - } -} diff --git a/app/Containers/Dashboard/Actions/Pages/UpdatePageAction.php b/app/Containers/Dashboard/Actions/Pages/UpdatePageAction.php index d2fdae9..1f2ef20 100644 --- a/app/Containers/Dashboard/Actions/Pages/UpdatePageAction.php +++ b/app/Containers/Dashboard/Actions/Pages/UpdatePageAction.php @@ -8,7 +8,6 @@ use App\Containers\Dashboard\Tasks\Content\GenerateSearchDataTask; class UpdatePageAction { public function __construct( - private readonly GeneratePagePathAction $generatePagePathAction, private readonly GenerateSearchDataTask $generateSearchDataTask, ) {} @@ -26,17 +25,6 @@ class UpdatePageAction $data['search_data'] = $this->generateSearchDataTask->run($data['content']); } - // Если страница не зарегистрирована, перегенерируем path - if ($page->is_registered == false) { - $subSectionId = $data['sub_section_id'] ?? $page->sub_section_id; - $slug = $data['slug'] ?? $page->slug; - - $data['path'] = $this->generatePagePathAction->run($slug, $subSectionId); - } - - // Удаляем sub_section_id — он не является полем модели Page - unset($data['sub_section_id']); - $page->update($data); return $page->fresh(); diff --git a/app/Containers/Dashboard/Actions/Posts/DeploySiteAction.php b/app/Containers/Dashboard/Actions/Posts/DeploySiteAction.php new file mode 100644 index 0000000..30ecfed --- /dev/null +++ b/app/Containers/Dashboard/Actions/Posts/DeploySiteAction.php @@ -0,0 +1,98 @@ + bool, 'message' => string] + */ + public function run(): array + { + try { + // Проверяем, не запущен ли уже деплой + if (file_exists($this->lockFile)) { + $lockContent = file_get_contents($this->lockFile); + $lockData = json_decode($lockContent, true); + + if ($lockData && isset($lockData['status']) && $lockData['status'] === 'running') { + return [ + 'success' => false, + 'message' => 'Деплой уже запущен! Подождите завершения текущего процесса.', + ]; + } + } + + // Создаём файл-триггер + $lockData = [ + 'status' => 'pending', + 'started_at' => now()->toDateTimeString(), + 'started_by' => auth()->id(), + ]; + + file_put_contents($this->lockFile, json_encode($lockData, JSON_PRETTY_PRINT)); + + Log::info('Deploy triggered', ['user_id' => auth()->id()]); + + return [ + 'success' => true, + 'message' => 'Деплой запущен! Процесс обновления сайта начнётся в течение 1 минуты.', + ]; + } catch (\Exception $e) { + Log::error('Deploy trigger failed', ['exception' => $e->getMessage()]); + + return [ + 'success' => false, + 'message' => 'Ошибка при запуске деплоя: ' . $e->getMessage(), + ]; + } + } + + /** + * Проверяет статус деплоя + */ + public function getStatus(): array + { + if (!file_exists($this->lockFile)) { + return ['status' => 'idle', 'message' => 'Деплой не запущен']; + } + + $content = file_get_contents($this->lockFile); + $data = json_decode($content, true); + + if (!$data) { + return ['status' => 'error', 'message' => 'Ошибка чтения статуса']; + } + + // Читаем лог если есть + $log = ''; + if (file_exists($this->logFile)) { + $log = file_get_contents($this->logFile); + } + + return [ + 'status' => $data['status'] ?? 'unknown', + 'started_at' => $data['started_at'] ?? null, + 'completed_at' => $data['completed_at'] ?? null, + 'message' => $data['message'] ?? '', + 'log' => $log, + ]; + } + + /** + * Очищает статус деплоя + */ + public function clearStatus(): void + { + if (file_exists($this->lockFile)) { + unlink($this->lockFile); + } + } +} diff --git a/app/Containers/Dashboard/UI/WEB/Controllers/DeployController.php b/app/Containers/Dashboard/UI/WEB/Controllers/DeployController.php new file mode 100644 index 0000000..8884d8e --- /dev/null +++ b/app/Containers/Dashboard/UI/WEB/Controllers/DeployController.php @@ -0,0 +1,77 @@ +environment() !== 'production') { + return response()->json([ + 'success' => false, + 'message' => 'Деплой доступен только на production', + ], 403); + } + + if (!$request->user()->hasRole('admin')) { + return response()->json([ + 'success' => false, + 'message' => 'Недостаточно прав для выполнения этой операции', + ], 403); + } + + $result = $this->deploySiteAction->run(); + + return response()->json($result); + } + + /** + * Проверяет статус деплоя + */ + public function status(Request $request): JsonResponse + { + if (app()->environment() !== 'production') { + return response()->json(['status' => 'disabled'], 403); + } + + if (!$request->user()->hasRole('admin')) { + return response()->json([ + 'success' => false, + 'message' => 'Недостаточно прав', + ], 403); + } + + $status = $this->deploySiteAction->getStatus(); + + return response()->json($status); + } + + /** + * Очищает статус деплоя + */ + public function clear(Request $request): JsonResponse + { + if (!$request->user()->hasRole('admin')) { + return response()->json([ + 'success' => false, + 'message' => 'Недостаточно прав', + ], 403); + } + + $this->deploySiteAction->clearStatus(); + + return response()->json(['success' => true]); + } +} diff --git a/app/Containers/Dashboard/UI/WEB/Routes/web.php b/app/Containers/Dashboard/UI/WEB/Routes/web.php index fc81e6a..f94387f 100755 --- a/app/Containers/Dashboard/UI/WEB/Routes/web.php +++ b/app/Containers/Dashboard/UI/WEB/Routes/web.php @@ -10,6 +10,7 @@ use App\Containers\Dashboard\UI\WEB\Controllers\CategoryController as NewsCatego use App\Containers\Dashboard\UI\WEB\Controllers\ContactWidgetController; use App\Containers\Dashboard\UI\WEB\Controllers\CreateSliderController; use App\Containers\Dashboard\UI\WEB\Controllers\CustomFormController; +use App\Containers\Dashboard\UI\WEB\Controllers\DeployController; use App\Containers\Dashboard\UI\WEB\Controllers\DepartmentController; use App\Containers\Dashboard\UI\WEB\Controllers\PageReferenceListController; use App\Containers\Dashboard\UI\WEB\Controllers\DepartmentProgramController; @@ -60,6 +61,9 @@ Route::post('/dashboard/logout', [AuthenticatedSessionController::class, 'destro // Authenticated dashboard routes Route::middleware(['access-check', 'dashboard.auth'])->group(function () { Route::get('/dashboard', IndexDashboardController::class)->name('dashboard.index'); + Route::post('/dashboard/deploy', [DeployController::class, 'deploy'])->name('dashboard.deploy'); + Route::get('/dashboard/deploy/status', [DeployController::class, 'status'])->name('dashboard.deploy.status'); + Route::post('/dashboard/deploy/clear', [DeployController::class, 'clear'])->name('dashboard.deploy.clear'); // CRUD постов Route::prefix('/dashboard/posts')->name('dashboard.posts.')->group(function () { diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Http/Middleware/HandleInertiaRequests.php index c766982..3c76b92 100755 --- a/app/Http/Middleware/HandleInertiaRequests.php +++ b/app/Http/Middleware/HandleInertiaRequests.php @@ -28,6 +28,9 @@ class HandleInertiaRequests extends Middleware return [ ...parent::share($request), + 'app' => [ + 'env' => app()->environment(), + ], 'auth' => [ 'user' => $request->user() ? $request->user()->only('id', 'name', 'email', 'created_at') : null, ], diff --git a/app/Ship/Middleware/HandleInertiaRequests.php b/app/Ship/Middleware/HandleInertiaRequests.php index 49cea60..511a1d4 100755 --- a/app/Ship/Middleware/HandleInertiaRequests.php +++ b/app/Ship/Middleware/HandleInertiaRequests.php @@ -29,6 +29,9 @@ class HandleInertiaRequests extends Middleware return [ ...parent::share($request), + 'app' => [ + 'env' => app()->environment(), + ], 'auth' => [ 'user' => $request->user() ? $request->user()->only('id', 'name', 'email', 'created_at') : null, ], diff --git a/resources/js/Pages/Dashboard/Components/ContentBuilder/blocks/TabBlock.vue b/resources/js/Pages/Dashboard/Components/ContentBuilder/blocks/TabBlock.vue index 6bc27ee..3e226ea 100644 --- a/resources/js/Pages/Dashboard/Components/ContentBuilder/blocks/TabBlock.vue +++ b/resources/js/Pages/Dashboard/Components/ContentBuilder/blocks/TabBlock.vue @@ -4,7 +4,7 @@
Запуск скрипта деплоя и пересборка сервера
+{{ deployOutput }}
+ Заполните информацию о новой странице
+ +Заполните информацию о новой странице
+{{ page.title }}
+ +{{ page.title }}
+