https://github.com/rootinc/laravel-s3-file-model
https://github.com/rootinc/laravel-s3-file-model
Last synced: 8 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/rootinc/laravel-s3-file-model
- Owner: rootinc
- Created: 2020-06-23T20:19:23.000Z (about 6 years ago)
- Default Branch: master
- Last Pushed: 2023-03-03T20:18:05.000Z (over 3 years ago)
- Last Synced: 2025-10-06T00:42:48.531Z (9 months ago)
- Language: PHP
- Size: 37.1 KB
- Stars: 0
- Watchers: 2
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Laravel S3 File Model
Provides a File Model that supports direct uploads / downloads from S3 for a Laravel App.
## Installation
1. `composer require rootinc/laravel-s3-file-model`
2. Run `php artisan vendor:publish --provider="RootInc\LaravelS3FileModel\FileModelServiceProvider"` to create `File` model in `app`, `FileTest` in `tests\Unit`, `FileFactory` in `database\factories`, `2020_03_12_152841_create_files_table` in `database\migrations`, and `FileController` in `app\Http\Controllers`
3. Run `php artisan vendor:publish --provider="Aws\Laravel\AwsServiceProvider` which adds `aws.php` in the `config` folder
4. In the `aws.php` file, change `'region' => env('AWS_REGION', 'us-east-1'),` to use `AWS_DEFAULT_REGION`
5. In `config\filesystems.php`, add key `'directory' => '', // root dir` to `public` and add key `'directory' => env('AWS_UPLOAD_FOLDER'),` to `s3`
6. In `tests\TestCase`, add this function:
```
protected function get1x1RedPixelImage()
{
return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg==";
}
```
7. Update routing. We can use this as an example: `Route::apiResource('files', 'FileController')->only(['index', 'store', 'update', 'destroy']);`
8. :tada:
## Update from 0.1.* to 0.2.*
If we are using API versioning paradigm, 0.2.* lets us abstract the file model so that in a child class, we can import a different version of the File.
For example, our `FileController` would look something like this:
```
newInstance();
}
...
```
Now when running the parent's functionality, it will make use of the right model.
Note -- the FileController's `update` and `delete` methods have been switched from `public function method(Request $request, File $file)` to `public function method(Request $request, $file_id)`
The same has been updated for tests. That way, if we want to make use of Laravel 8's updated factory classes, we can do so like below:
```
newInstance();
}
protected function getFileFactory($count=1, $create=true, $properties=[])
{
$files;
$factory = File::factory()->count($count);
if ($create)
{
$files = $factory->create($properties);
}
else
{
$files = $factory->make($properties);
}
$len = count($files);
if ($len === 1)
{
return $files[0];
}
else if ($len === 0)
{
return null;
}
else
{
return $files;
}
}
...
```
## Example React FileUploader
```
import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import api from '../../helpers/api';
const propTypes = {
afterSuccess: PropTypes.func,
file: PropTypes.object,
cloudUpload: PropTypes.bool,
style: PropTypes.object,
public: PropTypes.bool,
};
const defaultProps = {
afterSuccess: () => {},
cloudUpload: false,
style: {},
public: false,
};
function FileUploader(props){
const elInput = useRef(null);
const [file, setFile] = useState(null);
const [draggingState, setDraggingState] = useState(false);
const [percentCompleted, setPercentCompleted] = useState(null);
// Use dependency on props.file for when we load an existing file
useEffect(() => {
setFile(props.file)
}, [props.file]);
const dragOver = () => {
if (percentCompleted === null)
{
setDraggingState(true)
}
};
const dragEnd = () => {
setDraggingState(false)
};
const nullImportValue = () => {
ReactDOM.findDOMNode(elInput.current).value = null;
};
const handleChange = (blob) => {
const reader = new FileReader();
reader.addEventListener("load", () => {
if (props.cloudUpload)
{
pingUpload({
file_name: blob.name,
file_type: blob.type,
public: props.public,
}, blob); //XMLHttpRequest can take a raw file blob, which works better for streaming the file
}
else
{
upload({
file_name: blob.name,
file_type: blob.type,
file_data: reader.result,
public: props.public,
});
}
}, false);
reader.readAsDataURL(blob);
};
const pingUpload = async (data, blob) => {
const response = file
? await api.putFile(file.id, data)
: await api.postFile(data)
response.ok
? cloudUpload(response, blob)
: error(response)
}
const cloudUpload = async (response, blob) => {
const putCloudObject = () => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("PUT", response.data.payload.upload_url);
xhr.setRequestHeader("Content-Type", response.data.payload.file.file_type);
xhr.setRequestHeader("Cache-Control", `max-age=${60*60*7}`); //cache for a week (in case a developer uploads with disable cache checked)
xhr.onload = () => {
resolve(xhr);
};
xhr.onerror = () => {
reject(new Error(xhr.statusText));
};
xhr.upload.onprogress = (e) => {
const percentCompleted = Math.round( (e.loaded / e.total) * 100 );
setPercentCompleted(percentCompleted);
};
//thankfully blobs can be sent up, and this works better https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/send
xhr.send(blob);
});
}
const cloudResponse = await putCloudObject();
cloudResponse.status === 200
? success(response)
: error(cloudResponse.response)
}
const upload = async (data) => {
const config = {
onUploadProgress: (e) => {
const percentCompleted = Math.round( (e.loaded / e.total) * 100 );
setPercentCompleted(percentCompleted);
}
};
const response = file
? await api.putFile(file.id, data, config)
: await api.postFile(data, config)
response.ok
? success(response)
: error(response)
};
const success = (response) => {
nullImportValue();
if (response.data.status === "success")
{
setFile(response.data.payload.file)
}
else
{
alert(response.data.payload.errors[0]);
}
setPercentCompleted(null)
props.afterSuccess(response.data.payload.file)
};
const error = (error) => {
nullImportValue();
setPercentCompleted(null)
alert(window._genericErrorMessage);
};
const renderInstructions = () => {
if (percentCompleted === null)
{
return (
{
ReactDOM.findDOMNode(elInput.current).click();
}}
>
{file ? "Replace" : "Choose"} File or drag it here.
);
}
else if (percentCompleted < 100)
{
return (
{percentCompleted}%
);
}
else
{
return ;
}
};
const renderFileInfo = () => {
if (file)
{
return (
Current File:
{file.title}
{
const result = prompt("New Title?", file.title);
if (result)
{
const response = await api.putFile(file.id, {title: result});
response.ok
? success(response)
: error(response)
}
}}
>
Rename
Original Name: {file.file_name}
);
}
else
{
return null;
}
}
const style = Object.assign({
border: "2px dashed black",
borderRadius: "10px",
backgroundColor: draggingState ? "white" : "lightgray",
height: "250px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
}, props.style);
return (
{e.stopPropagation();}}
onDrag={(e) => {e.preventDefault();}}
onDragStart={(e) => {e.preventDefault();}}
onDragEnd={(e) => {e.preventDefault(); dragEnd();}}
onDragOver={(e) => {e.preventDefault(); dragOver();}}
onDragEnter={(e) => {e.preventDefault(); dragOver();}}
onDragLeave={(e) => {e.preventDefault(); dragEnd();}}
onDrop={(e) => {
e.preventDefault();
dragEnd();
if (percentCompleted === null)
{
const droppedFiles = e.dataTransfer.files;
handleChange(droppedFiles[0]);
}
}}
>
{
renderInstructions()
}
{
renderFileInfo()
}
{
handleChange(e.target.files[0]);
}}
/>
);
}
FileUploader.propTypes = propTypes;
FileUploader.defaultProps = defaultProps;
export default FileUploader;
```
## Contributing
Thank you for considering contributing to the Laravel S3 File Model! To encourage active collaboration, we encourage pull requests, not just issues.
If you file an issue, the issue should contain a title and a clear description of the issue. You should also include as much relevant information as possible and a code sample that demonstrates the issue. The goal of a issue is to make it easy for yourself - and others - to replicate the bug and develop a fix.
## License
The Laravel S3 File Model is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT).