getNodesOfText, createNode, and bulkCreateNodes now require the session user, look up the target text, and respond 403 unless the user owns the text or is an admin. paves the way for moving these endpoints out of the admin-only group.
218 lines
6.8 KiB
PHP
218 lines
6.8 KiB
PHP
<?php
|
|
|
|
namespace App\Node;
|
|
|
|
use App\Exceptions\BadRequestException;
|
|
use App\Node\UseCases\BulkCreateNodesRequest;
|
|
use App\Node\NodeRepository;
|
|
use App\Node\UseCases\BulkCreateNodes;
|
|
use App\Node\UseCases\CreateNode;
|
|
use App\Node\UseCases\CreateNodeRequest;
|
|
use App\Text\Text;
|
|
use App\Text\TextRepository;
|
|
use App\User\User;
|
|
use DomainException;
|
|
use Psr\Http\Message\ResponseInterface as Response;
|
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
|
|
|
class NodeController
|
|
{
|
|
public function __construct(
|
|
private NodeRepository $nodeRepository,
|
|
private TextRepository $textRepository,
|
|
) {}
|
|
|
|
public function getNodesOfText(
|
|
Request $request,
|
|
Response $response,
|
|
int $textId,
|
|
): Response {
|
|
$user = $request->getAttribute('user');
|
|
if (!$user instanceof User) {
|
|
return $this->errorResponse(
|
|
$response,
|
|
401,
|
|
'unauthenticated'
|
|
);
|
|
}
|
|
|
|
$text = $this->textRepository->find($textId);
|
|
|
|
if ($text === null) {
|
|
return $response->withStatus(404);
|
|
}
|
|
|
|
if (!$this->userMayAccessText($user, $text)) {
|
|
return $this->errorResponse(
|
|
$response,
|
|
403,
|
|
'forbidden'
|
|
);
|
|
}
|
|
|
|
$nodes = $this->nodeRepository->findByTextId($textId);
|
|
|
|
$data = array_map(function ($node) {
|
|
return [
|
|
'id' => $node->getId(),
|
|
'title' => $node->getTitle(),
|
|
'parentNodeId' => $node->getParentNode()?->getId(),
|
|
];
|
|
}, $nodes);
|
|
|
|
$response->getBody()->write(json_encode(array_values($data)));
|
|
return $response->withHeader('Content-Type', 'application/json');
|
|
}
|
|
|
|
public function createNode(
|
|
Request $request,
|
|
Response $response,
|
|
CreateNode $createNodeUseCase,
|
|
): Response {
|
|
$user = $request->getAttribute('user');
|
|
if (!$user instanceof User) {
|
|
return $this->errorResponse(
|
|
$response,
|
|
401,
|
|
'unauthenticated'
|
|
);
|
|
}
|
|
|
|
$data = json_decode((string) $request->getBody(), true) ?? [];
|
|
|
|
$textId = isset($data['textId']) ? (int) $data['textId'] : null;
|
|
$title = $data['title'] ?? null;
|
|
$parentNodeId = isset($data['parentNodeId']) ? (int) $data['parentNodeId'] : null;
|
|
|
|
if ($textId !== null) {
|
|
$ownershipResponse = $this->checkTextOwnership(
|
|
$user,
|
|
$textId,
|
|
$response,
|
|
);
|
|
if ($ownershipResponse !== null) {
|
|
return $ownershipResponse;
|
|
}
|
|
}
|
|
|
|
try {
|
|
$node = $createNodeUseCase->execute(new CreateNodeRequest(
|
|
textId: $textId,
|
|
title: $title,
|
|
parentNodeId: $parentNodeId,
|
|
));
|
|
} catch (BadRequestException $e) {
|
|
$response->getBody()->write(json_encode(['error' => $e->getMessage()]));
|
|
return $response->withStatus(400)->withHeader('Content-Type', 'application/json');
|
|
} catch (DomainException $e) {
|
|
$response->getBody()->write(json_encode(['error' => $e->getMessage()]));
|
|
return $response->withStatus(404)->withHeader('Content-Type', 'application/json');
|
|
}
|
|
|
|
$response->getBody()->write(json_encode([
|
|
'id' => $node->getId(),
|
|
'title' => $node->getTitle(),
|
|
'parentNodeId' => $node->getParentNode()?->getId(),
|
|
]));
|
|
return $response->withStatus(201)->withHeader('Content-Type', 'application/json');
|
|
}
|
|
|
|
public function bulkCreateNodes(
|
|
Request $request,
|
|
Response $response,
|
|
BulkCreateNodes $bulkCreateNodesUseCase,
|
|
): Response {
|
|
$user = $request->getAttribute('user');
|
|
if (!$user instanceof User) {
|
|
return $this->errorResponse(
|
|
$response,
|
|
401,
|
|
'unauthenticated'
|
|
);
|
|
}
|
|
|
|
$data = json_decode((string) $request->getBody(), true) ?? [];
|
|
|
|
$textId = isset($data['textId']) ? (int) $data['textId'] : null;
|
|
$parentNodeId = isset($data['parentNodeId']) ? (int) $data['parentNodeId'] : null;
|
|
$titlePrefix = isset($data['titlePrefix']) ? (string) $data['titlePrefix'] : null;
|
|
$count = isset($data['count']) ? (int) $data['count'] : null;
|
|
|
|
if ($textId !== null) {
|
|
$ownershipResponse = $this->checkTextOwnership(
|
|
$user,
|
|
$textId,
|
|
$response,
|
|
);
|
|
if ($ownershipResponse !== null) {
|
|
return $ownershipResponse;
|
|
}
|
|
}
|
|
|
|
try {
|
|
$nodes = $bulkCreateNodesUseCase->execute(new BulkCreateNodesRequest(
|
|
textId: $textId,
|
|
parentNodeId: $parentNodeId,
|
|
titlePrefix: $titlePrefix,
|
|
count: $count,
|
|
));
|
|
} catch (BadRequestException $e) {
|
|
$response->getBody()->write(json_encode(['error' => $e->getMessage()]));
|
|
return $response->withStatus(400)->withHeader('Content-Type', 'application/json');
|
|
} catch (DomainException $e) {
|
|
$response->getBody()->write(json_encode(['error' => $e->getMessage()]));
|
|
return $response->withStatus(404)->withHeader('Content-Type', 'application/json');
|
|
}
|
|
|
|
$result = array_map(function ($node) {
|
|
return [
|
|
'id' => $node->getId(),
|
|
'title' => $node->getTitle(),
|
|
'parentNodeId' => $node->getParentNode()?->getId(),
|
|
];
|
|
}, $nodes);
|
|
|
|
$response->getBody()->write(json_encode(array_values($result)));
|
|
return $response->withStatus(201)->withHeader('Content-Type', 'application/json');
|
|
}
|
|
|
|
private function checkTextOwnership(
|
|
User $user,
|
|
int $textId,
|
|
Response $response,
|
|
): ?Response {
|
|
$text = $this->textRepository->find($textId);
|
|
if ($text === null) {
|
|
return null;
|
|
}
|
|
if (!$this->userMayAccessText($user, $text)) {
|
|
return $this->errorResponse(
|
|
$response,
|
|
403,
|
|
'forbidden'
|
|
);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private function userMayAccessText(User $user, Text $text): bool
|
|
{
|
|
if ($user->isAdmin()) {
|
|
return true;
|
|
}
|
|
return $text->getUser()->getId() === $user->getId();
|
|
}
|
|
|
|
private function errorResponse(
|
|
Response $response,
|
|
int $status,
|
|
string $message,
|
|
): Response {
|
|
$response->getBody()->write(
|
|
json_encode(['error' => $message])
|
|
);
|
|
|
|
return $response->withStatus($status)
|
|
->withHeader('Content-Type', 'application/json');
|
|
}
|
|
}
|