Browse Source

compelete show file (without test)

pull/2/head
Mohammad Khazaee 2 years ago
parent
commit
ce91822f1c
  1. 27
      app/Http/Controllers/FileController.php
  2. 2
      app/Http/Controllers/Traits/FileTrait.php
  3. 2
      app/Http/Middleware/BindCollectionModelMiddleware.php
  4. 12
      app/Http/Middleware/BindFileModelMiddleware.php
  5. 5
      app/Http/Requests/FileStoreRequest.php
  6. 134
      app/Image/ImageProcessor.php
  7. 30
      app/Image/Traits/ModulateTrait.php
  8. 5
      app/Models/Collection.php
  9. 14
      app/Models/File.php
  10. 4
      routes/api.php
  11. 1
      storage/framework/.gitignore
  12. BIN
      storage/stub/image1.png
  13. BIN
      storage/stub/image2.jpeg
  14. BIN
      storage/stub/image3.png
  15. 21
      tests/Feature/FileDeleteTest.php
  16. 20
      tests/Feature/FileShowTest.php
  17. 34
      tests/Feature/FileStoreTest.php
  18. 21
      tests/Feature/FileUpdateTest.php

27
app/Http/Controllers/FileController.php

@ -17,9 +17,18 @@ use function PHPUnit\Framework\isNull;
class FileController extends Controller
{
public function show($collection, $uuid, $ext)
public function show($collection, $uuid, $ext, Request $request, ImageProcessor $imageProcessor)
{
dump($collection, $uuid, $ext);
if ($request->castParams == 'resource') {
return new FileResource(app()->file);
}
if (!is_null(array_intersect(array_keys($request->all()), $this->availableParams))) {
$imageProcessor->process(app()->file->getPath(), app()->file->getModifiedPath(), $request->all());
return response()->file(app()->file->getModifiedPath());
}
return response()->file(app()->file->getPath());
}
public function private($collection, $path)
@ -29,14 +38,16 @@ class FileController extends Controller
public function store(FileStoreRequest $request, ImageProcessor $imageProcessor)
{
$request->file = $imageProcessor->convertImage($request->file->path(),'/tmp/' . app()->uuid . '.' . app()->collection->ext);
$request->file = $imageProcessor->convertImage($request->file->path(), '/tmp/' . app()->uuid . '.' . app()->collection->ext);
if (!is_null(app()->collection->process)) {
$request->file = $imageProcessor->process($request->file->path(),'/tmp/' . app()->uuid. '.' . app()->collection->ext, app()->collection->process);
$request->file = $imageProcessor->process($request->file->path(), '/tmp/' . app()->uuid . '.' . app()->collection->ext, app()->collection->process);
}
DB::transaction(function () use ($request) {
$fileResource = null;
DB::transaction(function () use ($request,&$fileResource) {
$uuid = app()->uuid;
$request->resourceFile = File::create([
$fileResource = File::create([
'uuid' => $uuid,
'original_name' => $request->name,
'public' => $request->public,
@ -58,12 +69,12 @@ class FileController extends Controller
File::where('user_id', auth()->id())->delete();
}
$storedFile = Storage::disk(app()->collection->disk)->putFileAs('', $request->file,$request->resourceFile->server_path . $request->resourceFile->uuid . '.' . app()->collection->ext);
$storedFile = Storage::disk(app()->collection->disk)->putFileAs($fileResource->server_path, $request->file, $fileResource->uuid . '.' . app()->collection->ext);
if (app()->collection->public) {
Storage::disk(app()->collection->disk)->setVisibility($storedFile, 'public');
}
});
return new FileResource($request->resourceFile);
return new FileResource($fileResource);
}
public function update(Request $request, $id)

2
app/Http/Controllers/Traits/FileTrait.php

@ -4,5 +4,5 @@ namespace App\Http\Controllers\Traits;
trait FileTrait
{
public $availableParams = ['r','w','h','canv','brightness','saturation','hue','rotation','flip'];
}

2
app/Http/Middleware/BindCollectionModelMiddleware.php

@ -18,7 +18,7 @@ class BindCollectionModelMiddleware
*/
public function handle(Request $request, Closure $next)
{
if ($request->route()->action['as'] == 'api.files.store') {
if (($request->route()->action['as'] == 'api.files.store') || ($request->route()->action['as'] == "api.files.show")) {
app()->singleton('collection', function () use ($request) {
return Collection::where('name', $request->route('collection_name'))->firstOrFail();
});

12
app/Http/Middleware/BindFileModelMiddleware.php

@ -32,6 +32,18 @@ class BindFileModelMiddleware
}
}
if ($request->route()->action['as'] == 'api.files.show') {
$file = File::findOrFail($request->route('uuid'));
$Collection = Collection::where('name', $request->route('collection_name'))->firstOrFail();
if (Storage::disk($Collection->disk)->exists($file->server_path . $file->uuid . '.' . $Collection->ext)) {
app()->bind('file', function () use ($file) {
return $file;
});
}else{
abort(404);
}
}
return $next($request);
}
}

5
app/Http/Requests/FileStoreRequest.php

@ -30,8 +30,7 @@ class FileStoreRequest extends FormRequest
return false;
}
if (app()->collection->count !== 1 && (app()->collection->count <= File::where('model_id', auth()->id())->count()) && !app()->collection->tmp_support) {
if (app()->collection->count !== 1 && (app()->collection->count <= File::where('user_id', auth()->id())->where('collction_id',app()->collection->id)->count()) && !app()->collection->tmp_support) {
return false;
}
if (!app()->bound('file') && is_null($this->file('file'))) {
@ -40,7 +39,7 @@ class FileStoreRequest extends FormRequest
if (!$this->hasFile('file')) {
$this->replace([
'file' => new \Illuminate\Http\File(Storage::disk(app()->collection->disk)->path(app()->file->server_path . app()->file->uuid . '.' . app()->collection->ext), app()->file->uuid . '.' . app()->collection->ext)
'file' => new \Illuminate\Http\File(app()->file->getPath(), app()->file->uuid . '.' . app()->collection->ext)
]);
}

134
app/Image/ImageProcessor.php

@ -2,50 +2,97 @@
namespace App\Image;
use App\Http\Controllers\Traits\FileTrait;
use App\Image\Traits\ModulateTrait;
use Illuminate\Contracts\Validation\Rule;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule as ValidationRule;
use Jcupitt\Vips\Image;
use Jcupitt\Vips\Interpretation;
class ImageProcessor
{
public function process(string $filename, string $target, array $options = ["w" => null, "h" => null, "r" => null,"flip" => null , "canv" => null,"rotation" => null],$saveOptions = ['Q' => 100])
use ModulateTrait, FileTrait;
protected $defaultW = 2000;
protected $defaultH = 2000;
public function process(string $filename, string $target, array $options = [], $saveOptions = ['Q' => 100])
{
if (!isset($options['w']) && !isset($options['h'])) {
$image = Image::thumbnail($filename, getimagesize($filename)[1]);
if (array_key_exists('w', $options) && array_key_exists('h', $options) && array_key_exists('r', $options)) {
unset($options['r']);
}
if (isset($options['r'])) {
$rArray = explode(':', $options['r']);
if (array_key_exists('r', $options)) {
$options['r'] = explode(':', $options['r']);
if (count($options['r']) != 2) {
unset($options['r']);
}
if (isset($options['w']) && !isset($options['h'])) {
$options['h'] = $options['w'] * $rArray[1];
$options['w'] = $options['w'] * $rArray[0];
$validator = Validator::make($options, [
'r' => ['nullable', 'array'],
'r.*' => ['nullable', 'numeric'],
]);
if ($validator->fails()) {
foreach ($validator->messages()->getMessages() as $field_name => $messages) {
unset($options[explode('.', $field_name)[0]]);
}
}
}
if (array_key_exists('r', $options)) {
if (!isset($options['w']) && isset($options['h'])) {
$options['h'] = $options['h'] * $rArray[1];
$options['w'] = $options['h'] * $rArray[0];
if (array_key_exists('w', $options) && !array_key_exists('h', $options)) {
$options['w'] = $options['w'] * $options['r'][0];
$options['h'] = $options['w'] * $options['r'][1];
}
if (!isset($options['w']) && !isset($options['h'])) {
$options['h'] = getimagesize($filename)[0] * $rArray[1];
$options['w'] = getimagesize($filename)[0] * $rArray[0];
if (!array_key_exists('w', $options) && array_key_exists('h', $options)) {
$options['w'] = $options['h'] * $options['r'][0];
$options['h'] = $options['h'] * $options['r'][1];
}
if (!array_key_exists('w', $options) && !array_key_exists('h', $options)) {
$options['w'] = getimagesize($filename)[0] * $options['r'][0];
$options['h'] = getimagesize($filename)[0] * $options['r'][1];
}
unset($options['r']);
}
if (isset($options['w']) && !isset($options['h'])) {
$image = Image::thumbnail($filename, $options['w'], ['height' => getimagesize($filename)[1]]);
$validator = Validator::make($options, [
'w' => ['numeric', 'between:1,2000'],
'h' => ['numeric', 'between:1,2000'],
'canv' => ['boolean'],
'brightness' => ['numeric', 'between:0,100'],
'saturation' => ['numeric', 'between:0,100'],
'hue' => ['numeric', 'between:0,100'],
'rotation' => ['numeric'],
'flip' => ['string', ValidationRule::in(['h', 'v', 'hv'])]
]);
if ($validator->fails()) {
foreach ($validator->messages()->getMessages() as $field_name => $messages) {
unset($options[$field_name]);
}
}
if (!isset($options['w']) && isset($options['h'])) {
$image = Image::thumbnail($filename, getimagesize($filename)[0], ['height' => $options['h']]);
if (!array_key_exists('w', $options) && !array_key_exists('h', $options)) {
$image = Image::newFromFile($filename);
}
if (array_key_exists('w', $options) && !array_key_exists('h', $options)) {
$image = Image::thumbnail($filename, $options['w'], ['height' => $this->defaultH]);
}
if (isset($options['w']) && isset($options['h']) && !($options['canv'] == true)) {
$image = Image::thumbnail($filename, $options['w'], ['height' => $options['h'], 'crop' => 'centre']);
if (!array_key_exists('w', $options) && array_key_exists('h', $options)) {
$image = Image::thumbnail($filename, $this->defaultW, ['height' => $options['h']]);
}
if (isset($options['w']) && isset($options['h']) && $options['canv'] == true) {
if (array_key_exists('w', $options) && array_key_exists('h', $options)) {
if (array_key_exists('canv', $options) && ($options['canv'] == true)) {
$image = Image::thumbnail($filename, $options['w'], ['height' => $options['h']]);
$widthH = ($options['h'] - $image->height) / 2;
$widthW = ($options['w'] - $image->width) / 2;
@ -56,17 +103,20 @@ class ImageProcessor
$options['h'],
['extend' => 'background', 'background' => 1024]
);
} else {
$image = Image::thumbnail($filename, $options['w'], ['height' => $options['h'], 'crop' => 'centre']);
}
}
if (isset($options['brightness']) || isset($options['saturation']) || isset($options['hue'])) {
$image = $this->brightness($image, isset($options['brightness']) ? $options['brightness'] : 1.0, isset($options['saturation']) ? $options['saturation'] : 1.0, isset($options['hue']) ? $options['hue'] : 0.0);
if (array_key_exists('brightness', $options) || array_key_exists('saturation', $options) || array_key_exists('hue', $options)) {
$image = $this->brightness($image, array_key_exists('brightness', $options) ? $options['brightness'] : 1.0, array_key_exists('saturation', $options) ? $options['saturation'] : 1.0, array_key_exists('hue', $options) ? $options['hue'] : 0.0);
}
if (isset($options['rotation'])) {
if (array_key_exists('rotation', $options)) {
$image = $image->rotate($options['rotation']);
}
if (isset($options['flip'])) {
if (array_key_exists('flip', $options)) {
if ($options['flip'] == "h") {
$image = $image->fliphor();
}
@ -80,44 +130,20 @@ class ImageProcessor
$image = $image->flipver();
}
}
$image->writeToFile($target,$saveOptions);
$image->writeToFile($target, $saveOptions);
return new \Illuminate\Http\File($target);
}
public function createFakeImage($stub,$path, $saveOptions = ['Q' => 100],$options = ["w" => null, "h" => null, "r" => null,"flip" => null , "canv" => null,"rotation" => null])
public function createFakeImage($stub, $path, $saveOptions = ['Q' => 100], $options = ["w" => null, "h" => null, "r" => null, "flip" => null, "canv" => null, "rotation" => null])
{
$image = $this->process($stub,$path,$options,$saveOptions);
return $this->process($stub, $path, $options, $saveOptions);
}
public function brightness($image, float $brightness = 1.0, float $saturation = 1.0, float $hue = 0.0)
{
$oldInterpretation = $image->interpretation;
$hue %= 360;
if ($hue < 0) {
$hue = 360 + $hue;
}
if ($image->hasAlpha()) {
$imageWithoutAlpha = $image->extract_band(0, ['n' => $image->bands - 1]);
$alpha = $image->extract_band($image->bands - 1, ['n' => 1]);
return $imageWithoutAlpha
->colourspace(Interpretation::LCH)
->linear([$brightness, $saturation, 1.0], [0.0, 0.0, $hue])
->colourspace($oldInterpretation)
->bandjoin($alpha);
}
return $image
->colourspace(Interpretation::LCH)
->linear([$brightness, $saturation, 1.0], [0.0, 0.0, $hue])
->colourspace($oldInterpretation);
}
public function convertImage(string $filePath,string $target,array $options = ['Q' => 100]) : \Illuminate\Http\File
public function convertImage(string $filePath, string $target, array $options = ['Q' => 100]): \Illuminate\Http\File
{
$tmpFile = \Jcupitt\Vips\Image::newFromFile($filePath);
$tmpFile->writeToFile($target,$options);
$tmpFile->writeToFile($target, $options);
return new \Illuminate\Http\File($target);
}
}

30
app/Image/Traits/ModulateTrait.php

@ -0,0 +1,30 @@
<?php
namespace App\Image\Traits;
use Jcupitt\Vips\Interpretation;
trait ModulateTrait {
public function brightness($image, float $brightness = 1.0, float $saturation = 1.0, float $hue = 0.0)
{
$oldInterpretation = $image->interpretation;
$hue %= 360;
if ($hue < 0) {
$hue = 360 + $hue;
}
if ($image->hasAlpha()) {
$imageWithoutAlpha = $image->extract_band(0, ['n' => $image->bands - 1]);
$alpha = $image->extract_band($image->bands - 1, ['n' => 1]);
return $imageWithoutAlpha
->colourspace(Interpretation::LCH)
->linear([$brightness, $saturation, 1.0], [0.0, 0.0, $hue])
->colourspace($oldInterpretation)
->bandjoin($alpha);
}
return $image
->colourspace(Interpretation::LCH)
->linear([$brightness, $saturation, 1.0], [0.0, 0.0, $hue])
->colourspace($oldInterpretation);
}
}

5
app/Models/Collection.php

@ -39,6 +39,11 @@ class Collection extends Model
'mimetypes' => 'array',
];
public function files()
{
return $this->hasMany(File::class);
}
protected function exts(): Attribute
{
return Attribute::make(

14
app/Models/File.php

@ -8,6 +8,7 @@ use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
class File extends Model
@ -49,5 +50,18 @@ class File extends Model
);
}
public function collection()
{
return $this->belongsTo(Collection::class);
}
public function getPath()
{
return Storage::disk($this->collection->disk)->path($this->server_path . $this->uuid . '.' . $this->collection->ext);
}
public function getModifiedPath()
{
return Storage::disk(app()->collection->disk)->path(app()->file->server_path . app()->file->uuid . '-modified.' . app()->collection->ext);
}
}

4
routes/api.php

@ -31,7 +31,7 @@ Route::get('newTmp/{uuid}', function ($uuid) {
Route::group(['as' => 'api.'], function () {
Route::apiResource('collections', CollectionController::class);
Route::delete('collections/{collection}', [CollectionController::class, "destroy"])->withTrashed();
// Route::get('{collection_name}/{uuid}.{extention}', [FileController::class, 'show'])->name('files.show');
Route::get('{collection_name}/{path}', [FileController::class, 'private'])->name('files.private');
Route::get('{collection_name}/{uuid}.{extention}', [FileController::class, 'show'])->name('files.show');
// Route::get('{collection_name}/{path}', [FileController::class, 'private'])->name('files.private');
Route::post('{collection_name}/{model_id?}', [FileController::class, 'store'])->name('files.store');
});

1
storage/framework/.gitignore

@ -1,3 +1,4 @@
compiled.php
config.php
down

BIN
storage/stub/image1.png

Binary file not shown.

After

Width: 600  |  Height: 1200  |  Size: 620 KiB

BIN
storage/stub/image2.jpeg

Binary file not shown.

After

Width: 720  |  Height: 1204  |  Size: 124 KiB

BIN
storage/stub/image3.png

Binary file not shown.

After

Width: 423  |  Height: 750  |  Size: 42 KiB

21
tests/Feature/FileDeleteTest.php

@ -0,0 +1,21 @@
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
class FileDeleteTest extends TestCase
{
/**
* A basic feature test example.
*
* @return void
*/
public function test_example()
{
$this->assertTrue(true);
}
}

20
tests/Feature/FileShowTest.php

@ -0,0 +1,20 @@
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
class FileShowTest extends TestCase
{
/**
* A basic feature test example.
*
* @return void
*/
public function test_example()
{
$this->assertTrue(true);
}
}

34
tests/Feature/FileStoreTest.php

@ -31,22 +31,24 @@ class FileStoreTest extends Bootstrap
'tmp_support' => false
]);
$response = $this->postJson(route('api.files.store', ['collection_name' => $collection->name]), $data);
$response = $this->loginAs()->postJson(route('api.files.store', ['collection_name' => $collection->name]), $data);
$response->assertForbidden();
}
public function test_tmp_false_and_collection_is_full_forbidden()
{
$randomCount = rand(2, 10);
$collection = Collection::factory()->createQuietly([
'tmp_support' => false,
'count' => $randomCount
]);
for ($i = $randomCount; $i > 0; $i--) {
$uuid = app()->uuid;
$this->one(File::class, [
'uuid' => $uuid,
'user_id' => auth()->id(),
'user_id' => 1,
'collection_id' => $collection->id,
'server_path' => '/' . date('y') . '/' . date('m') . '/' . $uuid . '.' . $collection->ext,
]);
@ -58,7 +60,7 @@ class FileStoreTest extends Bootstrap
"description" => 'lfjdsklfslfsdlfasdfsfhgsfgsdf',
"public" => 1
];
$response = $this->postJson(route('api.files.store', ['collection_name' => $collection->name]), $data);
$response = $this->loginAs()->postJson(route('api.files.store', ['collection_name' => $collection->name]), $data);
$response->assertForbidden();
}
@ -74,7 +76,7 @@ class FileStoreTest extends Bootstrap
"public" => 1
];
$response = $this->postJson(route('api.files.store', ['collection_name' => $collection->name]), $data);
$response = $this->loginAs()->postJson(route('api.files.store', ['collection_name' => $collection->name]), $data);
$response->assertForbidden();
}
@ -94,7 +96,7 @@ class FileStoreTest extends Bootstrap
"public" => 1
];
$response = $this->postJson(route('api.files.store', ['collection_name' => $collection->name]), $data);
$response = $this->loginAs()->postJson(route('api.files.store', ['collection_name' => $collection->name]), $data);
$response->assertForbidden();
}
@ -104,7 +106,7 @@ class FileStoreTest extends Bootstrap
public function test_store_dynamic_validation_unprocessable($collectionFields, $dataFields)
{
$collection = Collection::factory()->create($collectionFields);
$response = $this->postJson(route('api.files.store', ['collection_name' => $collection->name]), $dataFields);
$response = $this->loginAs()->postJson(route('api.files.store', ['collection_name' => $collection->name]), $dataFields);
$response->assertUnprocessable();
}
@ -143,7 +145,7 @@ class FileStoreTest extends Bootstrap
"public" => 1
];
$response = $this->postJson(route('api.files.store', ['collection_name' => $collection->name]), $data);
$response = $this->loginAs()->postJson(route('api.files.store', ['collection_name' => $collection->name]), $data);
$response->assertUnprocessable();
}
@ -171,7 +173,7 @@ class FileStoreTest extends Bootstrap
"file" => UploadedFile::fake()->image('test.png'),
"public" => 1
];
$response = $this->postJson(route('api.files.store', ['collection_name' => $collection->name]), $data);
$response = $this->loginAs()->postJson(route('api.files.store', ['collection_name' => $collection->name]), $data);
$response->assertCreated();
}
@ -202,7 +204,7 @@ class FileStoreTest extends Bootstrap
"public" => 1
];
$response = $this->postJson(route('api.files.store', ['collection_name' => $collection->name]), $data);
$response = $this->loginAs()->postJson(route('api.files.store', ['collection_name' => $collection->name]), $data);
$response->assertCreated();
}
@ -211,12 +213,13 @@ class FileStoreTest extends Bootstrap
$collection = Collection::factory()->createQuietly([
'alt_required' => false,
'description_required' => false,
'tmp_support' => true,
'tmp_support' => false,
'max_width' => 2000,
'max_height' => 2000,
'min_width' => 1,
'min_height' => 1,
'min_file_size' => 0
'min_file_size' => 0,
'count' => 1
]);
$uuid = app()->uuid;
$file = File::factory()->createQuietly([
@ -233,7 +236,7 @@ class FileStoreTest extends Bootstrap
"public" => 1
];
$response = $this->postJson(route('api.files.store', ['collection_name' => $collection->name]), $data);
$response = $this->loginAs()->postJson(route('api.files.store', ['collection_name' => $collection->name]), $data);
$response->assertCreated();
}
@ -242,12 +245,13 @@ class FileStoreTest extends Bootstrap
$collection = Collection::factory()->createQuietly([
'alt_required' => false,
'description_required' => false,
'tmp_support' => true,
'tmp_support' => false,
'max_width' => 2000,
'max_height' => 2000,
'min_width' => 1,
'min_height' => 1,
'min_file_size' => 0
'min_file_size' => 0,
'count' => 1
]);
$uuid = app()->uuid;
$file = File::factory()->createQuietly([
@ -264,7 +268,7 @@ class FileStoreTest extends Bootstrap
"public" => 1
];
$response = $this->postJson(route('api.files.store', ['collection_name' => $collection->name]), $data);
$response = $this->loginAs()->postJson(route('api.files.store', ['collection_name' => $collection->name]), $data);
$response->assertCreated();
}

21
tests/Feature/FileUpdateTest.php

@ -0,0 +1,21 @@
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
class FileUpdateTest extends TestCase
{
/**
* A basic feature test example.
*
* @return void
*/
public function test_example()
{
$this->assertTrue(true);
}
}
Loading…
Cancel
Save