Added the ability to add links to the project.
This commit is contained in:
10
app/application/app/Dto/Builder/ProjectLink.php
Normal file
10
app/application/app/Dto/Builder/ProjectLink.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Builder;
|
||||
|
||||
final readonly class ProjectLink
|
||||
{
|
||||
public function __construct(
|
||||
|
||||
) { }
|
||||
}
|
21
app/application/app/Dto/Service/Admin/Project/Link/Index.php
Normal file
21
app/application/app/Dto/Service/Admin/Project/Link/Index.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Service\Admin\Project\Link;
|
||||
|
||||
use App\Dto\Builder\ProjectLink;
|
||||
use App\Dto\Service\Pages;
|
||||
|
||||
final readonly class Index extends Pages
|
||||
{
|
||||
public function __construct(
|
||||
private ProjectLink $linkBuilderDto,
|
||||
int $page,
|
||||
) {
|
||||
parent::__construct($page);
|
||||
}
|
||||
|
||||
public function getLinkBuilderDto(): ProjectLink
|
||||
{
|
||||
return $this->linkBuilderDto;
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Service\Admin\Project\Link;
|
||||
|
||||
use App\Dto\Service\Dto;
|
||||
|
||||
final readonly class StoreUpdate extends Dto
|
||||
{
|
||||
public function __construct(
|
||||
private string $title,
|
||||
private string $link,
|
||||
private int $sort,
|
||||
private ?int $languageId,
|
||||
) { }
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function getLink(): string
|
||||
{
|
||||
return $this->link;
|
||||
}
|
||||
|
||||
public function getSort(): int
|
||||
{
|
||||
return $this->sort;
|
||||
}
|
||||
|
||||
public function getLanguageId(): ?int
|
||||
{
|
||||
return $this->languageId;
|
||||
}
|
||||
}
|
@@ -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
|
||||
{
|
||||
|
@@ -0,0 +1,100 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Admin\Projects;
|
||||
|
||||
use App\Dto\QuerySettingsDto;
|
||||
use App\Http\Controllers\Admin\Controller;
|
||||
use App\Http\Requests\Admin\Projects\Links\IndexRequest;
|
||||
use App\Http\Requests\Admin\Projects\Links\StoreUpdateRequest;
|
||||
use App\Services\Admin\Project\LinkService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
final class LinksController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly LinkService $linkService
|
||||
) { }
|
||||
|
||||
public function index(int $projectId, IndexRequest $request): View
|
||||
{
|
||||
$user = $request->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());
|
||||
}
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Admin\Projects\Links;
|
||||
|
||||
use App\Contracts\FormRequestDto;
|
||||
use App\Dto\Builder\ProjectLink;
|
||||
use App\Dto\Service\Admin\Project\Link\Index;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
final class IndexRequest extends FormRequest implements FormRequestDto
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'page' => ['nullable', 'numeric', 'min:1']
|
||||
];
|
||||
}
|
||||
|
||||
public function getDto(): Index
|
||||
{
|
||||
return new Index(
|
||||
linkBuilderDto: new ProjectLink(),
|
||||
page: (int) $this->input('page', 1),
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Admin\Projects\Links;
|
||||
|
||||
use App\Contracts\FormRequestDto;
|
||||
use App\Dto\Service\Admin\Project\Link\StoreUpdate;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
final class StoreUpdateRequest extends FormRequest implements FormRequestDto
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'title' => ['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,
|
||||
);
|
||||
}
|
||||
}
|
@@ -52,4 +52,9 @@ final class Project extends Model implements StorageContract
|
||||
{
|
||||
return $this->hasMany(ProjectContent::class);
|
||||
}
|
||||
|
||||
public function links(): HasMany
|
||||
{
|
||||
return $this->hasMany(ProjectLink::class);
|
||||
}
|
||||
}
|
||||
|
46
app/application/app/Models/ProjectLink.php
Normal file
46
app/application/app/Models/ProjectLink.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Scopes\SortScope;
|
||||
use Illuminate\Database\Eloquent\Attributes\ScopedBy;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
#[ScopedBy([SortScope::class])]
|
||||
final class ProjectLink extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
protected $table = 'project_links';
|
||||
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = false;
|
||||
|
||||
/**
|
||||
* The model's default values for attributes.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $attributes = [
|
||||
'sort' => 100,
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'title',
|
||||
'link',
|
||||
'sort',
|
||||
'language_id',
|
||||
];
|
||||
|
||||
public function language(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(ProjectLanguage::class);
|
||||
}
|
||||
}
|
34
app/application/app/Policies/ProjectLinkPolicy.php
Normal file
34
app/application/app/Policies/ProjectLinkPolicy.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\ProjectLink;
|
||||
use App\Models\User;
|
||||
|
||||
final readonly class ProjectLinkPolicy extends Policy
|
||||
{
|
||||
public function viewAny(User $user): bool
|
||||
{
|
||||
return $user->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');
|
||||
}
|
||||
}
|
32
app/application/app/Repositories/ProjectLinkRepository.php
Normal file
32
app/application/app/Repositories/ProjectLinkRepository.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Contracts\Search;
|
||||
use App\Services\ProjectLink\BuilderCommand;
|
||||
use App\Services\Search\CreateSearchInstanceCommand;
|
||||
use App\Dto\Builder\ProjectLink as ProjectLinkBuilderDto;
|
||||
use App\Models\ProjectLink;
|
||||
|
||||
final readonly class ProjectLinkRepository
|
||||
{
|
||||
public function __construct(
|
||||
private CreateSearchInstanceCommand $createSearchInstanceCommand,
|
||||
private BuilderCommand $builderCommand
|
||||
) { }
|
||||
|
||||
public function getLinksFromProject(int $projectId, ProjectLinkBuilderDto $projectLinkBuilderDto, array $with = []): Search
|
||||
{
|
||||
$query = $this->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();
|
||||
}
|
||||
}
|
203
app/application/app/Services/Admin/Project/LinkService.php
Normal file
203
app/application/app/Services/Admin/Project/LinkService.php
Normal file
@@ -0,0 +1,203 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Admin\Project;
|
||||
|
||||
use App\Dto\Builder\ProjectLink as ProjectLinkBuilderDto;
|
||||
use App\Dto\QuerySettingsDto;
|
||||
use App\Dto\Service\Admin\Project\Link\StoreUpdate;
|
||||
use App\Models\ProjectLink;
|
||||
use App\Models\User;
|
||||
use App\Repositories\ProjectLinkRepository;
|
||||
use App\Repositories\ProjectRepository;
|
||||
use App\Services\ProjectLink\ProjectLinkCommandHandler;
|
||||
use App\Services\Service;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\ServiceResults\ServiceResultArray;
|
||||
use App\ServiceResults\ServiceResultError;
|
||||
use App\ServiceResults\ServiceResultSuccess;
|
||||
use App\ServiceResults\StoreUpdateResult;
|
||||
|
||||
final class LinkService extends Service
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ProjectRepository $projectRepository,
|
||||
private readonly ProjectLinkRepository $projectLinkRepository,
|
||||
private readonly ProjectLinkCommandHandler $projectLinkCommandHandler,
|
||||
) { }
|
||||
|
||||
public function index(int $projectId, ProjectLinkBuilderDto $linkBuilderDto, QuerySettingsDto $querySettingsDto, User $user): ServiceResultError | ServiceResultArray
|
||||
{
|
||||
$project = $this->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(),
|
||||
];
|
||||
}
|
||||
}
|
15
app/application/app/Services/ProjectLink/BuilderCommand.php
Normal file
15
app/application/app/Services/ProjectLink/BuilderCommand.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Services\ProjectLink;
|
||||
|
||||
use App\Dto\Builder\ProjectLink as ProjectLinkBuilderDto;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
|
||||
final readonly class BuilderCommand
|
||||
{
|
||||
public function execute(Relation | Builder $query, ProjectLinkBuilderDto $projectLinkBuilderDto): Relation | Builder
|
||||
{
|
||||
return $query;
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Services\ProjectLink;
|
||||
|
||||
use App\Models\Project;
|
||||
use App\Models\ProjectLink;
|
||||
|
||||
final readonly class ProjectLinkCommandHandler
|
||||
{
|
||||
public function handleStore(Project $project, array $data): ProjectLink
|
||||
{
|
||||
return $project->links()->create($data);
|
||||
}
|
||||
|
||||
public function handleUpdate(ProjectLink $link, array $data): ProjectLink
|
||||
{
|
||||
$link->update($data);
|
||||
return $link;
|
||||
}
|
||||
|
||||
public function handleDestroy(ProjectLink $link): void
|
||||
{
|
||||
$link->delete();
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user