Added the ability to add links to the project.

This commit is contained in:
2024-04-15 22:53:34 +05:00
parent 4583908230
commit 63ea5dac92
29 changed files with 756 additions and 13 deletions

View File

@@ -0,0 +1,10 @@
<?php declare(strict_types=1);
namespace App\Dto\Builder;
final readonly class ProjectLink
{
public function __construct(
) { }
}

View 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;
}
}

View File

@@ -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;
}
}

View File

@@ -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
{

View File

@@ -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());
}
}

View File

@@ -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),
);
}
}

View File

@@ -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,
);
}
}

View File

@@ -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);
}
}

View 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);
}
}

View 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');
}
}

View 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();
}
}

View 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(),
];
}
}

View 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;
}
}

View File

@@ -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();
}
}