ahmedkandel / nova-s3-multipart-upload

A Laravel Nova resource tool to upload files directly to Amazon S3. You can (upload | download | delete) single, multiple, small or big files.
MIT License
22 stars 22 forks source link

Nova S3 Multipart Upload

Latest Version on Packagist Total Downloads License

A Laravel Nova resource tool to upload files directly to Amazon S3. You can (upload | download | delete) single, multiple, small or big files.

screenshot

⚡ Features

📌 Installation

composer require "ahmedkandel/nova-s3-multipart-upload"

💡 Usage

In your Nova resource class add NovaS3MultipartUpload tool to fields:

use Ahmedkandel\NovaS3MultipartUpload\NovaS3MultipartUpload;

class Post extends Resource
{
    public function fields(Request $request)
    {
        return [
            // ...
            NovaS3MultipartUpload::make('Video'),
        ];
    }
}

NB the make method requires a "human readable" name, it will try to guess the attribute name. You may pass the attribute name as a second argument.

NB the attribute name is used to store the file_path OR file/s information in case of storeAsArray/storeAsMultipleArray OR relationship model/s in case of hasOne/hasMany.

NB hasOne/hasMany methods are valid also for morphOne/morphMany


In your model class add all the attributes that will be filled to $fillable:

class Post extends Model
{
    protected $fillable = ['video'];
}

When using storeAsArray or storeAsMultipleArray methods you will need to cast the attribute to an array in your model class:

class Post extends Model
{
    protected $casts = [
        'videos' => 'array',
    ];
}

⚙️ S3 configuration

After creating your S3 bucket and connecting it to your Laravel project, You will need an extra step to configure the S3 bucket's "Cross-origin resource sharing (CORS)" with either JSON or XML (this is NOT the "Bucket policy"):

JSON

[
    {
        "AllowedOrigins": [
            "http://your-domain.com"
        ],
        "AllowedMethods": [
            "GET",
            "PUT"
        ],
        "AllowedHeaders": [
            "Authorization",
            "x-amz-date",
            "x-amz-content-sha256",
            "content-type"
        ],
        "ExposeHeaders": [
            "ETag"
        ],
        "MaxAgeSeconds": 3000
    },
    {
        "AllowedOrigins": [
            "*"
        ],
        "AllowedMethods": [
            "GET"
        ],
        "MaxAgeSeconds": 3000
    }
]

XML

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  <CORSRule>
    <AllowedOrigin>http://your-domain.com</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedHeader>Authorization</AllowedHeader>
    <AllowedHeader>x-amz-date</AllowedHeader>
    <AllowedHeader>x-amz-content-sha256</AllowedHeader>
    <AllowedHeader>content-type</AllowedHeader>
    <ExposeHeader>ETag</ExposeHeader>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
  </CORSRule>
  <CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
  </CORSRule>
</CORSConfiguration>

NB please replace http://your-domain.com with your front-end domain.

✂️ Methods

✔️ Example

Uploading multiple files with their (names, sizes, metadata). Then save files information as multiple array in files attribute in User model.

User model class:

class User extends Model
{
    protected $fillable = ['files'];

    protected $casts = [
        'files' => 'array',
    ];
}

User resource class:

use Ahmedkandel\NovaS3MultipartUpload\NovaS3MultipartUpload;

class User extends Resource
{
    public function fields(Request $request)
    {
        return [
            // ...
            NovaS3MultipartUpload::make('Files')
                ->path($request->user()->id.'-uploads')
                ->storeAsMultipleArray('file_path')
                ->storeName('file_name')
                ->storeSize('file_size')
                ->storeMeta(
                    [
                        'file_author' => [
                            'name' => 'Author',
                            'placeholder' => 'your name please!',
                            'default' => 'No Author',
                        ],
                        'file_description' => [
                            'name' => 'Description',
                        ],
                    ]
                ),
        ];
    }
}

Preview:

upload

When files are uploaded the user model files attribute \App\User::find(1)->files will have the following value:

[
    {
        "file_name": "Video.mkv",
        "file_path": "1-uploads/To5SvZLTyT1XQcUWCTpmqH6GfrgLmoep0tP6EV9n.mkv",
        "file_size": 207683,
        "file_author": "No Author",
        "file_description": null
    },
    {
        "file_name": "Document.pdf",
        "file_path": "1-uploads/RnEBFKa0EqDoKQRkXf27l4dZ7fQ8MTyBofMK202b.pdf",
        "file_size": 77395,
        "file_author": "Ahmed Kandil",
        "file_description": "DDD"
    }
]

🛂 Authorization

Nova authorize the user to see the resource tool using ->canSee(closure) method which accepts a Closure that should return true or false.

We have added more methods to this tool for more granulare control:

e.g. if you would like to allow the user to upload only if no file attribute is empty:

->canUpload(function () {
    return empty($this->model()->video);
})

e.g. if you would like to allow the user to delete files in his own model (Post or Video):

->canDelete(function () {
    return request()->user()->id === $this->model()->user_id;
})

NB by default all actions are authorized until you disallow them.

🔌 Plugins

⚠️ Notes

This package is a resource tool NOT a resource field. This means it is only visible under the resource detail view as a panel and can not be inserted in another panel or tab, it must be a direct child of the resource fields array.

Why? After a lot of thinking we found that uploading file in the same request of creating a new model is not a good idea. Because the response duration will depend on the file upload progress, which means the user have to wait without any visiual indication hoping that everything will work. So we decided to separate the files (upload | download | delete) process from the model (create | edit) process.

📜 Changelog

Please see CHANGELOG for more information on what has changed recently.

🤝 License

The MIT License (MIT). Please see License File for more information.