From 1485180e20fe228d90cde00f78a0561757fb634f Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Thu, 2 May 2024 23:59:38 +0500 Subject: [PATCH 01/13] Updated the captcha service to 0.8.1. --- .env.example | 1 + captcha-app/.env.example | 24 +++++++++++++++++++++++ docker-compose-prod.yml | 31 +++++++++++++++++++++++++++++- docker-compose-prod_docker-hub.yml | 31 +++++++++++++++++++++++++++++- docker-compose.yml | 31 +++++++++++++++++++++++++++++- 5 files changed, 115 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index 054a452..e8286b0 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,6 @@ DOCKER_APP_PORT=8080 DOCKER_CAPTCHA_PORT=8081 +DOCKER_CAPTCHA_WEBSOCKET_PORT=8082 DOCKER_DB_PORT=3306 MYSQL_ROOT_PASSWORD=root_pass DB_DATABASE=my-projetcs diff --git a/captcha-app/.env.example b/captcha-app/.env.example index df7cfa3..cac3e0f 100644 --- a/captcha-app/.env.example +++ b/captcha-app/.env.example @@ -7,6 +7,12 @@ APP_FORCE_HTTPS=false CAPTCHA_VERIFICATION_DATA_VIEW_LIMIT_IN_MINUTES=1 +APP_CAPTCHA=false +CAPTCHA_API_DOMAIN=http://your-domain-captcha-or-IP:8081 +CAPTCHA_PRIVATE_TOKEN= +CAPTCHA_STATIC_PATH=http://your-domain-captcha-or-IP:8081/captcha +CAPTCHA_PUBLIC_TOKEN= + LOGIN_MAX_REQUEST=20 LOGIN_MAX_EMAIL_REQUEST=10 @@ -70,3 +76,21 @@ VITE_PUSHER_HOST="${PUSHER_HOST}" VITE_PUSHER_PORT="${PUSHER_PORT}" VITE_PUSHER_SCHEME="${PUSHER_SCHEME}" VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" + +REVERB_APP_ID= +REVERB_APP_KEY= +REVERB_APP_SECRET= +REVERB_HOST="captcha-reverb" +REVERB_PORT=9000 +REVERB_SCHEME=http +# * or localhost.com or localhost.com, localhost.net +REVERB_ALLOWED_ORIGINS="*" + +REVERB_HOST_CLIENT="localhost" +REVERB_PORT_CLIENT=8081 +REVERB_SCHEME_CLIENT=http + +VITE_REVERB_APP_KEY="${REVERB_APP_KEY}" +VITE_REVERB_HOST="${REVERB_HOST}" +VITE_REVERB_PORT="${REVERB_PORT}" +VITE_REVERB_SCHEME="${REVERB_SCHEME}" diff --git a/docker-compose-prod.yml b/docker-compose-prod.yml index 4240172..05ef617 100644 --- a/docker-compose-prod.yml +++ b/docker-compose-prod.yml @@ -19,7 +19,7 @@ services: # restart: always captcha-app: - image: korelf/service-captcha:0.7.1 + image: korelf/service-captcha:0.8.1 # restart: always cap_drop: - ALL @@ -34,6 +34,35 @@ services: env_file: captcha-app/.env ports: - ${DOCKER_CAPTCHA_PORT}:9000 + captcha-queue: + image: korelf/service-captcha:0.8.1 + # restart: always + depends_on: + - db + - captcha-redis + environment: + CONTAINER_ROLE: queue + env_file: captcha-app/.env + captcha-reverb: + image: korelf/service-captcha:0.8.1 + # restart: always + depends_on: + - db + - captcha-redis + environment: + CONTAINER_ROLE: websockets + env_file: captcha-app/.env + ports: + - ${DOCKER_CAPTCHA_WEBSOCKET_PORT}:9000 + captcha-scheduler: + image: korelf/service-captcha:0.8.1 + # restart: always + depends_on: + - db + - captcha-redis + environment: + CONTAINER_ROLE: scheduler + env_file: captcha-app/.env captcha-redis: image: redis:3.0-alpine # restart: always diff --git a/docker-compose-prod_docker-hub.yml b/docker-compose-prod_docker-hub.yml index 2713baf..8f9f639 100644 --- a/docker-compose-prod_docker-hub.yml +++ b/docker-compose-prod_docker-hub.yml @@ -18,7 +18,7 @@ services: # restart: always captcha-app: - image: korelf/service-captcha:0.7.1 + image: korelf/service-captcha:0.8.1 # restart: always cap_drop: - ALL @@ -33,6 +33,35 @@ services: env_file: captcha-app/.env ports: - ${DOCKER_CAPTCHA_PORT}:9000 + captcha-queue: + image: korelf/service-captcha:0.8.1 + # restart: always + depends_on: + - db + - captcha-redis + environment: + CONTAINER_ROLE: queue + env_file: captcha-app/.env + captcha-reverb: + image: korelf/service-captcha:0.8.1 + # restart: always + depends_on: + - db + - captcha-redis + environment: + CONTAINER_ROLE: websockets + env_file: captcha-app/.env + ports: + - ${DOCKER_CAPTCHA_WEBSOCKET_PORT}:9000 + captcha-scheduler: + image: korelf/service-captcha:0.8.1 + # restart: always + depends_on: + - db + - captcha-redis + environment: + CONTAINER_ROLE: scheduler + env_file: captcha-app/.env captcha-redis: image: redis:3.0-alpine # restart: always diff --git a/docker-compose.yml b/docker-compose.yml index 6045f4d..5b39030 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,7 +17,7 @@ services: image: redis:3.0-alpine captcha-app: - image: korelf/service-captcha:0.7.0 + image: korelf/service-captcha:0.8.1 cap_drop: - ALL cap_add: @@ -31,6 +31,35 @@ services: env_file: captcha-app/.env ports: - ${DOCKER_CAPTCHA_PORT}:9000 + captcha-queue: + image: korelf/service-captcha:0.8.1 + # restart: always + depends_on: + - db + - captcha-redis + environment: + CONTAINER_ROLE: queue + env_file: captcha-app/.env + captcha-reverb: + image: korelf/service-captcha:0.8.1 + # restart: always + depends_on: + - db + - captcha-redis + environment: + CONTAINER_ROLE: websockets + env_file: captcha-app/.env + ports: + - ${DOCKER_CAPTCHA_WEBSOCKET_PORT}:9000 + captcha-scheduler: + image: korelf/service-captcha:0.8.1 + # restart: always + depends_on: + - db + - captcha-redis + environment: + CONTAINER_ROLE: scheduler + env_file: captcha-app/.env captcha-redis: image: redis:3.0-alpine db: From 303af6f20320830f167a07925e4380a174c63d1d Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Fri, 3 May 2024 00:00:18 +0500 Subject: [PATCH 02/13] Fixed menu width. --- app/application/resources/site/scss/app.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/application/resources/site/scss/app.scss b/app/application/resources/site/scss/app.scss index 8842c1b..e1378e1 100644 --- a/app/application/resources/site/scss/app.scss +++ b/app/application/resources/site/scss/app.scss @@ -242,6 +242,9 @@ body.mobile-menu-open { #mobile-menu { display: none; } + .wrapper .section-container { + flex: auto; + } .main-container { flex-direction: row; } @@ -255,6 +258,7 @@ body.mobile-menu-open { padding: 7px 10px 7px 0; } #menu { + flex: none; background: #eee; display: block; width: 175px; From 35c2368ae2e7c3e02670ed6896edf149ecbd5d77 Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Fri, 3 May 2024 00:01:39 +0500 Subject: [PATCH 03/13] In the admin panel in the feedback section I added a date. --- .../resources/views/admin/projects/feedbacks/index.blade.php | 4 ++++ .../views/admin/projects/feedbacks/project.blade.php | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/app/application/resources/views/admin/projects/feedbacks/index.blade.php b/app/application/resources/views/admin/projects/feedbacks/index.blade.php index 41a62d2..366fd74 100644 --- a/app/application/resources/views/admin/projects/feedbacks/index.blade.php +++ b/app/application/resources/views/admin/projects/feedbacks/index.blade.php @@ -8,6 +8,7 @@ {{ __('admin-sections.Project') }} + {{ __('validation.attributes.date') }} {{ __('site.attributes.message') }} {{ __('site.attributes.name') }} {{ __('site.attributes.email') }} @@ -17,6 +18,9 @@ @foreach($feedbacks as $feedback) {{ $feedback->project->name }} + + {{ $feedback->created_at->diffForHumans() }} + {!! $feedback->message !!}

{{ $feedback->name }}

diff --git a/app/application/resources/views/admin/projects/feedbacks/project.blade.php b/app/application/resources/views/admin/projects/feedbacks/project.blade.php index 9b7a4b5..871056e 100644 --- a/app/application/resources/views/admin/projects/feedbacks/project.blade.php +++ b/app/application/resources/views/admin/projects/feedbacks/project.blade.php @@ -8,6 +8,7 @@ + @@ -16,6 +17,9 @@ @foreach($feedbacks as $feedback) + + @can('viewAny', \App\Models\DocumentationVersion::class) + + + + @endcan @can('viewAny', \App\Models\ProjectContent::class)
{{ __('validation.attributes.date') }} {{ __('site.attributes.message') }} {{ __('site.attributes.name') }} {{ __('site.attributes.email') }}
+ {{ $feedback->created_at->diffForHumans() }} + {!! $feedback->message !!}

{{ $feedback->name }}

From 478cbe47628d93490cdaf6cc8868b26318a17074 Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Fri, 3 May 2024 00:03:35 +0500 Subject: [PATCH 04/13] Refactoring. --- .../app/Http/Controllers/Site/FeedbackController.php | 1 - .../app/Http/Controllers/Site/ProjectsController.php | 4 ++-- app/application/app/Http/Middleware/ProjectAndLanguage.php | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/application/app/Http/Controllers/Site/FeedbackController.php b/app/application/app/Http/Controllers/Site/FeedbackController.php index 5448daa..123e577 100644 --- a/app/application/app/Http/Controllers/Site/FeedbackController.php +++ b/app/application/app/Http/Controllers/Site/FeedbackController.php @@ -3,7 +3,6 @@ namespace App\Http\Controllers\Site; use App\Http\Requests\Site\Feedback\SendRequest; -use App\Models\ProjectFeedback; use App\Services\Site\FeedbackService; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; diff --git a/app/application/app/Http/Controllers/Site/ProjectsController.php b/app/application/app/Http/Controllers/Site/ProjectsController.php index cd227ac..8db8737 100644 --- a/app/application/app/Http/Controllers/Site/ProjectsController.php +++ b/app/application/app/Http/Controllers/Site/ProjectsController.php @@ -16,7 +16,6 @@ public function index(Request $request): View { $user = $request->user(); $project = $request->get('project'); - $websiteTranslations = $request->get('websiteTranslations'); if (\is_null($project)) { $with = ['storage']; @@ -28,7 +27,8 @@ public function index(Request $request): View return \view('site.projects.index', $result->getData()); } - $result = $this->projectService->getAboutByProject($project, $websiteTranslations, $request->user()); + $websiteTranslations = $request->get('websiteTranslations'); + $result = $this->projectService->getAboutByProject($project, $websiteTranslations, $user); if ($result->isError()) { $this->errors($result); } diff --git a/app/application/app/Http/Middleware/ProjectAndLanguage.php b/app/application/app/Http/Middleware/ProjectAndLanguage.php index 15215af..36a7a63 100644 --- a/app/application/app/Http/Middleware/ProjectAndLanguage.php +++ b/app/application/app/Http/Middleware/ProjectAndLanguage.php @@ -7,7 +7,7 @@ use Symfony\Component\HttpFoundation\Response; use Closure; -class ProjectAndLanguage extends ProjectLanguage +final class ProjectAndLanguage extends ProjectLanguage { public function handle(Request $request, Closure $next): Response { From 156d8a9f68c1559d21152382592d3129aeb15722 Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Fri, 3 May 2024 00:06:59 +0500 Subject: [PATCH 05/13] Added middleware IsWebsiteTranslations and IsProject. This fixes errors where Project or WebsiteTranslations are not found. --- .../app/Http/Middleware/IsProject.php | 25 +++++++++++++++++++ .../Http/Middleware/IsWebsiteTranslations.php | 25 +++++++++++++++++++ app/application/routes/web-project.php | 16 ++++++------ 3 files changed, 59 insertions(+), 7 deletions(-) create mode 100644 app/application/app/Http/Middleware/IsProject.php create mode 100644 app/application/app/Http/Middleware/IsWebsiteTranslations.php diff --git a/app/application/app/Http/Middleware/IsProject.php b/app/application/app/Http/Middleware/IsProject.php new file mode 100644 index 0000000..a4b701f --- /dev/null +++ b/app/application/app/Http/Middleware/IsProject.php @@ -0,0 +1,25 @@ +get('project'); + if (\is_null($project)) { + \abort(Response::HTTP_NOT_FOUND); + } + + if ($project instanceof Project === false) { + \report("$project must be an instance of Project"); + \abort(Response::HTTP_NOT_FOUND); + } + + return $next($request); + } +} diff --git a/app/application/app/Http/Middleware/IsWebsiteTranslations.php b/app/application/app/Http/Middleware/IsWebsiteTranslations.php new file mode 100644 index 0000000..261af69 --- /dev/null +++ b/app/application/app/Http/Middleware/IsWebsiteTranslations.php @@ -0,0 +1,25 @@ +get('websiteTranslations'); + if (\is_null($websiteTranslations)) { + \abort(Response::HTTP_NOT_FOUND); + } + + if ($websiteTranslations instanceof WebsiteTranslations === false) { + \report("$websiteTranslations must be an instance of WebsiteTranslations"); + \abort(Response::HTTP_NOT_FOUND); + } + + return $next($request); + } +} diff --git a/app/application/routes/web-project.php b/app/application/routes/web-project.php index bd15ea9..330eff2 100644 --- a/app/application/routes/web-project.php +++ b/app/application/routes/web-project.php @@ -3,11 +3,13 @@ use Illuminate\Support\Facades\Route; Route::get('/', [\App\Http\Controllers\Site\ProjectsController::class, 'index'])->name('home'); -Route::get('/language/{language}', [\App\Http\Controllers\Site\ProjectsController::class, 'index'])->name('home-language'); +Route::middleware([\App\Http\Middleware\IsProject::class, \App\Http\Middleware\IsWebsiteTranslations::class])->group(function () { + Route::get('/language/{language}', [\App\Http\Controllers\Site\ProjectsController::class, 'index'])->name('home-language'); -Route::get('feedback', [\App\Http\Controllers\Site\FeedbackController::class, 'index'])->name('feedback'); -Route::get('feedback/language/{language}', [\App\Http\Controllers\Site\FeedbackController::class, 'index'])->name('feedback-language'); -Route::get('feedback/success', [\App\Http\Controllers\Site\FeedbackController::class, 'index'])->name('feedback.success'); -Route::get('feedback/success/language/{language}', [\App\Http\Controllers\Site\FeedbackController::class, 'index'])->name('feedback.success-language'); -Route::post('feedback', [\App\Http\Controllers\Site\FeedbackController::class, 'send'])->name('feedback.send'); -Route::post('feedback/language/{language}', [\App\Http\Controllers\Site\FeedbackController::class, 'send'])->name('feedback.send-language'); + Route::prefix('feedback')->group(function () { + Route::get('/', [\App\Http\Controllers\Site\FeedbackController::class, 'index'])->name('feedback'); + Route::get('language/{language}', [\App\Http\Controllers\Site\FeedbackController::class, 'index'])->name('feedback-language'); + Route::post('/', [\App\Http\Controllers\Site\FeedbackController::class, 'send'])->name('feedback.send'); + Route::post('language/{language}', [\App\Http\Controllers\Site\FeedbackController::class, 'send'])->name('feedback.send-language'); + }); +}); From 45504791c01c2e0baf66d9901cbe956d79cea59d Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Fri, 17 May 2024 21:05:13 +0500 Subject: [PATCH 06/13] A documentation section has been added to the admin panel. --- .../app/Dto/Builder/Documentation.php | 15 + .../app/Dto/Builder/DocumentationCategory.php | 15 + .../app/Dto/Builder/DocumentationVersion.php | 15 + .../Admin/Project/Documentation/Index.php | 21 ++ .../Project/Documentation/StoreUpdate.php | 42 +++ .../Project/DocumentationCategory/Index.php | 21 ++ .../DocumentationCategory/StoreUpdate.php | 42 +++ .../DocumentationCategoryContent/Content.php | 21 ++ .../DocumentationCategoryContent/Contents.php | 23 ++ .../Project/DocumentationContent/Content.php | 27 ++ .../Project/DocumentationContent/Contents.php | 23 ++ .../Project/DocumentationVersion/Index.php | 21 ++ .../DocumentationVersion/StoreUpdate.php | 36 +++ .../app/Enums/DocumentationVersionStatus.php | 36 +++ app/application/app/Enums/Permission.php | 2 + .../DocumentationCategory/ParentException.php | 8 + .../ContentSaveException.php | 8 + .../ContentSaveException.php | 8 + .../Exceptions/Services/ServiceException.php | 19 ++ .../DocumentationCategoriesController.php | 105 +++++++ .../DocumentationVersionController.php | 111 ++++++++ .../Projects/DocumentationsController.php | 105 +++++++ .../DocumentVersions/IndexRequest.php | 29 ++ .../DocumentVersions/StoreUpdateRequest.php | 35 +++ .../DocumentationCategories/IndexRequest.php | 29 ++ .../StoreUpdateRequest.php | 54 ++++ .../Projects/Documentations/IndexRequest.php | 29 ++ .../Documentations/StoreUpdateRequest.php | 56 ++++ app/application/app/Models/Documentation.php | 65 +++++ .../app/Models/DocumentationCategory.php | 75 +++++ .../Models/DocumentationCategoryContent.php | 24 ++ .../app/Models/DocumentationContent.php | 25 ++ .../app/Models/DocumentationVersion.php | 67 +++++ app/application/app/Models/Project.php | 5 + .../Policies/DocumentationCategoryPolicy.php | 34 +++ .../app/Policies/DocumentationPolicy.php | 34 +++ .../Policies/DocumentationVersionPolicy.php | 39 +++ .../DocumentationCategoryRepository.php | 86 ++++++ .../Repositories/DocumentationRepository.php | 51 ++++ .../DocumentationVersionRepository.php | 51 ++++ .../Project/DocumentationCategoryService.php | 257 ++++++++++++++++++ .../Admin/Project/DocumentationService.php | 239 ++++++++++++++++ .../Project/DocumentationVersionService.php | 222 +++++++++++++++ .../Services/Documentation/BuilderCommand.php | 19 ++ .../DocumentationCommandHandler.php | 36 +++ .../DocumentationCategory/BuilderCommand.php | 19 ++ .../DocumentationCategoryCommandHandler.php | 44 +++ .../ModelSyncCommand.php | 46 ++++ .../DocumentationContent/ModelSyncCommand.php | 47 ++++ .../DocumentationVersion/BuilderCommand.php | 19 ++ .../DocumentationVersionCommandHandler.php | 37 +++ .../View/Components/Volt/Forms/Checkbox.php | 4 +- .../app/View/Components/Volt/Forms/Input.php | 7 + app/application/composer.json | 3 +- app/application/composer.lock | 170 +++++++++++- ...2024_04_30_114847_create_documentation.php | 124 +++++++++ app/application/lang/en.json | 13 +- app/application/lang/en/admin-sections.php | 3 + app/application/lang/en/permissions.php | 2 + app/application/lang/en/validation.php | 6 + app/application/lang/en/version-status.php | 7 + app/application/lang/ru.json | 13 +- app/application/lang/ru/admin-sections.php | 3 + app/application/lang/ru/permissions.php | 2 + app/application/lang/ru/validation.php | 6 + app/application/lang/ru/version-status.php | 7 + app/application/package-lock.json | 9 + app/application/package.json | 1 + app/application/resources/prism/app.js | 32 +++ .../resources/views/_prism.blade.php | 1 + .../_scripts/_click-content-enable.blade.php | 33 +++ .../documentation-categories/_from.blade.php | 34 +++ .../documentation-categories/_top.blade.php | 25 ++ .../documentation-categories/create.blade.php | 19 ++ .../documentation-categories/edit.blade.php | 20 ++ .../documentation-categories/index.blade.php | 67 +++++ .../documentation-versions/_from.blade.php | 10 + .../documentation-versions/_top.blade.php | 18 ++ .../documentation-versions/create.blade.php | 17 ++ .../documentation-versions/edit.blade.php | 18 ++ .../documentation-versions/index.blade.php | 74 +++++ .../documentation-versions/show.blade.php | 45 +++ .../projects/documentations/_from.blade.php | 35 +++ .../projects/documentations/_top.blade.php | 25 ++ .../projects/documentations/create.blade.php | 19 ++ .../projects/documentations/edit.blade.php | 20 ++ .../projects/documentations/index.blade.php | 67 +++++ .../views/admin/projects/show.blade.php | 12 + .../components/volt/forms/checkbox.blade.php | 2 +- .../components/volt/forms/input.blade.php | 7 +- .../volt/forms/textarea-wysiwyg.blade.php | 21 +- app/application/routes/web.php | 5 + app/application/vite.config.js | 2 + 93 files changed, 3495 insertions(+), 10 deletions(-) create mode 100644 app/application/app/Dto/Builder/Documentation.php create mode 100644 app/application/app/Dto/Builder/DocumentationCategory.php create mode 100644 app/application/app/Dto/Builder/DocumentationVersion.php create mode 100644 app/application/app/Dto/Service/Admin/Project/Documentation/Index.php create mode 100644 app/application/app/Dto/Service/Admin/Project/Documentation/StoreUpdate.php create mode 100644 app/application/app/Dto/Service/Admin/Project/DocumentationCategory/Index.php create mode 100644 app/application/app/Dto/Service/Admin/Project/DocumentationCategory/StoreUpdate.php create mode 100644 app/application/app/Dto/Service/Admin/Project/DocumentationCategoryContent/Content.php create mode 100644 app/application/app/Dto/Service/Admin/Project/DocumentationCategoryContent/Contents.php create mode 100644 app/application/app/Dto/Service/Admin/Project/DocumentationContent/Content.php create mode 100644 app/application/app/Dto/Service/Admin/Project/DocumentationContent/Contents.php create mode 100644 app/application/app/Dto/Service/Admin/Project/DocumentationVersion/Index.php create mode 100644 app/application/app/Dto/Service/Admin/Project/DocumentationVersion/StoreUpdate.php create mode 100644 app/application/app/Enums/DocumentationVersionStatus.php create mode 100644 app/application/app/Exceptions/Services/DocumentationCategory/ParentException.php create mode 100644 app/application/app/Exceptions/Services/DocumentationCategoryContent/ContentSaveException.php create mode 100644 app/application/app/Exceptions/Services/DocumentationContent/ContentSaveException.php create mode 100644 app/application/app/Exceptions/Services/ServiceException.php create mode 100644 app/application/app/Http/Controllers/Admin/Projects/DocumentationCategoriesController.php create mode 100644 app/application/app/Http/Controllers/Admin/Projects/DocumentationVersionController.php create mode 100644 app/application/app/Http/Controllers/Admin/Projects/DocumentationsController.php create mode 100644 app/application/app/Http/Requests/Admin/Projects/DocumentVersions/IndexRequest.php create mode 100644 app/application/app/Http/Requests/Admin/Projects/DocumentVersions/StoreUpdateRequest.php create mode 100644 app/application/app/Http/Requests/Admin/Projects/DocumentationCategories/IndexRequest.php create mode 100644 app/application/app/Http/Requests/Admin/Projects/DocumentationCategories/StoreUpdateRequest.php create mode 100644 app/application/app/Http/Requests/Admin/Projects/Documentations/IndexRequest.php create mode 100644 app/application/app/Http/Requests/Admin/Projects/Documentations/StoreUpdateRequest.php create mode 100644 app/application/app/Models/Documentation.php create mode 100644 app/application/app/Models/DocumentationCategory.php create mode 100644 app/application/app/Models/DocumentationCategoryContent.php create mode 100644 app/application/app/Models/DocumentationContent.php create mode 100644 app/application/app/Models/DocumentationVersion.php create mode 100644 app/application/app/Policies/DocumentationCategoryPolicy.php create mode 100644 app/application/app/Policies/DocumentationPolicy.php create mode 100644 app/application/app/Policies/DocumentationVersionPolicy.php create mode 100644 app/application/app/Repositories/DocumentationCategoryRepository.php create mode 100644 app/application/app/Repositories/DocumentationRepository.php create mode 100644 app/application/app/Repositories/DocumentationVersionRepository.php create mode 100644 app/application/app/Services/Admin/Project/DocumentationCategoryService.php create mode 100644 app/application/app/Services/Admin/Project/DocumentationService.php create mode 100644 app/application/app/Services/Admin/Project/DocumentationVersionService.php create mode 100644 app/application/app/Services/Documentation/BuilderCommand.php create mode 100644 app/application/app/Services/Documentation/DocumentationCommandHandler.php create mode 100644 app/application/app/Services/DocumentationCategory/BuilderCommand.php create mode 100644 app/application/app/Services/DocumentationCategory/DocumentationCategoryCommandHandler.php create mode 100644 app/application/app/Services/DocumentationCategoryContent/ModelSyncCommand.php create mode 100644 app/application/app/Services/DocumentationContent/ModelSyncCommand.php create mode 100644 app/application/app/Services/DocumentationVersion/BuilderCommand.php create mode 100644 app/application/app/Services/DocumentationVersion/DocumentationVersionCommandHandler.php create mode 100644 app/application/database/migrations/2024_04_30_114847_create_documentation.php create mode 100644 app/application/lang/en/version-status.php create mode 100644 app/application/lang/ru/version-status.php create mode 100644 app/application/resources/prism/app.js create mode 100644 app/application/resources/views/_prism.blade.php create mode 100644 app/application/resources/views/admin/_scripts/_click-content-enable.blade.php create mode 100644 app/application/resources/views/admin/projects/documentation-categories/_from.blade.php create mode 100644 app/application/resources/views/admin/projects/documentation-categories/_top.blade.php create mode 100644 app/application/resources/views/admin/projects/documentation-categories/create.blade.php create mode 100644 app/application/resources/views/admin/projects/documentation-categories/edit.blade.php create mode 100644 app/application/resources/views/admin/projects/documentation-categories/index.blade.php create mode 100644 app/application/resources/views/admin/projects/documentation-versions/_from.blade.php create mode 100644 app/application/resources/views/admin/projects/documentation-versions/_top.blade.php create mode 100644 app/application/resources/views/admin/projects/documentation-versions/create.blade.php create mode 100644 app/application/resources/views/admin/projects/documentation-versions/edit.blade.php create mode 100644 app/application/resources/views/admin/projects/documentation-versions/index.blade.php create mode 100644 app/application/resources/views/admin/projects/documentation-versions/show.blade.php create mode 100644 app/application/resources/views/admin/projects/documentations/_from.blade.php create mode 100644 app/application/resources/views/admin/projects/documentations/_top.blade.php create mode 100644 app/application/resources/views/admin/projects/documentations/create.blade.php create mode 100644 app/application/resources/views/admin/projects/documentations/edit.blade.php create mode 100644 app/application/resources/views/admin/projects/documentations/index.blade.php diff --git a/app/application/app/Dto/Builder/Documentation.php b/app/application/app/Dto/Builder/Documentation.php new file mode 100644 index 0000000..fdec08d --- /dev/null +++ b/app/application/app/Dto/Builder/Documentation.php @@ -0,0 +1,15 @@ +isPublic; + } +} diff --git a/app/application/app/Dto/Builder/DocumentationCategory.php b/app/application/app/Dto/Builder/DocumentationCategory.php new file mode 100644 index 0000000..242a20c --- /dev/null +++ b/app/application/app/Dto/Builder/DocumentationCategory.php @@ -0,0 +1,15 @@ +isPublic; + } +} diff --git a/app/application/app/Dto/Builder/DocumentationVersion.php b/app/application/app/Dto/Builder/DocumentationVersion.php new file mode 100644 index 0000000..e366aa3 --- /dev/null +++ b/app/application/app/Dto/Builder/DocumentationVersion.php @@ -0,0 +1,15 @@ +isPublic; + } +} diff --git a/app/application/app/Dto/Service/Admin/Project/Documentation/Index.php b/app/application/app/Dto/Service/Admin/Project/Documentation/Index.php new file mode 100644 index 0000000..4570b8e --- /dev/null +++ b/app/application/app/Dto/Service/Admin/Project/Documentation/Index.php @@ -0,0 +1,21 @@ +documentation; + } +} diff --git a/app/application/app/Dto/Service/Admin/Project/Documentation/StoreUpdate.php b/app/application/app/Dto/Service/Admin/Project/Documentation/StoreUpdate.php new file mode 100644 index 0000000..a34d4d6 --- /dev/null +++ b/app/application/app/Dto/Service/Admin/Project/Documentation/StoreUpdate.php @@ -0,0 +1,42 @@ +slug; + } + + public function isPublic(): bool + { + return $this->isPublic; + } + + public function getCategoryId(): ?int + { + return $this->categoryId; + } + + public function getSort(): int + { + return $this->sort; + } + + public function getContents(): Contents + { + return $this->contents; + } +} diff --git a/app/application/app/Dto/Service/Admin/Project/DocumentationCategory/Index.php b/app/application/app/Dto/Service/Admin/Project/DocumentationCategory/Index.php new file mode 100644 index 0000000..c118bae --- /dev/null +++ b/app/application/app/Dto/Service/Admin/Project/DocumentationCategory/Index.php @@ -0,0 +1,21 @@ +documentationCategory; + } +} diff --git a/app/application/app/Dto/Service/Admin/Project/DocumentationCategory/StoreUpdate.php b/app/application/app/Dto/Service/Admin/Project/DocumentationCategory/StoreUpdate.php new file mode 100644 index 0000000..e9866cf --- /dev/null +++ b/app/application/app/Dto/Service/Admin/Project/DocumentationCategory/StoreUpdate.php @@ -0,0 +1,42 @@ +slug; + } + + public function isPublic(): bool + { + return $this->isPublic; + } + + public function getSort(): int + { + return $this->sort; + } + + public function getParentId(): ?int + { + return $this->parentId; + } + + public function getContents(): Contents + { + return $this->contents; + } +} diff --git a/app/application/app/Dto/Service/Admin/Project/DocumentationCategoryContent/Content.php b/app/application/app/Dto/Service/Admin/Project/DocumentationCategoryContent/Content.php new file mode 100644 index 0000000..91b2763 --- /dev/null +++ b/app/application/app/Dto/Service/Admin/Project/DocumentationCategoryContent/Content.php @@ -0,0 +1,21 @@ +languageId; + } + + public function getTitle(): string + { + return $this->title; + } +} diff --git a/app/application/app/Dto/Service/Admin/Project/DocumentationCategoryContent/Contents.php b/app/application/app/Dto/Service/Admin/Project/DocumentationCategoryContent/Contents.php new file mode 100644 index 0000000..7d76533 --- /dev/null +++ b/app/application/app/Dto/Service/Admin/Project/DocumentationCategoryContent/Contents.php @@ -0,0 +1,23 @@ +contents[$content->getLanguageId()] = $content; + } + + public function getContent(int $languageId): ?Content + { + return $this->contents[$languageId] ?? null; + } + + public function getContents(): array + { + return $this->contents; + } +} diff --git a/app/application/app/Dto/Service/Admin/Project/DocumentationContent/Content.php b/app/application/app/Dto/Service/Admin/Project/DocumentationContent/Content.php new file mode 100644 index 0000000..4712e6e --- /dev/null +++ b/app/application/app/Dto/Service/Admin/Project/DocumentationContent/Content.php @@ -0,0 +1,27 @@ +languageId; + } + + public function getTitle(): string + { + return $this->title; + } + + public function getContent(): string + { + return $this->content; + } +} diff --git a/app/application/app/Dto/Service/Admin/Project/DocumentationContent/Contents.php b/app/application/app/Dto/Service/Admin/Project/DocumentationContent/Contents.php new file mode 100644 index 0000000..cee8db1 --- /dev/null +++ b/app/application/app/Dto/Service/Admin/Project/DocumentationContent/Contents.php @@ -0,0 +1,23 @@ +contents[$content->getLanguageId()] = $content; + } + + public function getContent(int $languageId): ?Content + { + return $this->contents[$languageId] ?? null; + } + + public function getContents(): array + { + return $this->contents; + } +} diff --git a/app/application/app/Dto/Service/Admin/Project/DocumentationVersion/Index.php b/app/application/app/Dto/Service/Admin/Project/DocumentationVersion/Index.php new file mode 100644 index 0000000..21c226c --- /dev/null +++ b/app/application/app/Dto/Service/Admin/Project/DocumentationVersion/Index.php @@ -0,0 +1,21 @@ +documentationVersionBuildDto; + } +} diff --git a/app/application/app/Dto/Service/Admin/Project/DocumentationVersion/StoreUpdate.php b/app/application/app/Dto/Service/Admin/Project/DocumentationVersion/StoreUpdate.php new file mode 100644 index 0000000..d2a935e --- /dev/null +++ b/app/application/app/Dto/Service/Admin/Project/DocumentationVersion/StoreUpdate.php @@ -0,0 +1,36 @@ +title; + } + + public function getSlug(): string + { + return $this->slug; + } + + public function isPublic(): bool + { + return $this->isPublic; + } + + public function getStatus(): DocumentationVersionStatus + { + return $this->status; + } +} diff --git a/app/application/app/Enums/DocumentationVersionStatus.php b/app/application/app/Enums/DocumentationVersionStatus.php new file mode 100644 index 0000000..14ab233 --- /dev/null +++ b/app/application/app/Enums/DocumentationVersionStatus.php @@ -0,0 +1,36 @@ +name); + } + + public static function toArray(): array + { + $items = []; + foreach (self::cases() as $item) { + $items[] = [ + 'name' => $item->name, + 'value' => $item->value, + 'title' => $item->getTitle(), + ]; + } + return $items; + } + + public static function toCollection(): Collection + { + return collect(self::toArray()); + } +} diff --git a/app/application/app/Enums/Permission.php b/app/application/app/Enums/Permission.php index f939554..9ae05d4 100644 --- a/app/application/app/Enums/Permission.php +++ b/app/application/app/Enums/Permission.php @@ -12,6 +12,8 @@ enum Permission: string case ProjectLink = 'project-link'; case ProjectTranslation = 'project-translation'; case ProjectFeedback = 'project-feedback'; + case Documentation = 'documentation'; + case DocumentationCategory = 'documentation-category'; public function getPermissions(): array { diff --git a/app/application/app/Exceptions/Services/DocumentationCategory/ParentException.php b/app/application/app/Exceptions/Services/DocumentationCategory/ParentException.php new file mode 100644 index 0000000..e2a0265 --- /dev/null +++ b/app/application/app/Exceptions/Services/DocumentationCategory/ParentException.php @@ -0,0 +1,8 @@ +serviceResultError->getMessage()); + } + + public function getServiceResultError(): ServiceResultErrorContract + { + return $this->serviceResultError; + } +} diff --git a/app/application/app/Http/Controllers/Admin/Projects/DocumentationCategoriesController.php b/app/application/app/Http/Controllers/Admin/Projects/DocumentationCategoriesController.php new file mode 100644 index 0000000..554984a --- /dev/null +++ b/app/application/app/Http/Controllers/Admin/Projects/DocumentationCategoriesController.php @@ -0,0 +1,105 @@ +user(); + $data = $request->getDto(); + $querySettingsDto = new QuerySettingsDto( + limit: 20, + page: $data->getPage(), + queryWith: [] + ); + + $result = $this->documentationCategoryService->index($projectId, $versionId, $data->getDocumentationCategory(), $querySettingsDto, $user); + if ($result->isError()) { + $this->errors($result); + } + + return view('admin/projects/documentation-categories/index', $result->getData()); + } + + public function create(int $projectId, int $versionId, Request $request): View + { + $user = $request->user(); + $result = $this->documentationCategoryService->create($projectId, $versionId, $user); + if ($result->isError()) { + $this->errors($result); + } + + return view('admin/projects/documentation-categories/create', $result->getData()); + } + + public function edit(int $projectId, int $versionId, int $id, Request $request): View + { + $user = $request->user(); + $result = $this->documentationCategoryService->edit($projectId, $versionId, $id, $user); + if ($result->isError()) { + $this->errors($result); + } + + return view('admin/projects/documentation-categories/edit', $result->getData()); + } + + public function store(int $projectId, int $versionId, StoreUpdateRequest $request): RedirectResponse + { + $data = $request->getDto(); + $user = $request->user(); + $result = $this->documentationCategoryService->store($projectId, $versionId, $data, $user); + if ($result->isError()) { + return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage()); + } + + return redirect()->route('admin.projects.documentation-versions.categories.edit', [ + 'project' => $projectId, + 'version' => $versionId, + 'category' => $result->getModel()->id, + ])->withSuccess($result->getMessage()); + } + + public function update(int $projectId, int $versionId, int $id, StoreUpdateRequest $request): RedirectResponse + { + $data = $request->getDto(); + $user = $request->user(); + $result = $this->documentationCategoryService->update($projectId, $versionId, $id, $data, $user); + if ($result->isError()) { + return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage()); + } + + return redirect()->route('admin.projects.documentation-versions.categories.edit', [ + 'project' => $projectId, + 'version' => $versionId, + 'category' => $result->getModel()->id, + ])->withSuccess($result->getMessage()); + } + + public function destroy(int $projectId, int $versionId, int $id, Request $request): RedirectResponse + { + $user = $request->user(); + $result = $this->documentationCategoryService->destroy($projectId, $versionId, $id, $user); + if ($result->isError()) { + return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage()); + } + + return redirect()->route('admin.projects.documentation-versions.categories.index', [ + 'project' => $projectId, + 'version' => $versionId, + ])->withSuccess($result->getMessage()); + } +} diff --git a/app/application/app/Http/Controllers/Admin/Projects/DocumentationVersionController.php b/app/application/app/Http/Controllers/Admin/Projects/DocumentationVersionController.php new file mode 100644 index 0000000..36f1374 --- /dev/null +++ b/app/application/app/Http/Controllers/Admin/Projects/DocumentationVersionController.php @@ -0,0 +1,111 @@ +user(); + $data = $request->getDto(); + $querySettingsDto = new QuerySettingsDto( + limit: 20, + page: $data->getPage(), + queryWith: [] + ); + + $result = $this->documentationVersionService->index($projectId, $data->getDocumentationVersionBuildDto(), $querySettingsDto, $user); + if ($result->isError()) { + $this->errors($result); + } + + return view('admin/projects/documentation-versions/index', $result->getData()); + } + + public function show(int $projectId, int $id, Request $request): View + { + $user = $request->user(); + $result = $this->documentationVersionService->show($projectId, $id, $user); + if ($result->isError()) { + $this->errors($result); + } + + return view('admin/projects/documentation-versions/show', $result->getData()); + } + + public function create(int $projectId, Request $request): View + { + $user = $request->user(); + $result = $this->documentationVersionService->create($projectId, $user); + if ($result->isError()) { + $this->errors($result); + } + + return view('admin/projects/documentation-versions/create', $result->getData()); + } + + public function edit(int $projectId, int $id, Request $request): View + { + $user = $request->user(); + $result = $this->documentationVersionService->edit($projectId, $id, $user); + if ($result->isError()) { + $this->errors($result); + } + + return view('admin/projects/documentation-versions/edit', $result->getData()); + } + + public function store(int $projectId, StoreUpdateRequest $request): RedirectResponse + { + $data = $request->getDto(); + $user = $request->user(); + $result = $this->documentationVersionService->store($projectId, $data, $user); + if ($result->isError()) { + return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage()); + } + + return redirect()->route('admin.projects.documentation-versions.edit', [ + 'project' => $projectId, + 'documentation_version' => $result->getModel()->id, + ])->withSuccess($result->getMessage()); + } + + public function update(int $projectId, int $id, StoreUpdateRequest $request): RedirectResponse + { + $data = $request->getDto(); + $user = $request->user(); + $result = $this->documentationVersionService->update($projectId, $id, $data, $user); + if ($result->isError()) { + return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage()); + } + + return redirect()->route('admin.projects.documentation-versions.edit', [ + 'project' => $projectId, + 'documentation_version' => $result->getModel()->id, + ])->withSuccess($result->getMessage()); + } + + public function destroy(int $projectId, int $id, Request $request): RedirectResponse + { + $user = $request->user(); + $result = $this->documentationVersionService->destroy($projectId, $id, $user); + if ($result->isError()) { + return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage()); + } + + return redirect()->route('admin.projects.documentation-versions.index', ['project' => $projectId])->withSuccess($result->getMessage()); + } +} diff --git a/app/application/app/Http/Controllers/Admin/Projects/DocumentationsController.php b/app/application/app/Http/Controllers/Admin/Projects/DocumentationsController.php new file mode 100644 index 0000000..3dee28c --- /dev/null +++ b/app/application/app/Http/Controllers/Admin/Projects/DocumentationsController.php @@ -0,0 +1,105 @@ +user(); + $data = $request->getDto(); + $querySettingsDto = new QuerySettingsDto( + limit: 20, + page: $data->getPage(), + queryWith: [] + ); + + $result = $this->documentationService->index($projectId, $versionId, $data->getDocumentation(), $querySettingsDto, $user); + if ($result->isError()) { + $this->errors($result); + } + + return view('admin/projects/documentations/index', $result->getData()); + } + + public function create(int $projectId, int $versionId, Request $request): View + { + $user = $request->user(); + $result = $this->documentationService->create($projectId, $versionId, $user); + if ($result->isError()) { + $this->errors($result); + } + + return view('admin/projects/documentations/create', $result->getData()); + } + + public function edit(int $projectId, int $versionId, int $id, Request $request): View + { + $user = $request->user(); + $result = $this->documentationService->edit($projectId, $versionId, $id, $user); + if ($result->isError()) { + $this->errors($result); + } + + return view('admin/projects/documentations/edit', $result->getData()); + } + + public function store(int $projectId, int $versionId, StoreUpdateRequest $request): RedirectResponse + { + $data = $request->getDto(); + $user = $request->user(); + $result = $this->documentationService->store($projectId, $versionId, $data, $user); + if ($result->isError()) { + return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage()); + } + + return redirect()->route('admin.projects.documentation-versions.documentations.edit', [ + 'project' => $projectId, + 'version' => $versionId, + 'documentation' => $result->getModel()->id, + ])->withSuccess($result->getMessage()); + } + + public function update(int $projectId, int $versionId, int $id, StoreUpdateRequest $request): RedirectResponse + { + $data = $request->getDto(); + $user = $request->user(); + $result = $this->documentationService->update($projectId, $versionId, $id, $data, $user); + if ($result->isError()) { + return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage()); + } + + return redirect()->route('admin.projects.documentation-versions.documentations.edit', [ + 'project' => $projectId, + 'version' => $versionId, + 'documentation' => $result->getModel()->id, + ])->withSuccess($result->getMessage()); + } + + public function destroy(int $projectId, int $versionId, int $id, Request $request): RedirectResponse + { + $user = $request->user(); + $result = $this->documentationService->destroy($projectId, $versionId, $id, $user); + if ($result->isError()) { + return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage()); + } + + return redirect()->route('admin.projects.documentation-versions.documentations.index', [ + 'project' => $projectId, + 'version' => $versionId, + ])->withSuccess($result->getMessage()); + } +} diff --git a/app/application/app/Http/Requests/Admin/Projects/DocumentVersions/IndexRequest.php b/app/application/app/Http/Requests/Admin/Projects/DocumentVersions/IndexRequest.php new file mode 100644 index 0000000..64d728e --- /dev/null +++ b/app/application/app/Http/Requests/Admin/Projects/DocumentVersions/IndexRequest.php @@ -0,0 +1,29 @@ + ['nullable', 'numeric', 'min:1'] + ]; + } + + public function getDto(): Index + { + return new Index( + documentationVersionBuildDto: new DocumentationVersion(), + page: (int) $this->input('page', 1) + ); + } +} diff --git a/app/application/app/Http/Requests/Admin/Projects/DocumentVersions/StoreUpdateRequest.php b/app/application/app/Http/Requests/Admin/Projects/DocumentVersions/StoreUpdateRequest.php new file mode 100644 index 0000000..dbedcb4 --- /dev/null +++ b/app/application/app/Http/Requests/Admin/Projects/DocumentVersions/StoreUpdateRequest.php @@ -0,0 +1,35 @@ + ['required', 'string', 'max:255'], + 'slug' => ['required', 'string', 'max:70', 'regex:/^[a-z0-9._-]+$/'], + 'is_public' => ['required', 'boolean'], + 'status' => ['required', new Enum(DocumentationVersionStatus::class)], + ]; + } + + public function getDto(): StoreUpdate + { + return new StoreUpdate( + title: $this->input('title'), + slug: $this->input('slug'), + isPublic: (bool) $this->input('is_public', false), + status: DocumentationVersionStatus::from((int) $this->input('status')), + ); + } +} diff --git a/app/application/app/Http/Requests/Admin/Projects/DocumentationCategories/IndexRequest.php b/app/application/app/Http/Requests/Admin/Projects/DocumentationCategories/IndexRequest.php new file mode 100644 index 0000000..3793048 --- /dev/null +++ b/app/application/app/Http/Requests/Admin/Projects/DocumentationCategories/IndexRequest.php @@ -0,0 +1,29 @@ + ['nullable', 'numeric', 'min:1'] + ]; + } + + public function getDto(): Index + { + return new Index( + documentationCategory: new DocumentationCategory(), + page: (int) $this->input('page', 1) + ); + } +} diff --git a/app/application/app/Http/Requests/Admin/Projects/DocumentationCategories/StoreUpdateRequest.php b/app/application/app/Http/Requests/Admin/Projects/DocumentationCategories/StoreUpdateRequest.php new file mode 100644 index 0000000..a3560c7 --- /dev/null +++ b/app/application/app/Http/Requests/Admin/Projects/DocumentationCategories/StoreUpdateRequest.php @@ -0,0 +1,54 @@ + ['required', 'string', 'max:200', 'regex:/^[a-z0-9._-]+$/'], + 'is_public' => ['required', 'boolean'], + 'sort' => ['required', 'integer', 'min:-1000', 'max:1000'], + 'parent_id' => ['nullable', 'integer', 'exists:documentation_categories,id'], + 'content.*.title' => ['required', 'string', 'max:255'], + ]; + } + + public function getDto(): StoreUpdate + { + $parentId = $this->input('parent_id', null); + if (!\is_null($parentId)) { + $parentId = (int) $parentId; + } + + return new StoreUpdate( + slug: $this->input('slug'), + isPublic: (bool) $this->input('is_public', false), + sort: (int) $this->input('sort'), + contents: $this->getContents(), + parentId: $parentId, + ); + } + + private function getContents(): Contents + { + $contents = new Contents(); + foreach ($this->input('content', []) as $languageId => $content) { + $contents->addContent(new Content( + languageId: (int) $languageId, + title: $content['title'], + )); + } + return $contents; + } +} diff --git a/app/application/app/Http/Requests/Admin/Projects/Documentations/IndexRequest.php b/app/application/app/Http/Requests/Admin/Projects/Documentations/IndexRequest.php new file mode 100644 index 0000000..fe194c2 --- /dev/null +++ b/app/application/app/Http/Requests/Admin/Projects/Documentations/IndexRequest.php @@ -0,0 +1,29 @@ + ['nullable', 'numeric', 'min:1'] + ]; + } + + public function getDto(): Index + { + return new Index( + documentation: new Documentation(), + page: (int) $this->input('page', 1) + ); + } +} diff --git a/app/application/app/Http/Requests/Admin/Projects/Documentations/StoreUpdateRequest.php b/app/application/app/Http/Requests/Admin/Projects/Documentations/StoreUpdateRequest.php new file mode 100644 index 0000000..f39aa1a --- /dev/null +++ b/app/application/app/Http/Requests/Admin/Projects/Documentations/StoreUpdateRequest.php @@ -0,0 +1,56 @@ + ['required', 'string', 'max:200', 'regex:/^[a-z0-9._-]+$/'], + 'is_public' => ['required', 'boolean'], + 'sort' => ['required', 'integer', 'min:-1000', 'max:1000'], + 'category_id' => ['nullable', 'integer', 'exists:documentation_categories,id'], + 'content.*.title' => ['required', 'string', 'max:255'], + 'content.*.content' => ['nullable', 'string'], + ]; + } + + public function getDto(): StoreUpdate + { + $categoryId = $this->input('category_id', null); + if (!\is_null($categoryId)) { + $categoryId = (int) $categoryId; + } + + return new StoreUpdate( + slug: $this->input('slug'), + isPublic: (bool) $this->input('is_public', false), + sort: (int) $this->input('sort'), + contents: $this->getContents(), + categoryId: $categoryId, + ); + } + + private function getContents(): Contents + { + $contents = new Contents(); + foreach ($this->input('content', []) as $languageId => $content) { + $contents->addContent(new Content( + languageId: (int) $languageId, + title: $content['title'], + content: $content['content'] ?? '', + )); + } + return $contents; + } +} diff --git a/app/application/app/Models/Documentation.php b/app/application/app/Models/Documentation.php new file mode 100644 index 0000000..e237937 --- /dev/null +++ b/app/application/app/Models/Documentation.php @@ -0,0 +1,65 @@ + 100, + 'is_public' => true, + ]; + + /** + * The attributes that are mass assignable. + * + * @var array + */ + protected $fillable = [ + 'slug', + 'is_public', + 'category_id', + ]; + + /** + * Get the attributes that should be cast. + * + * @return array + */ + protected function casts(): array + { + return [ + 'is_public' => 'boolean', + ]; + } + + public function category(): BelongsTo + { + return $this->belongsTo(DocumentationCategory::class, 'category_id', 'id'); + } + + public function contents(): HasMany + { + return $this->hasMany(DocumentationContent::class, 'documentation_id', 'id'); + } + + public function content(): HasOne + { + return $this->hasOne(DocumentationContent::class, 'documentation_id', 'id'); + } +} diff --git a/app/application/app/Models/DocumentationCategory.php b/app/application/app/Models/DocumentationCategory.php new file mode 100644 index 0000000..7173e49 --- /dev/null +++ b/app/application/app/Models/DocumentationCategory.php @@ -0,0 +1,75 @@ + 100, + 'is_public' => true, + ]; + + /** + * The attributes that are mass assignable. + * + * @var array + */ + protected $fillable = [ + 'slug', + 'sort', + 'parent_id', + 'is_public', + ]; + + /** + * Get the attributes that should be cast. + * + * @return array + */ + protected function casts(): array + { + return [ + 'is_public' => 'boolean', + ]; + } + + public function parent(): BelongsTo + { + return $this->belongsTo(DocumentationCategory::class, 'parent_id'); + } + + public function contents(): HasMany + { + return $this->hasMany(DocumentationCategoryContent::class, 'category_id'); + } + + public function content(): HasOne + { + return $this->hasOne(DocumentationCategoryContent::class, 'category_id'); + } + + public function version(): BelongsTo + { + return $this->belongsTo(DocumentationVersion::class, 'version_id'); + } +} diff --git a/app/application/app/Models/DocumentationCategoryContent.php b/app/application/app/Models/DocumentationCategoryContent.php new file mode 100644 index 0000000..4477716 --- /dev/null +++ b/app/application/app/Models/DocumentationCategoryContent.php @@ -0,0 +1,24 @@ + true, + 'status' => DocumentationVersionStatus::CurrentVersion, + ]; + + /** + * The attributes that are mass assignable. + * + * @var array + */ + protected $fillable = [ + 'title', + 'slug', + 'is_public', + 'status', + ]; + + /** + * Get the attributes that should be cast. + * + * @return array + */ + protected function casts(): array + { + return [ + 'is_public' => 'boolean', + 'status' => DocumentationVersionStatus::class, + ]; + } + + public function project(): BelongsTo + { + return $this->belongsTo(Project::class); + } + + public function documentations(): HasMany + { + return $this->hasMany(Documentation::class, 'version_id'); + } + + public function categories(): HasMany + { + return $this->hasMany(DocumentationCategory::class, 'version_id'); + } +} diff --git a/app/application/app/Models/Project.php b/app/application/app/Models/Project.php index fc2e671..b0ea794 100644 --- a/app/application/app/Models/Project.php +++ b/app/application/app/Models/Project.php @@ -62,4 +62,9 @@ public function feedbacks(): HasMany { return $this->hasMany(ProjectFeedback::class); } + + public function documentationVersions(): HasMany + { + return $this->hasMany(DocumentationVersion::class); + } } diff --git a/app/application/app/Policies/DocumentationCategoryPolicy.php b/app/application/app/Policies/DocumentationCategoryPolicy.php new file mode 100644 index 0000000..50b575e --- /dev/null +++ b/app/application/app/Policies/DocumentationCategoryPolicy.php @@ -0,0 +1,34 @@ +hasPermission('documentation-category.view'); + } + + public function view(User $user, DocumentationCategory $category): bool + { + return $user->hasPermission('documentation-category.view'); + } + + public function create(User $user): bool + { + return $user->hasPermission('documentation-category.create'); + } + + public function update(User $user, DocumentationCategory $category): bool + { + return $user->hasPermission('documentation.update'); + } + + public function delete(User $user, DocumentationCategory $category): bool + { + return $user->hasPermission('documentation-category.delete'); + } +} diff --git a/app/application/app/Policies/DocumentationPolicy.php b/app/application/app/Policies/DocumentationPolicy.php new file mode 100644 index 0000000..2f68f3a --- /dev/null +++ b/app/application/app/Policies/DocumentationPolicy.php @@ -0,0 +1,34 @@ +hasPermission('documentation.view'); + } + + public function view(User $user, Documentation $documentation): bool + { + return $user->hasPermission('documentation.view'); + } + + public function create(User $user): bool + { + return $user->hasPermission('documentation.create'); + } + + public function update(User $user, Documentation $documentation): bool + { + return $user->hasPermission('documentation.update'); + } + + public function delete(User $user, Documentation $documentation): bool + { + return $user->hasPermission('documentation.delete'); + } +} diff --git a/app/application/app/Policies/DocumentationVersionPolicy.php b/app/application/app/Policies/DocumentationVersionPolicy.php new file mode 100644 index 0000000..55b7704 --- /dev/null +++ b/app/application/app/Policies/DocumentationVersionPolicy.php @@ -0,0 +1,39 @@ +hasPermission('documentation.view'); + } + + public function view(User $user, DocumentationVersion $documentationVersion): bool + { + // Not a mistake or typo. Shared rights with Documentation. + return $user->hasPermission('documentation.view'); + } + + public function create(User $user): bool + { + // Not a mistake or typo. Shared rights with Documentation. + return $user->hasPermission('documentation.create'); + } + + public function update(User $user, DocumentationVersion $documentationVersion): bool + { + // Not a mistake or typo. Shared rights with Documentation. + return $user->hasPermission('documentation.update'); + } + + public function delete(User $user, DocumentationVersion $documentationVersion): bool + { + // Not a mistake or typo. Shared rights with Documentation. + return $user->hasPermission('documentation.delete'); + } +} diff --git a/app/application/app/Repositories/DocumentationCategoryRepository.php b/app/application/app/Repositories/DocumentationCategoryRepository.php new file mode 100644 index 0000000..2f9f47f --- /dev/null +++ b/app/application/app/Repositories/DocumentationCategoryRepository.php @@ -0,0 +1,86 @@ +builderCommand->execute( + query: DocumentationCategory::query()->where('version_id', $versionId)->with($with), + documentationCategoryBuilderDto: $documentationCategoryBuilderDto + ); + + return $this->createSearchInstanceCommand->execute($query); + } + + public function getCategoryById(int $id): ?DocumentationCategory + { + return DocumentationCategory::query()->where('id', $id)->first(); + } + + public function getCategoryByCode(string $code): ?DocumentationCategory + { + return DocumentationCategory::query()->where('code', $code)->first(); + } + + public function isExistsSlug(int $versionId, string $slug, ?int $exceptId = null): bool + { + return DocumentationCategory::query() + ->where('version_id', $versionId) + ->where('slug', Str::lower($slug)) + ->when($exceptId, function (Builder $query, int $exceptId) { + $query->where('id', '!=', $exceptId); + }) + ->withTrashed() + ->exists(); + } + + public function getForSelect(?ProjectLanguage $defaultLanguage, ?DocumentationCategory $exceptCategory = null, array $withExcepts = []): array + { + $with = [ + 'content' => function (HasOne $hasOne) use ($defaultLanguage) { + $hasOne->when($defaultLanguage, function (Builder $query, ProjectLanguage $defaultLanguage) { + $query->where('language_id', $defaultLanguage->id); + }); + } + ]; + + $categories = DocumentationCategory::query() + ->with($with) + ->when($exceptCategory, function (Builder $query, DocumentationCategory $exceptCategory) { + $query->whereNotIn( + 'id', + $exceptCategory->descendantsAndSelf()->pluck('id')->toArray() + ); + }) + ->when($withExcepts, function (Builder $query) use ($withExcepts) { + $query->withTrashed()->whereNull('deleted_at')->orWhereIn('id', $withExcepts); + })->get(); + + return $categories->map(function (DocumentationCategory $documentationCategory) use ($defaultLanguage) { + return [ + 'id' => $documentationCategory->id, + 'title' => $documentationCategory->content?->title ?? $documentationCategory->slug, + ]; + }) + ->pluck('title', 'id') + ->toArray(); + } +} diff --git a/app/application/app/Repositories/DocumentationRepository.php b/app/application/app/Repositories/DocumentationRepository.php new file mode 100644 index 0000000..2a2072f --- /dev/null +++ b/app/application/app/Repositories/DocumentationRepository.php @@ -0,0 +1,51 @@ +builderCommand->execute( + query: Documentation::query()->where('version_id', $versionId)->with($with), + documentationBuilderDto: $documentationBuilderDto + ); + + return $this->createSearchInstanceCommand->execute($query); + } + + public function getDocumentationById(int $id): ?Documentation + { + return Documentation::query()->where('id', $id)->first(); + } + + public function getDocumentationByCode(string $code): ?Documentation + { + return Documentation::query()->where('code', $code)->first(); + } + + public function isExistsSlug(int $versionId, string $slug, ?int $exceptId = null): bool + { + return Documentation::query() + ->where('version_id', $versionId) + ->where('slug', Str::lower($slug)) + ->when($exceptId, function (Builder $query, int $exceptId) { + $query->where('id', '!=', $exceptId); + }) + ->withTrashed() + ->exists(); + } +} diff --git a/app/application/app/Repositories/DocumentationVersionRepository.php b/app/application/app/Repositories/DocumentationVersionRepository.php new file mode 100644 index 0000000..4fc878b --- /dev/null +++ b/app/application/app/Repositories/DocumentationVersionRepository.php @@ -0,0 +1,51 @@ +builderCommand->execute( + query: DocumentationVersion::query()->where('project_id', $projectId)->with($with), + documentationVersionBuilderDto: $documentationVersionBuilderDto + ); + + return $this->createSearchInstanceCommand->execute($query); + } + + public function getVersionById(int $id): ?DocumentationVersion + { + return DocumentationVersion::query()->where('id', $id)->first(); + } + + public function getVersionByCode(string $code): ?DocumentationVersion + { + return DocumentationVersion::query()->where('code', $code)->first(); + } + + public function isExistsSlug(int $projectId, string $slug, ?int $exceptId = null): bool + { + return DocumentationVersion::query() + ->where('project_id', $projectId) + ->where('slug', Str::lower($slug)) + ->when($exceptId, function (Builder $query, int $exceptId) { + $query->where('id', '!=', $exceptId); + }) + ->withTrashed() + ->exists(); + } +} diff --git a/app/application/app/Services/Admin/Project/DocumentationCategoryService.php b/app/application/app/Services/Admin/Project/DocumentationCategoryService.php new file mode 100644 index 0000000..f64ba24 --- /dev/null +++ b/app/application/app/Services/Admin/Project/DocumentationCategoryService.php @@ -0,0 +1,257 @@ +documentationVersionRepository->getVersionById($versionId); + $project = $version?->project; + if (\is_null($version) || $project?->id !== $projectId) { + return $this->errNotFound(__('Not Found')); + } + + if ($user->cannot('viewAny', DocumentationCategory::class)) { + return $this->errFobidden(__('Access is denied')); + } + + $defaultLanguage = $project->languages()->where('is_default', 1)->first(); + $with = [ + 'content' => function (HasOne $hasOne) use ($defaultLanguage) { + /** @var ?ProjectLanguage $defaultLanguage */ + $hasOne->when($defaultLanguage, function (Builder $query, ProjectLanguage $defaultLanguage) { + $query->where('language_id', $defaultLanguage->id); + }); + } + ]; + $with = array_merge($with, $querySettingsDto->getQueryWith()); + + $categories = $this->documentationCategoryRepository->getCategories( + $version->id, + $documentationCategoryBuilderDto, + $with + )->pagination( + $querySettingsDto->getLimit(), + $querySettingsDto->getPage() + ); + + return $this->result([ + 'version' => $version, + 'project' => $project, + 'categories' => $categories, + ]); + } + + public function create(int $projectId, int $versionId, User $user): ServiceResultError | ServiceResultArray + { + $version = $this->documentationVersionRepository->getVersionById($versionId); + $project = $version?->project; + if (\is_null($version) || $project?->id !== $projectId) { + return $this->errNotFound(__('Not Found')); + } + + if ($user->cannot('create', DocumentationCategory::class)) { + return $this->errFobidden(__('Access is denied')); + } + + $defaultLanguage = $project->languages->where('is_default', 1)->first(); + return $this->result([ + 'version' => $version, + 'project' => $project, + 'category' => new DocumentationCategory(), + 'categories' => $this->documentationCategoryRepository->getForSelect($defaultLanguage), + ]); + } + + public function edit(int $projectId, int $versionId, int $categoryId, User $user): ServiceResultError | ServiceResultArray + { + $version = $this->documentationVersionRepository->getVersionById($versionId); + $project = $version?->project; + if (\is_null($version) || $project?->id !== $projectId) { + return $this->errNotFound(__('Not Found')); + } + + $category = $this->documentationCategoryRepository->getCategoryById($categoryId); + if (\is_null($category)) { + return $this->errNotFound(__('Not Found')); + } + + if ($user->cannot('view', $category)) { + return $this->errFobidden(__('Access is denied')); + } + + $withCategories = []; + if ($category->parent_id) { + $withCategories[] = $category->parent_id; + } + $defaultLanguage = $project->languages->where('is_default', 1)->first(); + return $this->result([ + 'version' => $version, + 'project' => $project, + 'category' => $category, + 'categories' => $this->documentationCategoryRepository->getForSelect($defaultLanguage, $category, $withCategories), + ]); + } + + public function store(int $projectId, int $versionId, StoreUpdate $data, User $user): ServiceResultError | StoreUpdateResult + { + if ($user->cannot('create', DocumentationCategory::class)) { + return $this->errFobidden(__('Access is denied')); + } + + $version = $this->documentationVersionRepository->getVersionById($versionId); + $project = $version?->project; + if (\is_null($version) || $project?->id !== $projectId) { + return $this->errNotFound(__('Not Found')); + } + + try { + $category = DB::transaction(function () use ($data, $version, $user, $versionId, $project) { + if ($this->documentationCategoryRepository->isExistsSlug($versionId, $data->getSlug()) !== false) { + $error = $this->errValidate( + __('validation.unique', ['attribute' => __('validation.attributes.slug')]), + ['slug' => __('validation.unique', ['attribute' => __('validation.attributes.slug')])] + ); + throw new ServiceException($error); + } + + $dataCategory = $this->getDataCategory($data); + $category = $this->categoryCommandHandler->handleStore($version, $dataCategory); + $this->contentSaveCommand->execute($project, $category, $data->getContents()); + + return $category; + }); + } catch (ServiceException $e) { + return $e->getServiceResultError(); + } catch (\Throwable $e) { + report($e); + return $this->errService(__('Server Error')); + } + + return $this->resultStoreUpdateModel($category, __('Category successfully created')); + } + + public function update(int $projectId, int $versionId, int $categoryId, StoreUpdate $data, User $user): ServiceResultError | StoreUpdateResult + { + $version = $this->documentationVersionRepository->getVersionById($versionId); + $project = $version?->project; + if (\is_null($version) || $project?->id !== $projectId) { + return $this->errNotFound(__('Not Found')); + } + + $category = $this->documentationCategoryRepository->getCategoryById($categoryId); + if (\is_null($category)) { + return $this->errNotFound(__('Not Found')); + } + + if ($user->cannot('view', $category)) { + return $this->errFobidden(__('Access is denied')); + } + + if ($data->getParentId() === $category->id) { + return $this->errValidate( + __('validation.parent', ['attribute' => __('validation.attributes.parent_id')]), + ['parent_id' => __('validation.parent', ['attribute' => __('validation.attributes.parent_id')])] + ); + } + + try { + $category = DB::transaction(function () use ($data, $category, $versionId, $project) { + if ($this->documentationCategoryRepository->isExistsSlug($versionId, $data->getSlug(), $category->id) !== false) { + $error = $this->errValidate( + __('validation.unique', ['attribute' => __('validation.attributes.slug')]), + ['slug' => __('validation.unique', ['attribute' => __('validation.attributes.slug')])] + ); + throw new ServiceException($error); + } + + $dataCategory = $this->getDataCategory($data); + $category = $this->categoryCommandHandler->handleUpdate($category, $dataCategory); + $this->contentSaveCommand->execute($project, $category, $data->getContents()); + + return $category; + }); + } catch (ServiceException $e) { + return $e->getServiceResultError(); + } catch (ParentException $e) { + return $this->errValidate( + __('validation.parent_cycle_detected', ['attribute' => __('validation.attributes.parent_id')]), + ['parent_id' => __('validation.parent_cycle_detected', ['attribute' => __('validation.attributes.parent_id')])] + ); + } catch (\Throwable $e) { + report($e); + return $this->errService(__('Server Error')); + } + + return $this->resultStoreUpdateModel($category, __('Category updated successfully')); + } + + public function destroy(int $projectId, int $versionId, int $categoryId, User $user): ServiceResultError|ServiceResultSuccess + { + $version = $this->documentationVersionRepository->getVersionById($versionId); + $project = $version?->project; + if (\is_null($version) || $project?->id !== $projectId) { + return $this->errNotFound(__('Not Found')); + } + + $category = $this->documentationCategoryRepository->getCategoryById($categoryId); + if (\is_null($category)) { + return $this->errNotFound(__('Not Found')); + } + + if ($user->cannot('delete', $category)) { + return $this->errFobidden(__('Access is denied')); + } + + try { + DB::transaction(function () use ($category) { + $this->categoryCommandHandler->handleDestroy($category); + }); + } catch (\Throwable $e) { + report($e); + return $this->errService(__('Server Error')); + } + + return $this->ok(__('Category successfully deleted')); + } + + private function getDataCategory(StoreUpdate $data): array + { + return [ + 'slug' => $data->getSlug(), + 'is_public' => $data->isPublic(), + 'sort' => $data->getSort(), + 'parent_id' => $data->getParentId(), + ]; + } +} diff --git a/app/application/app/Services/Admin/Project/DocumentationService.php b/app/application/app/Services/Admin/Project/DocumentationService.php new file mode 100644 index 0000000..8317486 --- /dev/null +++ b/app/application/app/Services/Admin/Project/DocumentationService.php @@ -0,0 +1,239 @@ +documentationVersionRepository->getVersionById($versionId); + $project = $version?->project; + if (\is_null($version) || $project?->id !== $projectId) { + return $this->errNotFound(__('Not Found')); + } + + if ($user->cannot('viewAny', Documentation::class)) { + return $this->errFobidden(__('Access is denied')); + } + + $defaultLanguage = $project->languages()->where('is_default', 1)->first(); + $with = [ + 'content' => function (HasOne $hasOne) use ($defaultLanguage) { + /** @var ?ProjectLanguage $defaultLanguage */ + $hasOne->when($defaultLanguage, function (Builder $query, ProjectLanguage $defaultLanguage) { + $query->where('language_id', $defaultLanguage->id); + }); + } + ]; + $with = array_merge($with, $querySettingsDto->getQueryWith()); + + $documentations = $this->documentationRepository->getDocumentations( + $version->id, + $documentationBuilderDto, + $with + )->pagination( + $querySettingsDto->getLimit(), + $querySettingsDto->getPage() + ); + + return $this->result([ + 'version' => $version, + 'project' => $project, + 'documentations' => $documentations, + ]); + } + + public function create(int $projectId, int $versionId, User $user): ServiceResultError | ServiceResultArray + { + $version = $this->documentationVersionRepository->getVersionById($versionId); + $project = $version?->project; + if (\is_null($version) || $project?->id !== $projectId) { + return $this->errNotFound(__('Not Found')); + } + + if ($user->cannot('create', Documentation::class)) { + return $this->errFobidden(__('Access is denied')); + } + + $defaultLanguage = $project->languages->where('is_default', 1)->first(); + return $this->result([ + 'version' => $version, + 'project' => $project, + 'documentation' => new Documentation(), + 'categories' => $this->documentationCategoryRepository->getForSelect($defaultLanguage), + ]); + } + + public function edit(int $projectId, int $versionId, int $documentationId, User $user): ServiceResultError | ServiceResultArray + { + $version = $this->documentationVersionRepository->getVersionById($versionId); + $project = $version?->project; + if (\is_null($version) || $project?->id !== $projectId) { + return $this->errNotFound(__('Not Found')); + } + + $documentation = $this->documentationRepository->getDocumentationById($documentationId); + if (\is_null($documentation)) { + return $this->errNotFound(__('Not Found')); + } + + if ($user->cannot('view', $documentation)) { + return $this->errFobidden(__('Access is denied')); + } + + $withCategories = []; + if ($documentation->category_id) { + $withCategories[] = $documentation->category_id; + } + $defaultLanguage = $project->languages->where('is_default', 1)->first(); + return $this->result([ + 'version' => $version, + 'project' => $project, + 'documentation' => $documentation, + 'categories' => $this->documentationCategoryRepository->getForSelect($defaultLanguage, null, $withCategories), + ]); + } + + public function store(int $projectId, int $versionId, StoreUpdate $data, User $user): ServiceResultError | StoreUpdateResult + { + if ($user->cannot('create', Documentation::class)) { + return $this->errFobidden(__('Access is denied')); + } + + $version = $this->documentationVersionRepository->getVersionById($versionId); + $project = $version?->project; + if (\is_null($version) || $project?->id !== $projectId) { + return $this->errNotFound(__('Not Found')); + } + + if ($this->documentationRepository->isExistsSlug($versionId, $data->getSlug()) !== false) { + return $this->errValidate( + __('validation.unique', ['attribute' => __('validation.attributes.slug')]), + ['slug' => __('validation.unique', ['attribute' => __('validation.attributes.slug')])] + ); + } + + try { + $documentation = DB::transaction(function () use ($data, $version, $user, $project) { + $dataDocumentation = $this->getDataDocumentation($data); + $documentation = $this->documentationCommandHandler->handleStore($version, $dataDocumentation); + $this->contentSaveCommand->execute($project, $documentation, $data->getContents()); + + return $documentation; + }); + } catch (\Throwable $e) { + report($e); + return $this->errService(__('Server Error')); + } + + return $this->resultStoreUpdateModel($documentation, __('Documentation created successfully')); + } + + public function update(int $projectId, int $versionId, int $documentationId, StoreUpdate $data, User $user): ServiceResultError | StoreUpdateResult + { + $version = $this->documentationVersionRepository->getVersionById($versionId); + $project = $version?->project; + if (\is_null($version) || $project?->id !== $projectId) { + return $this->errNotFound(__('Not Found')); + } + + $documentation = $this->documentationRepository->getDocumentationById($documentationId); + if (\is_null($documentation)) { + return $this->errNotFound(__('Not Found')); + } + + if ($user->cannot('view', $documentation)) { + return $this->errFobidden(__('Access is denied')); + } + + if ($this->documentationRepository->isExistsSlug($versionId, $data->getSlug(), $documentation->id) !== false) { + return $this->errValidate( + __('validation.unique', ['attribute' => __('validation.attributes.slug')]), + ['slug' => __('validation.unique', ['attribute' => __('validation.attributes.slug')])] + ); + } + + try { + $documentation = DB::transaction(function () use ($data, $documentation, $project) { + $dataDocumentation = $this->getDataDocumentation($data); + $documentation = $this->documentationCommandHandler->handleUpdate($documentation, $dataDocumentation); + $this->contentSaveCommand->execute($project, $documentation, $data->getContents()); + + return $documentation; + }); + } catch (\Throwable $e) { + report($e); + return $this->errService(__('Server Error')); + } + + return $this->resultStoreUpdateModel($documentation, __('Documentation updated successfully')); + } + + public function destroy(int $projectId, int $versionId, int $documentationId, User $user): ServiceResultError|ServiceResultSuccess + { + $version = $this->documentationVersionRepository->getVersionById($versionId); + $project = $version?->project; + if (\is_null($version) || $project?->id !== $projectId) { + return $this->errNotFound(__('Not Found')); + } + + $documentation = $this->documentationRepository->getDocumentationById($documentationId); + if (\is_null($documentation)) { + return $this->errNotFound(__('Not Found')); + } + + if ($user->cannot('delete', $documentation)) { + return $this->errFobidden(__('Access is denied')); + } + + try { + DB::transaction(function () use ($documentation) { + $this->documentationCommandHandler->handleDestroy($documentation); + }); + } catch (\Throwable $e) { + report($e); + return $this->errService(__('Server Error')); + } + + return $this->ok(__('Documentation successfully removed')); + } + + private function getDataDocumentation(StoreUpdate $data): array + { + return [ + 'slug' => $data->getSlug(), + 'is_public' => $data->isPublic(), + 'sort' => $data->getSort(), + 'category_id' => $data->getCategoryId(), + ]; + } +} diff --git a/app/application/app/Services/Admin/Project/DocumentationVersionService.php b/app/application/app/Services/Admin/Project/DocumentationVersionService.php new file mode 100644 index 0000000..ae9d904 --- /dev/null +++ b/app/application/app/Services/Admin/Project/DocumentationVersionService.php @@ -0,0 +1,222 @@ +projectRepository->getProjectById($projectId); + if (\is_null($project)) { + return $this->errNotFound(__('Not Found')); + } + + if ($user->cannot('viewAny', DocumentationVersion::class)) { + return $this->errFobidden(__('Access is denied')); + } + + $versions = $this->documentationVersionRepository->getVersions( + $project->id, + $documentationVersionBuilderDto, + $querySettingsDto->getQueryWith() + )->pagination( + $querySettingsDto->getLimit(), + $querySettingsDto->getPage() + ); + + return $this->result([ + 'versions' => $versions, + 'project' => $project, + ]); + } + + public function show(int $projectId, int $versionId, User $user): ServiceResultError | ServiceResultArray + { + $project = $this->projectRepository->getProjectById($projectId); + if (\is_null($project)) { + return $this->errNotFound(__('Not Found')); + } + + $version = $this->documentationVersionRepository->getVersionById($versionId); + if (\is_null($version)) { + return $this->errNotFound(__('Not Found')); + } + + if ($user->cannot('view', $version)) { + return $this->errFobidden(__('Access is denied')); + } + + return $this->result([ + 'version' => $version, + 'project' => $project, + ]); + } + + public function create(int $projectId, User $user): ServiceResultError | ServiceResultArray + { + $project = $this->projectRepository->getProjectById($projectId); + if (\is_null($project)) { + return $this->errNotFound(__('Not Found')); + } + + if ($user->cannot('create', DocumentationVersion::class)) { + return $this->errFobidden(__('Access is denied')); + } + + return $this->result([ + 'version' => new DocumentationVersion(), + 'statuses' => DocumentationVersionStatus::toCollection()->pluck( 'title', 'value')->toArray(), + 'project' => $project, + ]); + } + + public function edit(int $projectId, int $versionId, User $user): ServiceResultError | ServiceResultArray + { + $project = $this->projectRepository->getProjectById($projectId); + if (\is_null($project)) { + return $this->errNotFound(__('Not Found')); + } + + $version = $this->documentationVersionRepository->getVersionById($versionId); + if (\is_null($version)) { + return $this->errNotFound(__('Not Found')); + } + + if ($user->cannot('view', $version)) { + return $this->errFobidden(__('Access is denied')); + } + + return $this->result([ + 'version' => $version, + 'statuses' => DocumentationVersionStatus::toCollection()->pluck( 'title', 'value')->toArray(), + 'project' => $project, + ]); + } + + public function store(int $projectId, StoreUpdate $data, User $user): ServiceResultError | StoreUpdateResult + { + if ($user->cannot('create', DocumentationVersion::class)) { + return $this->errFobidden(__('Access is denied')); + } + + $project = $this->projectRepository->getProjectById($projectId); + if (\is_null($project)) { + return $this->errNotFound(__('Not Found')); + } + + if ($this->documentationVersionRepository->isExistsSlug($projectId, $data->getSlug()) !== false) { + return $this->errValidate( + __('validation.unique', ['attribute' => __('validation.attributes.slug')]), + ['slug' => __('validation.unique', ['attribute' => __('validation.attributes.slug')])] + ); + } + + try { + $version = DB::transaction(function () use ($data, $project, $user) { + $dataVersion = $this->getDataVersion($data); + return $this->documentationVersionCommandHandler->handleStore($project, $dataVersion); + }); + } catch (\Throwable $e) { + report($e); + return $this->errService(__('Server Error')); + } + + return $this->resultStoreUpdateModel($version, __('Documentation version created successfully')); + } + + public function update(int $projectId, int $versionId, StoreUpdate $data, User $user): ServiceResultError | StoreUpdateResult + { + $project = $this->projectRepository->getProjectById($projectId); + if (\is_null($project)) { + return $this->errNotFound(__('Not Found')); + } + + $version = $this->documentationVersionRepository->getVersionById($versionId); + if (\is_null($version)) { + return $this->errNotFound(__('Not Found')); + } + + if ($user->cannot('update', $version)) { + return $this->errFobidden(__('Access is denied')); + } + + if ($this->documentationVersionRepository->isExistsSlug($projectId, $data->getSlug(), $version->id) !== false) { + return $this->errValidate( + __('validation.unique', ['attribute' => __('validation.attributes.slug')]), + ['slug' => __('validation.unique', ['attribute' => __('validation.attributes.slug')])] + ); + } + + try { + $version = DB::transaction(function () use ($data, $version) { + $dataVersion = $this->getDataVersion($data); + return $this->documentationVersionCommandHandler->handleUpdate($version, $dataVersion); + }); + } catch (\Throwable $e) { + report($e); + return $this->errService(__('Server Error')); + } + + return $this->resultStoreUpdateModel($version, __('Documentation version updated successfully')); + } + + public function destroy(int $projectId, int $versionId, User $user): ServiceResultError|ServiceResultSuccess + { + $project = $this->projectRepository->getProjectById($projectId); + if (\is_null($project)) { + return $this->errNotFound(__('Not Found')); + } + + $version = $this->documentationVersionRepository->getVersionById($versionId); + if (\is_null($version)) { + return $this->errNotFound(__('Not Found')); + } + + if ($user->cannot('delete', $version)) { + return $this->errFobidden(__('Access is denied')); + } + + try { + DB::transaction(function () use ($version) { + $this->documentationVersionCommandHandler->handleDestroy($version); + }); + } catch (\Throwable $e) { + report($e); + return $this->errService(__('Server Error')); + } + + return $this->ok(__('Documentation version successfully removed')); + } + + private function getDataVersion(StoreUpdate $data): array + { + return [ + 'title' => $data->getTitle(), + 'slug' => $data->getSlug(), + 'is_public' => $data->isPublic(), + 'status' => $data->getStatus(), + ]; + } +} diff --git a/app/application/app/Services/Documentation/BuilderCommand.php b/app/application/app/Services/Documentation/BuilderCommand.php new file mode 100644 index 0000000..24485cc --- /dev/null +++ b/app/application/app/Services/Documentation/BuilderCommand.php @@ -0,0 +1,19 @@ +isPublic() !== null) { + $query->where('is_public', $documentationBuilderDto->isPublic()); + } + + return $query; + } +} diff --git a/app/application/app/Services/Documentation/DocumentationCommandHandler.php b/app/application/app/Services/Documentation/DocumentationCommandHandler.php new file mode 100644 index 0000000..f793d7d --- /dev/null +++ b/app/application/app/Services/Documentation/DocumentationCommandHandler.php @@ -0,0 +1,36 @@ +documentations()->create($data); + } + + public function handleUpdate(Documentation $documentation, array $data): Documentation + { + if (isset($data['slug'])) { + $data['slug'] = Str::lower($data['slug']); + } + + $documentation->update($data); + $documentation->touch(); + + return $documentation; + } + + public function handleDestroy(Documentation $documentation): void + { + $documentation->update([ + 'slug' => $documentation->slug . '#delete:' . $documentation->id, + ]); + $documentation->delete(); + } +} diff --git a/app/application/app/Services/DocumentationCategory/BuilderCommand.php b/app/application/app/Services/DocumentationCategory/BuilderCommand.php new file mode 100644 index 0000000..c7e2b8c --- /dev/null +++ b/app/application/app/Services/DocumentationCategory/BuilderCommand.php @@ -0,0 +1,19 @@ +isPublic() !== null) { + $query->where('is_public', $documentationCategoryBuilderDto->isPublic()); + } + + return $query; + } +} diff --git a/app/application/app/Services/DocumentationCategory/DocumentationCategoryCommandHandler.php b/app/application/app/Services/DocumentationCategory/DocumentationCategoryCommandHandler.php new file mode 100644 index 0000000..1c5140b --- /dev/null +++ b/app/application/app/Services/DocumentationCategory/DocumentationCategoryCommandHandler.php @@ -0,0 +1,44 @@ +categories()->create($data); + } + + public function handleUpdate(DocumentationCategory $category, array $data): DocumentationCategory + { + if (!empty($data['parent_id'])) { + $category->parent_id = $data['parent_id']; + if ($category->ancestors()->withTrashed()->where('id', $category->id)->exists()) { + throw new ParentException('Category ID occurs in the parent chain (category: ' . $category->id . ', parent_id: ' . $category->parent_id . ').'); + } + } + + if (isset($data['slug'])) { + $data['slug'] = Str::lower($data['slug']); + } + + $category->update($data); + $category->touch(); + + return $category; + } + + public function handleDestroy(DocumentationCategory $category): void + { + $category->update([ + 'slug' => $category->slug . '#delete:' . $category->id, + ]); + $category->delete(); + } +} diff --git a/app/application/app/Services/DocumentationCategoryContent/ModelSyncCommand.php b/app/application/app/Services/DocumentationCategoryContent/ModelSyncCommand.php new file mode 100644 index 0000000..c4939ed --- /dev/null +++ b/app/application/app/Services/DocumentationCategoryContent/ModelSyncCommand.php @@ -0,0 +1,46 @@ +languages; + $categoryContents = $category->contents; + + $newContents = []; + foreach ($contents->getContents() as $content) { + /** @var Content $content */ + $language = $languages->firstWhere('id', $content->getLanguageId()); + if (!$language) { + throw new ContentSaveException('Language not found: ' . $content->getLanguageId()); + } + + $model = $categoryContents->firstWhere('language_id', $language->id); + $data = $this->getData($content); + if (\is_null($model)) { + $newContents[] = array_merge(['language_id' => $content->getLanguageId()], $data); + continue; + } + $model->update($data); + } + + if (!empty($newContents)) { + $category->contents()->createMany($newContents); + } + } + + private function getData(Content $content): array + { + return [ + 'title' => $content->getTitle(), + ]; + } +} diff --git a/app/application/app/Services/DocumentationContent/ModelSyncCommand.php b/app/application/app/Services/DocumentationContent/ModelSyncCommand.php new file mode 100644 index 0000000..aa1153a --- /dev/null +++ b/app/application/app/Services/DocumentationContent/ModelSyncCommand.php @@ -0,0 +1,47 @@ +languages; + $documentationContents = $documentation->contents; + + $newContents = []; + foreach ($contents->getContents() as $content) { + /** @var Content $content */ + $language = $languages->firstWhere('id', $content->getLanguageId()); + if (!$language) { + throw new ContentSaveException('Language not found: ' . $content->getLanguageId()); + } + + $model = $documentationContents->firstWhere('language_id', $language->id); + $data = $this->getData($content); + if (\is_null($model)) { + $newContents[] = array_merge(['language_id' => $content->getLanguageId()], $data); + continue; + } + $model->update($data); + } + + if (!empty($newContents)) { + $documentation->contents()->createMany($newContents); + } + } + + private function getData(Content $content): array + { + return [ + 'title' => $content->getTitle(), + 'content' => $content->getContent(), + ]; + } +} diff --git a/app/application/app/Services/DocumentationVersion/BuilderCommand.php b/app/application/app/Services/DocumentationVersion/BuilderCommand.php new file mode 100644 index 0000000..6ae0a00 --- /dev/null +++ b/app/application/app/Services/DocumentationVersion/BuilderCommand.php @@ -0,0 +1,19 @@ +isPublic() !== null) { + $query->where('is_public', $documentationVersionBuilderDto->isPublic()); + } + + return $query; + } +} diff --git a/app/application/app/Services/DocumentationVersion/DocumentationVersionCommandHandler.php b/app/application/app/Services/DocumentationVersion/DocumentationVersionCommandHandler.php new file mode 100644 index 0000000..c57b02d --- /dev/null +++ b/app/application/app/Services/DocumentationVersion/DocumentationVersionCommandHandler.php @@ -0,0 +1,37 @@ +documentationVersions()->create($data); + } + + public function handleUpdate(DocumentationVersion $version, array $data): DocumentationVersion + { + if (isset($data['slug'])) { + $data['slug'] = Str::lower($data['slug']); + } + + $version->update($data); + $version->touch(); + + return $version; + } + + public function handleDestroy(DocumentationVersion $version): void + { + $version->update([ + 'slug' => $version->slug . '#delete:' . $version->id, + ]); + $version->delete(); + } +} diff --git a/app/application/app/View/Components/Volt/Forms/Checkbox.php b/app/application/app/View/Components/Volt/Forms/Checkbox.php index b9f432f..fbc94d1 100644 --- a/app/application/app/View/Components/Volt/Forms/Checkbox.php +++ b/app/application/app/View/Components/Volt/Forms/Checkbox.php @@ -27,12 +27,12 @@ private function getTitle(): string private function getCheckboxValue(): string { - return (string) old($this->getRequestName(), $this->checkboxValue); + return (string) $this->checkboxValue; } private function getUserValue(): string { - return (string) $this->userValue; + return (string) old($this->getRequestName(), $this->userValue); } private function getNotCheckedValue(): ?string diff --git a/app/application/app/View/Components/Volt/Forms/Input.php b/app/application/app/View/Components/Volt/Forms/Input.php index 6b65e04..3f744f7 100644 --- a/app/application/app/View/Components/Volt/Forms/Input.php +++ b/app/application/app/View/Components/Volt/Forms/Input.php @@ -13,6 +13,7 @@ public function __construct( private readonly string $type = 'text', private readonly ?string $value = '', private readonly ?string $example = null, + private readonly ?string $allowedCharacters = null, ) { } protected function getName(): string @@ -40,6 +41,11 @@ private function getExample(): ?string return $this->example; } + public function getAllowedCharacters(): ?string + { + return $this->allowedCharacters; + } + /** * @inheritDoc */ @@ -52,6 +58,7 @@ public function render(): View 'type' => $this->getType(), 'value' => $this->getValue(), 'example' => $this->getExample(), + 'allowedCharacters' => $this->getAllowedCharacters(), ]); } } diff --git a/app/application/composer.json b/app/application/composer.json index 71e3603..7d50ce7 100644 --- a/app/application/composer.json +++ b/app/application/composer.json @@ -9,7 +9,8 @@ "intervention/image-laravel": "^1.2", "kor-elf/captcha-rule-for-laravel": "^1.0", "laravel/framework": "^11.0", - "laravel/tinker": "^2.9" + "laravel/tinker": "^2.9", + "staudenmeir/laravel-adjacency-list": "^1.0" }, "require-dev": { "barryvdh/laravel-debugbar": "^3.13", diff --git a/app/application/composer.lock b/app/application/composer.lock index 0b3bc9f..2c6a584 100644 --- a/app/application/composer.lock +++ b/app/application/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5e3226c3090c81571c830e2c383433ab", + "content-hash": "f7873c3a33599ee1ce0bbce99af9db22", "packages": [ { "name": "brick/math", @@ -3381,6 +3381,174 @@ ], "time": "2023-11-08T05:53:05+00:00" }, + { + "name": "staudenmeir/eloquent-has-many-deep-contracts", + "version": "v1.2", + "source": { + "type": "git", + "url": "https://github.com/staudenmeir/eloquent-has-many-deep-contracts.git", + "reference": "bcbe1a921caad7201b324e297eddb696d4bd8647" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staudenmeir/eloquent-has-many-deep-contracts/zipball/bcbe1a921caad7201b324e297eddb696d4bd8647", + "reference": "bcbe1a921caad7201b324e297eddb696d4bd8647", + "shasum": "" + }, + "require": { + "illuminate/database": "^11.0", + "php": "^8.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Staudenmeir\\EloquentHasManyDeepContracts\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jonas Staudenmeir", + "email": "mail@jonas-staudenmeir.de" + } + ], + "description": "Contracts for staudenmeir/eloquent-has-many-deep", + "support": { + "issues": "https://github.com/staudenmeir/eloquent-has-many-deep-contracts/issues", + "source": "https://github.com/staudenmeir/eloquent-has-many-deep-contracts/tree/v1.2" + }, + "time": "2024-01-18T01:20:44+00:00" + }, + { + "name": "staudenmeir/laravel-adjacency-list", + "version": "v1.22", + "source": { + "type": "git", + "url": "https://github.com/staudenmeir/laravel-adjacency-list.git", + "reference": "0ec695e5d4094434f4a7adb956ebd23e228970ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staudenmeir/laravel-adjacency-list/zipball/0ec695e5d4094434f4a7adb956ebd23e228970ea", + "reference": "0ec695e5d4094434f4a7adb956ebd23e228970ea", + "shasum": "" + }, + "require": { + "illuminate/database": "^11.0", + "php": "^8.2", + "staudenmeir/eloquent-has-many-deep-contracts": "^1.2", + "staudenmeir/laravel-cte": "^1.11" + }, + "require-dev": { + "barryvdh/laravel-ide-helper": "^3.0", + "larastan/larastan": "^2.0", + "mockery/mockery": "^1.5.1", + "orchestra/testbench": "^9.0", + "phpunit/phpunit": "^10.5", + "singlestoredb/singlestoredb-laravel": "^1.5.4", + "staudenmeir/eloquent-has-many-deep": "^1.20" + }, + "suggest": { + "barryvdh/laravel-ide-helper": "Provide type hints for attributes and relations." + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Staudenmeir\\LaravelAdjacencyList\\IdeHelperServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Staudenmeir\\LaravelAdjacencyList\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jonas Staudenmeir", + "email": "mail@jonas-staudenmeir.de" + } + ], + "description": "Recursive Laravel Eloquent relationships with CTEs", + "support": { + "issues": "https://github.com/staudenmeir/laravel-adjacency-list/issues", + "source": "https://github.com/staudenmeir/laravel-adjacency-list/tree/v1.22" + }, + "funding": [ + { + "url": "https://paypal.me/JonasStaudenmeir", + "type": "custom" + } + ], + "time": "2024-04-19T12:11:45+00:00" + }, + { + "name": "staudenmeir/laravel-cte", + "version": "v1.11", + "source": { + "type": "git", + "url": "https://github.com/staudenmeir/laravel-cte.git", + "reference": "1e7063021febf9db7e47d8ea602f582b00d55da6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staudenmeir/laravel-cte/zipball/1e7063021febf9db7e47d8ea602f582b00d55da6", + "reference": "1e7063021febf9db7e47d8ea602f582b00d55da6", + "shasum": "" + }, + "require": { + "illuminate/database": "^11.0", + "php": "^8.2" + }, + "require-dev": { + "orchestra/testbench": "^9.0", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Staudenmeir\\LaravelCte\\DatabaseServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Staudenmeir\\LaravelCte\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jonas Staudenmeir", + "email": "mail@jonas-staudenmeir.de" + } + ], + "description": "Laravel queries with common table expressions", + "support": { + "issues": "https://github.com/staudenmeir/laravel-cte/issues", + "source": "https://github.com/staudenmeir/laravel-cte/tree/v1.11" + }, + "funding": [ + { + "url": "https://paypal.me/JonasStaudenmeir", + "type": "custom" + } + ], + "time": "2024-03-09T10:12:45+00:00" + }, { "name": "symfony/clock", "version": "v7.0.5", diff --git a/app/application/database/migrations/2024_04_30_114847_create_documentation.php b/app/application/database/migrations/2024_04_30_114847_create_documentation.php new file mode 100644 index 0000000..db67fa3 --- /dev/null +++ b/app/application/database/migrations/2024_04_30_114847_create_documentation.php @@ -0,0 +1,124 @@ +id(); + $table->string('title', 255); + $table->string('slug', 100); + + $table->unsignedBigInteger('project_id')->index(); + $table->foreign('project_id')->references('id')->on('projects'); + + $table->boolean('is_public')->default(0); + $table->unsignedInteger('status')->index(); + $table->timestamps(); + $table->softDeletes()->index(); + + $table->unique(['project_id', 'slug']); + $table->index(['is_public', 'deleted_at']); + }); + + Schema::create('documentation_categories', function (Blueprint $table) { + $table->id(); + $table->string('slug'); + $table->boolean('is_public')->default(0); + $table->integer('sort')->index(); + + $table->unsignedBigInteger('version_id')->index(); + $table->foreign('version_id')->references('id')->on('documentation_versions'); + + $table->unsignedBigInteger('parent_id')->nullable()->index(); + $table->foreign('parent_id')->references('id')->on('documentation_categories'); + + $table->timestamps(); + $table->softDeletes()->index(); + + $table->index(['slug', 'version_id', 'deleted_at']); + $table->index(['slug', 'version_id', 'is_public', 'deleted_at'], 'slug_version_id_is_public_deleted_at_index'); + + $table->index(['parent_id', 'deleted_at']); + $table->index(['parent_id', 'is_public', 'deleted_at']); + + $table->unique(['version_id', 'slug']); + }); + + Schema::create('documentation_category_content', function (Blueprint $table) { + $table->id(); + + $table->unsignedBigInteger('category_id')->index(); + $table->foreign('category_id')->references('id')->on('documentation_categories'); + + $table->unsignedBigInteger('language_id')->index(); + $table->foreign('language_id')->references('id')->on('project_languages'); + + $table->string('title', 255); + $table->timestamps(); + $table->softDeletes(); + + $table->index(['category_id', 'language_id', 'deleted_at'], 'category_language_deleted_index'); + }); + + Schema::create('documentation', function (Blueprint $table) { + $table->id(); + $table->string('slug'); + + $table->unsignedBigInteger('version_id')->index(); + $table->foreign('version_id')->references('id')->on('documentation_versions'); + + $table->unsignedBigInteger('category_id')->nullable()->index(); + $table->foreign('category_id')->references('id')->on('documentation_categories'); + + $table->boolean('is_public')->default(0)->index(); + $table->integer('sort')->index(); + $table->timestamps(); + $table->softDeletes()->index(); + + $table->index(['slug', 'version_id', 'deleted_at']); + $table->index(['slug', 'version_id', 'is_public', 'deleted_at']); + + $table->index(['category_id', 'deleted_at']); + $table->index(['category_id', 'is_public', 'deleted_at']); + + $table->unique(['version_id', 'slug']); + }); + + Schema::create('documentation_content', function (Blueprint $table) { + $table->id(); + + $table->unsignedBigInteger('documentation_id')->index(); + $table->foreign('documentation_id')->references('id')->on('documentation'); + + $table->unsignedBigInteger('language_id')->index(); + $table->foreign('language_id')->references('id')->on('project_languages'); + + $table->string('title', 255); + $table->longText('content')->nullable(); + $table->timestamps(); + $table->softDeletes(); + + $table->index(['documentation_id', 'language_id', 'deleted_at'], 'content_documentation_language_deleted_index'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('documentation_content'); + Schema::dropIfExists('documentation'); + Schema::dropIfExists('documentation_category_content'); + Schema::dropIfExists('documentation_categories'); + Schema::dropIfExists('documentation_versions'); + } +}; diff --git a/app/application/lang/en.json b/app/application/lang/en.json index 8376977..5c85fc4 100644 --- a/app/application/lang/en.json +++ b/app/application/lang/en.json @@ -256,5 +256,16 @@ "The link has been deleted": "The link has been deleted", "Language not found": "Language not found", "Project not found": "Project not found", - "Translations successfully updated": "Translations successfully updated" + "Translations successfully updated": "Translations successfully updated", + "allowed characters:": "allowed characters:", + "Documentation version created successfully": "Documentation version created successfully", + "Documentation version updated successfully": "Documentation version updated successfully", + "Documentation version successfully removed": "Documentation version successfully removed", + "List": "List", + "Documentation created successfully": "Documentation created successfully", + "Documentation updated successfully": "Documentation updated successfully", + "Documentation successfully removed": "Documentation successfully removed", + "Category successfully created": "Category successfully created", + "Category updated successfully": "Category updated successfully", + "Category successfully deleted": "Category successfully deleted" } diff --git a/app/application/lang/en/admin-sections.php b/app/application/lang/en/admin-sections.php index 1fd967f..ac1b527 100644 --- a/app/application/lang/en/admin-sections.php +++ b/app/application/lang/en/admin-sections.php @@ -13,4 +13,7 @@ 'Links project' => 'Links from the project', 'Translations' => 'Translations', 'Feedback' => 'Feedback', + 'Documentation version' => 'Documentation version', + 'Documentation' => 'Documentation', + 'Categories' => 'Categories', ]; diff --git a/app/application/lang/en/permissions.php b/app/application/lang/en/permissions.php index 31c6a21..359f03a 100644 --- a/app/application/lang/en/permissions.php +++ b/app/application/lang/en/permissions.php @@ -17,4 +17,6 @@ 'ProjectLink' => 'Links from the project', 'ProjectTranslation' => 'Translations', 'ProjectFeedback' => 'Feedback', + 'Documentation' => 'Documentation', + 'DocumentationCategory' => 'Documentation category', ]; diff --git a/app/application/lang/en/validation.php b/app/application/lang/en/validation.php index 90940c1..bb54933 100644 --- a/app/application/lang/en/validation.php +++ b/app/application/lang/en/validation.php @@ -151,6 +151,8 @@ 'no_type' => 'The :attribute can only use: :type.', 'captcha' => 'Failed to pass human verification.', 'http_host' => 'The :attribute must be a valid domain.', + 'parent' => 'A parent cannot refer to itself.', + 'parent_cycle_detected' => 'Category ID occurs in the parent chain.', 'attributes' => [ 'address' => 'address', 'affiliate_url' => 'affiliate URL', @@ -301,5 +303,9 @@ 'translations' => 'translations', 'translations.*.code' => 'translations code', 'translations.*.text' => 'translations', + 'parent_id' => 'parent', + 'content.*.title' => 'title', + 'content.*.content' => 'content', + 'category_id' => 'category', ], ]; diff --git a/app/application/lang/en/version-status.php b/app/application/lang/en/version-status.php new file mode 100644 index 0000000..be2e30d --- /dev/null +++ b/app/application/lang/en/version-status.php @@ -0,0 +1,7 @@ + 'Not Supported', + 'Supported' => 'Supported', + 'CurrentVersion' => 'Current version', + 'FutureVersion' => 'Future version', +]; diff --git a/app/application/lang/ru.json b/app/application/lang/ru.json index 2f1f239..463224c 100644 --- a/app/application/lang/ru.json +++ b/app/application/lang/ru.json @@ -256,5 +256,16 @@ "The link has been deleted": "Ссылка удалена", "Language not found": "Язык не найден", "Project not found": "Проект не найден", - "Translations successfully updated": "Переводы успешно обновлены" + "Translations successfully updated": "Переводы успешно обновлены", + "allowed characters:": "разрешенные символы:", + "Documentation version created successfully": "Версия документации успешно создана", + "Documentation version updated successfully": "Версия документации успешно обновлена", + "Documentation version successfully removed": "Версия документации успешно удалена", + "List": "Список", + "Documentation created successfully": "Документация успешно создана", + "Documentation updated successfully": "Документация успешно обновлена", + "Documentation successfully removed": "Документация успешно удалена", + "Category successfully created": "Категория успешно создана", + "Category updated successfully": "Категория успешно обновлена", + "Category successfully deleted": "Категория успешно удалена" } diff --git a/app/application/lang/ru/admin-sections.php b/app/application/lang/ru/admin-sections.php index c900541..9308e01 100644 --- a/app/application/lang/ru/admin-sections.php +++ b/app/application/lang/ru/admin-sections.php @@ -13,4 +13,7 @@ 'Links project' => 'Ссылки от проекта', 'Translations' => 'Переводы', 'Feedback' => 'Обратная связь', + 'Documentation version' => 'Версия документации', + 'Documentation' => 'Документация', + 'Categories' => 'Категории', ]; diff --git a/app/application/lang/ru/permissions.php b/app/application/lang/ru/permissions.php index cd7954a..48150d5 100644 --- a/app/application/lang/ru/permissions.php +++ b/app/application/lang/ru/permissions.php @@ -17,4 +17,6 @@ 'ProjectLink' => 'Ссылки от проекта', 'ProjectTranslation' => 'Переводы', 'ProjectFeedback' => 'Обратная связь', + 'Documentation' => 'Документация', + 'DocumentationCategory' => 'Категория документации', ]; diff --git a/app/application/lang/ru/validation.php b/app/application/lang/ru/validation.php index d6865b9..f504da7 100644 --- a/app/application/lang/ru/validation.php +++ b/app/application/lang/ru/validation.php @@ -151,6 +151,8 @@ 'no_type' => 'Значение поля :attribute может использовать только: :type.', 'captcha' => 'Не удалось пройти проверку человеком.', 'http_host' => 'Значение поля :attribute не является доменом или имеет некорректный формат.', + 'parent' => 'Родитель не может ссылаться на себя.', + 'parent_cycle_detected' => 'ID категории встречается в родительской цепочке.', 'attributes' => [ 'address' => 'адрес', 'affiliate_url' => 'Партнёрская ссылка', @@ -301,5 +303,9 @@ 'translations' => 'переводы', 'translations.*.code' => 'код перевода', 'translations.*.text' => 'переводы', + 'parent_id' => 'родитель', + 'content.*.title' => 'заголовок', + 'content.*.content' => 'контент', + 'category_id' => 'категория', ], ]; diff --git a/app/application/lang/ru/version-status.php b/app/application/lang/ru/version-status.php new file mode 100644 index 0000000..8993cd3 --- /dev/null +++ b/app/application/lang/ru/version-status.php @@ -0,0 +1,7 @@ + 'Не поддерживается', + 'Supported' => 'Поддерживается', + 'CurrentVersion' => 'Текущая версия', + 'FutureVersion' => 'Будущая версия', +]; diff --git a/app/application/package-lock.json b/app/application/package-lock.json index 85bd7ef..7a04fe0 100644 --- a/app/application/package-lock.json +++ b/app/application/package-lock.json @@ -13,6 +13,7 @@ "notyf": "^3.10.0", "nouislider": "^15.2.0", "onscreen": "^1.4.0", + "prismjs": "^1.29.0", "sass": "^1.47.0", "simplebar": "^5.3.4", "smooth-scroll": "^16.1.3", @@ -1766,6 +1767,14 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", diff --git a/app/application/package.json b/app/application/package.json index 7fd2962..bd59b6e 100644 --- a/app/application/package.json +++ b/app/application/package.json @@ -14,6 +14,7 @@ "notyf": "^3.10.0", "nouislider": "^15.2.0", "onscreen": "^1.4.0", + "prismjs": "^1.29.0", "sass": "^1.47.0", "simplebar": "^5.3.4", "smooth-scroll": "^16.1.3", diff --git a/app/application/resources/prism/app.js b/app/application/resources/prism/app.js new file mode 100644 index 0000000..9a59248 --- /dev/null +++ b/app/application/resources/prism/app.js @@ -0,0 +1,32 @@ +import Prism from 'prismjs'; + +import "prismjs/components/prism-core.js"; +import "prismjs/components/prism-clike.js"; +import "prismjs/components/prism-markup.js"; +import "prismjs/components/prism-markup-templating.js" +import "prismjs/components/prism-go.js"; +import "prismjs/components/prism-go-module.js"; +import "prismjs/components/prism-java.js"; +import "prismjs/components/prism-javascript.js"; +import "prismjs/components/prism-css.js"; +import "prismjs/components/prism-ruby.js"; +import "prismjs/components/prism-python.js"; +import "prismjs/components/prism-c.js"; +import "prismjs/components/prism-csharp.js"; +import "prismjs/components/prism-cpp.js"; +import "prismjs/components/prism-nginx.js"; +import "prismjs/components/prism-docker.js"; +import "prismjs/components/prism-diff.js"; +import "prismjs/components/prism-php.js"; + + +import "prismjs/plugins/toolbar/prism-toolbar.css"; +import "prismjs/plugins/toolbar/prism-toolbar.js"; +import "prismjs/plugins/copy-to-clipboard/prism-copy-to-clipboard.js"; +import "prismjs/plugins/line-numbers/prism-line-numbers.css"; +import "prismjs/plugins/line-numbers/prism-line-numbers.js"; +import "prismjs/plugins/show-language/prism-show-language.js" +import "prismjs/plugins/treeview/prism-treeview.css" +import "prismjs/plugins/treeview/prism-treeview.js" + +import "prismjs/themes/prism.css"; diff --git a/app/application/resources/views/_prism.blade.php b/app/application/resources/views/_prism.blade.php new file mode 100644 index 0000000..63aee8e --- /dev/null +++ b/app/application/resources/views/_prism.blade.php @@ -0,0 +1 @@ +@vite('resources/prism/app.js') diff --git a/app/application/resources/views/admin/_scripts/_click-content-enable.blade.php b/app/application/resources/views/admin/_scripts/_click-content-enable.blade.php new file mode 100644 index 0000000..cc80d15 --- /dev/null +++ b/app/application/resources/views/admin/_scripts/_click-content-enable.blade.php @@ -0,0 +1,33 @@ + diff --git a/app/application/resources/views/admin/projects/documentation-categories/_from.blade.php b/app/application/resources/views/admin/projects/documentation-categories/_from.blade.php new file mode 100644 index 0000000..f83b9b0 --- /dev/null +++ b/app/application/resources/views/admin/projects/documentation-categories/_from.blade.php @@ -0,0 +1,34 @@ +@csrf + + + + + + + + + + +@canany(['create', 'update'], $category) + +@endcanany + +@push('scripts') + @include('admin._scripts._click-content-enable', ['classParent' => 'tab-pane', 'classCheckbox' => 'content-enable', 'classContent' => 'language-content']) +@endpush diff --git a/app/application/resources/views/admin/projects/documentation-categories/_top.blade.php b/app/application/resources/views/admin/projects/documentation-categories/_top.blade.php new file mode 100644 index 0000000..068a81f --- /dev/null +++ b/app/application/resources/views/admin/projects/documentation-categories/_top.blade.php @@ -0,0 +1,25 @@ +
+ @can('create', \App\Models\DocumentationCategory::class) + + + {{ __('Create') }} + + @endcan + @can('viewAny', \App\Models\DocumentationCategory::class) + + + {{ __('List') }} + + @endcan + @can('viewAny', \App\Models\Documentation::class) + + + {{ __('admin-sections.Documentation') }} + + @endcan +
+ diff --git a/app/application/resources/views/admin/projects/documentation-categories/create.blade.php b/app/application/resources/views/admin/projects/documentation-categories/create.blade.php new file mode 100644 index 0000000..83a2b01 --- /dev/null +++ b/app/application/resources/views/admin/projects/documentation-categories/create.blade.php @@ -0,0 +1,19 @@ +@section('meta_title', __('admin-sections.Categories')) +@section('h1') + {{ __('admin-sections.Project') . ': ' . $project->name . ' (' . $version->title . ')' }} +@endsection + + @include('admin.projects.documentation-categories._top') +
+
+
+
+

{{ __('admin-sections.Categories') }}

+
+ @include('admin.projects.documentation-categories._from') +
+
+
+
+
+
diff --git a/app/application/resources/views/admin/projects/documentation-categories/edit.blade.php b/app/application/resources/views/admin/projects/documentation-categories/edit.blade.php new file mode 100644 index 0000000..925ee69 --- /dev/null +++ b/app/application/resources/views/admin/projects/documentation-categories/edit.blade.php @@ -0,0 +1,20 @@ +@section('meta_title', __('admin-sections.Categories')) +@section('h1') + {{ __('admin-sections.Project') . ': ' . $project->name . ' (' . $version->title . ')' }} +@endsection + + @include('admin.projects.documentation-categories._top') +
+
+
+
+

{{ __('admin-sections.Categories') }}

+
+ @method('PUT') + @include('admin.projects.documentation-categories._from') +
+
+
+
+
+
diff --git a/app/application/resources/views/admin/projects/documentation-categories/index.blade.php b/app/application/resources/views/admin/projects/documentation-categories/index.blade.php new file mode 100644 index 0000000..564c747 --- /dev/null +++ b/app/application/resources/views/admin/projects/documentation-categories/index.blade.php @@ -0,0 +1,67 @@ +@section('meta_title', __('admin-sections.Categories')) +@section('h1') + {{ __('admin-sections.Project') . ': ' . $project->name . ' (' . $version->title . ')' }} +@endsection + + @include('admin.projects.documentation-categories._top') +
+
+

{{ __('admin-sections.Categories') }}

+
+ + + + + + + + + + + @foreach($categories as $category) + + + + + + + @endforeach + +
{{ __('validation.attributes.title') }}{{ __('validation.attributes.slug') }}{{ __('validation.attributes.is_public') }}
+ {{ $category->content?->title }} + + {{ $category->slug }} + + @if($category->is_public) + {{ __('Yes') }} + @else + {{ __('No') }} + @endif + + + + + + + @can('delete', $category) +
+ @csrf + @method('DELETE') + +
+ @endcan +
+ +
+
+
+ @push('scripts') + @include('admin._scripts._click-confirm', ['alert' => __('Do you want to delete?')]) + @endpush +
diff --git a/app/application/resources/views/admin/projects/documentation-versions/_from.blade.php b/app/application/resources/views/admin/projects/documentation-versions/_from.blade.php new file mode 100644 index 0000000..584f006 --- /dev/null +++ b/app/application/resources/views/admin/projects/documentation-versions/_from.blade.php @@ -0,0 +1,10 @@ +@csrf + + + + + + +@canany(['create', 'update'], $version) + +@endcanany diff --git a/app/application/resources/views/admin/projects/documentation-versions/_top.blade.php b/app/application/resources/views/admin/projects/documentation-versions/_top.blade.php new file mode 100644 index 0000000..6010f38 --- /dev/null +++ b/app/application/resources/views/admin/projects/documentation-versions/_top.blade.php @@ -0,0 +1,18 @@ + +
+ @can('create', \App\Models\DocumentationVersion::class) + + + {{ __('Create') }} + + @endcan + @can('viewAny', \App\Models\DocumentationVersion::class) + + + {{ __('List') }} + + @endcan +
+ diff --git a/app/application/resources/views/admin/projects/documentation-versions/create.blade.php b/app/application/resources/views/admin/projects/documentation-versions/create.blade.php new file mode 100644 index 0000000..bfd8739 --- /dev/null +++ b/app/application/resources/views/admin/projects/documentation-versions/create.blade.php @@ -0,0 +1,17 @@ +@section('meta_title', __('admin-sections.Documentation version')) +@section('h1', __('admin-sections.Project') . ': ' . $project->name) + + @include('admin.projects.documentation-versions._top') +
+
+
+
+

{{ __('admin-sections.Documentation version') }}

+
+ @include('admin.projects.documentation-versions._from') +
+
+
+
+
+
diff --git a/app/application/resources/views/admin/projects/documentation-versions/edit.blade.php b/app/application/resources/views/admin/projects/documentation-versions/edit.blade.php new file mode 100644 index 0000000..00d86af --- /dev/null +++ b/app/application/resources/views/admin/projects/documentation-versions/edit.blade.php @@ -0,0 +1,18 @@ +@section('meta_title', __('admin-sections.Documentation version')) +@section('h1', __('admin-sections.Project') . ': ' . $project->name) + + @include('admin.projects.documentation-versions._top') +
+
+
+
+

{{ __('admin-sections.Documentation version') }}

+
+ @method('PUT') + @include('admin.projects.documentation-versions._from') +
+
+
+
+
+
diff --git a/app/application/resources/views/admin/projects/documentation-versions/index.blade.php b/app/application/resources/views/admin/projects/documentation-versions/index.blade.php new file mode 100644 index 0000000..f442bee --- /dev/null +++ b/app/application/resources/views/admin/projects/documentation-versions/index.blade.php @@ -0,0 +1,74 @@ +@section('meta_title', __('admin-sections.Documentation version')) +@section('h1', __('admin-sections.Project') . ': ' . $project->name) + + @include('admin.projects.documentation-versions._top') +
+
+

{{ __('admin-sections.Documentation version') }}

+
+ + + + + + + + + + + + @foreach($versions as $version) + + + + + + + + @endforeach + +
{{ __('validation.attributes.title') }}{{ __('validation.attributes.slug') }}{{ __('validation.attributes.status') }}{{ __('validation.attributes.is_public') }}
+ + + {{ $version->title }} + + + {{ $version->slug }} + + {{ $version->status->getTitle() }} + + @if($version->is_public) + {{ __('Yes') }} + @else + {{ __('No') }} + @endif + + + + + + + @can('delete', $version) +
+ @csrf + @method('DELETE') + +
+ @endcan +
+ +
+
+
+ @push('scripts') + @include('admin._scripts._click-confirm', ['alert' => __('Do you want to delete?')]) + @endpush +
diff --git a/app/application/resources/views/admin/projects/documentation-versions/show.blade.php b/app/application/resources/views/admin/projects/documentation-versions/show.blade.php new file mode 100644 index 0000000..2100a7d --- /dev/null +++ b/app/application/resources/views/admin/projects/documentation-versions/show.blade.php @@ -0,0 +1,45 @@ +@section('meta_title', __('admin-sections.Documentation version')) +@section('h1', __('admin-sections.Project') . ': ' . $project->name) + + @include('admin.projects.documentation-versions._top') +
+
+

{{ __('admin-sections.Documentation version') }}: {{ $version->title }}

+
+ + + + + + + + @can('viewAny', \App\Models\Documentation::class) + + + + @endcan + @can('viewAny', \App\Models\DocumentationCategory::class) + + + + @endcan + +
{{ __('admin-sections.Sections') }}
+ + + {{ __('admin-sections.Documentation') }} + +
+ + + {{ __('admin-sections.Categories') }} + +
+
+
+
+
diff --git a/app/application/resources/views/admin/projects/documentations/_from.blade.php b/app/application/resources/views/admin/projects/documentations/_from.blade.php new file mode 100644 index 0000000..e2764f6 --- /dev/null +++ b/app/application/resources/views/admin/projects/documentations/_from.blade.php @@ -0,0 +1,35 @@ +@csrf + + + + + + + + + + +@canany(['create', 'update'], $documentation) + +@endcanany + +@push('scripts') + @include('admin._scripts._click-content-enable', ['classParent' => 'tab-pane', 'classCheckbox' => 'content-enable', 'classContent' => 'language-content']) +@endpush diff --git a/app/application/resources/views/admin/projects/documentations/_top.blade.php b/app/application/resources/views/admin/projects/documentations/_top.blade.php new file mode 100644 index 0000000..9ee8ee1 --- /dev/null +++ b/app/application/resources/views/admin/projects/documentations/_top.blade.php @@ -0,0 +1,25 @@ +
+ @can('create', \App\Models\Documentation::class) + + + {{ __('Create') }} + + @endcan + @can('viewAny', \App\Models\Documentation::class) + + + {{ __('List') }} + + @endcan + @can('viewAny', \App\Models\DocumentationCategory::class) + + + {{ __('admin-sections.Categories') }} + + @endcan +
+ diff --git a/app/application/resources/views/admin/projects/documentations/create.blade.php b/app/application/resources/views/admin/projects/documentations/create.blade.php new file mode 100644 index 0000000..b03d0d5 --- /dev/null +++ b/app/application/resources/views/admin/projects/documentations/create.blade.php @@ -0,0 +1,19 @@ +@section('meta_title', __('admin-sections.Documentation')) +@section('h1') + {{ __('admin-sections.Project') . ': ' . $project->name . ' (' . $version->title . ')' }} +@endsection + + @include('admin.projects.documentations._top') +
+
+
+
+

{{ __('admin-sections.Documentation') }}

+
+ @include('admin.projects.documentations._from') +
+
+
+
+
+
diff --git a/app/application/resources/views/admin/projects/documentations/edit.blade.php b/app/application/resources/views/admin/projects/documentations/edit.blade.php new file mode 100644 index 0000000..b7c9be3 --- /dev/null +++ b/app/application/resources/views/admin/projects/documentations/edit.blade.php @@ -0,0 +1,20 @@ +@section('meta_title', __('admin-sections.Documentation')) +@section('h1') + {{ __('admin-sections.Project') . ': ' . $project->name . ' (' . $version->title . ')' }} +@endsection + + @include('admin.projects.documentations._top') +
+
+
+
+

{{ __('admin-sections.Documentation') }}

+
+ @method('PUT') + @include('admin.projects.documentations._from') +
+
+
+
+
+
diff --git a/app/application/resources/views/admin/projects/documentations/index.blade.php b/app/application/resources/views/admin/projects/documentations/index.blade.php new file mode 100644 index 0000000..3bbeb85 --- /dev/null +++ b/app/application/resources/views/admin/projects/documentations/index.blade.php @@ -0,0 +1,67 @@ +@section('meta_title', __('admin-sections.Documentation')) +@section('h1') + {{ __('admin-sections.Project') . ': ' . $project->name . ' (' . $version->title . ')' }} +@endsection + + @include('admin.projects.documentations._top') +
+
+

{{ __('admin-sections.Documentation') }}

+
+ + + + + + + + + + + @foreach($documentations as $documentation) + + + + + + + @endforeach + +
{{ __('validation.attributes.title') }}{{ __('validation.attributes.slug') }}{{ __('validation.attributes.is_public') }}
+ {{ $documentation->content?->title }} + + {{ $documentation->slug }} + + @if($documentation->is_public) + {{ __('Yes') }} + @else + {{ __('No') }} + @endif + + + + + + + @can('delete', $documentation) +
+ @csrf + @method('DELETE') + +
+ @endcan +
+ +
+
+
+ @push('scripts') + @include('admin._scripts._click-confirm', ['alert' => __('Do you want to delete?')]) + @endpush +
diff --git a/app/application/resources/views/admin/projects/show.blade.php b/app/application/resources/views/admin/projects/show.blade.php index 1217b98..73a2608 100644 --- a/app/application/resources/views/admin/projects/show.blade.php +++ b/app/application/resources/views/admin/projects/show.blade.php @@ -12,6 +12,18 @@
+ + + {{ __('admin-sections.Documentation') }} + +
diff --git a/app/application/resources/views/components/volt/forms/checkbox.blade.php b/app/application/resources/views/components/volt/forms/checkbox.blade.php index a88014b..2acee40 100644 --- a/app/application/resources/views/components/volt/forms/checkbox.blade.php +++ b/app/application/resources/views/components/volt/forms/checkbox.blade.php @@ -2,7 +2,7 @@ @if(!is_null($notCheckedValue)) @endif - + diff --git a/app/application/resources/views/components/volt/forms/input.blade.php b/app/application/resources/views/components/volt/forms/input.blade.php index 5d6d205..e93fb39 100644 --- a/app/application/resources/views/components/volt/forms/input.blade.php +++ b/app/application/resources/views/components/volt/forms/input.blade.php @@ -1,12 +1,15 @@
- @if(!empty($title) || !empty($example)) + @if(!empty($title) || !empty($example) || !empty($allowedCharacters)) @endif - + @error($requestName) {{ $message }} @enderror diff --git a/app/application/resources/views/components/volt/forms/textarea-wysiwyg.blade.php b/app/application/resources/views/components/volt/forms/textarea-wysiwyg.blade.php index 32ddf08..e945606 100644 --- a/app/application/resources/views/components/volt/forms/textarea-wysiwyg.blade.php +++ b/app/application/resources/views/components/volt/forms/textarea-wysiwyg.blade.php @@ -1,6 +1,6 @@
- + @error($requestName) {{ $message }} @enderror @@ -18,7 +18,26 @@ plugins: 'advlist code emoticons link lists table codesample', toolbar: 'bold italic | bullist numlist | link emoticons codesample', referrer_policy: 'origin', + codesample_global_prismjs: true, + codesample_languages: [ + {text: 'HTML/XML', value: 'markup'}, + {text: 'JavaScript', value: 'javascript'}, + {text: 'CSS', value: 'css'}, + {text: 'PHP', value: 'php'}, + {text: 'Ruby', value: 'ruby'}, + {text: 'Python', value: 'python'}, + {text: 'Java', value: 'java'}, + {text: 'C', value: 'c'}, + {text: 'C#', value: 'csharp'}, + {text: 'C++', value: 'cpp'}, + {text: 'Go', value: 'go'}, + {text: 'Nginx', value: 'nginx'}, + {text: 'Docker', value: 'docker'}, + {text: "Treeview", value: "treeview"}, + {text: "Diff", value: "diff"}, + ], }); }); + @include('_prism') @endpushonce diff --git a/app/application/routes/web.php b/app/application/routes/web.php index 9ad267d..cc040a5 100644 --- a/app/application/routes/web.php +++ b/app/application/routes/web.php @@ -33,6 +33,11 @@ Route::get('feedbacks', [\App\Http\Controllers\Admin\Projects\FeedbacksController::class, 'project'])->name('feedbacks.index'); + Route::resource('documentation-versions', \App\Http\Controllers\Admin\Projects\DocumentationVersionController::class)->where(['documentation_version' => '[0-9]+']); + Route::prefix('documentation-versions/{version}')->as('documentation-versions.')->group(function () { + Route::resource('documentations', \App\Http\Controllers\Admin\Projects\DocumentationsController::class)->except(['show'])->where(['documentation' => '[0-9]+']); + Route::resource('categories', \App\Http\Controllers\Admin\Projects\DocumentationCategoriesController::class)->except(['show'])->where(['category' => '[0-9]+']); + })->where(['version' => '[0-9]+']); })->where(['project' => '[0-9]+']); Route::get('feedbacks', [\App\Http\Controllers\Admin\Projects\FeedbacksController::class, 'index'])->name('feedbacks.index'); diff --git a/app/application/vite.config.js b/app/application/vite.config.js index 04dcacf..a03e8af 100644 --- a/app/application/vite.config.js +++ b/app/application/vite.config.js @@ -13,6 +13,8 @@ export default defineConfig({ 'resources/site/scss/app.scss', 'resources/site/js/app.js', + + 'resources/prism/app.js', ], refresh: true, }), From 21026cdf52f2a05b983911a07912f1552b0c5f3d Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Fri, 17 May 2024 21:06:12 +0500 Subject: [PATCH 07/13] Added prismjs support. --- .../resources/views/site/projects/about.blade.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/application/resources/views/site/projects/about.blade.php b/app/application/resources/views/site/projects/about.blade.php index 6f2d3fd..8452bcc 100644 --- a/app/application/resources/views/site/projects/about.blade.php +++ b/app/application/resources/views/site/projects/about.blade.php @@ -2,10 +2,14 @@ @section('h1', $content->title) - {!! $content->description !!} +
{!! $content->description !!}

@foreach($links as $link)

{{ $link->title }}

@endforeach + + @push('scripts') + @include('_prism') + @endpush
From 3bc508b8b09698b1d19cb2ac1b5d3b24264c7d2e Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Fri, 17 May 2024 23:27:18 +0500 Subject: [PATCH 08/13] I fixed it so that prismjs would not overlap when selecting another language. --- app/application/resources/site/scss/app.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/application/resources/site/scss/app.scss b/app/application/resources/site/scss/app.scss index e1378e1..4d7f427 100644 --- a/app/application/resources/site/scss/app.scss +++ b/app/application/resources/site/scss/app.scss @@ -176,6 +176,7 @@ body.mobile-menu-open { padding: 10px 0; margin: 0; border-radius: 0 0 5px 5px; + z-index: 100; a { text-decoration: none; From b6e1c50486ac17c4f25d1fc82f6c1d14e028b10a Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Fri, 17 May 2024 23:39:26 +0500 Subject: [PATCH 09/13] I fixed this so that prismjs doesn't overlap the menu in the mobile version. --- app/application/resources/site/scss/app.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/application/resources/site/scss/app.scss b/app/application/resources/site/scss/app.scss index 4d7f427..3236656 100644 --- a/app/application/resources/site/scss/app.scss +++ b/app/application/resources/site/scss/app.scss @@ -79,7 +79,7 @@ body { margin-left: auto; cursor: pointer; position: relative; - z-index: 10; + z-index: 110; .open { display: block; @@ -138,6 +138,7 @@ body.mobile-menu-open { overflow: auto; width: 100%; height: 100%; + z-index: 100; } } #language { From 78e6a1e396bcb495f00292e3cfebb4f06f739c5d Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Sat, 18 May 2024 20:18:41 +0500 Subject: [PATCH 10/13] Added a check for accessibility permission to the project. --- app/application/app/Http/Middleware/IsProject.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/application/app/Http/Middleware/IsProject.php b/app/application/app/Http/Middleware/IsProject.php index a4b701f..8b6ca5b 100644 --- a/app/application/app/Http/Middleware/IsProject.php +++ b/app/application/app/Http/Middleware/IsProject.php @@ -20,6 +20,13 @@ public function handle(Request $request, \Closure $next): Response \abort(Response::HTTP_NOT_FOUND); } + if ( + $project->is_public === false + && ( $request->user() === null || $request->user()->cannot('view', $project) ) + ) { + \abort(Response::HTTP_FORBIDDEN); + } + return $next($request); } } From 42701a24db7d69c040afafec00f79bdec32e7cd9 Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Sat, 18 May 2024 22:58:05 +0500 Subject: [PATCH 11/13] Fixed an error when saving a project when there is no language data. --- app/application/app/View/Components/Volt/Forms/Languages.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/application/app/View/Components/Volt/Forms/Languages.php b/app/application/app/View/Components/Volt/Forms/Languages.php index f422d6f..d8b0cf3 100644 --- a/app/application/app/View/Components/Volt/Forms/Languages.php +++ b/app/application/app/View/Components/Volt/Forms/Languages.php @@ -19,7 +19,7 @@ protected function getName(): string private function getValue(): array { $value = old($this->getRequestName(), null); - if (\is_null($value)) { + if (\is_null($value) || !isset($value['items']) || !\is_array($value['items'])) { return $this->value; } From e74456ee2fdd393f3d6c20ad9ce9ef4d4a18cb34 Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Sun, 19 May 2024 19:47:02 +0500 Subject: [PATCH 12/13] A documentation section has been added to the site. --- .../app/Dto/Builder/Documentation.php | 10 +- .../app/Dto/Builder/DocumentationCategory.php | 10 +- .../DocumentationCategory/Category.php | 20 +++ .../Project/Translation/Translations.php | 14 +- .../app/Dto/Service/Site/Documentation.php | 47 ++++++ app/application/app/Enums/CacheTag.php | 1 + .../app/Enums/DocumentationVersionStatus.php | 14 +- .../app/Enums/Site/ProjectSection.php | 22 ++- .../Site/DocumentationController.php | 96 +++++++++++ .../Http/Middleware/DocumentationVersion.php | 40 +++++ app/application/app/Models/Documentation.php | 3 + .../app/Models/DocumentationVersion.php | 11 ++ .../app/Providers/AppServiceProvider.php | 2 + .../DocumentationCategoryRepository.php | 15 +- .../Repositories/DocumentationRepository.php | 16 +- .../DocumentationService/DefaultVersion.php | 17 ++ .../Project/DocumentationVersionService.php | 6 + .../Services/Documentation/BuilderCommand.php | 12 +- .../DocumentationCategory/BuilderCommand.php | 12 +- .../Services/Site/DocumentationService.php | 151 ++++++++++++++++++ .../View/Components/Site/ChooseVersion.php | 48 ++++++ .../Components/Site/DocumentationVersion.php | 23 +++ app/application/lang/en/site.php | 5 + app/application/lang/ru/site.php | 5 + .../site/js/_choose-documentation-version.js | 8 + app/application/resources/site/js/app.js | 1 + app/application/resources/site/scss/app.scss | 99 +++++++++++- .../components/site/choose-version.blade.php | 18 +++ .../site/documentation-version.blade.php | 10 ++ .../resources/views/layout/site.blade.php | 7 + .../projects/documentation/category.blade.php | 13 ++ .../projects/documentation/index.blade.php | 13 ++ .../no-default-version.blade.php | 6 + .../projects/documentation/view.blade.php | 9 ++ app/application/routes/web-project.php | 16 ++ 35 files changed, 766 insertions(+), 34 deletions(-) create mode 100644 app/application/app/Dto/Builder/DocumentationCategory/Category.php create mode 100644 app/application/app/Dto/Service/Site/Documentation.php create mode 100644 app/application/app/Http/Controllers/Site/DocumentationController.php create mode 100644 app/application/app/Http/Middleware/DocumentationVersion.php create mode 100644 app/application/app/ServiceResults/Site/DocumentationService/DefaultVersion.php create mode 100644 app/application/app/Services/Site/DocumentationService.php create mode 100644 app/application/app/View/Components/Site/ChooseVersion.php create mode 100644 app/application/app/View/Components/Site/DocumentationVersion.php create mode 100644 app/application/resources/site/js/_choose-documentation-version.js create mode 100644 app/application/resources/views/components/site/choose-version.blade.php create mode 100644 app/application/resources/views/components/site/documentation-version.blade.php create mode 100644 app/application/resources/views/site/projects/documentation/category.blade.php create mode 100644 app/application/resources/views/site/projects/documentation/index.blade.php create mode 100644 app/application/resources/views/site/projects/documentation/no-default-version.blade.php create mode 100644 app/application/resources/views/site/projects/documentation/view.blade.php diff --git a/app/application/app/Dto/Builder/Documentation.php b/app/application/app/Dto/Builder/Documentation.php index fdec08d..c0202de 100644 --- a/app/application/app/Dto/Builder/Documentation.php +++ b/app/application/app/Dto/Builder/Documentation.php @@ -2,14 +2,22 @@ namespace App\Dto\Builder; +use App\Dto\Builder\DocumentationCategory\Category; + final readonly class Documentation { public function __construct( - private ?bool $isPublic = null, + private ?bool $isPublic = null, + private ?Category $categoryId = null, ) { } public function isPublic(): ?bool { return $this->isPublic; } + + public function getCategoryId(): ?Category + { + return $this->categoryId; + } } diff --git a/app/application/app/Dto/Builder/DocumentationCategory.php b/app/application/app/Dto/Builder/DocumentationCategory.php index 242a20c..7bdc68c 100644 --- a/app/application/app/Dto/Builder/DocumentationCategory.php +++ b/app/application/app/Dto/Builder/DocumentationCategory.php @@ -2,14 +2,22 @@ namespace App\Dto\Builder; +use App\Dto\Builder\DocumentationCategory\Category; + final readonly class DocumentationCategory { public function __construct( - private ?bool $isPublic = null, + private ?bool $isPublic = null, + private ?Category $parentId = null, ) { } public function isPublic(): ?bool { return $this->isPublic; } + + public function getParentId(): ?Category + { + return $this->parentId; + } } diff --git a/app/application/app/Dto/Builder/DocumentationCategory/Category.php b/app/application/app/Dto/Builder/DocumentationCategory/Category.php new file mode 100644 index 0000000..73e0e3a --- /dev/null +++ b/app/application/app/Dto/Builder/DocumentationCategory/Category.php @@ -0,0 +1,20 @@ +categoryId; + } + + public function isCategoryNull(): bool + { + return $this->getCategoryId() === null; + } +} diff --git a/app/application/app/Dto/Service/Admin/Project/Translation/Translations.php b/app/application/app/Dto/Service/Admin/Project/Translation/Translations.php index ed702cb..f7831bb 100644 --- a/app/application/app/Dto/Service/Admin/Project/Translation/Translations.php +++ b/app/application/app/Dto/Service/Admin/Project/Translation/Translations.php @@ -2,6 +2,7 @@ namespace App\Dto\Service\Admin\Project\Translation; +use App\Enums\DocumentationVersionStatus; use App\Exceptions\Dto\Admin\Project\Transaction\TranslationsException; final class Translations @@ -23,7 +24,7 @@ public function getTranslations(): array public static function getTranslationCodes(): array { - return [ + $translations = [ 'site.Menu', 'site.Powered by service', 'site.About project', @@ -38,6 +39,17 @@ public static function getTranslationCodes(): array 'site.attributes.message', 'site.Message sent successfully', 'Server Error', + 'site.Documentation', + 'site.Documentation not created', + 'site.Choose version', + 'site.alert-status-not-supported', + 'site.alert-status-future', ]; + + foreach (DocumentationVersionStatus::cases() as $status) { + $translations[] = $status->getCodeForTranslation(); + } + + return $translations; } } diff --git a/app/application/app/Dto/Service/Site/Documentation.php b/app/application/app/Dto/Service/Site/Documentation.php new file mode 100644 index 0000000..a8d141c --- /dev/null +++ b/app/application/app/Dto/Service/Site/Documentation.php @@ -0,0 +1,47 @@ +project; + } + + public function getVersion(): DocumentationVersion + { + return $this->version; + } + + public function getWebsiteTranslations(): WebsiteTranslations + { + return $this->websiteTranslations; + } + + public function getUser(): ?User + { + return $this->user; + } + + public function toArray(): array + { + return [ + 'project' => $this->getProject(), + 'version' => $this->getVersion(), + 'websiteTranslations' => $this->getWebsiteTranslations(), + ]; + } +} diff --git a/app/application/app/Enums/CacheTag.php b/app/application/app/Enums/CacheTag.php index 316900c..a9e7b6f 100644 --- a/app/application/app/Enums/CacheTag.php +++ b/app/application/app/Enums/CacheTag.php @@ -9,6 +9,7 @@ enum CacheTag: string { case Project = 'project'; case ProjectTranslation = 'project_translation'; + case DocumantationVersion = 'documantation_version'; public function getCache(): TaggedCache { diff --git a/app/application/app/Enums/DocumentationVersionStatus.php b/app/application/app/Enums/DocumentationVersionStatus.php index 14ab233..7cb4c9d 100644 --- a/app/application/app/Enums/DocumentationVersionStatus.php +++ b/app/application/app/Enums/DocumentationVersionStatus.php @@ -2,6 +2,7 @@ namespace App\Enums; +use App\Services\WebsiteTranslations; use Illuminate\Support\Collection; enum DocumentationVersionStatus: int @@ -11,9 +12,18 @@ enum DocumentationVersionStatus: int case CurrentVersion = 100; case FutureVersion = 150; - public function getTitle(): string + public function getTitle(?WebsiteTranslations $websiteTranslations = null): string { - return __('version-status.' . $this->name); + if (\is_null($websiteTranslations)) { + return __($this->getCodeForTranslation()); + } + + return $websiteTranslations->translate($this->getCodeForTranslation()); + } + + public function getCodeForTranslation(): string + { + return 'version-status.' . $this->name; } public static function toArray(): array diff --git a/app/application/app/Enums/Site/ProjectSection.php b/app/application/app/Enums/Site/ProjectSection.php index 740c7d8..b61c8ec 100644 --- a/app/application/app/Enums/Site/ProjectSection.php +++ b/app/application/app/Enums/Site/ProjectSection.php @@ -5,15 +5,18 @@ use App\Models\Project; use App\Models\ProjectLanguage; -enum ProjectSection +enum ProjectSection: string { - case Home; - case Feedback; - case FeedbackSend; + case Home = 'home'; + case Feedback = 'feedback'; + case FeedbackSend = 'feedback.send'; + case Documentation = 'documentation'; + case DocumentationVersion = 'documentation.version'; + case DocumentationCategory = 'documentation.category'; + case DocumentationView = 'documentation.view'; - public function url(Project $project, ?ProjectLanguage $language = null): string + public function url(Project $project, ?ProjectLanguage $language = null, array $parameters = []): string { - $parameters = []; $prefixProject = ''; if ($project->http_host === null) { $prefixProject = 'project.'; @@ -26,12 +29,7 @@ public function url(Project $project, ?ProjectLanguage $language = null): string $prefixLanguage = '-language'; } - $route = match ($this) { - self::Home => \route($prefixProject . 'home' . $prefixLanguage, $parameters, false), - - self::Feedback => \route($prefixProject . 'feedback' . $prefixLanguage, $parameters, false), - self::FeedbackSend => \route($prefixProject . 'feedback.send' . $prefixLanguage, $parameters, false), - }; + $route = \route($prefixProject . $this->value . $prefixLanguage, $parameters, false); return $project->http_host . $route; } diff --git a/app/application/app/Http/Controllers/Site/DocumentationController.php b/app/application/app/Http/Controllers/Site/DocumentationController.php new file mode 100644 index 0000000..feb935e --- /dev/null +++ b/app/application/app/Http/Controllers/Site/DocumentationController.php @@ -0,0 +1,96 @@ +get('project'); + $websiteTranslations = $request->get('websiteTranslations'); + + $result = $this->documentationService->defaultVersion($project, $request->user()); + if ($result->isError()) { + if ($result->getCode() === 404) { + return view('site.projects.documentation.no-default-version', [ + 'project' => $project, + 'websiteTranslations' => $websiteTranslations, + ]); + } + $this->errors($result); + } + + $url = \App\Enums\Site\ProjectSection::DocumentationVersion->url($project, $websiteTranslations->getLanguage(), ['version' => $result->getVersion()->slug]); + /** + * 302 redirect because the documentation version can change at any time. + */ + return redirect($url, 302); + } + + public function index(Request $request): View + { + $documentation = new Documentation( + project: $request->get('project'), + version: $request->get('version'), + websiteTranslations: $request->get('websiteTranslations'), + user: $request->user(), + ); + $result = $this->documentationService->index($documentation); + + if ($result->isError()) { + $this->errors($result); + } + + return view('site.projects.documentation.index', $result->getData()); + } + + public function category(string $slug, Request $request): View + { + $documentation = new Documentation( + project: $request->get('project'), + version: $request->get('version'), + websiteTranslations: $request->get('websiteTranslations'), + user: $request->user(), + ); + $result = $this->documentationService->category($slug, $documentation); + + if ($result->isError()) { + $this->errors($result); + } + if ($result->isTranslation()) { + return $this->viewPageWithoutTranslation($result); + } + + return view('site.projects.documentation.category', $result->getData()); + } + + public function view(string $slug, Request $request): View + { + $documentation = new Documentation( + project: $request->get('project'), + version: $request->get('version'), + websiteTranslations: $request->get('websiteTranslations'), + user: $request->user(), + ); + $result = $this->documentationService->view($slug, $documentation); + + if ($result->isError()) { + $this->errors($result); + } + if ($result->isTranslation()) { + return $this->viewPageWithoutTranslation($result); + } + + return view('site.projects.documentation.view', $result->getData()); + } +} diff --git a/app/application/app/Http/Middleware/DocumentationVersion.php b/app/application/app/Http/Middleware/DocumentationVersion.php new file mode 100644 index 0000000..e71fb39 --- /dev/null +++ b/app/application/app/Http/Middleware/DocumentationVersion.php @@ -0,0 +1,40 @@ +get('project'); + $versionSlug = $request->route()?->parameter('version'); + if ($versionSlug === null || $project === null) { + \abort(Response::HTTP_NOT_FOUND); + } + + $seconds = 3600; + $version = CacheTag::DocumantationVersion->getCache()->remember(self::class . $project->id . '-' . $versionSlug, $seconds, function () use ($project, $versionSlug) { + return $project->documentationVersions()->where('slug', $versionSlug)->first() ?? false; + }); + if ($version === false) { + \abort(Response::HTTP_NOT_FOUND); + } + + unset($request->route()->parameters['version']); + + $request->attributes->set('version', $version); + + if ( + $version->is_public === false + && ( $request->user() === null || $request->user()->cannot('view', $version) ) + ) { + \abort(Response::HTTP_FORBIDDEN); + } + + return $next($request); + } +} diff --git a/app/application/app/Models/Documentation.php b/app/application/app/Models/Documentation.php index e237937..dd11136 100644 --- a/app/application/app/Models/Documentation.php +++ b/app/application/app/Models/Documentation.php @@ -2,6 +2,8 @@ namespace App\Models; +use App\Models\Scopes\SortScope; +use Illuminate\Database\Eloquent\Attributes\ScopedBy; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -9,6 +11,7 @@ use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\SoftDeletes; +#[ScopedBy([SortScope::class])] final class Documentation extends Model { use HasFactory, SoftDeletes; diff --git a/app/application/app/Models/DocumentationVersion.php b/app/application/app/Models/DocumentationVersion.php index 384b08d..4025555 100644 --- a/app/application/app/Models/DocumentationVersion.php +++ b/app/application/app/Models/DocumentationVersion.php @@ -3,6 +3,7 @@ namespace App\Models; use App\Enums\DocumentationVersionStatus; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -37,6 +38,16 @@ final class DocumentationVersion extends Model 'status', ]; + /** + * The "booted" method of the model. + */ + protected static function booted(): void + { + static::addGlobalScope('status', function (Builder $builder) { + $builder->orderBy('status', 'desc'); + }); + } + /** * Get the attributes that should be cast. * diff --git a/app/application/app/Providers/AppServiceProvider.php b/app/application/app/Providers/AppServiceProvider.php index 20abed2..670578a 100644 --- a/app/application/app/Providers/AppServiceProvider.php +++ b/app/application/app/Providers/AppServiceProvider.php @@ -59,6 +59,8 @@ public function boot(): void Route::pattern('language', '[a-z_]+'); Route::pattern('project', '[a-z0-9_-]+'); + Route::pattern('slug', '[a-z0-9._-]+'); + Route::pattern('version', '[a-z0-9._-]+'); $this->configureRateLimiting(); Gate::define('AdminPanel', [\App\Policies\AdminPanel::class, 'view']); diff --git a/app/application/app/Repositories/DocumentationCategoryRepository.php b/app/application/app/Repositories/DocumentationCategoryRepository.php index 2f9f47f..81e2d65 100644 --- a/app/application/app/Repositories/DocumentationCategoryRepository.php +++ b/app/application/app/Repositories/DocumentationCategoryRepository.php @@ -4,6 +4,7 @@ use App\Contracts\Search; use App\Models\DocumentationCategory; +use App\Models\DocumentationVersion; use App\Models\ProjectLanguage; use App\Services\DocumentationCategory\BuilderCommand; use App\Services\Search\CreateSearchInstanceCommand; @@ -35,9 +36,19 @@ public function getCategoryById(int $id): ?DocumentationCategory return DocumentationCategory::query()->where('id', $id)->first(); } - public function getCategoryByCode(string $code): ?DocumentationCategory + public function getCategoryBySlugWithContent(string $slug, int $versionId, ProjectLanguage $language): ?DocumentationCategory { - return DocumentationCategory::query()->where('code', $code)->first(); + $with = [ + 'content' => function (HasOne $hasOne) use ($language) { + $hasOne->where('language_id', $language->id); + } + ]; + + return DocumentationCategory::query() + ->where('version_id', $versionId) + ->where('slug', $slug) + ->with($with) + ->first(); } public function isExistsSlug(int $versionId, string $slug, ?int $exceptId = null): bool diff --git a/app/application/app/Repositories/DocumentationRepository.php b/app/application/app/Repositories/DocumentationRepository.php index 2a2072f..d6d22e8 100644 --- a/app/application/app/Repositories/DocumentationRepository.php +++ b/app/application/app/Repositories/DocumentationRepository.php @@ -5,9 +5,11 @@ use App\Dto\Builder\Documentation as DocumentationBuilderDto; use App\Contracts\Search; use App\Models\Documentation; +use App\Models\ProjectLanguage; use App\Services\Documentation\BuilderCommand; use App\Services\Search\CreateSearchInstanceCommand; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Support\Str; final readonly class DocumentationRepository @@ -32,9 +34,19 @@ public function getDocumentationById(int $id): ?Documentation return Documentation::query()->where('id', $id)->first(); } - public function getDocumentationByCode(string $code): ?Documentation + public function getDocumentationBySlugWithContent(string $slug, int $versionId, ProjectLanguage $language): ?Documentation { - return Documentation::query()->where('code', $code)->first(); + $with = [ + 'content' => function (HasOne $hasOne) use ($language) { + $hasOne->where('language_id', $language->id); + } + ]; + + return Documentation::query() + ->where('version_id', $versionId) + ->where('slug', $slug) + ->with($with) + ->first(); } public function isExistsSlug(int $versionId, string $slug, ?int $exceptId = null): bool diff --git a/app/application/app/ServiceResults/Site/DocumentationService/DefaultVersion.php b/app/application/app/ServiceResults/Site/DocumentationService/DefaultVersion.php new file mode 100644 index 0000000..f329d70 --- /dev/null +++ b/app/application/app/ServiceResults/Site/DocumentationService/DefaultVersion.php @@ -0,0 +1,17 @@ +version; + } +} diff --git a/app/application/app/Services/Admin/Project/DocumentationVersionService.php b/app/application/app/Services/Admin/Project/DocumentationVersionService.php index ae9d904..22ef657 100644 --- a/app/application/app/Services/Admin/Project/DocumentationVersionService.php +++ b/app/application/app/Services/Admin/Project/DocumentationVersionService.php @@ -5,6 +5,7 @@ use App\Dto\QuerySettingsDto; use App\Dto\Builder\DocumentationVersion as DocumentationVersionBuilderDto; use App\Dto\Service\Admin\Project\DocumentationVersion\StoreUpdate; +use App\Enums\CacheTag; use App\Enums\DocumentationVersionStatus; use App\Models\DocumentationVersion; use App\Models\User; @@ -14,6 +15,7 @@ use App\ServiceResults\ServiceResultError; use App\ServiceResults\ServiceResultSuccess; use App\ServiceResults\StoreUpdateResult; +use App\Services\ClearCacheCommandHandler; use App\Services\DocumentationVersion\DocumentationVersionCommandHandler; use App\Services\Service; use Illuminate\Support\Facades\DB; @@ -24,6 +26,7 @@ public function __construct( private readonly ProjectRepository $projectRepository, private readonly DocumentationVersionRepository $documentationVersionRepository, private readonly DocumentationVersionCommandHandler $documentationVersionCommandHandler, + private readonly ClearCacheCommandHandler $clearCacheCommandHandler, ) { } public function index(int $projectId, DocumentationVersionBuilderDto $documentationVersionBuilderDto, QuerySettingsDto $querySettingsDto, User $user): ServiceResultError | ServiceResultArray @@ -138,6 +141,7 @@ public function store(int $projectId, StoreUpdate $data, User $user): ServiceRes $dataVersion = $this->getDataVersion($data); return $this->documentationVersionCommandHandler->handleStore($project, $dataVersion); }); + $this->clearCacheCommandHandler->byTag(CacheTag::DocumantationVersion); } catch (\Throwable $e) { report($e); return $this->errService(__('Server Error')); @@ -174,6 +178,7 @@ public function update(int $projectId, int $versionId, StoreUpdate $data, User $ $dataVersion = $this->getDataVersion($data); return $this->documentationVersionCommandHandler->handleUpdate($version, $dataVersion); }); + $this->clearCacheCommandHandler->byTag(CacheTag::DocumantationVersion); } catch (\Throwable $e) { report($e); return $this->errService(__('Server Error')); @@ -202,6 +207,7 @@ public function destroy(int $projectId, int $versionId, User $user): ServiceResu DB::transaction(function () use ($version) { $this->documentationVersionCommandHandler->handleDestroy($version); }); + $this->clearCacheCommandHandler->byTag(CacheTag::DocumantationVersion); } catch (\Throwable $e) { report($e); return $this->errService(__('Server Error')); diff --git a/app/application/app/Services/Documentation/BuilderCommand.php b/app/application/app/Services/Documentation/BuilderCommand.php index 24485cc..65c14bf 100644 --- a/app/application/app/Services/Documentation/BuilderCommand.php +++ b/app/application/app/Services/Documentation/BuilderCommand.php @@ -10,10 +10,12 @@ { public function execute(Relation | Builder $query, DocumentationBuilderDto $documentationBuilderDto): Relation | Builder { - if ($documentationBuilderDto->isPublic() !== null) { - $query->where('is_public', $documentationBuilderDto->isPublic()); - } - - return $query; + return $query + ->when($documentationBuilderDto->isPublic(), function (Builder $query) use ($documentationBuilderDto) { + $query->where('is_public', $documentationBuilderDto->isPublic()); + }) + ->when($documentationBuilderDto->getCategoryId(), function (Builder $query) use ($documentationBuilderDto) { + $query->where('category_id', $documentationBuilderDto->getCategoryId()->getCategoryId()); + }); } } diff --git a/app/application/app/Services/DocumentationCategory/BuilderCommand.php b/app/application/app/Services/DocumentationCategory/BuilderCommand.php index c7e2b8c..2eaf1d6 100644 --- a/app/application/app/Services/DocumentationCategory/BuilderCommand.php +++ b/app/application/app/Services/DocumentationCategory/BuilderCommand.php @@ -10,10 +10,12 @@ { public function execute(Relation | Builder $query, DocumentationCategoryBuilderDto $documentationCategoryBuilderDto): Relation | Builder { - if ($documentationCategoryBuilderDto->isPublic() !== null) { - $query->where('is_public', $documentationCategoryBuilderDto->isPublic()); - } - - return $query; + return $query + ->when($documentationCategoryBuilderDto->isPublic(), function (Builder $query) use ($documentationCategoryBuilderDto) { + $query->where('is_public', $documentationCategoryBuilderDto->isPublic()); + }) + ->when($documentationCategoryBuilderDto->getParentId(), function (Builder $query) use ($documentationCategoryBuilderDto) { + $query->where('parent_id', $documentationCategoryBuilderDto->getParentId()->getCategoryId()); + }); } } diff --git a/app/application/app/Services/Site/DocumentationService.php b/app/application/app/Services/Site/DocumentationService.php new file mode 100644 index 0000000..1c9bfaf --- /dev/null +++ b/app/application/app/Services/Site/DocumentationService.php @@ -0,0 +1,151 @@ +cannot('viewAny', DocumentationVersion::class)) { + $isPublic = 1; + } + $version = CacheTag::DocumantationVersion->getCache() + ->remember(self::class . $project->id . '_' . $isPublic ?? 0, $seconds, function () use ($project, $isPublic) { + $versions = $project->documentationVersions() + ->when($isPublic, function (Builder $query) { + $query->where('is_public', 1); + }) + ->limit(10) + ->get(); + return $versions->firstWhere('status', DocumentationVersionStatus::CurrentVersion) + ?? + $versions->first() ?? false; + }); + + if ($version === false) { + return $this->errNotFound(__('Not Found')); + } + + return new DefaultVersion($version); + } + + public function index(Documentation $documentation): ServiceResultError | ServiceResultArray + { + return $this->result(array_merge($documentation->toArray(), [ + 'categories' => $this->getCategories($documentation, null), + 'documentations' => $this->getDocumentations($documentation, null), + ])); + } + + public function category(string $slug, Documentation $documentation): ServiceResultError | PagePossibleWithoutTranslation + { + $category = $this->documentationCategoryRepository->getCategoryBySlugWithContent($slug, $documentation->getVersion()->id, $documentation->getWebsiteTranslations()->getLanguage()); + if (!$category) { + return $this->errNotFound(__('Not Found')); + } + if ( + $category->is_public === false && + ($documentation->getUser() === null || $documentation->getUser()->cannot('view', $category)) + ) { + return $this->errFobidden(__('Access is denied')); + } + + $data = array_merge($documentation->toArray(), [ + 'category' => $category, + 'categories' => $this->getCategories($documentation, $category->id), + 'documentations' => $this->getDocumentations($documentation, $category->id), + ]); + return $this->resultSitePage($documentation->getProject(), $documentation->getWebsiteTranslations(), $data, \is_null($category->content?->title)); + } + + public function view(string $slug, Documentation $documentation): ServiceResultError | PagePossibleWithoutTranslation + { + $document = $this->documentationRepository->getDocumentationBySlugWithContent($slug, $documentation->getVersion()->id, $documentation->getWebsiteTranslations()->getLanguage()); + if (!$document) { + return $this->errNotFound(__('Not Found')); + } + if ( + $document->is_public === false && + ($documentation->getUser() === null || $documentation->getUser()->cannot('view', $document)) + ) { + return $this->errFobidden(__('Access is denied')); + } + + $data = array_merge($documentation->toArray(), [ + 'documentation' => $document, + ]); + return $this->resultSitePage($documentation->getProject(), $documentation->getWebsiteTranslations(), $data, \is_null($document->content?->title)); + } + + private function getCategories(Documentation $documentation, ?int $parentId): Collection + { + $isPublic = null; + if ($documentation->getUser() === null || $documentation->getUser()->cannot('viewAny', DocumentationCategory::class)) { + $isPublic = true; + } + $builderDto = new DocumentationCategoryBuilderDto( + isPublic: $isPublic, + parentId: new DocumentationCategoryBuilderDto\Category($parentId), + ); + $with = [ + 'content' => function (HasOne $hasOne) use ($documentation) { + $hasOne->where('language_id', $documentation->getWebsiteTranslations()->getLanguage()->id); + } + ]; + return $this->documentationCategoryRepository->getCategories( + $documentation->getVersion()->id, + $builderDto, + $with + )->all(); + } + + private function getDocumentations(Documentation $documentation, ?int $categoryId): Collection + { + $isPublic = null; + if ($documentation->getUser() === null || $documentation->getUser()->cannot('viewAny', ModelDocumentation::class)) { + $isPublic = true; + } + $builderDto = new DocumentationBuilderDto( + isPublic: $isPublic, + categoryId: new DocumentationCategoryBuilderDto\Category($categoryId), + ); + $with = [ + 'content' => function (HasOne $hasOne) use ($documentation) { + $hasOne->where('language_id', $documentation->getWebsiteTranslations()->getLanguage()->id); + } + ]; + return $this->documentationRepository->getDocumentations( + $documentation->getVersion()->id, + $builderDto, + $with + )->all(); + } +} diff --git a/app/application/app/View/Components/Site/ChooseVersion.php b/app/application/app/View/Components/Site/ChooseVersion.php new file mode 100644 index 0000000..e2f63fb --- /dev/null +++ b/app/application/app/View/Components/Site/ChooseVersion.php @@ -0,0 +1,48 @@ +user) || $this->user->cannot('viewAny', DocumentationVersion::class)) { + $isPublic = 1; + } + + $seconds = 3600 * 12; + $versions = CacheTag::DocumantationVersion->getCache() + ->remember(self::class . $this->project->id . '-' . $isPublic ?? 0, $seconds, function () use ($isPublic) { + return $this->project->documentationVersions() + ->when($isPublic, function (Builder $query) { + $query->where('is_public', 1); + }) + ->get(); + }); + + return view('components.site.choose-version', [ + 'websiteTranslations' => $this->websiteTranslations, + 'versions' => $versions, + 'version' => $this->version, + 'project' => $this->project, + ]); + } +} diff --git a/app/application/app/View/Components/Site/DocumentationVersion.php b/app/application/app/View/Components/Site/DocumentationVersion.php new file mode 100644 index 0000000..dfd0dc0 --- /dev/null +++ b/app/application/app/View/Components/Site/DocumentationVersion.php @@ -0,0 +1,23 @@ + $this->websiteTranslations, + 'version' => $this->version, + ]); + } +} diff --git a/app/application/lang/en/site.php b/app/application/lang/en/site.php index edb4844..21b7500 100644 --- a/app/application/lang/en/site.php +++ b/app/application/lang/en/site.php @@ -16,4 +16,9 @@ 'email' => 'email', 'message' => 'message', ], + 'Documentation' => 'Documentation', + 'Documentation not created' => 'Documentation not created', + 'Choose version' => 'Choose version', + 'alert-status-not-supported' => 'WARNING! You are viewing the documentation for an older version.', + 'alert-status-future' => 'WARNING! You\'re browsing the documentation for an upcoming version. The documentation and features of this release are subject to change.', ]; diff --git a/app/application/lang/ru/site.php b/app/application/lang/ru/site.php index c7fa6e4..188f154 100644 --- a/app/application/lang/ru/site.php +++ b/app/application/lang/ru/site.php @@ -16,4 +16,9 @@ 'email' => 'email', 'message' => 'сообщение', ], + 'Documentation' => 'Документация', + 'Documentation not created' => 'Документация не создана', + 'Choose version' => 'Выберите версию', + 'alert-status-not-supported' => 'ВНИМАНИЕ! Вы просматриваете документацию для старой версии.', + 'alert-status-future' => 'ВНИМАНИЕ! Вы просматриваете документацию к будущей версии. Документация и функции этого выпуска могут быть изменены.', ]; diff --git a/app/application/resources/site/js/_choose-documentation-version.js b/app/application/resources/site/js/_choose-documentation-version.js new file mode 100644 index 0000000..ba5ac52 --- /dev/null +++ b/app/application/resources/site/js/_choose-documentation-version.js @@ -0,0 +1,8 @@ +let blockDocumentationVersion = document.querySelector('#documentation-version'); +blockDocumentationVersion.querySelector('.documentation-version__button').addEventListener('click', (e) => { + if (blockDocumentationVersion.classList.contains('active')) { + blockDocumentationVersion.classList.remove('active'); + } else { + blockDocumentationVersion.classList.add('active'); + } +}); diff --git a/app/application/resources/site/js/app.js b/app/application/resources/site/js/app.js index ade6ee6..9d10b37 100644 --- a/app/application/resources/site/js/app.js +++ b/app/application/resources/site/js/app.js @@ -1,2 +1,3 @@ import './_menu.js'; import './_choose-language.js'; +import './_choose-documentation-version.js'; diff --git a/app/application/resources/site/scss/app.scss b/app/application/resources/site/scss/app.scss index 3236656..3438838 100644 --- a/app/application/resources/site/scss/app.scss +++ b/app/application/resources/site/scss/app.scss @@ -43,7 +43,8 @@ body { grid-template-columns: 1fr 1fr; grid-template-areas: "logo menu" - "language language"; + "language language" + "documentation-version documentation-version"; padding: 8px; box-shadow: 0 3px 9px rgba(0,0,0,0.48); background: #eee; @@ -195,6 +196,77 @@ body.mobile-menu-open { border-radius: 5px 5px 0 0; } } + +#documentation-version { + grid-area: documentation-version; + margin-top: 10px; + + .status { + font-size: 13px; + color: #444; + display: block; + } + .status.status__100 { + color: #0a6f4d; + } + .status.status__50 { + color: #0a53be; + } + .status.status__0 { + color: #6c0e22; + } +} +.documentation-version__block { + position: relative; +} +.documentation-version__button { + border: 0; + box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.48); + background: #ccc; + width: 100%; + cursor: pointer; + font-size: 18px; + position: relative; + padding: 10px; + border-radius: 5px; + color: #000; +} +.documentation-version__button__str { + position: absolute; + top: calc(50% - 8px); + right: 10px; +} +.documentation-version__list { + background: #ddd; + display: none; + position: absolute; + top: 100%; + left: 0; + width: 100%; + list-style: none; + box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.48); + padding: 10px 0; + margin: 0; + border-radius: 0 0 5px 5px; + z-index: 100; + + a { + text-decoration: none; + color: #000; + font-size: 16px; + display: block; + padding: 10px 20px; + } +} +#documentation-version.active { + .documentation-version__list { + display: block; + } + .documentation-version__button { + border-radius: 5px 5px 0 0; + } +} + .main-container { display: flex; flex-direction: column; @@ -225,6 +297,15 @@ body.mobile-menu-open { font-weight: bold; } } +.alert-version { + padding: 20px; + margin-top: 20px; + box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.5); + font-size: 16px; +} +.alert-version.version-status-not-supported { + color: #6c0e22; +} @media (min-width: 1000px) { body { @@ -232,8 +313,8 @@ body.mobile-menu-open { margin: 0 auto; } .header { - grid-template-areas: "logo language"; - grid-template-columns: 1fr 200px; + grid-template-areas: "logo documentation-version language"; + grid-template-columns: 1fr 200px 200px; } .header_logo { width: 150px; @@ -250,6 +331,7 @@ body.mobile-menu-open { .main-container { flex-direction: row; } + #language { margin-right: 10px; } @@ -259,6 +341,17 @@ body.mobile-menu-open { .language__button { padding: 7px 10px 7px 0; } + + #documentation-version { + margin-right: 10px; + } + .documentation-version__block { + margin-top: 0px; + } + .documentation-version__button { + padding: 7px 10px 7px 0; + } + #menu { flex: none; background: #eee; diff --git a/app/application/resources/views/components/site/choose-version.blade.php b/app/application/resources/views/components/site/choose-version.blade.php new file mode 100644 index 0000000..9f6820c --- /dev/null +++ b/app/application/resources/views/components/site/choose-version.blade.php @@ -0,0 +1,18 @@ +
+
+ + +
+
diff --git a/app/application/resources/views/components/site/documentation-version.blade.php b/app/application/resources/views/components/site/documentation-version.blade.php new file mode 100644 index 0000000..816cec0 --- /dev/null +++ b/app/application/resources/views/components/site/documentation-version.blade.php @@ -0,0 +1,10 @@ +@if($version->status === \App\Enums\DocumentationVersionStatus::NotSupported) +
+ {{ $websiteTranslations->translate('site.alert-status-not-supported') }} +
+@endif +@if($version->status === \App\Enums\DocumentationVersionStatus::FutureVersion) +
+ {{ $websiteTranslations->translate('site.alert-status-future') }} +
+@endif diff --git a/app/application/resources/views/layout/site.blade.php b/app/application/resources/views/layout/site.blade.php index 570c009..2026b25 100644 --- a/app/application/resources/views/layout/site.blade.php +++ b/app/application/resources/views/layout/site.blade.php @@ -30,6 +30,9 @@ + @if($attributes->has('documentationVersion')) + + @endif
@@ -37,11 +40,15 @@
+ @if($attributes->has('documentationVersion')) + + @endif

@yield('h1', '')

@includeWhen($errors->any(), 'layout.site._errors', ['errors' => $errors->all()]) diff --git a/app/application/resources/views/site/projects/documentation/category.blade.php b/app/application/resources/views/site/projects/documentation/category.blade.php new file mode 100644 index 0000000..06e4931 --- /dev/null +++ b/app/application/resources/views/site/projects/documentation/category.blade.php @@ -0,0 +1,13 @@ +@section('meta_title', $category->content?->title . ' - ' . $project->name . ' ' . $version->title) +@section('h1', $category->content?->title) + + + @foreach($categories as $category) + @continue(! $category->content?->title) +

{{ $category->content->title }}

+ @endforeach + @foreach($documentations as $documentation) + @continue(! $documentation->content?->title) +

{{ $documentation->content->title }}

+ @endforeach +
diff --git a/app/application/resources/views/site/projects/documentation/index.blade.php b/app/application/resources/views/site/projects/documentation/index.blade.php new file mode 100644 index 0000000..9391de0 --- /dev/null +++ b/app/application/resources/views/site/projects/documentation/index.blade.php @@ -0,0 +1,13 @@ +@section('meta_title', $websiteTranslations->translate('site.Documentation') . ' - ' . $project->name . ' ' . $version->title) +@section('h1', $websiteTranslations->translate('site.Documentation')) + + + @foreach($categories as $category) + @continue(! $category->content?->title) +

{{ $category->content->title }}

+ @endforeach + @foreach($documentations as $documentation) + @continue(! $documentation->content?->title) +

{{ $documentation->content->title }}

+ @endforeach +
diff --git a/app/application/resources/views/site/projects/documentation/no-default-version.blade.php b/app/application/resources/views/site/projects/documentation/no-default-version.blade.php new file mode 100644 index 0000000..464dbae --- /dev/null +++ b/app/application/resources/views/site/projects/documentation/no-default-version.blade.php @@ -0,0 +1,6 @@ +@section('meta_title', $websiteTranslations->translate('site.Documentation not created')) +@section('h1', $websiteTranslations->translate('site.Documentation not created')) + + + + diff --git a/app/application/resources/views/site/projects/documentation/view.blade.php b/app/application/resources/views/site/projects/documentation/view.blade.php new file mode 100644 index 0000000..c831e15 --- /dev/null +++ b/app/application/resources/views/site/projects/documentation/view.blade.php @@ -0,0 +1,9 @@ +@section('meta_title', $documentation->content?->title . ' - ' . $project->name . ' ' . $version->title) +@section('h1', $documentation->content?->title) + + +
{!! $documentation->content->content !!}
+ @push('scripts') + @include('_prism') + @endpush +
diff --git a/app/application/routes/web-project.php b/app/application/routes/web-project.php index 330eff2..8dfef3e 100644 --- a/app/application/routes/web-project.php +++ b/app/application/routes/web-project.php @@ -6,6 +6,22 @@ Route::middleware([\App\Http\Middleware\IsProject::class, \App\Http\Middleware\IsWebsiteTranslations::class])->group(function () { Route::get('/language/{language}', [\App\Http\Controllers\Site\ProjectsController::class, 'index'])->name('home-language'); + Route::prefix('docs')->group(function () { + Route::get('/', [\App\Http\Controllers\Site\DocumentationController::class, 'defaultVersion'])->name('documentation'); + Route::get('language/{language}', [\App\Http\Controllers\Site\DocumentationController::class, 'defaultVersion'])->name('documentation-language'); + + Route::middleware([\App\Http\Middleware\DocumentationVersion::class])->prefix('{version}')->group(function () { + Route::get('/', [\App\Http\Controllers\Site\DocumentationController::class, 'index'])->name('documentation.version'); + Route::get('language/{language}', [\App\Http\Controllers\Site\DocumentationController::class, 'index'])->name('documentation.version-language'); + + Route::get('category/{slug}', [\App\Http\Controllers\Site\DocumentationController::class, 'category'])->name('documentation.category'); + Route::get('category/{slug}/language/{language}', [\App\Http\Controllers\Site\DocumentationController::class, 'category'])->name('documentation.category-language'); + + Route::get('{slug}', [\App\Http\Controllers\Site\DocumentationController::class, 'view'])->name('documentation.view'); + Route::get('{slug}/language/{language}', [\App\Http\Controllers\Site\DocumentationController::class, 'view'])->name('documentation.view-language'); + }); + }); + Route::prefix('feedback')->group(function () { Route::get('/', [\App\Http\Controllers\Site\FeedbackController::class, 'index'])->name('feedback'); Route::get('language/{language}', [\App\Http\Controllers\Site\FeedbackController::class, 'index'])->name('feedback-language'); From 9f6924935de0ec819c917a266e8843f9314d4051 Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Sun, 19 May 2024 21:01:04 +0500 Subject: [PATCH 13/13] Fixed .dockerignore. --- app/.dockerignore | 14 ++++++++++++++ app/docker/.dockerignore | 8 -------- 2 files changed, 14 insertions(+), 8 deletions(-) create mode 100644 app/.dockerignore delete mode 100644 app/docker/.dockerignore diff --git a/app/.dockerignore b/app/.dockerignore new file mode 100644 index 0000000..3058dfc --- /dev/null +++ b/app/.dockerignore @@ -0,0 +1,14 @@ +**/.env +**/*.env +**/.env.example +**/storage/app/* +**/storage/debugbar +**/storage/framework/cache/* +**/storage/framework/sessions/* +**/storage/framework/views/* +**/storage/framework/testing/* +**/storage/logs/* +**/vendor/ +**/node_modules/ +**/public/build/ +**/public/storage diff --git a/app/docker/.dockerignore b/app/docker/.dockerignore deleted file mode 100644 index c286cf9..0000000 --- a/app/docker/.dockerignore +++ /dev/null @@ -1,8 +0,0 @@ -**/*.env -**/*.env.example -application/bootstrap/cache/* -application/storage/** -application/vendor/** -application/node_modules/** -application/public/build/** -application/public/storage