From 45504791c01c2e0baf66d9901cbe956d79cea59d Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Fri, 17 May 2024 21:05:13 +0500 Subject: [PATCH] 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 @@ + @can('viewAny', \App\Models\DocumentationVersion::class) + + + + + {{ __('admin-sections.Documentation') }} + + + + @endcan @can('viewAny', \App\Models\ProjectContent::class) 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, }),