Skip to content

Commit

Permalink
Merge pull request #1 from CakeDC/feature/test-app-fixes
Browse files Browse the repository at this point in the history
Improvements after testing the plugin with an example application
  • Loading branch information
ACampanario authored Apr 2, 2024
2 parents db4ccd2 + 7d04085 commit 7917862
Show file tree
Hide file tree
Showing 9 changed files with 77 additions and 50 deletions.
22 changes: 18 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,25 @@ The recommended way to install composer packages is:

`composer require cakedc/cakephp-uppy`

Then, ensure the plugin is added to your application, in `config/plugins.php` add

```php
'CakeDC/Uppy'
```

Important: By default, the plugin will add a number of routes to your application `/uppy/files` in order to
upload and view the uploaded files. You should protect these endpoints in case your application requires
authentication to upload or manage files. Failing to do so would allow an authenticated user to *upload* files
to your application.

This plugin uses `uppy_files` as a table to store filedata as filename, filesize, path in S3, ...

To create table run in console `bin/cake migrations migrate`
Run the Plugin migrations to create a table to hold the uploaded files details

`bin/cake migrations migrate -p CakeDC/Uppy`

You must configure the connection parameters with S3 in `config/uppy.php`
Then copy the file `vendor/cakedc/cakephp-uppy/config/uppy.php` into `config/uppy.php`. Tweak this file to
provide the required configuration for your S3 compatible connection.

```php
<?php
Expand Down Expand Up @@ -72,8 +86,8 @@ return [
- lifeTimeGetObject = life time generated link to access file in S3
- lifeTimePutObject = life time generated link to post file in S3
- region = configured region S3
- endpoint = endpoint server to PUT/POST/GET S3 files
- key = S3 account key
- endpoint = endpoint server to PUT/POST/GET S3 files
- key = S3 account key
- secret = S3 account secret
- bucket = bucket name

Expand Down
4 changes: 0 additions & 4 deletions config/Migrations/20220412110508_CreateUppyFiles.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,11 @@ public function change(): void
'default' => null,
'limit' => null,
'null' => true,
'precision' => 6,
'scale' => 6,
])
->addColumn('modified', 'timestamp', [
'default' => null,
'limit' => null,
'null' => true,
'precision' => 6,
'scale' => 6,
])
->addColumn('metadata', 'text', [
'default' => null,
Expand Down
1 change: 1 addition & 0 deletions config/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
['path' => '/uppy'],
function (RouteBuilder $routes): void {
$routes->setRouteClass(DashedRoute::class);
$routes->fallbacks();
}
);
};
2 changes: 2 additions & 0 deletions config/uppy.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
return [
'Uppy' => [
'Props' => [
// the Table Alias to identify the user who uploaded the file
'usersAliasModel' => 'Users',
// the Table className to identify the user who uploaded the file
'usersModel' => 'Users',
'deleteFileS3' => true,
'tableFiles' => 'uppy_files',
Expand Down
29 changes: 17 additions & 12 deletions src/Controller/FilesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ class FilesController extends AppController
public function initialize(): void
{
parent::initialize();
if (!$this->components()->has('FormProtection')) {
$this->loadComponent('FormProtection');
}
$this->FormProtection->setConfig('unlockedActions', ['sign','save']);
}

Expand Down Expand Up @@ -77,13 +80,15 @@ public function view(string|int|null $id = null): ?Response
public function save(): void
{
$this->getRequest()->allowMethod('post');
$this->viewBuilder()->setClassName('Json');

$items = $this->getRequest()->getData('items');

$files = [];
$result = [];
foreach ($items as $item) {
if (!isset($item['model'])) {
$tableAlias = $item['model'] ?? null;
if (!$tableAlias) {
$result['error'] = true;
$result['message'] = __('model is required');
$this->set('result', $result);
Expand All @@ -92,16 +97,17 @@ public function save(): void
return;
}
try {
$relationTable = $this->fetchTable($item['model']);
} catch (MissingTableClassException) {
$relationTable = $this->fetchTable($tableAlias);
} catch (MissingTableClassException | UnexpectedValueException) {

Check failure on line 101 in src/Controller/FilesController.php

View workflow job for this annotation

GitHub Actions / cs-stan / Coding Standard & Static Analysis

Caught class CakeDC\Uppy\Controller\UnexpectedValueException not found.

Check failure on line 101 in src/Controller/FilesController.php

View workflow job for this annotation

GitHub Actions / cs-stan / Coding Standard & Static Analysis

UndefinedClass

src/Controller/FilesController.php:101:51: UndefinedClass: Class, interface or enum named CakeDC\Uppy\Controller\UnexpectedValueException does not exist (see https://psalm.dev/019)
$result['error'] = true;
$result['message'] = __('there is no table {0} to associate the file', $item['model']);
$result['message'] = __('there is no table {0} to associate the file', $tableAlias);
$this->set('result', $result);
$this->viewBuilder()->setOption('serialize', ['result']);

return;
}
if (!isset($item['foreign_key'])) {
$foreignKey = $item['foreign_key'] ?? null;
if (!$foreignKey) {
$result['error'] = true;
$result['message'] = __('foreign key is required');
$this->set('result', $result);
Expand All @@ -110,10 +116,10 @@ public function save(): void
return;
}
try {
$register = $relationTable->get($item['foreign_key']);
$register = $relationTable->get($foreignKey);
} catch (RecordNotFoundException) {
$result['error'] = true;
$result['message'] = __('there is no record {0} to associate the file', $item['foreign_key']);
$result['message'] = __('there is no record with id {0} to associate the file', $foreignKey);
$this->set('result', $result);
$this->viewBuilder()->setOption('serialize', ['result']);

Expand All @@ -123,10 +129,10 @@ public function save(): void
$file->filename = $item['filename'];
$file->filesize = $item['filesize'];
$file->extension = $item['extension'];
$model = Configure::read('Uppy.Props.usersModel');
$model = Configure::readOrFail('Uppy.Props.usersModel');
$relation_key = Inflector::singularize(mb_strtolower($model)) . '_id';
$file->user_id = $register->{$relation_key};
$file->model = $item['model'];
$file->model = $tableAlias;
$files[] = $file;
}

Expand All @@ -138,7 +144,6 @@ public function save(): void
$result['message'] = __('The association to file could not be saved');
}

$this->viewBuilder()->setClassName('Json');
$this->set('result', $result);
$this->viewBuilder()->setOption('serialize', ['result']);
}
Expand Down Expand Up @@ -190,8 +195,8 @@ public function sign(): ?Response
$filename = Text::uuid() . '-' . Text::slug($this->getRequest()->getData('filename'));

$contentType = $this->getRequest()->getData('contentType');
if (!in_array($contentType, Configure::read('Uppy.AcceptedContentTypes'))) {
throw new PageOutOfBoundsException(__('contenType {0} is not valid', $contentType));
if (!in_array($contentType, Configure::read('Uppy.AcceptedContentTypes', []))) {
throw new PageOutOfBoundsException(__('contenType {0} is not valid', h($contentType)));

Check failure on line 199 in src/Controller/FilesController.php

View workflow job for this annotation

GitHub Actions / cs-stan / Coding Standard & Static Analysis

Function h not found.

Check failure on line 199 in src/Controller/FilesController.php

View workflow job for this annotation

GitHub Actions / cs-stan / Coding Standard & Static Analysis

UndefinedFunction

src/Controller/FilesController.php:199:82: UndefinedFunction: Function CakeDC\Uppy\Controller\h does not exist (see https://psalm.dev/021)
}

$presignedRequest = $this->createPresignedRequest($filename, $contentType);
Expand Down
14 changes: 7 additions & 7 deletions src/Model/Table/FilesTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,15 @@ public function initialize(array $config): void
{
parent::initialize($config);

$this->setTable(Configure::read('Uppy.Props.tableFiles'));
$this->setTable(Configure::readOrFail('Uppy.Props.tableFiles'));
$this->setDisplayField('id');
$this->setPrimaryKey('id');

$this->addBehavior('Timestamp');

$this->belongsTo(Configure::read('Uppy.Props.usersAliasModel'), [
$this->belongsTo(Configure::readOrFail('Uppy.Props.usersAliasModel'), [
'foreignKey' => 'user_id',
'className' => Configure::read('Uppy.Props.usersModel'),
'className' => Configure::readOrFail('Uppy.Props.usersModel'),
]);
}

Expand Down Expand Up @@ -100,13 +100,13 @@ public function validationDefault(Validator $validator): Validator

$validator
->scalar('mime_type')
->inList('mime_type', Configure::read('Uppy.AcceptedContentTypes'))
->inList('mime_type', Configure::read('Uppy.AcceptedContentTypes', []))
->maxLength('mime_type', 128)
->allowEmptyString('mime_type');

$validator
->scalar('extension')
->inList('extension', Configure::read('Uppy.AcceptedExtensions'))
->inList('extension', Configure::read('Uppy.AcceptedExtensions', []))
->maxLength('extension', 32)
->allowEmptyString('extension');

Expand Down Expand Up @@ -149,7 +149,7 @@ public function buildRules(RulesChecker $rules): RulesChecker
$rules->add(
$rules->existsIn(
'user_id',
Configure::read('Uppy.Props.usersAliasModel')
Configure::readOrFail('Uppy.Props.usersAliasModel')
),
['errorField' => 'user_id']
);
Expand All @@ -167,7 +167,7 @@ public function buildRules(RulesChecker $rules): RulesChecker
*/
public function afterDelete(EventInterface $event, File $entity, ArrayObject $options): void
{
if (Configure::read('Uppy.Props.deleteFileS3')) {
if (Configure::read('Uppy.Props.deleteFileS3', false)) {
$this->deleteObject($entity->path, $entity->filename);
}
}
Expand Down
9 changes: 9 additions & 0 deletions src/UppyPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,19 @@
namespace CakeDC\Uppy;

use Cake\Core\BasePlugin;
use Cake\Core\Configure;
use Cake\Core\PluginApplicationInterface;

/**
* Plugin for UppyManager
*/
class UppyPlugin extends BasePlugin
{
public function bootstrap(PluginApplicationInterface $app): void

Check failure on line 24 in src/UppyPlugin.php

View workflow job for this annotation

GitHub Actions / cs-stan / Coding Standard & Static Analysis

Missing doc comment for function bootstrap()
{
parent::bootstrap($app);
if (file_exists(CONFIG . 'uppy.php')) {

Check failure on line 27 in src/UppyPlugin.php

View workflow job for this annotation

GitHub Actions / cs-stan / Coding Standard & Static Analysis

Constant CONFIG not found.

Check failure on line 27 in src/UppyPlugin.php

View workflow job for this annotation

GitHub Actions / cs-stan / Coding Standard & Static Analysis

UndefinedConstant

src/UppyPlugin.php:27:25: UndefinedConstant: Const CONFIG is not defined (see https://psalm.dev/020)
Configure::load('uppy');
}
}
}
40 changes: 20 additions & 20 deletions src/Util/S3Trait.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,14 @@ protected function deleteObject(?string $path, ?string $name): bool
{
$path = $path ?? '';
if (Configure::read('Uppy.S3.config.connection') !== 'dummy') {
$s3Client = new S3Client(Configure::read('Uppy.S3.config'));
$exist = $s3Client->doesObjectExist(Configure::read('Uppy.S3.bucket'), $path);
$s3Client = new S3Client(Configure::readOrFail('Uppy.S3.config'));
$exist = $s3Client->doesObjectExist(Configure::readOrFail('Uppy.S3.bucket'), $path);
if ($exist) {
$s3Client->deleteObject([
'Bucket' => Configure::read('Uppy.S3.bucket'),
'Bucket' => Configure::readOrFail('Uppy.S3.bucket'),
'Key' => $path,
]);
if ($s3Client->doesObjectExist(Configure::read('Uppy.S3.bucket'), $path)) {
if ($s3Client->doesObjectExist(Configure::readOrFail('Uppy.S3.bucket'), $path)) {
return false;
}
}
Expand All @@ -67,9 +67,9 @@ protected function presignedUrl(?string $path, ?string $name): string
return 'https://example.com';
}

$s3Client = new S3Client(Configure::read('Uppy.S3.config'));
$s3Client = new S3Client(Configure::readOrFail('Uppy.S3.config'));
$cmd = $s3Client->getCommand('GetObject', [
'Bucket' => Configure::read('Uppy.S3.bucket'),
'Bucket' => Configure::readOrFail('Uppy.S3.bucket'),
'Key' => $path,
]);
$request = $s3Client->createPresignedRequest($cmd, Configure::read('Uppy.S3.contants.lifeTimeGetObject'));
Expand All @@ -91,15 +91,15 @@ protected function createPresignedRequest(string $path, string $contentType): Re
return (new Request())
->withUri(new Uri('https://example.com'));
} else {
$s3Client = new S3Client(Configure::read('Uppy.S3.config'));
$s3Client = new S3Client(Configure::readOrFail('Uppy.S3.config'));
$command = $s3Client->getCommand('putObject', [
'Bucket' => Configure::read('Uppy.S3.bucket'),
'Bucket' => Configure::readOrFail('Uppy.S3.bucket'),
'Key' => $path,
'ContentType' => $contentType,
'Body' => '',
]);

return $s3Client->createPresignedRequest($command, Configure::read('Uppy.S3.contants.lifeTimePutObject'));
return $s3Client->createPresignedRequest($command, Configure::readOrFail('Uppy.S3.contants.lifeTimePutObject'));

Check warning on line 102 in src/Util/S3Trait.php

View workflow job for this annotation

GitHub Actions / cs-stan / Coding Standard & Static Analysis

Line exceeds 120 characters; contains 124 characters
}
}

Expand All @@ -113,8 +113,8 @@ protected function createPresignedRequest(string $path, string $contentType): Re
*/
public function uploadDir(string $source, string $target): void
{
$s3Client = new S3Client(Configure::read('Uppy.S3.config'));
$dest = 's3://' . Configure::read('Uppy.S3.bucket') . DS . $target;
$s3Client = new S3Client(Configure::readOrFail('Uppy.S3.config'));
$dest = 's3://' . Configure::readOrFail('Uppy.S3.bucket') . DS . $target;
$manager = new Transfer($s3Client, $source, $dest);
$manager->transfer();

Expand All @@ -140,9 +140,9 @@ public function uploadDir(string $source, string $target): void
*/
public function uploadFile(string $sourceFilePath, string $destinationS3Path): void
{
$s3Client = new S3Client(Configure::read('Uppy.S3.config'));
$s3Client = new S3Client(Configure::readOrFail('Uppy.S3.config'));
$s3Options = [
'Bucket' => Configure::read('Uppy.S3.bucket'),
'Bucket' => Configure::readOrFail('Uppy.S3.bucket'),
'Key' => $destinationS3Path,
'SourceFile' => $sourceFilePath,
];
Expand All @@ -168,7 +168,7 @@ public function uploadFile(string $sourceFilePath, string $destinationS3Path): v
*/
public function deleteFile(string $fileS3Path): void
{
$s3Client = new S3Client(Configure::read('Uppy.S3.config'));
$s3Client = new S3Client(Configure::readOrFail('Uppy.S3.config'));
$s3Options = [
'Bucket' => Configure::read('Uppy.S3.bucket'),
'Key' => $fileS3Path,
Expand All @@ -186,9 +186,9 @@ public function deleteFile(string $fileS3Path): void
*/
public function deleteDir(string $fileS3Path): void
{
$s3Client = new S3Client(Configure::read('Uppy.S3.config'));
$s3Client = new S3Client(Configure::readOrFail('Uppy.S3.config'));
if ($this->folderExists($fileS3Path)) {
$s3Client->deleteMatchingObjects(Configure::read('Uppy.S3.bucket'), $fileS3Path);
$s3Client->deleteMatchingObjects(Configure::readOrFail('Uppy.S3.bucket'), $fileS3Path);
} else {
throw new Exception('File doesn\'t exist. Please try again.');
}
Expand All @@ -202,9 +202,9 @@ public function deleteDir(string $fileS3Path): void
*/
public function fileExists(string $filename): bool
{
$s3Client = new S3Client(Configure::read('Uppy.S3.config'));
$s3Client = new S3Client(Configure::readOrFail('Uppy.S3.config'));

return $s3Client->doesObjectExist(Configure::read('Uppy.S3.bucket'), $filename);
return $s3Client->doesObjectExist(Configure::readOrFail('Uppy.S3.bucket'), $filename);
}

/**
Expand All @@ -216,9 +216,9 @@ public function fileExists(string $filename): bool
*/
public function folderExists(string $filename): bool
{
$s3Client = new S3Client(Configure::read('Uppy.S3.config'));
$s3Client = new S3Client(Configure::readOrFail('Uppy.S3.config'));
$list = $s3Client->listObjectsV2([
'Bucket' => Configure::read('Uppy.S3.bucket'),
'Bucket' => Configure::readOrFail('Uppy.S3.bucket'),
'Prefix' => $filename,
]);
if ($list['Contents'] > 0) {
Expand Down
6 changes: 3 additions & 3 deletions templates/element/assets.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,6 @@
sprintf('let file_not_saved = "%s";', __('The file could not be saved. Please, try again.'))
);
?>
<?= $this->Html->css('CakeDC/Uppy.uppy.min.css', ['block' => true]) ?>
<?= $this->Html->script('CakeDC/Uppy.uppy.min.js', ['block' => true]) ?>
<?= $this->Html->script('CakeDC/Uppy.add.js', ['block' => 'bottom_script']);
<?= $this->Html->css('CakeDC/Uppy.uppy.min.css') ?>
<?= $this->Html->script('CakeDC/Uppy.uppy.min.js') ?>
<?= $this->Html->script('CakeDC/Uppy.add.js');

0 comments on commit 7917862

Please sign in to comment.