Browse Source

merge

master
masoud 2 years ago
parent
commit
acc001ee36
  1. 1
      .gitignore
  2. 13
      Dockerfile
  3. 1
      app/Console/Commands/TestGenerator.php
  4. 9
      app/Http/Controllers/CollectionController.php
  5. 3
      app/Http/Controllers/Controller.php
  6. 87
      app/Http/Controllers/FileController.php
  7. 8
      app/Http/Controllers/Traits/FileTrait.php
  8. 6
      app/Http/Kernel.php
  9. 14
      app/Http/Middleware/BindCollectionModelMiddleware.php
  10. 75
      app/Http/Middleware/BindFileModelMiddleware.php
  11. 72
      app/Http/Requests/FileStoreRequest.php
  12. 34
      app/Http/Requests/FileUpdateRequest.php
  13. 2
      app/Http/Resources/CollectionResource.php
  14. 1
      app/Http/Resources/FileResource.php
  15. 271
      app/Image/ImageProcessor.php
  16. 8
      app/Image/Traits/ModulateTrait.php
  17. 41
      app/Models/Collection.php
  18. 31
      app/Models/File.php
  19. 3
      app/Models/Traits/Validatable.php
  20. 2
      app/Providers/AppServiceProvider.php
  21. 3
      app/Providers/EventServiceProvider.php
  22. 1
      app/Providers/RouteServiceProvider.php
  23. 31
      app/Providers/UuidServiceProvider.php
  24. 193
      app/Utilities/Helpers/Image.php
  25. 5
      composer.json
  26. 47
      composer.lock
  27. 3
      config/app.php
  28. 8
      config/filesystems.php
  29. 9
      database/factories/BaseFactory.php
  30. 57
      database/factories/CollectionFactory.php
  31. 9
      database/factories/Documents/PolicyDocumentFactory.php
  32. 39
      database/factories/FileFactory.php
  33. 2
      database/factories/ILaravelFactory.php
  34. 2
      database/factories/UserFactory.php
  35. 1
      database/migrations/2022_07_27_073858_create_files_table.php
  36. 5
      database/migrations/2022_07_27_073906_create_collections_table.php
  37. 50
      database/seeders/DatabaseSeeder.php
  38. 1
      docker-compose.yml
  39. BIN
      public/image-modified.webp
  40. 18
      resources/views/welcome.blade.php
  41. 19
      routes/api.php
  42. 96
      routes/web.php
  43. 1
      storage/framework/.gitignore
  44. 0
      storage/stub/image.png
  45. BIN
      storage/stub/image2.jpeg
  46. BIN
      storage/stub/image3.png
  47. 8
      tests/Base/FactoryMethodsTrait.php
  48. 8
      tests/Bootstrap.php
  49. 56
      tests/Feature/Collection/CollectionDeleteTest.php
  50. 34
      tests/Feature/Collection/CollectionShowTest.php
  51. 51
      tests/Feature/Collection/CollectionStoreTest.php
  52. 67
      tests/Feature/Collection/CollectionUpdateTest.php
  53. 47
      tests/Feature/FileDeleteTest.php
  54. 28
      tests/Feature/FileShowTest.php
  55. 206
      tests/Feature/FileStoreTest.php
  56. 47
      tests/Feature/FileUpdateTest.php
  57. 22
      tests/Feature/Traits/FileImageTrait.php
  58. 52
      tests/Feature/Traits/FileShowTrait.php
  59. 89
      tests/Feature/Traits/FileTraits.php

1
.gitignore

@ -3,6 +3,7 @@
/public/hot
/public/storage
/storage/*.key
/storage/stub/*.webp
/vendor
.env
.env.backup

13
Dockerfile

@ -8,7 +8,12 @@ RUN apt-get -y update \
&& apt-get -y install --no-install-recommends libvips42 \
&& apt-get -y install libvips-tools \
&& apt-get -y install libvips-dev \
&& pecl install vips \
&& echo 'extension="vips.so"' > /usr/local/etc/php/conf.d/20-vips.ini
RUN echo 'ffi.enable = "true"' >> /usr/local/etc/php/conf.d/20-vips.ini
&& pecl install vips
#<<<<<<< HEAD
# && echo 'extension="vips.so"' > /usr/local/etc/php/conf.d/20-vips.ini
#RUN echo 'ffi.enable = "true"' >> /usr/local/etc/php/conf.d/20-vips.ini
#
#=======
# && echo 'extension="vips.so"' > /usr/local/etc/php/conf.d/20-vips.ini \
# && echo "ffi.enable=true" >> /usr/local/etc/php/php.ini
#>>>>>>> 68568fb8827dca49383e3294693017472af49313

1
app/Console/Commands/TestGenerator.php

@ -46,6 +46,7 @@ class TestGenerator extends Command
$models = $this->getSelectedModels();
// if (!is_dir('tests/Feature/' . ucfirst($model))) {
// mkdir('tests/Feature/' . ucfirst($model), 0777, true);
// } else {

9
app/Http/Controllers/CollectionController.php

@ -2,8 +2,10 @@
namespace App\Http\Controllers;
use App\Documents\PolicyDocument;
use App\Http\Resources\CollectionResource;
use App\Models\Collection;
use App\Utilities\Polices\BasePolicy;
use Illuminate\Http\Request;
class CollectionController extends Controller
@ -11,23 +13,30 @@ class CollectionController extends Controller
public function show(Collection $collection)
{
BasePolicy::allow(app('modelDocument')?->getPolicy,$collection);
return new CollectionResource($collection);
}
public function store(Request $request)
{
BasePolicy::allow(app('modelDocument')?->storePolicy);
$collection = Collection::create($request->all());
return new CollectionResource($collection);
}
public function update(Request $request, Collection $collection)
{
BasePolicy::allow(app('modelDocument')?->updatePolicy, $collection);
$collection->update($request->all());
return new CollectionResource($collection);
}
public function destroy(Collection $collection)
{
BasePolicy::allow(app('modelDocument')?->{$collection->trashed() ? 'restorePolicy' : 'deletePolicy'},$collection);
if ($collection->trashed()) {
return $collection->restore();
}

3
app/Http/Controllers/Controller.php

@ -2,6 +2,7 @@
namespace App\Http\Controllers;
use App\Http\Controllers\Traits\FileTrait;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
@ -9,7 +10,7 @@ use Illuminate\Routing\Controller as BaseController;
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
use AuthorizesRequests, DispatchesJobs, ValidatesRequests, FileTrait;
protected function resourceAbilityMap()
{

87
app/Http/Controllers/FileController.php

@ -3,53 +3,45 @@
namespace App\Http\Controllers;
use App\Http\Requests\FileStoreRequest;
use App\Http\Requests\FileUpdateRequest;
use App\Http\Resources\FileResource;
use App\Jobs\FileConversionQueue;
use App\Image\ImageProcessor;
use App\Models\File;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Storage;
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);
}
public function private($collection, $path)
{
Storage::disk(app()->collection->disk)->download($path);
if (!is_null(array_intersect(array_keys($request->all()), $this->availableParams))) {
$data = $imageProcessor->processToBuffer(app()->file->getPath(), $request->all());
$response = Response::make($data, 200);
$response->header('Content-Type','image/webp');
return $response;
}
return response()->file(app()->file->getPath());
}
public function store(Request $request)
public function store(FileStoreRequest $request, ImageProcessor $imageProcessor)
{
// skip policy ---and first level----------
$validated = $request->validate([
"file" => [
"mimes:" . app()->collection->getExts(),
"mimetypes:" . app()->collection->getMimeTypes(),
"dimensions:min_width=" . app()->collection->min_width . ",min_height=" . app()->collection->min_height . ',max_width=' . app()->collection->max_width . ',max_height=' . app()->collection->max_height,
"max:" . app()->collection->max_size,
"min:" . app()->collection->min_size,
],
"alts" => [app()->collection->alt_required ? "required" : "null", 'array'],
"alts.*" => [app()->collection->alt_required ? "required" : "null"],
"description" => [app()->collection->description_required ? "required" : "null"],
]);
DB::beginTransaction();
$urlStorage = '/' . date('y') . '/' . date('m') . app()->getFileUuid;
$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);
}
try {
$file = File::create([
'uuid' => app()->getFileUuid,
$fileResource = null;
DB::transaction(function () use ($request, &$fileResource) {
$uuid = app()->uuid;
$fileResource = File::create([
'uuid' => $uuid,
'original_name' => $request->name,
'public' => $request->public,
'ext' => $request->file->extension(),
@ -64,35 +56,34 @@ class FileController extends Controller
'ip' => $request->ip(),
'collection_id' => app()->collection->id,
'published_at' => $request->published_at,
'server_path' => $urlStorage
'server_path' => '/' . date('y') . '/' . date('m') . '/',
]);
if (!app()->collection->tmp_support && app()->collection->count == 1) {
File::where('user_id', auth()->id())->delete();
}
$file = $request->file->storeAs($urlStorage, app()->getFileUuid, app()->collection->disk);
if (app()->collection->public) {
Storage::setVisibility($file, 'public');
}
DB::commit();
} catch (\Exception $e) {
DB::rollback();
}
$storedFile = Storage::disk(app()->collection->disk)->putFileAs($fileResource->server_path, $request->file, $fileResource->uuid . '.' . app()->collection->ext);
FileConversionQueue::dispatch($file, app()->collection);
return new FileResource($file);
if (app()->collection->public) {
Storage::disk(app()->collection->disk)->setVisibility($storedFile, 'public');
}
});
return new FileResource($fileResource);
}
public function update(Request $request, $id)
public function update(FileUpdateRequest $request)
{
//
app()->file->update($request->all());
return new FileResource(app()->file);
}
public function destroy($id)
public function destroy()
{
//
if (app()->file->trashed()) {
return app()->file->restore();
}
return app()->file->delete();
}
}

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

@ -0,0 +1,8 @@
<?php
namespace App\Http\Controllers\Traits;
trait FileTrait
{
public $availableParams = ['q','r','w','h','canv','brightness','saturation','hue','rotation','flip'];
}

6
app/Http/Kernel.php

@ -3,6 +3,7 @@
namespace App\Http;
use App\Http\Middleware\BindCollectionModelMiddleware;
use App\Http\Middleware\BindFileModelMiddleware;
use App\Http\Middleware\BindModelDocumentMiddleware;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
@ -44,8 +45,9 @@ class Kernel extends HttpKernel
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
// BindModelDocumentMiddleware::class,
BindCollectionModelMiddleware::class
BindModelDocumentMiddleware::class,
BindCollectionModelMiddleware::class,
BindFileModelMiddleware::class,
],
];

14
app/Http/Middleware/BindCollectionModelMiddleware.php

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

75
app/Http/Middleware/BindFileModelMiddleware.php

@ -0,0 +1,75 @@
<?php
namespace App\Http\Middleware;
use App\Models\Collection;
use App\Models\File;
use Closure;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class BindFileModelMiddleware
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
*/
public function handle(Request $request, Closure $next)
{
if (($request->route()->action['as'] == 'api.files.store') && is_null($request->file('file'))) {
$file = File::find($request->file);
if (!is_null($file)) {
$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;
});
}
}
}
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);
}
}
if ($request->route()->action['as'] == 'api.files.update') {
$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);
}
}
if ($request->route()->action['as'] == 'api.files.destroy') {
$file = File::withTrashed()->findOrFail($request->route('uuid'));
$Collection = Collection::withTrashed()->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);
}
}

72
app/Http/Requests/FileStoreRequest.php

@ -0,0 +1,72 @@
<?php
namespace App\Http\Requests;
use App\Models\File;
use App\Utilities\Polices\BasePolicy;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Storage;
class FileStoreRequest extends FormRequest
{
public function isImage($file)
{
if (@is_array(getimagesize($file))) {
return true;
} else {
return false;
}
}
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
if (!app()->collection->tmp_support && !$this->model_id) {
return false;
}
if (app()->collection->count !== 1 && (app()->collection->count <= File::where('user_id', auth()->id())->where('collection_id',app()->collection->id)->count()) && !app()->collection->tmp_support) {
return false;
}
if (!app()->bound('file') && is_null($this->file('file'))) {
return false;
}
if (!$this->hasFile('file')) {
$this->replace([
'file' => new \Illuminate\Http\File(app()->file->getPath(), app()->file->uuid . '.' . app()->collection->ext)
]);
}
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, mixed>
*/
public function rules()
{
return [
"file" => [
"mimes:" . app()->collection->getExts(),
"mimetypes:" . app()->collection->getMimeTypes(),
!$this->isImage(request()->file->path()) ?: "dimensions:min_width=" . app()->collection->min_width . ",min_height=" . app()->collection->min_height . ',max_width=' . app()->collection->max_width . ',max_height=' . app()->collection->max_height,
"max:" . app()->collection->max_file_size,
"min:" . app()->collection->min_file_size,
],
"alts" => [app()->collection->alt_required ? "required" : "nullable", 'array'],
"alts.*" => [app()->collection->alt_required ? "required" : "nullable", 'max:1000'],
"description" => [app()->collection->description_required ? "required" : "nullable", 'max:300'],
'original_name' => ["string", "nullable", 'max:300'],
'published_at' => ['date_format:Y-m-d H:i:s', 'nullable'],
];
}
}

34
app/Http/Requests/FileUpdateRequest.php

@ -0,0 +1,34 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class FileUpdateRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, mixed>
*/
public function rules()
{
return [
"alts" => [app()->collection->alt_required ? "required" : "nullable", 'array'],
"alts.*" => [app()->collection->alt_required ? "required" : "nullable", 'max:1000'],
"description" => [app()->collection->description_required ? "required" : "nullable", 'max:300'],
'original_name' => ["string", "nullable", 'max:300'],
'published_at' => ['date_format:Y-m-d H:i:s', 'nullable'],
];
}
}

2
app/Http/Resources/CollectionResource.php

@ -32,7 +32,7 @@ class CollectionResource extends JsonResource
"description_required" => $this->description_required,
"count" => $this->count,
"exts" => $this->exts,
"avalible_exts" => $this->avalible_exts,
"ext" => $this->ext,
"mimetypes" => $this->mimetypes,
"model" => $this->model,
"expire_date" => $this->expire_date,

1
app/Http/Resources/FileResource.php

@ -21,6 +21,7 @@ class FileResource extends JsonResource
"mimetype" => $this->mimetype,
"width" => $this->width,
"height" => $this->height,
"server_path" => $this->server_path,
"file_size" => $this->file_size,
"sort" => $this->sort,
"alts" => $this->alts,

271
app/Image/ImageProcessor.php

@ -0,0 +1,271 @@
<?php
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
{
use ModulateTrait, FileTrait;
protected $defaultW = 2000;
protected $defaultH = 2000;
public function processToBuffer(string $filename, array $options = [], $saveOptions = ['Q' => 100])
{
if (array_key_exists('w', $options) && array_key_exists('h', $options) && array_key_exists('r', $options)) {
unset($options['r']);
}
if (array_key_exists('r', $options)) {
$options['r'] = explode(':', $options['r']);
if (count($options['r']) != 2) {
unset($options['r']);
}
$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 (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 (!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']);
}
$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 (!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 (!array_key_exists('w', $options) && array_key_exists('h', $options)) {
$image = Image::thumbnail($filename, $this->defaultW, ['height' => $options['h']]);
}
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;
$image = $image->embed(
$widthW,
$widthH,
$options['w'],
$options['h'],
['extend' => 'background', 'background' => [255,255,255]]
);
} else {
$image = Image::thumbnail($filename, $options['w'], ['height' => $options['h'], 'crop' => 'centre']);
}
}
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 (array_key_exists('rotation', $options)) {
$image = $image->rotate($options['rotation']);
}
if (array_key_exists('flip', $options)) {
if ($options['flip'] == "h") {
$image = $image->fliphor();
}
if ($options['flip'] == "v") {
$image = $image->flipver();
}
if ($options['flip'] == "hv") {
$image = $image->fliphor();
$image = $image->flipver();
}
}
if (array_key_exists('q', $options)) {
$saveOptions['Q'] = $options['q'];
}
if (app()->file->ext == 'png') {
$saveOptions = array_merge(['palette'=>true],$saveOptions);
}
return $image->writeToBuffer("." . app()->file->ext, $saveOptions);
}
public function process(string $filename, string $target, array $options = [], $saveOptions = ['Q' => 100])
{
if (array_key_exists('w', $options) && array_key_exists('h', $options) && array_key_exists('r', $options)) {
unset($options['r']);
}
if (array_key_exists('r', $options)) {
$options['r'] = explode(':', $options['r']);
if (count($options['r']) != 2) {
unset($options['r']);
}
$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 (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 (!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']);
}
$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 (!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 (!array_key_exists('w', $options) && array_key_exists('h', $options)) {
$image = Image::thumbnail($filename, $this->defaultW, ['height' => $options['h']]);
}
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;
$image = $image->embed(
$widthW,
$widthH,
$options['w'],
$options['h'],
['extend' => 'background', 'background' => 0]
);
} else {
$image = Image::thumbnail($filename, $options['w'], ['height' => $options['h'], 'crop' => 'centre']);
}
}
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 (array_key_exists('rotation', $options)) {
$image = $image->rotate($options['rotation']);
}
if (array_key_exists('flip', $options)) {
if ($options['flip'] == "h") {
$image = $image->fliphor();
}
if ($options['flip'] == "v") {
$image = $image->flipver();
}
if ($options['flip'] == "hv") {
$image = $image->fliphor();
$image = $image->flipver();
}
}
$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])
{
return $this->process($stub, $path, $options, $saveOptions);
}
public function convertImage(string $filePath, string $target, array $options = ['Q' => 100]): \Illuminate\Http\File
{
$tmpFile = \Jcupitt\Vips\Image::newFromFile($filePath);
$tmpFile->writeToFile($target, $options);
return new \Illuminate\Http\File($target);
}
}

8
app/Image/Processor.php → app/Image/Traits/ModulateTrait.php

@ -1,11 +1,12 @@
<?php
namespace App\Image;
namespace App\Image\Traits;
use Jcupitt\Vips\Interpretation;
class Processor {
public static function brightness($image, float $brightness = 1.0, float $saturation = 1.0, float $hue = 0.0) {
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) {
@ -25,4 +26,5 @@ class Processor {
->linear([$brightness, $saturation, 1.0], [0.0, 0.0, $hue])
->colourspace($oldInterpretation);
}
}

41
app/Models/Collection.php

@ -2,6 +2,8 @@
namespace App\Models;
use App\Models\Traits\Validatable;
use App\Models\Traits\ValidationMaker;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
@ -9,7 +11,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
class Collection extends Model
{
use HasFactory, SoftDeletes;
use HasFactory, SoftDeletes, Validatable, ValidationMaker;
protected $fillable = [
"name",
@ -28,7 +30,7 @@ class Collection extends Model
"alt_required",
"description_required",
"exts",
"avalible_exts",
"ext",
"mimetypes",
"model",
"expire_date",
@ -36,22 +38,46 @@ class Collection extends Model
protected $casts = [
'exts' => 'array',
'avalible_exts' => 'array',
'mimetypes' => 'array',
];
public function files()
{
return $this->hasMany(File::class);
}
protected function exts(): Attribute
{
return Attribute::make(
set: fn ($value) => json_encode($value),
);
}
protected function avalible_exts(): Attribute
public function rules(): array
{
return Attribute::make(
set: fn ($value) => json_encode($value),
);
return [
'name' => ['max:100', 'Required', 'string', 'unique:collections,name'],
"path" => ['max:255', 'nullable', 'string'],
"public" => ['nullable', 'boolean'],
"disk" => ['required', 'string', 'max:255'],
"count" => ['required', 'numeric', 'max:255'],
"tmp_support" => ['required', 'boolean'],
"remove_tmp_time" => ['date_format:Y-m-d H:i:s', 'nullable'],
"max_file_size" => ['nullable', 'numeric'],
"min_file_size" => ['nullable', 'numeric'],
"max_width" => ['nullable', 'numeric'],
"min_width" => ['nullable', 'numeric'],
"max_height" => ['nullable', 'numeric'],
"min_height" => ['nullable', 'numeric'],
"alt_required" => ['required', 'boolean'],
"description_required"=> ['required','boolean'],
"exts" => ['nullable'],
"ext" => ['string','max:100','nullable'],
"mimetypes"=> ['nullable'],
"expire_date" => ['date_format:Y-m-d H:i:s','nullable'],
];
}
protected function mimetypes(): Attribute
{
return Attribute::make(
@ -68,5 +94,4 @@ class Collection extends Model
{
return implode(",", app()->collection->mimetypes);
}
}

31
app/Models/File.php

@ -3,10 +3,12 @@
namespace App\Models;
use App\Models\Traits\Validatable;
use App\Models\Traits\ValidationMaker;
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
@ -15,6 +17,8 @@ class File extends Model
protected $primaryKey = 'uuid';
public $incrementing = false;
protected $fillable = [
"uuid",
"original_name",
@ -29,6 +33,7 @@ class File extends Model
"description",
"user_id",
"ip",
"model_id",
"collection_id",
"published_at",
];
@ -45,16 +50,18 @@ class File extends Model
);
}
// public function rules(): array
// {
// return [
// "image" => [
// "mimes:" . app()->collection->getExts(),
// "mimetypes:" . app()->collection->getMimeTypes(),
// "dimensions:min_width=" . app()->collection->min_width . ",min_height=" . app()->collection->min_height . ',max_width=' . app()->collection->max_width . ',max_height=' . app()->collection->max_height,
// "max:" . app()->collection->max_size,
// "min:" . app()->collection->min_size,
// ],
// ];
// }
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);
}
}

3
app/Models/Traits/Validatable.php

@ -59,7 +59,9 @@ trait Validatable
$rules = array_merge($rules, $this->createValidations());
$rules = array_merge($rules, $this->createValidations('category_data'));
$validator = Validator::make($this->toArray(), $rules);
if ($abort && $validator->fails()) {
throw new ValidationException($validator,
new JsonResponse([
@ -83,7 +85,6 @@ trait Validatable
return $abort ? $this : true;
}
public function getLoads()
{
return [];

2
app/Providers/AppServiceProvider.php

@ -27,7 +27,7 @@ class AppServiceProvider extends ServiceProvider
{
Storage::disk('local')->buildTemporaryUrlsUsing(function ($path, $expiration, $options) {
return URL::temporarySignedRoute(
'api.file.private',
'api.files.private',
$expiration,
array_merge($options, ['path' => $path])
);

3
app/Providers/EventServiceProvider.php

@ -2,10 +2,13 @@
namespace App\Providers;
use App\Models\File;
use App\Observers\FileObserver;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;
use PHPUnit\TextUI\XmlConfiguration\FileCollection;
class EventServiceProvider extends ServiceProvider
{

1
app/Providers/RouteServiceProvider.php

@ -2,6 +2,7 @@
namespace App\Providers;
use App\Models\File;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Http\Request;

31
app/Providers/UuidServiceProvider.php

@ -0,0 +1,31 @@
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Str;
class UuidServiceProvider extends ServiceProvider
{
/**
* Register services.
*
* @return void
*/
public function register()
{
//
}
/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
$this->app->bind('uuid', function () {
return Str::uuid();
});
}
}

193
app/Utilities/Helpers/Image.php

@ -0,0 +1,193 @@
<?php
namespace App\Utilities\Helpers;
use Faker\Provider\Base;
class Image extends Base {
/**
* @var string
*/
public const BASE_URL = 'https://via.placeholder.com';
public const FORMAT_JPG = 'jpg';
public const FORMAT_JPEG = 'jpeg';
public const FORMAT_PNG = 'png';
/**
* @var array
*
* @deprecated Categories are no longer used as a list in the placeholder API but referenced as string instead
*/
protected static $categories = [
'abstract', 'animals', 'business', 'cats', 'city', 'food', 'nightlife',
'fashion', 'people', 'nature', 'sports', 'technics', 'transport',
];
/**
* Generate the URL that will return a random image
*
* Set randomize to false to remove the random GET parameter at the end of the url.
*
* @example 'http://via.placeholder.com/640x480.png/CCCCCC?text=well+hi+there'
*
* @param int $width
* @param int $height
* @param string|null $category
* @param bool $randomize
* @param string|null $word
* @param bool $gray
* @param string $format
*
* @return string
*/
public static function imageUrl(
$width = 640,
$height = 480,
$category = null,
$randomize = true,
$word = null,
$gray = false,
$format = 'png'
) {
trigger_deprecation(
'fakerphp/faker',
'1.20',
'Provider is deprecated and will no longer be available in Faker 2. Please use a custom provider instead'
);
// Validate image format
$imageFormats = static::getFormats();
if (!in_array(strtolower($format), $imageFormats, true)) {
throw new \InvalidArgumentException(sprintf(
'Invalid image format "%s". Allowable formats are: %s',
$format,
implode(', ', $imageFormats)
));
}
$size = sprintf('%dx%d.%s', $width, $height, $format);
$imageParts = [];
if ($category !== null) {
$imageParts[] = $category;
}
if ($word !== null) {
$imageParts[] = $word;
}
if ($randomize === true) {
$imageParts[] = Lorem::word();
}
$backgroundColor = $gray === true ? 'CCCCCC' : str_replace('#', '', Color::safeHexColor());
return sprintf(
'%s/%s/%s%s',
self::BASE_URL,
$size,
$backgroundColor,
count($imageParts) > 0 ? '?text=' . urlencode(implode(' ', $imageParts)) : ''
);
}
/**
* Download a remote random image to disk and return its location
*
* Requires curl, or allow_url_fopen to be on in php.ini.
*
* @example '/path/to/dir/13b73edae8443990be1aa8f1a483bc27.png'
*
* @return bool|string
*/
public static function image(
$dir = null,
$width = 640,
$height = 480,
$category = null,
$fullPath = true,
$randomize = true,
$word = null,
$gray = false,
$format = 'png'
) {
trigger_deprecation(
'fakerphp/faker',
'1.20',
'Provider is deprecated and will no longer be available in Faker 2. Please use a custom provider instead'
);
$dir = null === $dir ? sys_get_temp_dir() : $dir; // GNU/Linux / OS X / Windows compatible
// Validate directory path
if (!is_dir($dir) || !is_writable($dir)) {
throw new \InvalidArgumentException(sprintf('Cannot write to directory "%s"', $dir));
}
// Generate a random filename. Use the server address so that a file
// generated at the same time on a different server won't have a collision.
$name = md5(uniqid(empty($_SERVER['SERVER_ADDR']) ? '' : $_SERVER['SERVER_ADDR'], true));
$filename = sprintf('%s.%s', $name, $format);
$filepath = $dir . DIRECTORY_SEPARATOR . $filename;
$url = static::imageUrl($width, $height, $category, $randomize, $word, $gray, $format);
// save file
if (function_exists('curl_exec')) {
// use cURL
$fp = fopen($filepath, 'w');
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_FILE, $fp);
$success = curl_exec($ch) && curl_getinfo($ch, CURLINFO_HTTP_CODE) === 200;
fclose($fp);
curl_close($ch);
if (!$success) {
unlink($filepath);
// could not contact the distant URL or HTTP error - fail silently.
return false;
}
} elseif (ini_get('allow_url_fopen')) {
// use remote fopen() via copy()
$success = copy($url, $filepath);
if (!$success) {
// could not contact the distant URL or HTTP error - fail silently.
return false;
}
} else {
return new \RuntimeException('The image formatter downloads an image from a remote HTTP server. Therefore, it requires that PHP can request remote hosts, either via cURL or fopen()');
}
return $fullPath ? $filepath : $filename;
}
public static function getFormats(): array
{
trigger_deprecation(
'fakerphp/faker',
'1.20',
'Provider is deprecated and will no longer be available in Faker 2. Please use a custom provider instead'
);
return array_keys(static::getFormatConstants());
}
public static function getFormatConstants(): array
{
trigger_deprecation(
'fakerphp/faker',
'1.20',
'Provider is deprecated and will no longer be available in Faker 2. Please use a custom provider instead'
);
return [
static::FORMAT_JPG => constant('IMAGETYPE_JPEG'),
static::FORMAT_JPEG => constant('IMAGETYPE_JPEG'),
static::FORMAT_PNG => constant('IMAGETYPE_PNG'),
];
}
}

5
composer.json

@ -7,12 +7,13 @@
"require": {
"php": "^8.0.2",
"guzzlehttp/guzzle": "^7.2",
"jcupitt/vips": "2.0.0",
"jenssegers/agent": "^2.6",
"jenssegers/mongodb": "^3.9",
"kosinix/grafika": "^2.0",
"laravel/framework": "^9.19",
"laravel/sanctum": "^2.14.1",
"laravel/tinker": "^2.7",
"jcupitt/vips" : "2.0.0"
"laravel/tinker": "^2.7"
},
"require-dev": {
"brianium/paratest": "^6.5",

47
composer.lock

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "aa091ad8935a98954cd65fdb252e3ab7",
"content-hash": "5ccb92d0caa97ee80e3508d0b7941f1c",
"packages": [
{
"name": "brick/math",
@ -1230,6 +1230,51 @@
],
"time": "2022-06-29T19:04:13+00:00"
},
{
"name": "kosinix/grafika",
"version": "2.0.8",
"source": {
"type": "git",
"url": "https://github.com/kosinix/grafika.git",
"reference": "211f61fc334b8b36616b23e8af7c5727971d96ee"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/kosinix/grafika/zipball/211f61fc334b8b36616b23e8af7c5727971d96ee",
"reference": "211f61fc334b8b36616b23e8af7c5727971d96ee",
"shasum": ""
},
"require": {
"php": ">=5.3"
},
"type": "library",
"autoload": {
"psr-4": {
"Grafika\\": "src/Grafika"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT",
"GPL-2.0+"
],
"authors": [
{
"name": "Nico Amarilla",
"homepage": "https://www.kosinix.com"
}
],
"description": "An image manipulation library for PHP.",
"homepage": "http://kosinix.github.io/grafika",
"keywords": [
"grafika"
],
"support": {
"issues": "https://github.com/kosinix/grafika/issues",
"source": "https://github.com/kosinix/grafika/tree/develop"
},
"time": "2017-06-20T03:13:49+00:00"
},
{
"name": "laravel/framework",
"version": "v9.21.6",

3
config/app.php

@ -195,7 +195,8 @@ return [
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
App\Providers\SettingServiceProvider::class,
// App\Providers\CollectionServiceProvider::class
App\Providers\UuidServiceProvider::class
],
/*

8
config/filesystems.php

@ -48,6 +48,14 @@ return [
],
],
'tmp' => [
'driver' => 'local',
'root' => '/tmp',
'url' => env('APP_URL') . '/storage',
'visibility' => 'public',
'throw' => false,
],
// 'public' => [
// 'driver' => 'local',
// 'root' => storage_path('app/public'),

9
database/factories/BaseFactory.php

@ -3,7 +3,7 @@
namespace Database\Factories;
use App\Image\ImageProcessor;
use Illuminate\Support\Str;
trait BaseFactory
@ -62,12 +62,11 @@ trait BaseFactory
return rand($min + 1, $min + 100);
}
public function withDependency()
public function withDependency($dependencyAttributes = [])
{
return $this->state(function (array $attributes) {
return $this->dependencyProvider();
return $this->state(function (array $attributes) use ($dependencyAttributes) {
return $this->dependencyProvider($dependencyAttributes);
});
}
}

57
database/factories/CollectionFactory.php

@ -10,6 +10,7 @@ use Illuminate\Database\Eloquent\Factories\Factory;
*/
class CollectionFactory extends Factory
{
use BaseFactory;
/**
* Define the model's default state.
*
@ -19,39 +20,55 @@ class CollectionFactory extends Factory
{
return [
"name" => fake()->name(),
"public" => "public",
"public" => rand(0, 1),
"disk" => "local",
"count" => rand(3, 18),
"tmp_support" => rand(0,1),
"remove_tmp_time" => 132,
"remove_tmp_time" => "2022-07-27 09:17:59",
"max_file_size" => rand(300, 2000),
"min_file_size" => rand(300,2000),
"max_width" => rand(300,2000),
"min_width" => rand(300,2000),
"max_height" => rand(300,2000),
"min_height" => rand(300,2000),
"alt_required" => rand(0,1),
"description_required" => rand(0,1),
'alt_required' => false,
'description_required' => false,
'tmp_support' => true,
'max_width' => 2000,
'max_height' => 2000,
'min_width' => 1,
'min_height' => 1,
'min_file_size' => 0,
"exts" => [
"jpg",
"jpeg",
"png",
"webp"
],
"avalible_exts" => [
"jpg",
"jpeg",
"png",
"webp"
],
"ext" => "webp",
"mimetypes" => [
"jpg",
"jpeg",
"png",
"webp"
"image/webp",
"image/png",
"image/jpeg",
"image/jpg"
],
"model" => fake()->name(),
"expire_date" => "2022-07-27 09:17:59"
];
}
public function testGeneratorConfig()
{
return [
'store' => '',
'update' => '',
'show' => '',
'delete' => '',
];
}
public function dependencyProvider($dependencyAttributes = [])
{
return [];
}
public function list()
{
return [
'name:gt'
];
}
}

9
database/factories/Documents/PolicyDocumentFactory.php

@ -4,10 +4,14 @@ namespace Database\Factories\Documents;
use App\Documents\ModelDocument;
use App\Documents\PolicyDocument;
use Database\Factories\BaseFactory;
use Illuminate\Database\Eloquent\Factories\Factory;
class PolicyDocumentFactory extends Factory
{
use BaseFactory;
protected $model = PolicyDocument::class;
public function definition()
@ -19,4 +23,9 @@ class PolicyDocumentFactory extends Factory
'needs' => [],
];
}
public function dependencyProvider($dependencyAttributes= [])
{
return [];
}
}

39
database/factories/FileFactory.php

@ -2,14 +2,22 @@
namespace Database\Factories;
use App\Image\ImageProcessor;
use App\Models\Collection;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\File>
*/
class FileFactory extends Factory
{
use BaseFactory;
private $uuid = null;
/**
* Define the model's default state.
*
@ -17,25 +25,48 @@ class FileFactory extends Factory
*/
public function definition()
{
$this->uuid = app()->uuid;
return [
"uuid" => 1,
"uuid" => $this->uuid,
"original_name" => fake()->name(),
"ext" => ['jpg', 'jpeg', 'png', 'webp'][rand(0, 3)],
"mimetype" => 'image',
"width" => rand(300, 2000),
"height" => rand(300, 2000),
"file_size" => rand(300, 2000),
"server_path" => date('y') . '/' . date('m'),
"sort" => rand(0, 23),
"server_path" => '/' . date('y') . '/' . date('m') . '/',
"alts" => [
'hello wroldswdfouiwref iuwrhgf ow rgfaw ghfawej',
'jhsf asduyfsadf sadf safsuf isfjsdfsudifsduiyf sdiuf sd'
],
"description" => 'ajsfoisahjfoaspf asduf safsafjsh lh',
"user_id" => rand(43724, 382348),
"user_id" => 1,
"ip" => "127.0. 0.1",
"collection_id" => Collection::factory()->create()->id,
// "collection_id" => $collection->id,
"published_at" => "2022-07-27 09:17:59",
];
}
public function dependencyProvider($dependencyAttributes = [])
{
if (array_key_exists('withImage', $dependencyAttributes)) {
if ($dependencyAttributes['withImage'] == true) {
unset($dependencyAttributes['withImage']);
$collection = Collection::factory()->createQuietly($dependencyAttributes);
$imageProcessor = new ImageProcessor;
$imageProcessor->createFakeImage(storage_path('stub') . '/image.png', Storage::disk($collection->disk)->path('/' . date('y') . '/' . date('m') . '/' . $this->uuid . '.' . $collection->ext));
}
} else {
$collection = Collection::factory()->createQuietly($dependencyAttributes);
}
return [
'collection_id' => $collection
];
}
}

2
database/factories/ILaravelFactory.php

@ -57,7 +57,7 @@ class ILaravelFactory extends Factory
];
}
public function dependencyProvider()
public function dependencyProvider($dependencyAttributes= [])
{
return [
'user_id' => UserDocument::factory()->create()->id

2
database/factories/UserFactory.php

@ -34,7 +34,7 @@ class UserFactory extends Factory
return [];
}
public function dependencyProvider()
public function dependencyProvider($dependencyAttributes= [])
{
return [];
}

1
database/migrations/2022_07_27_073858_create_files_table.php

@ -24,6 +24,7 @@ return new class extends Migration
$table->bigInteger("file_size")->nullable();
$table->bigInteger("sort")->nullable();
$table->json("alts")->nullable();
$table->unsignedBigInteger("model_id")->nullable();
$table->string("description")->nullable();
$table->unsignedBigInteger("user_id")->nullable();
$table->string("ip")->nullable();

5
database/migrations/2022_07_27_073906_create_collections_table.php

@ -17,7 +17,7 @@ return new class extends Migration
$table->id();
$table->string("name")->nullable()->unique();
$table->string("path")->nullable();
$table->string("public")->nullable();
$table->boolean("public")->nullable();
$table->string("disk")->nullable();
$table->integer("count")->nullable();
$table->boolean("tmp_support")->nullable();
@ -31,7 +31,8 @@ return new class extends Migration
$table->boolean("alt_required")->nullable();
$table->boolean("description_required")->nullable();
$table->json("exts")->nullable();
$table->json("avalible_exts")->nullable();
$table->string("ext")->nullable();
$table->json('image_processor')->nullable();
$table->json("mimetypes")->nullable();
$table->string("model")->unique()->nullable();
$table->time("expire_date")->nullable();

50
database/seeders/DatabaseSeeder.php

@ -2,8 +2,12 @@
namespace Database\Seeders;
use App\Image\ImageProcessor;
use App\Models\Collection;
use App\Models\File;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Storage;
class DatabaseSeeder extends Seeder
{
@ -21,7 +25,49 @@ class DatabaseSeeder extends Seeder
// 'email' => 'test@example.com',
// ]);
\App\Models\Collection::factory(10)->create();
\App\Models\File::factory(1)->create();
// \App\Models\Collection::factory()->create([
// "name" => "fuck",
// "count" => 1,
// "tmp_support" => 0,
// "remove_tmp_time" => 132,
// "max_file_size" => 100000000,
// "min_file_size" => 10,
// "max_width" =>2000,
// "min_width" =>10,
// "max_height" => 2000,
// "min_height" =>10,
// "alt_required" => 0,
// "description_required" =>0,
// "exts" => [
// "jpg",
// "jpeg",
// "png",
// "webp"
// ],
// "ext" => "webp",
// ]);
// \App\Models\Collection::factory(10)->create();
// \App\Models\File::factory()->create();
$collection = Collection::factory()->createQuietly([
'alt_required' => false,
'description_required' => false,
'tmp_support' => true,
'max_width' => 2000,
'max_height' => 2000,
'min_width' => 1,
'min_height' => 1,
'min_file_size' => 0,
'public' => false
]);
$uuid = app()->uuid;
$file = File::factory()->createQuietly([
'uuid' => $uuid,
'server_path' => '/' . date('y') . '/' . date('m') . '/',
'user_id' => auth()->id(),
'collection_id' => $collection->id
]);
$imageProcessor = new ImageProcessor;
$imageProcessor->createFakeImage(storage_path('stub') . '/image.png', Storage::disk($collection->disk)->path($file->server_path . $uuid . '.' . $collection->ext));
}
}

1
docker-compose.yml

@ -9,6 +9,7 @@ services:
- '8000:80'
volumes:
- '.:/var/www/html'
mysql:
image: 'mysql'
tmpfs: /var/lib/mysql

BIN
public/image-modified.webp

Binary file not shown.

18
resources/views/welcome.blade.php

@ -16,13 +16,9 @@
<body class="antialiased">
<form action="/upload" method="post" enctype="multipart/form-data">
<form action="fuck" method="post" enctype="multipart/form-data">
@csrf
<input type="file" name="file">
<br>
<input type="text" name="" placeholder=""><br>
<input type="text" name="" placeholder="ttt"><br>
<input type="text" name="" placeholder="ttt"><br>
@ -36,6 +32,18 @@
<input type="text" name="" placeholder="ttt"><br>
<input type="submit" value="s">
</form>
@dump($errors->any())
@if($errors->any())
<div class="alert alert-danger">
<p><strong>Opps Something went wrong</strong></p>
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
</body>
</html>

19
routes/api.php

@ -2,7 +2,10 @@
use App\Http\Controllers\CollectionController;
use App\Http\Controllers\FileController;
use App\Models\File;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Storage;
/*
|--------------------------------------------------------------------------
@ -19,10 +22,18 @@ use Illuminate\Support\Facades\Route;
// return $request->user();
// });
Route::get('newTmp/{uuid}', function ($uuid) {
$file = File::find($uuid);
return Storage::disk('local')->temporaryUrl($file->server_path . $file->uuid . '.webp', now()->addMinutes(5), ['collection_name' => 'Paris Renner II']);
})->name('newTmp');
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('file.show');
Route::get('{collection_name}/{path}', [FileController::class, 'private'])->name('file.private');
Route::post('{collection_name}', [FileController::class, 'store'])->name('file.store');
Route::delete('collections/{collection}', [CollectionController::class, "destroy"])->withTrashed()->name('collections.destroy');
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');
Route::put('{collection_name}/{uuid}.{extention}', [FileController::class, 'update'])->name('files.update');
Route::delete('{collection_name}/{uuid}.{extention}', [FileController::class, 'destroy'])->withTrashed()->name('files.destroy');
});

96
routes/web.php

@ -1,13 +1,6 @@
<?php
use App\Http\Controllers\FileController;
use App\Image\Processor;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Jcupitt\Vips\Config;
use Jcupitt\Vips\Extend;
use Jcupitt\Vips\Image;
use Jcupitt\Vips\Utils;
/*
|--------------------------------------------------------------------------
@ -19,92 +12,3 @@ use Jcupitt\Vips\Utils;
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('/', function (Request $request) {
// // $request->dddd = 'tesst';
// dump($request->);
if (!isset($request->w) && !isset($request->h)) {
$image = Image::thumbnail('../public/image.png', getimagesize('../public/image.png')[0]);
}
if ($request->r) {
$rArray = explode(':', $request->r);
if (isset($request->w) && !isset($request->h)) {
$request->h = $request->w * $rArray[1];
$request->w = $request->w * $rArray[0];
}
if (!isset($request->w) && isset($request->h)) {
$request->h = $request->h * $rArray[1];
$request->w = $request->h * $rArray[0];
}
if (!isset($request->w) && !isset($request->h)) {
$request->h = getimagesize('../public/image.png')[0] * $rArray[1];
$request->w = getimagesize('../public/image.png')[0] * $rArray[0];
}
}
if (isset($request->w) && !isset($request->h)) {
$image = Image::thumbnail('../public/image.png', $request->w, ['height' => getimagesize('../public/image.png')[1]]);
}
if (!isset($request->w) && isset($request->h)) {
$image = Image::thumbnail('../public/image.png', getimagesize('../public/image.png')[0], ['height' => $request->h]);
}
if (isset($request->w) && isset($request->h) && !($request->canv == true)) {
$image = Image::thumbnail('../public/image.png', $request->w, ['height' => $request->h, 'crop' => 'centre']);
}
if (isset($request->w) && isset($request->h) && $request->canv == true) {
$image = Image::thumbnail('../public/image.png', $request->w, ['height' => $request->h]);
$widthH = ($request->h - $image->height) / 2;
$widthW = ($request->w - $image->width) / 2;
$image = $image->embed(
$widthW,
$widthH,
$request->w,
$request->h,
['extend' => 'background','background'=>1024]
);
}
if (isset($request->brightness) || isset($request->saturation) || isset($request->hue)) {
$image = Processor::brightness($image, isset($request->brightness) ? $request->brightness : 1.0, isset($request->saturation) ? $request->saturation : 1.0, isset($request->hue) ? $request->hue : 0.0);
}
if ($request->rotation) {
$image = $image->rotate($request->rotation);
}
if ($request->flip == "h") {
$image = $image->fliphor();
}
if ($request->flip == "v") {
$image = $image->flipver();
}
if ($request->flip == "hv") {
$image = $image->fliphor();
$image = $image->flipver();
}
$image->writeToFile('image-modified.webp', [
'Q' => isset($request->q) ? $request->q : 100
]);
return response()->file(public_path("image-modified.webp" . $request->ext));
});
Route::get('/upload',function(){
return view('welcome');
});
Route::post('/upload',[FileController::class,'store']);

1
storage/framework/.gitignore

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

0
public/image.png → storage/stub/image.png

Before

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

After

Width: 423  |  Height: 750  |  Size: 468 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

8
tests/Base/FactoryMethodsTrait.php

@ -19,9 +19,9 @@ trait FactoryMethodsTrait
return Arr::except($model->toArray(), $model->getAppends());
}
public function one($class, $attributes = [])
public function one($class, $attributes = [], $dependencyAttributes = [])
{
return $class::factory()->createQuietly($attributes);
return $class::factory()->withDependency($dependencyAttributes)->createQuietly($attributes);
}
public function randomOne($class)
@ -29,9 +29,9 @@ trait FactoryMethodsTrait
return $class::inRandomOrder()->first();
}
public function trashed($class)
public function trashed($class, $dependencyAttributes = [])
{
$model = $this->one($class);
$model = $this->one($class,dependencyAttributes:$dependencyAttributes);
$model->delete();
$this->assertSoftDeleted($model);

8
tests/Bootstrap.php

@ -2,7 +2,13 @@
namespace Tests;
use Tests\Base\AuthMethodsTrait;
use Tests\Base\DynamicPolicyAndModelTrait;
use Tests\Base\FactoryMethodsTrait;
use Tests\Base\FreshMongodbDatabase;
use Tests\Base\MockMethodsTrait;
class Bootstrap extends TestCase
{
use AuthMethodsTrait,DynamicPolicyAndModelTrait,FactoryMethodsTrait,FreshMongodbDatabase,MockMethodsTrait;
}

56
tests/Feature/Collection/CollectionDeleteTest.php

@ -0,0 +1,56 @@
<?php
namespace Tests\Feature\Collection;
use App\Models\Collection;
use Tests\Bootstrap;
class CollectionDeleteTest extends Bootstrap
{
public function test_collection_delete_success()
{
$this->modelWithPolicy('collections', ['permission:collections.delete'])
->loginAs(['collections.delete'])
->deleteJson(route("api.collections.destroy", $collection = $this->one(Collection::class)))
->assertOk();
$this->loginAsAdmin()
->getJson(route("api.collections.show", $collection))
->assertNotFound();
}
public function test_collection_restore_success()
{
$this->modelWithPolicy('collections', ['permission:collections.restore'])
->loginAsUser(['collections.restore'])
->deleteJson(route("api.collections.destroy", $collection = $this->trashed(Collection::class)))
->assertOk();
$this->loginAsAdmin()
->getJson(route("api.collections.show", $collection))
->assertOk();
}
public function test_collection_delete_forbidden()
{
$this->modelWithPolicy('collections', ['permission:collections.delete'])
->loginAs(['wrong.permission'])
->deleteJson(route("api.collections.destroy", $collection = $this->one(Collection::class)))
->assertForbidden();
}
public function test_collection_restore_forbidden()
{
$this->modelWithPolicy('collections', ['permission:collections.restore'])
->loginAs(['wrong.permission'])
->deleteJson(route("api.collections.destroy", $collection = $this->trashed(Collection::class)))
->assertForbidden();
}
public function test_collection_delete_notFound()
{
$this->loginAsAdmin()
->deleteJson(route("api.collections.destroy", 0))
->assertNotFound();
}
}

34
tests/Feature/Collection/CollectionShowTest.php

@ -0,0 +1,34 @@
<?php
namespace Tests\Feature\Collection;
use App\Models\Collection;
use Tests\Bootstrap;
use App\Models\User;
class CollectionShowTest extends Bootstrap
{
public function test_collection_show_success()
{
$this->modelWithPolicy('collections', ['permission:collections.show'])
->loginAs(['collections.show'])
->getJson(route("api.collections.show", $collection = $this->one(Collection::class)))
->assertOk();
}
public function test_collection_show_not_found()
{
$this->modelWithPolicy('collections', ['permission:collections.show'])
->loginAs(['collections.show'])
->getJson(route("api.collections.show", 0))
->assertNotFound();
}
public function test_collection_show_forbidden()
{
$this->modelWithPolicy('collections', ['permission:collections.show'])
->loginAs(['wrong.permission'])
->getJson(route("api.collections.show", $collection = $this->one(Collection::class)), [])
->assertForbidden();
}
}

51
tests/Feature/Collection/CollectionStoreTest.php

@ -0,0 +1,51 @@
<?php
namespace Tests\Feature\Collection;
use App\Models\Collection;
use App\Documents\UserDocument;
use Tests\Bootstrap;
use Illuminate\Support\Arr;
class CollectionStoreTest extends Bootstrap
{
public function test_collection_store_success()
{
$this->modelWithPolicy('collections', ['permission:collections.store'])
->loginAs(['collections.store'])
->postJson(route('api.collections.store'), $collection = $this->make(Collection::class))
->assertCreated();
}
/**
* @testWith
* ["name:gtString"]
* ["name:numeric"]
* ["name:null"]
* ["path:gtString"]
* ["path:numeric"]
* ["public:string"]
* ["disk:numeric"]
* ["disk:null"]
* ["disk:gtString"]
* ["count:null"]
* ["count:string"]
* ["count:gt:100000"]
*
*/
public function test_collection_store_unprocessable($field)
{
$this->modelWithPolicy('collections', ['permission:collections.store'])
->loginAsAdmin()
->postJson(route("api.collections.store"), $collection = $this->make(collection::class, smash: $field, withDependency: true))
->assertUnprocessable();
}
public function test_collection_store_forbidden()
{
$this->modelWithPolicy('collections', ['permission:collections.store'])
->loginAs(['wrong.permission'])
->postJson(route("api.collections.store"), [])
->assertForbidden();
}
}

67
tests/Feature/Collection/CollectionUpdateTest.php

@ -0,0 +1,67 @@
<?php
namespace Tests\Feature\Collection;
use App\Models\Collection;
use Tests\Bootstrap;
use App\Models\User;
use Illuminate\Support\Arr;
class CollectionUpdateTest extends Bootstrap
{
public function test_collection_update_success()
{
$this->modelWithPolicy('collections', ['permission:collections.update'])
->loginAs(['collections.update'])
->putJson(
route("api.collections.update", $collection = $this->one(Collection::class)),
$update = $this->make(Collection::class, withDependency: true)
)
->assertOk();
}
/**
* @testWith
* ["name:gtString"]
* ["name:numeric"]
* ["name:null"]
* ["path:gtString"]
* ["path:numeric"]
* ["public:string"]
* ["disk:numeric"]
* ["disk:null"]
* ["disk:gtString"]
* ["count:null"]
* ["count:string"]
* ["count:gt:100000"]
*
*/
public function test_collection_update_unprocessable($field)
{
$this->modelWithPolicy('collections', ['permission:collections.update'])
->loginAs(['collections.update'])->putJson(
route("api.collections.update", $collection = $this->one(Collection::class)),
$update = $this->make(collection::class, smash: $field, withDependency: true)
)
->assertUnprocessable();
}
public function test_collection_update_forbidden()
{
$this->modelWithPolicy('collections', ['permission:collections.update'])
->loginAs(['wrong.permission'])
->putJson(
route("api.collections.update", $collection = $this->one(Collection::class)),
[]
)
->assertForbidden();
}
public function test_collection_update_not_found()
{
$this->loginAsUser(['collections.update'])
->putJson(route("api.collections.update", 0), [])
->assertNotFound();
}
}

47
tests/Feature/FileDeleteTest.php

@ -0,0 +1,47 @@
<?php
namespace Tests\Feature;
use App\Image\ImageProcessor;
use App\Models\Collection;
use App\Models\File;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Support\Facades\Storage;
use Tests\Bootstrap;
use Tests\TestCase;
class FileDeleteTest extends Bootstrap
{
// public function test_user_with_permission_can_not_delete_file()
// {
// $this->assertFalse("it's not mohammad's fault, I'm waiting for dynamic policy");
// }
public function test_user_with_permission_can_delete_file()
{
$file = $this->one(File::class, dependencyAttributes: ['withImage' => true]);
$collection = Collection::find($file->collection_id);
$response = $this->loginAs()->deleteJson(route('api.files.destroy', ['collection_name' => $collection->name, 'uuid' => $file->uuid, 'extention' => $collection->ext]));
$response->assertok();
}
public function test_file_restore_success()
{
$this->modelWithPolicy('collections', ['permission:collections.restore'])
->loginAsUser(['collections.restore'])
->deleteJson(route("api.collections.destroy", $collection = $this->trashed(Collection::class, dependencyAttributes: ['withImage' => true])))
->assertOk();
$this->loginAsAdmin()
->getJson(route("api.collections.show", $collection))
->assertOk();
}
public function test_file_delete_notFound()
{
$this->loginAsAdmin()
->deleteJson(route("api.files.destroy", ['collection_name' => 'not found', 'uuid' => 'wrong uuid', 'extention' => 'wrong ext']))
->assertNotFound();
}
}

28
tests/Feature/FileShowTest.php

@ -0,0 +1,28 @@
<?php
namespace Tests\Feature;
use App\Image\ImageProcessor;
use App\Models\Collection;
use App\Models\File;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Support\Facades\Storage;
use Tests\Feature\Traits\FileImageTrait;
use Tests\Feature\Traits\FileShowTrait;
use Tests\TestCase;
class FileShowTest extends TestCase
{
use FileShowTrait, FileImageTrait;
public function test_user_can_resize_image_with_width_and_height_fields_ok()
{
$file = $this->one(File::class, dependencyAttributes: ['withImage' => true]);
$collection = Collection::find($file->collection_id);
$w = rand(10, 2000);
$h = rand(10, 2000);
$canv = rand(0, 1);
$response = $this->loginAs()->getJson(route('api.files.show', ['collection_name' => $collection->name, 'uuid' => $file->uuid, 'extention' => $collection->ext, 'w' => $w, 'h' => $h, 'canv' => $canv]));
$response->assertOk()->assertHeader('content-type', $response->headers->get('content-type'));
}
}

206
tests/Feature/FileStoreTest.php

@ -0,0 +1,206 @@
<?php
namespace Tests\Feature;
use App\Http\Controllers\Traits\FileTrait;
use App\Image\ImageProcessor;
use App\Models\Collection;
use App\Models\File;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Tests\Bootstrap;
use Tests\TestCase;
use Illuminate\Support\Str;
use Tests\Feature\Traits\FileTraits;
class FileStoreTest extends Bootstrap
{
use FileTraits;
public function test_tmp_and_model_id_forbidden()
{
$data = [
"file" => UploadedFile::fake()->image('test.png'),
"alts" => ['1', '2', '3'],
"description" => 'lfjdsklfslfsdlfasdfsfhgsfgsdf',
"public" => 1
];
$collection = Collection::factory()->createQuietly([
'tmp_support' => false
]);
$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' => 1,
'collection_id' => $collection->id,
'server_path' => '/' . date('y') . '/' . date('m') . '/' . $uuid . '.' . $collection->ext,
]);
}
$data = [
"file" => UploadedFile::fake()->image('test.png'),
"alts" => ['1', '2', '3'],
"description" => 'lfjdsklfslfsdlfasdfsfhgsfgsdf',
"public" => 1
];
$response = $this->loginAs()->postJson(route('api.files.store', ['collection_name' => $collection->name]), $data);
$response->assertForbidden();
}
public function test_file_is_not_isset_forbidden()
{
$collection = Collection::factory()->createQuietly([
'tmp_support' => true
]);
$data = [
"file" => app()->uuid,
"alts" => ['1', '2', '3'],
"description" => 'lfjdsklfslfsdlfasdfsfhgsfgsdf',
"public" => 1
];
$response = $this->loginAs()->postJson(route('api.files.store', ['collection_name' => $collection->name]), $data);
$response->assertForbidden();
}
public function test_user_can_not_access_not_ownered_files()
{
$collection = Collection::factory()->createQuietly();
$file = File::factory()->createQuietly([
'user_id' => auth()->id() + 1234,
'collection_id' => $collection->id
]);
$data = [
"file" => $file->uuid,
"public" => 1
];
$response = $this->loginAs()->postJson(route('api.files.store', ['collection_name' => $collection->name]), $data);
$response->assertForbidden();
}
/**
* @dataProvider storeValidationTestProvider
*/
public function test_store_dynamic_validation_unprocessable($collectionFields, $dataFields)
{
$collection = Collection::factory()->createQuietly($collectionFields);
$response = $this->loginAs()->postJson(route('api.files.store', ['collection_name' => $collection->name]), $dataFields);
$response->assertUnprocessable();
}
public function test_store_static_validation_unprocessable()
{
$collection = Collection::factory()->createQuietly([
'min_file_size' => 0
]);
$data = [
"file" => UploadedFile::fake()->image('lol.png'),
"description" => Str::random(1000)
];
$this->modelWithPolicy('collections', ['permission:collections.store'])
->loginAsAdmin()
->postJson(route("api.files.store", ['collection_name' => $collection->name]), $data)
->assertUnprocessable();
}
/**
* @dataProvider storeValidationTestProvider
*/
public function test_store_dynamic_validation_stored_file_unprocessable($collectionFields)
{
$collectionFields['withImage'] = true;
$file = $this->one(File::class, dependencyAttributes: $collectionFields);
$collection = Collection::find($file->collection_id);
$data = [
"file" => $file->uuid,
];
$response = $this->loginAs()->postJson(route('api.files.store', ['collection_name' => $collection->name]), $data);
$response->assertUnprocessable();
}
public function test_user_can_store_sent_file_count_multiple_without_image_processor_ok()
{
$collectionFields['withImage'] = true;
$file = $this->one(File::class, dependencyAttributes: $collectionFields);
$collection = Collection::find($file->collection_id);
$data = [
"file" => UploadedFile::fake()->image('test.png'),
];
$response = $this->loginAs()->postJson(route('api.files.store', ['collection_name' => $collection->name]), $data);
$response->assertCreated();
}
public function test_user_can_store_sent_file_uuid_count_multiple_without_image_processor_ok()
{
$collectionFields['withImage'] = true;
$file = $this->one(File::class, dependencyAttributes: $collectionFields);
$collection = Collection::find($file->collection_id);
$data = [
"file" => $file->uuid,
"public" => 1
];
$response = $this->loginAs()->postJson(route('api.files.store', ['collection_name' => $collection->name]), $data);
$response->assertCreated();
}
public function test_user_can_store_sent_file_count_one_without_image_processor_ok()
{
$collection = Collection::factory()->createQuietly([
'count' => 1
]);
$data = [
"file" => UploadedFile::fake()->image('lol.png'),
"public" => 1
];
$response = $this->loginAs()->postJson(route('api.files.store', ['collection_name' => $collection->name, 'model_id' => 100]), $data);
$response->assertCreated();
}
public function test_user_can_store_sent_file_uuid_count_one_without_image_processor_ok()
{
$collectionFields['withImage'] = true;
$collectionFields['count'] = 1;
$file = $this->one(File::class, dependencyAttributes: $collectionFields);
$collection = Collection::find($file->collection_id);
$data = [
"file" => $file->uuid,
"public" => 1
];
$response = $this->loginAs()->postJson(route('api.files.store', ['collection_name' => $collection->name, 'model_id' => 100]), $data);
$response->assertCreated();
}
}

47
tests/Feature/FileUpdateTest.php

@ -0,0 +1,47 @@
<?php
namespace Tests\Feature;
use App\Image\ImageProcessor;
use App\Models\Collection;
use App\Models\File;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Support\Facades\Storage;
use Tests\Feature\Traits\FileTraits;
use Tests\TestCase;
class FileUpdateTest extends TestCase
{
use FileTraits;
// public function test_user_can_not_access_update_without_permission()
// {
// $this->assertFalse("it's not mohammad's fault, I'm waiting for dynamic policy");
// }
public function test_user_update_not_exits_files_not_found()
{
$data = [
"original_name" => 'original name is very bad',
"description" => 'this is a description for file and has beed updated',
];
$response = $this->loginAs()->putJson(route('api.files.update', ['collection_name' => 'not_exits', 'uuid' => app()->uuid, 'extention' => 'jpg']), $data);
$response->assertNotFound();
}
public function test_user_can_update_file()
{
$file = $this->one(File::class, dependencyAttributes: ['withImage' => true]);
$collection = Collection::find($file->collection_id);
$data = [
"original_name" => 'original name is very bad',
"description" => 'this is a description for file and has beed updated',
];
$response = $this->loginAs()->putJson(route('api.files.update', ['collection_name' => $collection->name, 'uuid' => $file->uuid, 'extention' => $collection->ext]), $data);
$response->assertok()->assertJson(['data' => $data]);
}
}

22
tests/Feature/Traits/FileImageTrait.php

@ -0,0 +1,22 @@
<?php
namespace Tests\Feature\Traits;
use App\Image\ImageProcessor;
use Illuminate\Support\Facades\Storage;
trait FileImageTrait
{
public function createImageForModel(\App\Models\Collection $collection, \App\Models\File $file): void
{
$imageProcessor = new ImageProcessor;
$imageProcessor->createFakeImage(storage_path('stub') . '/image.png', Storage::disk($collection->disk)->path($file->server_path . $file->uuid . '.' . $collection->ext));
}
public function deleteFakeImageForModel(\App\Models\File $file): void
{
unlink($file->getPath());
unlink($file->getModifiedPath());
}
}

52
tests/Feature/Traits/FileShowTrait.php

@ -0,0 +1,52 @@
<?php
namespace Tests\Feature\Traits;
use App\Models\Collection;
use App\Models\File;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
trait FileShowTrait
{
use FileImageTrait;
public function dimensions(\Illuminate\Http\File $image, $minWidth, $minHeight, $maxWidth, $maxHeight, $maxSize, $minSize) : bool
{
$data = [
'image' => $image
];
$validator = Validator::make($data, [
'avatar' => [
Rule::dimensions()->minWidth($minWidth)->minHeight($minHeight)->maxWidth($maxWidth)->maxHeight($maxHeight),
'max:' . $maxSize,
'min:' . $minSize
],
]);
return !$validator->fails();
}
public function createCollectionAndFileModelWithImage()
{
$collection = Collection::factory()->createQuietly([
'alt_required' => false,
'description_required' => false,
'tmp_support' => true,
'max_width' => 2000,
'max_height' => 2000,
'min_width' => 1,
'min_height' => 1,
'min_file_size' => 0
]);
$uuid = app()->uuid;
$file = File::factory()->createQuietly([
'uuid' => $uuid,
'server_path' => '/' . date('y') . '/' . date('m') . '/',
'user_id' => auth()->id(),
'collection_id' => $collection->id
]);
$this->createImageForModel($collection,$file);
return ['file' => $file ,'collection' => $collection];
}
}

89
tests/Feature/Traits/FileTraits.php

@ -0,0 +1,89 @@
<?php
namespace Tests\Feature\Traits;
use Illuminate\Http\UploadedFile;
trait FileTraits
{
private function defaultArray()
{
return [
[
'alt_required' => false,
'description_required' => false,
'tmp_support' => true,
'max_width' => 2000,
'max_height' => 2000,
'min_width' => 1,
'min_height' => 1,
'min_file_size' => 0
],
[
"file" => UploadedFile::fake()->image('test.png', 400, 400),
"public" => 1
]
];
}
private function defaultArrayForUpdate()
{
return [
[
'alt_required' => false,
'description_required' => false,
'tmp_support' => true,
'max_width' => 2000,
'max_height' => 2000,
'min_width' => 1,
'min_height' => 1,
'min_file_size' => 0
]
];
}
private function changeDefaultArrayAndReturn($defaultArray, $key, $value): array
{
$defaultArray[$key[0]][$key[1]] = $value;
return $defaultArray;
}
public function storeValidationTestProvider()
{
$data = [
$this->changeDefaultArrayAndReturn($this->defaultArray(), [0, 'alt_required'], true),
$this->changeDefaultArrayAndReturn($this->defaultArray(), [0, 'description_required'], true),
$this->changeDefaultArrayAndReturn($this->defaultArray(), [0, 'max_width'], 2),
$this->changeDefaultArrayAndReturn($this->defaultArray(), [0, 'max_height'], 2),
$this->changeDefaultArrayAndReturn($this->defaultArray(), [0, 'min_width'], 1500),
$this->changeDefaultArrayAndReturn($this->defaultArray(), [0, 'min_height'], 1500),
$this->changeDefaultArrayAndReturn($this->defaultArray(), [0, 'min_file_size'], 1000),
$this->changeDefaultArrayAndReturn($this->defaultArray(), [0, 'max_file_size'], 0),
$this->changeDefaultArrayAndReturn($this->defaultArray(), [0, 'exts'], ['jpg']),
$this->changeDefaultArrayAndReturn($this->defaultArray(), [0, 'mimetypes'], ['image/jpg']),
];
return $data;
}
public function storeValidationWithUuidTestProvider()
{
$data = [
$this->changeDefaultArrayAndReturn($this->defaultArray(), [0, 'alt_required'], true),
];
return $data;
}
public function updateValidationTestProvider()
{
$data = [
$this->changeDefaultArrayAndReturn($this->defaultArrayForUpdate(), [0, 'alt_required'], true),
$this->changeDefaultArrayAndReturn($this->defaultArrayForUpdate(), [0, 'description_required'], true),
];
return $data;
}
}
Loading…
Cancel
Save