Skip to content

Commit 2b7baed

Browse files
committed
wip, continue logic refactor
1 parent dd97ce9 commit 2b7baed

9 files changed

Lines changed: 111 additions & 135 deletions

src/Controller/File/DeleteElementFileController.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public function __construct(
4040
) {
4141
}
4242

43+
// todo: add support for etags
4344
#[Route(
4445
'/{id}/file',
4546
name: 'delete-element-file',

src/Controller/File/GetElementFileController.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public function __construct(
3333
) {
3434
}
3535

36+
// todo: add support for etags
3637
#[Route(
3738
'/{id}/file',
3839
name: 'get-element-file',

src/Controller/File/PostElementFileController.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\Routing\Attribute\Route;
1616

1717
/**
18+
* @todo add support for etags
1819
* @SuppressWarnings("PHPMD.UnusedFormalParameter")
1920
*/
2021
class PostElementFileController extends AbstractController

src/Controller/File/PutElementFileController.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Symfony\Component\Routing\Attribute\Route;
1414

1515
/**
16+
* @todo add support for etags
1617
* @SuppressWarnings("PHPMD.UnusedFormalParameter")
1718
*/
1819
class PutElementFileController extends AbstractController

src/Factory/Type/Request/PartialUploadRequestFactory.php

Lines changed: 6 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,15 @@
44

55
namespace App\Factory\Type\Request;
66

7-
use App\Factory\Exception\Client400BadContentExceptionFactory;
7+
use App\Service\HeaderParseService;
88
use App\Type\Request\PartialUploadRequest;
9-
use Symfony\Component\HttpFoundation\HeaderBag;
109
use Symfony\Component\HttpFoundation\Request;
1110

1211
class PartialUploadRequestFactory
1312
{
1413

1514
public function __construct(
16-
private Client400BadContentExceptionFactory $client400BadContentExceptionFactory,
15+
private HeaderParseService $headerParseService,
1716
)
1817
{
1918
}
@@ -22,10 +21,10 @@ public function createPartialUploadRequestFromRequest(Request $request): Partial
2221
{
2322
$headers = $request->headers;
2423

25-
$contentType = $this->getContentTypeFromHeaders($headers);
26-
$uploadOffset = $this->getUploadOffsetFromHeaders($headers);
27-
$isUploadComplete = $this->isUploadCompleteFromHeaders($headers);
28-
$contentLength = $this->getContentLengthFromHeaders($headers);
24+
$contentType = $this->headerParseService->getContentTypeFromHeaders($headers, 'application/partial-upload');
25+
$uploadOffset = $this->headerParseService->getUploadOffsetFromHeaders($headers);
26+
$isUploadComplete = $this->headerParseService->isUploadCompleteFromHeaders($headers);
27+
$contentLength = $this->headerParseService->getContentLengthFromHeaders($headers);
2928
$content = $request->getContent(true);
3029

3130
$partialUploadRequest = new PartialUploadRequest();
@@ -38,54 +37,5 @@ public function createPartialUploadRequestFromRequest(Request $request): Partial
3837
return $partialUploadRequest;
3938
}
4039

41-
private function getContentTypeFromHeaders(HeaderBag $headers): string
42-
{
43-
$contentType = $headers->get('Content-Type');
44-
if (null === $contentType) {
45-
throw $this->client400BadContentExceptionFactory->createFromDetail("Endpoint requires the header 'content-type' to be present.");
46-
}
47-
if ('application/partial-upload' !== $contentType) {
48-
throw $this->client400BadContentExceptionFactory->createFromDetail(sprintf("Expected content type 'application/partial-upload' for partial resumable uploads, got '%s'.", $contentType));
49-
}
50-
return $contentType;
51-
}
52-
53-
private function getUploadOffsetFromHeaders(HeaderBag $headers): int
54-
{
55-
return (int)$headers->get('Upload-Offset');
56-
}
57-
58-
59-
private function isUploadCompleteFromHeaders(HeaderBag $headers): ?bool
60-
{
61-
$possibleValues = [
62-
'?0' => false,
63-
'?1' => true,
64-
];
65-
$uploadCompleteHeader = $headers->get('Upload-Complete');
66-
67-
if (null === $uploadCompleteHeader) {
68-
return null;
69-
}
70-
if (!array_key_exists($uploadCompleteHeader, $possibleValues)) {
71-
throw $this->client400BadContentExceptionFactory->createFromDetail(sprintf("Header 'Upload-Complete' must contain a boolean value, either '?0' or '?1', got '%s'.", $uploadCompleteHeader));
72-
}
73-
74-
return $possibleValues[$uploadCompleteHeader];
75-
}
76-
77-
private function getContentLengthFromHeaders(HeaderBag $headers): ?int
78-
{
79-
$contentLength = $headers->get('Content-Length');
80-
if (null === $contentLength) {
81-
return null;
82-
}
83-
$contentLength = (int)$contentLength;
84-
if ($contentLength < 0) {
85-
throw $this->client400BadContentExceptionFactory->createFromDetail(sprintf("Header 'Content-Length' requires a non-negative integer as its value, got '%d'.", $contentLength));
86-
}
87-
88-
return $contentLength;
89-
}
9040

9141
}

src/Factory/Type/Request/ResumableUploadRequestFactory.php

Lines changed: 5 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@
55
namespace App\Factory\Type\Request;
66

77
use App\Factory\Exception\Client400BadContentExceptionFactory;
8+
use App\Service\HeaderParseService;
89
use App\Type\Request\ResumableUploadRequest;
9-
use Symfony\Component\HttpFoundation\HeaderBag;
1010
use Symfony\Component\HttpFoundation\Request;
1111

1212
class ResumableUploadRequestFactory
1313
{
1414

1515
public function __construct(
16+
private HeaderParseService $headerParseService,
1617
private Client400BadContentExceptionFactory $client400BadContentExceptionFactory,
1718
)
1819
{
@@ -22,9 +23,9 @@ public function createResumableUploadRequestFromRequest(Request $request): Resum
2223
{
2324
$headers = $request->headers;
2425

25-
$isUploadComplete = $this->getIsUploadCompleteFromHeaders($headers);
26-
$uploadLength = $this->getUploadLengthFromHeaders($headers);
27-
$contentLength = $this->getContentLengthFromHeaders($headers);
26+
$isUploadComplete = $this->headerParseService->isUploadCompleteFromHeaders($headers);
27+
$uploadLength = $this->headerParseService->getUploadLengthFromHeaders($headers);
28+
$contentLength = $this->headerParseService->getContentLengthFromHeaders($headers);
2829
$content = $request->getContent(true);
2930

3031
if ($uploadLength !== null && $contentLength !== null && $uploadLength !== $contentLength) {
@@ -40,51 +41,4 @@ public function createResumableUploadRequestFromRequest(Request $request): Resum
4041
return $resumableUploadRequest;
4142
}
4243

43-
44-
private function getIsUploadCompleteFromHeaders(HeaderBag $headers): ?bool
45-
{
46-
$possibleValues = [
47-
'?0' => false,
48-
'?1' => true,
49-
];
50-
$uploadCompleteHeader = $headers->get('Upload-Complete');
51-
52-
if (null === $uploadCompleteHeader) {
53-
return null;
54-
}
55-
if (!array_key_exists($uploadCompleteHeader, $possibleValues)) {
56-
throw $this->client400BadContentExceptionFactory->createFromDetail(sprintf("Header 'Upload-Complete' must contain a boolean value, either '?0' or '?1', got '%s'.", $uploadCompleteHeader));
57-
}
58-
59-
return $possibleValues[$uploadCompleteHeader];
60-
}
61-
62-
private function getUploadLengthFromHeaders(HeaderBag $headers): ?int
63-
{
64-
$uploadLength = $headers->get('Upload-Length');
65-
if (null === $uploadLength) {
66-
return null;
67-
}
68-
$uploadLength = (int)$uploadLength;
69-
if ($uploadLength < 0) {
70-
throw $this->client400BadContentExceptionFactory->createFromDetail(sprintf("Header 'Upload-Length' requires a non-negative integer as its value, got '%d'.", $uploadLength));
71-
}
72-
73-
return $uploadLength;
74-
}
75-
76-
private function getContentLengthFromHeaders(HeaderBag $headers): ?int
77-
{
78-
$contentLength = $headers->get('Content-Length');
79-
if (null === $contentLength) {
80-
return null;
81-
}
82-
$contentLength = (int)$contentLength;
83-
if ($contentLength < 0) {
84-
throw $this->client400BadContentExceptionFactory->createFromDetail(sprintf("Header 'Content-Length' requires a non-negative integer as its value, got '%d'.", $contentLength));
85-
}
86-
87-
return $contentLength;
88-
}
89-
9044
}

src/Security/UploadAccessChecker.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public function __construct(
1717
) {
1818
}
1919

20-
private function verifyUserCanUploadFileToElement(UuidInterface $userId, UuidInterface $elementId): void
20+
public function verifyUserCanUploadFileToElement(UuidInterface $userId, UuidInterface $elementId): void
2121
{
2222
// creating files only requires update privileges to the element itself
2323
if (!$this->accessChecker->hasAccessToElement($userId, $elementId, AccessType::UPDATE)) {

src/Service/HeaderParseService.php

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Service;
6+
7+
use App\Factory\Exception\Client400BadContentExceptionFactory;
8+
use App\Type\AppStateType;
9+
use Symfony\Component\HttpFoundation\HeaderBag;
10+
11+
class HeaderParseService
12+
{
13+
14+
public function __construct(
15+
private Client400BadContentExceptionFactory $client400BadContentExceptionFactory,
16+
)
17+
{
18+
}
19+
20+
public function getContentTypeFromHeaders(HeaderBag $headers, ?string $expectedContentType): string
21+
{
22+
$contentType = $headers->get('Content-Type');
23+
if (null === $contentType) {
24+
throw $this->client400BadContentExceptionFactory->createFromDetail("Endpoint requires the header 'content-type' to be present.");
25+
}
26+
if ($expectedContentType !== null) {
27+
if ($expectedContentType !== $contentType) {
28+
throw $this->client400BadContentExceptionFactory->createFromDetail(sprintf("Expected content type '%s' for partial resumable uploads, got '%s'.", $expectedContentType, $contentType));
29+
}
30+
}
31+
return $contentType;
32+
}
33+
34+
public function getUploadOffsetFromHeaders(HeaderBag $headers): int
35+
{
36+
return (int)$headers->get('Upload-Offset');
37+
}
38+
39+
40+
public function isUploadCompleteFromHeaders(HeaderBag $headers): ?bool
41+
{
42+
$possibleValues = [
43+
'?0' => false,
44+
'?1' => true,
45+
];
46+
$uploadCompleteHeader = $headers->get('Upload-Complete');
47+
48+
if (null === $uploadCompleteHeader) {
49+
return null;
50+
}
51+
if (!array_key_exists($uploadCompleteHeader, $possibleValues)) {
52+
throw $this->client400BadContentExceptionFactory->createFromDetail(sprintf("Header 'Upload-Complete' must contain a boolean value, either '?0' or '?1', got '%s'.", $uploadCompleteHeader));
53+
}
54+
55+
return $possibleValues[$uploadCompleteHeader];
56+
}
57+
58+
public function getContentLengthFromHeaders(HeaderBag $headers): ?int
59+
{
60+
$contentLength = $headers->get('Content-Length');
61+
if (null === $contentLength) {
62+
return null;
63+
}
64+
$contentLength = (int)$contentLength;
65+
if ($contentLength < 0) {
66+
throw $this->client400BadContentExceptionFactory->createFromDetail(sprintf("Header 'Content-Length' requires a non-negative integer as its value, got '%d'.", $contentLength));
67+
}
68+
69+
return $contentLength;
70+
}
71+
72+
public function getUploadLengthFromHeaders(HeaderBag $headers): ?int
73+
{
74+
$uploadLength = $headers->get('Upload-Length');
75+
if (null === $uploadLength) {
76+
return null;
77+
}
78+
$uploadLength = (int)$uploadLength;
79+
if ($uploadLength < 0) {
80+
throw $this->client400BadContentExceptionFactory->createFromDetail(sprintf("Header 'Upload-Length' requires a non-negative integer as its value, got '%d'.", $uploadLength));
81+
}
82+
83+
return $uploadLength;
84+
}
85+
86+
}

src/Service/UploadCreationService.php

Lines changed: 9 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use App\Response\NoContentResponse;
1616
use App\Security\AccessChecker;
1717
use App\Security\AuthProvider;
18+
use App\Security\UploadAccessChecker;
1819
use App\Type\AccessType;
1920
use App\Type\UploadElement;
2021
use AsyncAws\S3\S3Client;
@@ -36,31 +37,20 @@ class UploadCreationService
3637
{
3738
public function __construct(
3839
private AuthProvider $authProvider,
39-
private AccessChecker $accessChecker,
40+
private UploadAccessChecker $uploadAccessChecker,
4041
private EmberNexusConfiguration $emberNexusConfiguration,
4142
private S3Client $s3Client,
4243
private ElementManager $elementManager,
4344
private ElementService $elementService,
4445
private EventDispatcherInterface $eventDispatcher,
4546
private FileService $fileService,
47+
private HeaderParseService $headerParseService,
4648
private Client400BadContentExceptionFactory $client400BadContentExceptionFactory,
47-
private Client404NotFoundExceptionFactory $client404NotFoundExceptionFactory,
4849
private Server500LogicExceptionFactory $server500LogicExceptionFactory,
4950
) {
5051
}
5152

5253

53-
private function getElementFromElementManager(UuidInterface $elementId): NodeElementInterface|RelationElementInterface
54-
{
55-
$element = $this->elementManager->getElement($elementId);
56-
if (null === $element) {
57-
throw $this->client404NotFoundExceptionFactory->createFromTemplate();
58-
}
59-
60-
return $element;
61-
}
62-
63-
6454
private function setOrReplaceElementFileDirectly(NodeElementInterface|RelationElementInterface $element, Request $request): Response
6555
{
6656
$elementId = $element->getId();
@@ -218,23 +208,15 @@ private function createNewResumableUpload(UuidInterface $elementId, Request $req
218208
public function handleUploadCreationFromRequest(UuidInterface $elementId, Request $request): Response
219209
{
220210
$userId = $this->authProvider->getUserId();
221-
$this->verifyUserCanUploadFileToElement($userId, $elementId);
222-
$element = $this->getElementFromElementManager($elementId);
223-
224-
$method = $request->getMethod();
225-
if (!in_array($method, ['POST', 'PUT'])) {
226-
throw $this->client400BadContentExceptionFactory->createFromDetail(sprintf("Endpoint must use HTTP method 'POST' or 'PUT', but got '%s'.", $method));
227-
}
228-
if ('POST' === $method) {
229-
$this->verifyElementDoesNotHaveFile($element);
230-
}
211+
$this->uploadAccessChecker->verifyUserCanUploadFileToElement($userId, $elementId);
212+
$element = $this->elementManager->getElementOrFail($elementId);
231213

232-
$isUploadComplete = $this->getIsUploadCompleteFromHeader($request->headers);
214+
$isUploadComplete = $this->headerParseService->isUploadCompleteFromHeaders($request->headers);
233215

234-
if (null === $isUploadComplete || true === $isUploadComplete) {
235-
return $this->setOrReplaceElementFileDirectly($element, $request);
216+
if (false === $isUploadComplete) {
217+
return $this->createNewResumableUpload($elementId, $request, $userId);
236218
}
237219

238-
return $this->createNewResumableUpload($elementId, $request, $userId);
220+
return $this->setOrReplaceElementFileDirectly($element, $request);
239221
}
240222
}

0 commit comments

Comments
 (0)