diff --git a/app/application/app/Dto/Builder/ProjectLink.php b/app/application/app/Dto/Builder/ProjectLink.php
new file mode 100644
index 0000000..4d858d4
--- /dev/null
+++ b/app/application/app/Dto/Builder/ProjectLink.php
@@ -0,0 +1,10 @@
+linkBuilderDto;
+ }
+}
diff --git a/app/application/app/Dto/Service/Admin/Project/Link/StoreUpdate.php b/app/application/app/Dto/Service/Admin/Project/Link/StoreUpdate.php
new file mode 100644
index 0000000..755719b
--- /dev/null
+++ b/app/application/app/Dto/Service/Admin/Project/Link/StoreUpdate.php
@@ -0,0 +1,35 @@
+title;
+ }
+
+ public function getLink(): string
+ {
+ return $this->link;
+ }
+
+ public function getSort(): int
+ {
+ return $this->sort;
+ }
+
+ public function getLanguageId(): ?int
+ {
+ return $this->languageId;
+ }
+}
diff --git a/app/application/app/Enums/Permission.php b/app/application/app/Enums/Permission.php
index c4e7416..bdf8d14 100644
--- a/app/application/app/Enums/Permission.php
+++ b/app/application/app/Enums/Permission.php
@@ -9,6 +9,7 @@ enum Permission: string
case User = 'user';
case Project = 'project';
case ProjectContent = 'project-content';
+ case ProjectLink = 'project-link';
public function getPermissions(): array
{
diff --git a/app/application/app/Http/Controllers/Admin/Projects/LinksController.php b/app/application/app/Http/Controllers/Admin/Projects/LinksController.php
new file mode 100644
index 0000000..c937267
--- /dev/null
+++ b/app/application/app/Http/Controllers/Admin/Projects/LinksController.php
@@ -0,0 +1,100 @@
+user();
+ $data = $request->getDto();
+ $querySettingsDto = new QuerySettingsDto(
+ limit: 20,
+ page: $data->getPage(),
+ queryWith: ['language']
+ );
+
+ $result = $this->linkService->index($projectId, $data->getLinkBuilderDto(), $querySettingsDto, $user);
+ if ($result->isError()) {
+ $this->errors($result);
+ }
+
+ return view('admin/projects/links/index', $result->getData());
+ }
+
+ public function create(int $projectId, Request $request): View
+ {
+ $user = $request->user();
+ $result = $this->linkService->create($projectId, $user);
+ if ($result->isError()) {
+ $this->errors($result);
+ }
+
+ return view('admin/projects/links/create', $result->getData());
+ }
+
+ public function edit(int $projectId, int $id, Request $request): View
+ {
+ $user = $request->user();
+ $result = $this->linkService->edit($projectId, $id, $user);
+ if ($result->isError()) {
+ $this->errors($result);
+ }
+
+ return view('admin/projects/links/edit', $result->getData());
+ }
+
+ public function store(int $projectId, StoreUpdateRequest $request): RedirectResponse
+ {
+ $data = $request->getDto();
+ $user = $request->user();
+ $result = $this->linkService->store($projectId, $data, $user);
+ if ($result->isError()) {
+ return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
+ }
+
+ return redirect()->route('admin.projects.links.edit', [
+ 'project' => $projectId,
+ 'link' => $result->getModel()->id,
+ ])->withSuccess($result->getMessage());
+ }
+
+ public function update(int $projectId, int $id, StoreUpdateRequest $request): RedirectResponse
+ {
+ $data = $request->getDto();
+ $user = $request->user();
+ $result = $this->linkService->update($projectId, $id, $data, $user);
+ if ($result->isError()) {
+ return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
+ }
+
+ return redirect()->route('admin.projects.links.edit', [
+ 'project' => $projectId,
+ 'link' => $result->getModel()->id,
+ ])->withSuccess($result->getMessage());
+ }
+
+ public function destroy(int $projectId, int $id, Request $request): RedirectResponse
+ {
+ $user = $request->user();
+ $result = $this->linkService->destroy($projectId, $id, $user);
+ if ($result->isError()) {
+ return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
+ }
+
+ return redirect()->route('admin.projects.links.index', ['project' => $projectId])->withSuccess($result->getMessage());
+ }
+}
diff --git a/app/application/app/Http/Requests/Admin/Projects/Links/IndexRequest.php b/app/application/app/Http/Requests/Admin/Projects/Links/IndexRequest.php
new file mode 100644
index 0000000..b51ebe7
--- /dev/null
+++ b/app/application/app/Http/Requests/Admin/Projects/Links/IndexRequest.php
@@ -0,0 +1,29 @@
+ ['nullable', 'numeric', 'min:1']
+ ];
+ }
+
+ public function getDto(): Index
+ {
+ return new Index(
+ linkBuilderDto: new ProjectLink(),
+ page: (int) $this->input('page', 1),
+ );
+ }
+}
diff --git a/app/application/app/Http/Requests/Admin/Projects/Links/StoreUpdateRequest.php b/app/application/app/Http/Requests/Admin/Projects/Links/StoreUpdateRequest.php
new file mode 100644
index 0000000..e9ac344
--- /dev/null
+++ b/app/application/app/Http/Requests/Admin/Projects/Links/StoreUpdateRequest.php
@@ -0,0 +1,38 @@
+ ['required', 'string', 'max:255'],
+ 'link' => ['required', 'string', 'max:255', 'url:http,https'],
+ 'sort' => ['required', 'integer', 'min:-1000', 'max:1000'],
+ 'language_id' => ['nullable', 'integer', 'min:1'],
+ ];
+ }
+
+ public function getDto(): StoreUpdate
+ {
+ $languageId = $this->input('language_id');
+ if (! \is_null($languageId)) {
+ $languageId = (int) $languageId;
+ }
+
+ return new StoreUpdate(
+ title: $this->input('title'),
+ link: $this->input('link'),
+ sort: (int) $this->input('sort'),
+ languageId: $languageId,
+ );
+ }
+}
diff --git a/app/application/app/Models/Project.php b/app/application/app/Models/Project.php
index 6b94acb..5e2862b 100644
--- a/app/application/app/Models/Project.php
+++ b/app/application/app/Models/Project.php
@@ -52,4 +52,9 @@ public function contents(): HasMany
{
return $this->hasMany(ProjectContent::class);
}
+
+ public function links(): HasMany
+ {
+ return $this->hasMany(ProjectLink::class);
+ }
}
diff --git a/app/application/app/Models/ProjectLink.php b/app/application/app/Models/ProjectLink.php
new file mode 100644
index 0000000..738150f
--- /dev/null
+++ b/app/application/app/Models/ProjectLink.php
@@ -0,0 +1,46 @@
+ 100,
+ ];
+
+ protected $fillable = [
+ 'title',
+ 'link',
+ 'sort',
+ 'language_id',
+ ];
+
+ public function language(): BelongsTo
+ {
+ return $this->belongsTo(ProjectLanguage::class);
+ }
+}
diff --git a/app/application/app/Policies/ProjectLinkPolicy.php b/app/application/app/Policies/ProjectLinkPolicy.php
new file mode 100644
index 0000000..a8ed52a
--- /dev/null
+++ b/app/application/app/Policies/ProjectLinkPolicy.php
@@ -0,0 +1,34 @@
+hasPermission('project-link.view');
+ }
+
+ public function view(User $user, ProjectLink $link): bool
+ {
+ return $user->hasPermission('project-link.view');
+ }
+
+ public function create(User $user): bool
+ {
+ return $user->hasPermission('project-link.create');
+ }
+
+ public function update(User $user, ProjectLink $link): bool
+ {
+ return $user->hasPermission('project-link.update');
+ }
+
+ public function delete(User $user, ProjectLink $link): bool
+ {
+ return $user->hasPermission('project-link.delete');
+ }
+}
diff --git a/app/application/app/Repositories/ProjectLinkRepository.php b/app/application/app/Repositories/ProjectLinkRepository.php
new file mode 100644
index 0000000..3491e7a
--- /dev/null
+++ b/app/application/app/Repositories/ProjectLinkRepository.php
@@ -0,0 +1,32 @@
+builderCommand->execute(
+ query: ProjectLink::query()->where('project_id', $projectId)->with($with),
+ projectLinkBuilderDto: $projectLinkBuilderDto,
+ );
+
+ return $this->createSearchInstanceCommand->execute($query);
+ }
+
+ public function getLinkById(int $id): ?ProjectLink
+ {
+ return ProjectLink::query()->where('id', $id)->first();
+ }
+}
diff --git a/app/application/app/Services/Admin/Project/LinkService.php b/app/application/app/Services/Admin/Project/LinkService.php
new file mode 100644
index 0000000..e2b6930
--- /dev/null
+++ b/app/application/app/Services/Admin/Project/LinkService.php
@@ -0,0 +1,203 @@
+projectRepository->getProjectById($projectId);
+ if (\is_null($project)) {
+ return $this->errNotFound(__('Not Found'));
+ }
+
+ if ($user->cannot('viewAny', ProjectLink::class)) {
+ return $this->errFobidden(__('Access is denied'));
+ }
+
+ $links = $this->projectLinkRepository->getLinksFromProject(
+ $project->id,
+ $linkBuilderDto,
+ $querySettingsDto->getQueryWith()
+ )->pagination(
+ $querySettingsDto->getLimit(),
+ $querySettingsDto->getPage()
+ );
+
+ return $this->result([
+ 'links' => $links,
+ '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', ProjectLink::class)) {
+ return $this->errFobidden(__('Access is denied'));
+ }
+
+ return $this->result([
+ 'link' => new ProjectLink(),
+ 'project' => $project,
+ ]);
+ }
+
+ public function edit(int $projectId, int $linkId, User $user): ServiceResultError | ServiceResultArray
+ {
+ $project = $this->projectRepository->getProjectById($projectId);
+ if (\is_null($project)) {
+ return $this->errNotFound(__('Not Found'));
+ }
+
+ $link = $this->projectLinkRepository->getLinkById($linkId);
+ if (\is_null($link)) {
+ return $this->errNotFound(__('Not Found'));
+ }
+
+ if ($user->cannot('view', $link)) {
+ return $this->errFobidden(__('Access is denied'));
+ }
+
+ return $this->result([
+ 'link' => $link,
+ 'project' => $project,
+ ]);
+ }
+
+ public function store(int $projectId, StoreUpdate $data, User $user): ServiceResultError | StoreUpdateResult
+ {
+ if ($user->cannot('create', ProjectLink::class)) {
+ return $this->errFobidden(__('Access is denied'));
+ }
+
+ $project = $this->projectRepository->getProjectById($projectId);
+ if (\is_null($project)) {
+ return $this->errNotFound(__('Not Found'));
+ }
+
+ if (
+ $data->getLanguageId() !== null
+ && $project->languages()->where('id', $data->getLanguageId())->exists() === false
+ ) {
+ return $this->errValidate(
+ __('validation.exists', ['attribute' => __('validation.attributes.language_id')]),
+ ['language_id' => __('validation.exists', ['attribute' => __('validation.attributes.language_id')])]
+ );
+ }
+
+ try {
+ $link = DB::transaction(function () use ($data, $project, $user) {
+ $dataLink = $this->getDataLink($data);
+ return $this->projectLinkCommandHandler->handleStore($project, $dataLink);
+ });
+ } catch (\Throwable $e) {
+ report($e);
+ return $this->errService(__('Server Error'));
+ }
+
+ return $this->resultStoreUpdateModel($link, __('The link was successfully created'));
+ }
+
+ public function update(int $projectId, int $linkId, StoreUpdate $data, User $user): ServiceResultError | StoreUpdateResult
+ {
+ $project = $this->projectRepository->getProjectById($projectId);
+ if (\is_null($project)) {
+ return $this->errNotFound(__('Not Found'));
+ }
+
+ if (
+ $data->getLanguageId() !== null
+ && $project->languages()->where('id', $data->getLanguageId())->exists() === false
+ ) {
+ return $this->errValidate(
+ __('validation.exists', ['attribute' => __('validation.attributes.language_id')]),
+ ['language_id' => __('validation.exists', ['attribute' => __('validation.attributes.language_id')])]
+ );
+ }
+
+ $link = $this->projectLinkRepository->getLinkById($linkId);
+ if (\is_null($link)) {
+ return $this->errNotFound(__('Not Found'));
+ }
+
+ if ($user->cannot('update', $link)) {
+ return $this->errFobidden(__('Access is denied'));
+ }
+
+ try {
+ $link = DB::transaction(function () use ($data, $link) {
+ $dataLink = $this->getDataLink($data);
+ return $this->projectLinkCommandHandler->handleUpdate($link, $dataLink);
+ });
+ } catch (\Throwable $e) {
+ report($e);
+ return $this->errService(__('Server Error'));
+ }
+
+ return $this->resultStoreUpdateModel($link, __('The link was successfully updated'));
+ }
+
+ public function destroy(int $projectId, int $linkId, User $user): ServiceResultError|ServiceResultSuccess
+ {
+ $project = $this->projectRepository->getProjectById($projectId);
+ if (\is_null($project)) {
+ return $this->errNotFound(__('Not Found'));
+ }
+
+ $link = $this->projectLinkRepository->getLinkById($linkId);
+ if (\is_null($link)) {
+ return $this->errNotFound(__('Not Found'));
+ }
+
+ if ($user->cannot('delete', $link)) {
+ return $this->errFobidden(__('Access is denied'));
+ }
+
+ try {
+ DB::transaction(function () use ($link) {
+ $this->projectLinkCommandHandler->handleDestroy($link);
+ });
+ } catch (\Throwable $e) {
+ report($e);
+ return $this->errService(__('Server Error'));
+ }
+
+ return $this->ok(__('The link has been deleted'));
+ }
+
+ private function getDataLink(StoreUpdate $data): array
+ {
+ return [
+ 'title' => $data->getTitle(),
+ 'link' => $data->getLink(),
+ 'sort' => $data->getSort(),
+ 'language_id' => $data->getLanguageId(),
+ ];
+ }
+}
diff --git a/app/application/app/Services/ProjectLink/BuilderCommand.php b/app/application/app/Services/ProjectLink/BuilderCommand.php
new file mode 100644
index 0000000..22c62b9
--- /dev/null
+++ b/app/application/app/Services/ProjectLink/BuilderCommand.php
@@ -0,0 +1,15 @@
+links()->create($data);
+ }
+
+ public function handleUpdate(ProjectLink $link, array $data): ProjectLink
+ {
+ $link->update($data);
+ return $link;
+ }
+
+ public function handleDestroy(ProjectLink $link): void
+ {
+ $link->delete();
+ }
+}
diff --git a/app/application/lang/en.json b/app/application/lang/en.json
index a4fd870..c30cdf8 100644
--- a/app/application/lang/en.json
+++ b/app/application/lang/en.json
@@ -249,5 +249,9 @@
"There was an error adding a language": "There was an error adding a language",
"Select images": "Select images",
"loading": "loading",
- "Project information has been successfully updated": "Project information has been successfully updated"
+ "Project information has been successfully updated": "Project information has been successfully updated",
+ "in all languages": "in all languages",
+ "The link was successfully created": "The link was successfully created",
+ "The link was successfully updated": "The link was successfully updated",
+ "The link has been deleted": "The link has been deleted",
}
diff --git a/app/application/lang/en/admin-sections.php b/app/application/lang/en/admin-sections.php
index 503d7f2..946a4c3 100644
--- a/app/application/lang/en/admin-sections.php
+++ b/app/application/lang/en/admin-sections.php
@@ -10,4 +10,5 @@
'About project' => 'About the project',
'Languages' => 'Languages',
'Last update' => 'Last update',
+ 'Links project' => 'Links from the project',
];
diff --git a/app/application/lang/en/permissions.php b/app/application/lang/en/permissions.php
index 063e379..88b02d2 100644
--- a/app/application/lang/en/permissions.php
+++ b/app/application/lang/en/permissions.php
@@ -3,12 +3,16 @@
return [
'Role' => 'User group',
'User' => 'Users',
- 'Allowed to watch' => 'Allowed to watch',
+
+ 'Allowed to watch' => 'Allowed to watch',
'Allowed to create' => 'Allowed to create',
- 'Allowed to edit' => 'Allowed to edit',
+ 'Allowed to edit' => 'Allowed to edit',
'Allowed to delete' => 'Allowed to delete',
+
'Administrative panel allowed' => 'Administrative panel allowed',
- 'AdminPanel' => 'Administrative panel allowed',
- 'Project' => 'Projects',
+ 'AdminPanel' => 'Administrative panel allowed',
+
+ 'Project' => 'Projects',
'ProjectContent' => 'About the project',
+ 'ProjectLink' => 'Links from the project',
];
diff --git a/app/application/lang/en/validation.php b/app/application/lang/en/validation.php
index c8a8fc9..11fdad1 100644
--- a/app/application/lang/en/validation.php
+++ b/app/application/lang/en/validation.php
@@ -292,5 +292,7 @@
'logo' => 'logo',
'logo.file' => 'logo file',
'logo.delete' => 'remove logo',
+ 'link' => 'link',
+ 'language_id' => 'language',
],
];
diff --git a/app/application/lang/ru.json b/app/application/lang/ru.json
index 0547a0e..9a93a3c 100644
--- a/app/application/lang/ru.json
+++ b/app/application/lang/ru.json
@@ -249,5 +249,9 @@
"There was an error adding a language": "Произошла ошибка при добавлении языка",
"Select images": "Выберите изображения",
"loading": "загрузка",
- "Project information has been successfully updated": "Информация о проекте успешно обновлена"
+ "Project information has been successfully updated": "Информация о проекте успешно обновлена",
+ "in all languages": "на всех языках",
+ "The link was successfully created": "Ссылка успешно создана",
+ "The link was successfully updated": "Ссылка успешно обновлена",
+ "The link has been deleted": "Ссылка удалена"
}
diff --git a/app/application/lang/ru/admin-sections.php b/app/application/lang/ru/admin-sections.php
index 06e553e..5ec768a 100644
--- a/app/application/lang/ru/admin-sections.php
+++ b/app/application/lang/ru/admin-sections.php
@@ -10,4 +10,5 @@
'About project' => 'О проекте',
'Languages' => 'Языки',
'Last update' => 'Последнее обновление',
+ 'Links project' => 'Ссылки от проекта',
];
diff --git a/app/application/lang/ru/permissions.php b/app/application/lang/ru/permissions.php
index fac9c56..dfe8cd7 100644
--- a/app/application/lang/ru/permissions.php
+++ b/app/application/lang/ru/permissions.php
@@ -3,13 +3,16 @@
return [
'Role' => 'Группа пользователей',
'User' => 'Пользователи',
- 'CaptchaToken' => 'Токены для создания капчи',
- 'Allowed to watch' => 'Разрешено смотреть',
+
+ 'Allowed to watch' => 'Разрешено смотреть',
'Allowed to create' => 'Разрешено создать',
- 'Allowed to edit' => 'Разрешено редактировать',
+ 'Allowed to edit' => 'Разрешено редактировать',
'Allowed to delete' => 'Разрешено удалять',
+
'Administrative panel allowed' => 'Административная панель разрешена',
- 'AdminPanel' => 'Административная панель разрешена',
- 'Project' => 'Проекты',
+ 'AdminPanel' => 'Административная панель разрешена',
+
+ 'Project' => 'Проекты',
'ProjectContent' => 'О проекте',
+ 'ProjectLink' => 'Ссылки от проекта',
];
diff --git a/app/application/lang/ru/validation.php b/app/application/lang/ru/validation.php
index 22e30cc..5f48b83 100644
--- a/app/application/lang/ru/validation.php
+++ b/app/application/lang/ru/validation.php
@@ -292,5 +292,7 @@
'logo' => 'логотип',
'logo.file' => 'файл логотипа',
'logo.delete' => 'удалить логотип',
+ 'link' => 'ссылка',
+ 'language_id' => 'язык',
],
];
diff --git a/app/application/resources/views/admin/projects/links/_from.blade.php b/app/application/resources/views/admin/projects/links/_from.blade.php
new file mode 100644
index 0000000..0fd6dd4
--- /dev/null
+++ b/app/application/resources/views/admin/projects/links/_from.blade.php
@@ -0,0 +1,11 @@
+@csrf
+
{{ __('validation.attributes.title') }} | +{{ __('validation.attributes.link') }} | +{{ __('validation.attributes.language_id') }} | ++ |
---|---|---|---|
+ + + {{ $link->title }} + + | ++ {{ $link->link }} + | ++ {{ $link->language?->title ?? __('in all languages') }} + | ++ @can('delete', $link) + + @endcan + | +