Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ Changelog

Releases for CakePHP 5.0
------------------------
* 2.1.0
* Add pluggable storage drivers via `StorageAdapterInterface` and `AdapterFactory`
* Add Google Cloud Storage (`gcs`) adapter (requires `google/cloud-storage`)
* Add Cloudflare R2 (`r2`) adapter
* New config key `Uppy.driver` (env `UPPY_DRIVER`) selects the active backend
* Rename `Uppy.Props.deleteFileS3` to `deleteFileStorage`; the old key is still honored for backwards compatibility

* 2.0.3
* Update documentation

Expand Down
145 changes: 100 additions & 45 deletions Docs/Documentation/Configure.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,88 +6,143 @@ To configure the plugin first copy the base configuration to your project
$> cp vendor/cakedc/cakephp-uppy/config/uppy.php config/uppy.php
```

Then run migration to create table *uppy_files* where save data for files uploaded
Then run migration to create table *uppy_files* where data for uploaded files is stored

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

Note: you can change the name of this table in the configuration file *config/uppy.php*

## Storage drivers

The plugin supports three storage backends, selected via `Uppy.driver`:

| Driver | Backend | Required env vars | Extra packages |
|---------|--------------------------|----------------------------------------------------------------|--------------------------------------|
| `s3` | Amazon S3 | `S3_REGION`, `S3_KEY`, `S3_SECRET`, `S3_BUCKET` | none (uses `aws/aws-sdk-php`) |
| `gcs` | Google Cloud Storage | `GCS_PROJECT_ID`, `GCS_KEY_FILE_PATH`, `GCS_BUCKET` | `composer require google/cloud-storage:^1.50` |
| `r2` | Cloudflare R2 | `R2_ACCOUNT_ID`, `R2_KEY`, `R2_SECRET`, `R2_BUCKET`, `R2_PUBLIC_DOMAIN`, `R2_ENDPOINT` | none (R2 is S3-compatible) |

Set the driver via the `UPPY_DRIVER` env var (`s3` | `gcs` | `r2`), or override `Uppy.driver` directly in `config/uppy.php`. Default is `s3`.

All drivers implement `CakeDC\Uppy\Storage\StorageAdapterInterface` and are instantiated by `CakeDC\Uppy\Storage\AdapterFactory::create()`. To add a custom driver, implement the interface and register a new case in the factory.

## Configuration file *config/uppy.php*

By default, when debug is set to true 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.
By default, when debug is set to true the plugin will add a number of routes to your application under `/uppy/files` for uploading and viewing files. You should protect these endpoints if your application requires authentication to upload or manage files. Failing to do so would allow any visitor to *upload* files to your application.

### Default uppy config file:
### Default uppy config file

```php
<?php

return [
'Uppy' => [
// Storage driver: 's3' | 'gcs' | 'r2'
'driver' => env('UPPY_DRIVER', 's3'),

'Props' => [
'usersModel' => 'Users',
'deleteFileS3' => true,
'deleteFileStorage' => true,
'tableFiles' => 'uppy_files',
],

'AcceptedContentTypes' => [
'application/pdf',
'image/png',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.ms-excel',
'application/vnd.ms-powerpoint',
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
],

'AcceptedExtensions' => [
'pdf',
'png',
'pdf', 'png', 'doc', 'docx', 'xls', 'ppt', 'pptx',
],
/*
* S3 configuration to manage files
*/

// Amazon S3 — used when driver = 's3'
'S3' => [
'contants' => [
'constants' => [
'lifeTimeGetObject' => '+20 minutes',
'lifeTimePutObject' => '+5 minutes',
],
'config' => [
'version' => 'latest',
'region' => filter_var(env('S3_REGION', null)),
'endpoint' => filter_var(env('S3_END_POINT', null)),
'region' => env('S3_REGION', null),
'credentials' => [
'key' => filter_var(env('S3_KEY', null)),
'secret' => filter_var(env('S3_SECRET', null)),
'key' => env('S3_KEY', null),
'secret' => env('S3_SECRET', null),
],
],
'bucket' => filter_var(env('S3_BUCKET')),
'bucket' => env('S3_BUCKET', null),
],

// Google Cloud Storage — used when driver = 'gcs'
// Requires: composer require google/cloud-storage ^1.50
'GCS' => [
'projectId' => env('GCS_PROJECT_ID', null),
'keyFilePath' => env('GCS_KEY_FILE_PATH', null),
'bucket' => env('GCS_BUCKET', null),
'lifeTimePutObject' => '+5 minutes',
'lifeTimeGetObject' => '+20 minutes',
],

// Cloudflare R2 — used when driver = 'r2'
// R2 is S3-compatible; uses the AWS SDK with a custom endpoint.
'R2' => [
'bucket' => env('R2_BUCKET', null),
'publicDomain' => env('R2_PUBLIC_DOMAIN', null),
'constants' => [
'lifeTimePutObject' => '+5 minutes',
'lifeTimeGetObject' => '+20 minutes',
],
'config' => [
'version' => env('R2_VERSION', 'latest'),
'region' => env('R2_REGION', 'auto'),
'endpoint' => env('R2_ENDPOINT', null),
'credentials' => [
'key' => env('R2_KEY', null),
'secret' => env('R2_SECRET', null),
],
'use_path_style_endpoint' => true,
],
],
],
];
```

- usersModel = is the alias name used in you app
- deleteFileS3 = if the record of the file in the database is deleted and it's marked true, the deletion is launched in the S3 deposit
- tableFiles = name of table used to store file data, default is `uppy_files`
- AcceptedContentTypes = list of content-type stored in S3 and saved in database
- AcceptedExtensions = list of file extensions stored in S3 and saved in database
- 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
- secret = S3 account secret
- bucket = bucket name

Enpoints
-------

- /uppy/files/sign = sign with credentials and return signed url to upload file in S3 directly in front
- /uppy/files/save = save register just uploaded in database with correct S3 path
- /uppy/files/delete = delete register in database, if Uppy.Props.deleteFileS3 is true remove from S3
- /uppy/files/view = sign with credentials and return signed url to access file in S3 directly in front

Sample
-------

- /uppy/files = list of files in database and link to view/delete in S3
- /uppy/files/add = example uppy upload file to configured S3 and save data relationed in database
- /uppy/files/drag = example uppy drag and upload multiple file, using Dashboard to configured S3 and save data relationed in database
### Props

- `usersModel` — alias name used in your app for the users association
- `deleteFileStorage` — when `true`, deleting a file record also removes the object from the active storage backend. (Renamed from `deleteFileS3`; the old key still works as a fallback for backwards compatibility.)
- `tableFiles` — name of the table used to store file metadata, default `uppy_files`

### Accepted types

- `AcceptedContentTypes` — MIME types accepted for upload and persisted in the database
- `AcceptedExtensions` — file extensions accepted for upload and persisted in the database

### Per-driver options

- **S3 / R2** — `constants.lifeTimePutObject` / `constants.lifeTimeGetObject` control the signed URL lifetime for upload and read URLs respectively. `config` is passed directly to `Aws\S3\S3Client`.
- **GCS** — `lifeTimePutObject` / `lifeTimeGetObject` at the top level of the `GCS` block; `keyFilePath` is the path to a service-account JSON key file.
- **R2** — `publicDomain` is your custom domain or `pub-<hash>.r2.dev` URL; `endpoint` is the R2 S3-compatible endpoint (`https://<account-id>.r2.cloudflarestorage.com`).

In test environments, set `Uppy.S3.config.connection` or `Uppy.R2.config.connection` to `'dummy'` to bypass real network calls.

## Endpoints

- `/uppy/files/sign` — returns a signed URL for uploading a file directly from the browser to the configured storage
- `/uppy/files/save` — persists the database record for a file just uploaded, with the correct storage path
- `/uppy/files/delete` — deletes the database record and, when `Uppy.Props.deleteFileStorage` is `true`, also removes the object from storage
- `/uppy/files/view` — returns a signed URL for reading the file from the configured storage

## Sample pages (debug only)

These routes are only registered when `debug` is `true`:

- `/uppy/files` — list of files in the database with view/delete links
- `/uppy/files/add` — example single-file upload to the configured storage
- `/uppy/files/drag` — example multi-file drag-and-drop upload using Uppy Dashboard
2 changes: 1 addition & 1 deletion Docs/Home.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Home
====
The **Uppy** Plugin integrate https://github.com/transloadit/uppy to upload files directly to a configured S3 usign a signature, and in the callback response save data related to files uploaded in database.
The **Uppy** Plugin integrates https://github.com/transloadit/uppy to upload files directly from the browser to a configured cloud storage backend using a presigned URL, and on callback persists the related file metadata in the database. Supported backends: Amazon S3, Google Cloud Storage, and Cloudflare R2.

The plugin is thought as a base to extend and use your app specific controllers and views from.

Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ CakeDC Uppy Plugin

The **Uppy** plugin covers the following features:

* Upload directly files to configured S3 from frontside using a signature
* Save on configured table path to uploaded files on S3, mime_type, date, metadata and relation to user and specified related model in database indicated
* Upload files directly from the browser to a configured cloud storage backend using a presigned URL
* Pluggable storage drivers: **Amazon S3**, **Google Cloud Storage**, and **Cloudflare R2** (selectable via `Uppy.driver`)
* Persist file path, mime type, date, metadata and the relation to the user (or other related model) in the `uppy_files` table

Requirements
------------
Expand Down
3 changes: 3 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
"cakephp/migrations": "^4.0.0",
"markstory/asset_compress": "^5.0.0"
},
"suggest": {
"google/cloud-storage": "Required to use the GCS storage adapter (^1.50)"
},
"autoload": {
"psr-4": {
"CakeDC\\Uppy\\": "src"
Expand Down
82 changes: 69 additions & 13 deletions config/uppy.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,42 @@
declare(strict_types=1);

/**
**
Comment thread
flohdez marked this conversation as resolved.
Outdated
* Copyright 2023, Cake Development Corporation (https://www.cakedc.com)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2023, Cake Development Corporation (https://www.cakedc.com)
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*
* Uppy plugin configuration template.
* Copy this file to your application's config/ directory and customise it.
*
* Required env vars per driver:
* s3: S3_REGION, S3_KEY, S3_SECRET, S3_BUCKET
* gcs: GCS_PROJECT_ID, GCS_KEY_FILE_PATH, GCS_BUCKET
* r2: R2_ACCOUNT_ID, R2_KEY, R2_SECRET, R2_BUCKET, R2_PUBLIC_DOMAIN
*/
return [
'Uppy' => [
/*
* Storage driver: 's3' | 'gcs' | 'r2'
* Set UPPY_DRIVER in your .env file or override here.
*/
'driver' => env('UPPY_DRIVER', 's3'),

'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,
/*
* When true and a file record is deleted, the object is also
* removed from storage. Renamed from deleteFileS3 to apply to
* all drivers. Falls back to deleteFileS3 for backwards compat.
*/
'deleteFileStorage' => true,
'tableFiles' => 'uppy_files',
],

'AcceptedContentTypes' => [
'application/pdf',
'image/png',
Expand All @@ -29,6 +47,7 @@
'application/vnd.ms-powerpoint',
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
],

'AcceptedExtensions' => [
'pdf',
'png',
Expand All @@ -38,25 +57,62 @@
'ppt',
'pptx',
],

/*
* S3 configuration to manage files
*/
* Amazon S3 — used when driver = 's3'
* Set connection = 'dummy' in test environments.
*/
'S3' => [
'contants' => [
'constants' => [
'lifeTimeGetObject' => '+20 minutes',
'lifeTimePutObject' => '+5 minutes',
],
'config' => [
'version' => 'latest',
'connection' => 'real', //dummy
'region' => filter_var(env('S3_REGION', null)),
'endpoint' => filter_var(env('S3_END_POINT', null)),
'region' => env('S3_REGION', null),
'credentials' => [
'key' => env('S3_KEY', null),
'secret' => env('S3_SECRET', null),
],
],
'bucket' => env('S3_BUCKET', null),
],

/*
* Google Cloud Storage — used when driver = 'gcs'
* Requires: composer require google/cloud-storage ^1.50
*/
'GCS' => [
'projectId' => env('GCS_PROJECT_ID', null),
'keyFilePath' => env('GCS_KEY_FILE_PATH', null),
'bucket' => env('GCS_BUCKET', null),
'lifeTimePutObject' => '+5 minutes',
'lifeTimeGetObject' => '+20 minutes',
],

/*
* Cloudflare R2 — used when driver = 'r2'
* R2 is S3-compatible; uses AWS SDK with a custom endpoint.
* publicDomain: your custom domain or pub-<hash>.r2.dev URL.
* Set connection = 'dummy' in test environments.
*/
'R2' => [
'bucket' => env('R2_BUCKET', null),
'publicDomain' => env('R2_PUBLIC_DOMAIN', null),
'constants' => [
'lifeTimePutObject' => '+5 minutes',
'lifeTimeGetObject' => '+20 minutes',
],
'config' => [
'version' => env('R2_VERSION', 'latest'),
'region' => env('R2_REGION', 'auto'),
'endpoint' => env('R2_ENDPOINT', null),
'credentials' => [
'key' => filter_var(env('S3_KEY', null)),
'secret' => filter_var(env('S3_SECRET', null)),
'key' => env('R2_KEY', null),
'secret' => env('R2_SECRET', null),
],
'use_path_style_endpoint' => true,
],
'bucket' => filter_var(env('S3_BUCKET')),
],
],
];
Loading
Loading