-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #49 from RyanNerd/paperless
⛲ feat Document Management (upload/download files)
- Loading branch information
Showing
30 changed files
with
560 additions
and
68 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace Willow\Controllers\File; | ||
|
||
use Slim\Interfaces\RouteCollectorProxyInterface; | ||
use Willow\Controllers\IController; | ||
|
||
class FileController implements IController | ||
{ | ||
/** | ||
* Register routes and actions | ||
* @param RouteCollectorProxyInterface $group | ||
*/ | ||
final public function register(RouteCollectorProxyInterface $group): void { | ||
$group->post('/file/upload/{client_id}', FileUploadAction::class) | ||
->add(FileUploadValidator::class); | ||
|
||
$group->get('/file/download/{id}', FileDownloadAction::class); | ||
|
||
$group->get('/file/{id}', FileGetAction::class); | ||
|
||
$group->post('/file', FileUpdateAction::class); | ||
|
||
$group->delete('/file/{id}', FileDeleteAction::class); | ||
|
||
$group->get('/file/load/{client_id}', FileLoadAction::class); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace Willow\Controllers\File; | ||
|
||
use Willow\Controllers\DeleteActionBase; | ||
use Willow\Models\File; | ||
|
||
class FileDeleteAction extends DeleteActionBase | ||
{ | ||
/** | ||
* FileDeleteAction constructor | ||
* @param File $model | ||
*/ | ||
public function __construct(File $model) { | ||
$this->model = $model; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace Willow\Controllers\File; | ||
|
||
use JsonException; | ||
use Psr\Http\Message\ResponseInterface; | ||
use Slim\Psr7\Factory\StreamFactory; | ||
use Slim\Psr7\Request; | ||
use Slim\Psr7\Response; | ||
use Willow\Middleware\ResponseBody; | ||
use Willow\Middleware\ResponseCodes; | ||
use Willow\Models\File; | ||
use Willow\Models\FileRepresentation; | ||
|
||
class FileDownloadAction | ||
{ | ||
/** | ||
* FileGetAction constructor. | ||
* @param File $file | ||
*/ | ||
public function __construct(private File $file) { | ||
} | ||
|
||
/** | ||
* Handle 'file/download' request | ||
* @see https://dev.to/nauleyco/how-to-download-a-csv-file-with-slim-4-21a7 | ||
* @param Request $request | ||
* @param Response $response | ||
* @param array $args | ||
* @return ResponseInterface | ||
* @throws JsonException | ||
*/ | ||
public function __invoke(Request $request, Response $response, array $args): ResponseInterface { | ||
/** @var ResponseBody $responseBody */ | ||
$responseBody = $request->getAttribute('response_body'); | ||
|
||
|
||
/** | ||
* Load the File Model with the given id (PK) | ||
* @var File|FileRepresentation|null $fileModel | ||
*/ | ||
$fileModel = $this->file->makeVisible('Image')->find($args['id']); | ||
|
||
// If the record is not found then 404 error, otherwise status is 200. | ||
if ($fileModel === null) { | ||
$responseBody = $responseBody | ||
->setData(null) | ||
->setStatus(ResponseCodes::HTTP_NOT_FOUND); | ||
return $responseBody(); | ||
} | ||
|
||
return $response | ||
->withHeader('Content-Type', 'application/octet-stream') | ||
->withHeader('Content-Disposition', 'attachment; filename="' . $fileModel->FileName . '"') | ||
->withAddedHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0') | ||
->withHeader('Cache-Control', 'post-check=0, pre-check=0') | ||
->withHeader('Pragma', 'no-cache') | ||
->withBody((new StreamFactory())->createStream($fileModel->Image)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace Willow\Controllers\File; | ||
|
||
use Willow\Controllers\GetActionBase; | ||
use Willow\Models\File; | ||
|
||
class FileGetAction extends GetActionBase | ||
{ | ||
/** | ||
* FileGetAction constructor. | ||
* @param File $model | ||
*/ | ||
public function __construct(File $model) { | ||
$this->model = $model; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace Willow\Controllers\File; | ||
|
||
use JsonException; | ||
use Psr\Http\Message\ResponseInterface; | ||
use Slim\Psr7\Request; | ||
use Slim\Psr7\Response; | ||
use Willow\Middleware\ResponseBody; | ||
use Willow\Middleware\ResponseCodes; | ||
use Willow\Models\File; | ||
|
||
class FileLoadAction | ||
{ | ||
/** | ||
* FileLoadAction - helper action to quickly rehydrate just the File records for a client | ||
* @param File $file File model | ||
*/ | ||
public function __construct(private File $file) { | ||
} | ||
|
||
/** | ||
* Handle Load Request by returning all File records for a given client_id | ||
* @param Request $request | ||
* @param Response $response | ||
* @param array $args | ||
* @return ResponseInterface | ||
* @throws JsonException | ||
*/ | ||
public function __invoke(Request $request, Response $response, array $args): ResponseInterface { | ||
/** @var ResponseBody $responseBody */ | ||
$responseBody = $request->getAttribute('response_body'); | ||
|
||
// Load all models for the given client_id | ||
$file = $this->file->clone(); | ||
$documents = $file | ||
->where('ResidentId', '=', $args['client_id']) | ||
->orderBy('Updated', 'desc') | ||
->get(['Id', 'ResidentId', 'FileName', 'Description', 'MediaType', 'Size', 'Created', 'Updated']); | ||
|
||
// If the record is not found then 404 error, otherwise status is 200. | ||
if ($documents !== null && count($documents) > 0) { | ||
$data = $documents->toArray(); | ||
$status = ResponseCodes::HTTP_OK; | ||
} else { | ||
$data = null; | ||
$status = ResponseCodes::HTTP_NOT_FOUND; | ||
} | ||
|
||
// Set the status and data of the ResponseBody | ||
$responseBody = $responseBody | ||
->setData($data) | ||
->setStatus($status); | ||
|
||
// Return the response as JSON | ||
return $responseBody(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace Willow\Controllers\File; | ||
|
||
use Willow\Controllers\WriteActionBase; | ||
use Willow\Models\File; | ||
|
||
class FileUpdateAction extends WriteActionBase | ||
{ | ||
/** | ||
* FIleUpdateAction constructor. | ||
* @param File $model | ||
*/ | ||
public function __construct(File $model) { | ||
$this->model = $model; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
<?php | ||
namespace Willow\Controllers\File; | ||
|
||
use JsonException; | ||
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Http\Message\UploadedFileInterface; | ||
use Slim\Psr7\Request; | ||
use Slim\Psr7\Response; | ||
use Willow\Middleware\ResponseBody; | ||
use Willow\Middleware\ResponseCodes; | ||
use Willow\Models\File; | ||
use Willow\Models\FileRepresentation; | ||
|
||
class FileUploadAction | ||
{ | ||
public function __construct(private File $file) { | ||
} | ||
|
||
/** | ||
* @param Request $request | ||
* @param Response $response | ||
* @return ResponseInterface | ||
* @throws JsonException | ||
*/ | ||
public function __invoke(Request $request, Response $response): ResponseInterface { | ||
/** @var ResponseBody $responseBody */ | ||
$responseBody = $request->getAttribute('response_body'); | ||
|
||
$parsedRequest = $responseBody->getParsedRequest(); | ||
|
||
/** | ||
* @var $files UploadedFileInterface[] | ||
* @phpstan-ignore-next-line | ||
*/ | ||
$files = $parsedRequest['uploaded_files']; | ||
|
||
$file = $files['single_file']; | ||
$clientId = $parsedRequest['client_id']; | ||
|
||
/** @var File|FileRepresentation $document */ | ||
$document = clone $this->file; | ||
$document->ResidentId = $clientId; | ||
$document->Size = $file->getSize(); | ||
$document->FileName = $file->getClientFilename() ?? 'unknown'; | ||
$document->MediaType = $file->getClientMediaType(); | ||
$document->Image = $file->getStream()->getContents(); | ||
if ($document->save()) { | ||
$responseBody = $responseBody->setData( | ||
[ | ||
'Id' => $document->Id, | ||
'Size' => $document->Size, | ||
'FileName' => $document->FileName, | ||
'Type' => $document->MediaType | ||
] | ||
)->setStatus(ResponseCodes::HTTP_OK); | ||
return $responseBody(); | ||
} | ||
|
||
$responseBody = $responseBody | ||
->setData(null) | ||
->setStatus(ResponseCodes::HTTP_INTERNAL_SERVER_ERROR) | ||
->setMessage('Unable to save file'); | ||
return $responseBody(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace Willow\Controllers\File; | ||
|
||
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Http\Message\UploadedFileInterface; | ||
use Psr\Http\Server\RequestHandlerInterface as RequestHandler; | ||
use Slim\Psr7\Request; | ||
use Willow\Middleware\ResponseBody; | ||
use Willow\Middleware\ResponseCodes; | ||
use JsonException; | ||
use Respect\Validation\Validator as V; | ||
|
||
class FileUploadValidator | ||
{ | ||
/** | ||
* @param Request $request | ||
* @param RequestHandler $handler | ||
* @return ResponseInterface | ||
* @throws JsonException | ||
*/ | ||
public function __invoke(Request $request, RequestHandler $handler): ResponseInterface { | ||
/** @var ResponseBody $responseBody */ | ||
$responseBody = $request->getAttribute('response_body'); | ||
$parsedRequest = $responseBody->getParsedRequest(); | ||
|
||
/** | ||
* @var $files UploadedFileInterface[] | ||
* @phpstan-ignore-next-line | ||
*/ | ||
$files = $parsedRequest['uploaded_files'] ?? []; | ||
|
||
// Only 1 file allowed to be uploaded | ||
if (count($files) !== 1) { | ||
$responseBody->registerParam('required', 'uploaded_files', 'array', 'There must be only one uploaded file'); | ||
} | ||
|
||
// File is hard coded as 'single_file' | ||
$file = $files['single_file'] ?? null; | ||
|
||
// The single_file array element label is required | ||
if ($file === null) { | ||
$responseBody->registerParam('required', 'single_file', 'file', 'File must be labeled as single_file'); | ||
} | ||
|
||
// File size must be under 100MB | ||
if ($file && $file->getSize() > 104_857_600) { | ||
$responseBody->registerParam('invalid', 'single_file', 'file', 'File size exceeds maximum allowed'); | ||
} | ||
|
||
// client_id is required and must be an integer | ||
$clientId = $parsedRequest['client_id'] ?? null; | ||
if (!V::notEmpty()->validate($clientId)) { | ||
$responseBody->registerParam('required', 'client_id', 'integer', 'client_id is empty or invalid'); | ||
} else { | ||
if (!V::intVal()->validate($clientId)) { | ||
$responseBody->registerParam('invalid', 'client_id', 'integer', 'client_id must be an integer'); | ||
} | ||
} | ||
|
||
// If there are any invalid or missing required then send bad request response | ||
if ($responseBody->hasMissingRequiredOrInvalid()) { | ||
$responseBody = $responseBody->setData(null)->setStatus(ResponseCodes::HTTP_BAD_REQUEST); | ||
return $responseBody(); | ||
} | ||
|
||
return $handler->handle($request); | ||
} | ||
} |
Oops, something went wrong.