Browse Source

file

pull/2/head
Mohammad Khazaee 2 years ago
parent
commit
49cccaf720
  1. 2
      Dockerfile
  2. 3
      app/Http/Controllers/Controller.php
  3. 89
      app/Http/Controllers/FileController.php
  4. 15
      app/Http/Controllers/Traits/FileTrait.php
  5. 6
      app/Http/Kernel.php
  6. 12
      app/Http/Middleware/BindCollectionModelMiddleware.php
  7. 31
      app/Http/Middleware/BindFileModelMiddleware.php
  8. 2
      app/Http/Resources/CollectionResource.php
  9. 1
      app/Http/Resources/FileResource.php
  10. 110
      app/Image/ImageProcessor.php
  11. 28
      app/Image/Processor.php
  12. 10
      app/Models/Collection.php
  13. 1
      app/Models/File.php
  14. 3
      app/Providers/EventServiceProvider.php
  15. 31
      app/Providers/UuidServiceProvider.php
  16. 193
      app/Utilities/Helpers/Image.php
  17. 3
      config/app.php
  18. 13
      database/factories/BaseFactory.php
  19. 15
      database/factories/CollectionFactory.php
  20. 16
      database/factories/FileFactory.php
  21. 1
      database/migrations/2022_07_27_073858_create_files_table.php
  22. 3
      database/migrations/2022_07_27_073906_create_collections_table.php
  23. 23
      database/seeders/DatabaseSeeder.php
  24. 1
      php.ini
  25. BIN
      public/image-modified.webp
  26. 18
      resources/views/welcome.blade.php
  27. 2
      routes/api.php
  28. 28
      routes/web.php
  29. BIN
      storage/image-modified.webp
  30. 0
      storage/stub/image.png
  31. 2
      tests/Base/FactoryMethodsTrait.php
  32. 8
      tests/Bootstrap.php
  33. 138
      tests/Feature/FileStoreTest.php
  34. 63
      tests/Feature/Traits/FileTraits.php

2
Dockerfile

@ -10,5 +10,3 @@ RUN apt-get -y update \
&& apt-get -y install libvips-dev \
&& pecl install vips \
&& echo 'extension="vips.so"' > /usr/local/etc/php/conf.d/20-vips.ini
COPY ./php.ini /usr/local/etc/php/php-vips.ini

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()
{

89
app/Http/Controllers/FileController.php

@ -4,11 +4,14 @@ namespace App\Http\Controllers;
use App\Http\Requests\FileStoreRequest;
use App\Http\Resources\FileResource;
use App\Image\ImageProcessor;
use App\Jobs\FileConversionQueue;
use App\Models\File;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use Symfony\Component\Console\Input\Input;
class FileController extends Controller
{
@ -19,37 +22,71 @@ class FileController extends Controller
public function private($collection, $path)
{
Storage::disk(app()->collection->disk)->download($path);
// Storage::disk(app()->collection->disk)->download($path);
}
public function store(Request $request)
public function store(Request $request, ImageProcessor $imageProcessor)
{
// skip policy ---and first level----------
if (!app()->collection->tmp_support && !$request->model_id) {
return abort(403);
}
if (app()->collection->count !== 1 && (app()->collection->count <= File::where('model_id', auth()->id())->count()) && !app()->collection->tmp_support) {
return abort(403);
}
if (!isset(app()->file) && is_null($request->file('file'))) {
return abort(403);
}
$validated = $request->validate([
"file" => [
$fieldsValidate = [
"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'],
'public' => ['boolean', 'nullable'],
'published_at' => ['date_format:Y-m-d H:i:s', 'nullable'],
];
$fieldsValidate['file'] =
is_null($request->file('file')) ?
[
'string',
'required',
'max:300'
] :
[
"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"],
]);
!$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,
];
$validated = $request->validate($fieldsValidate);
if (is_null($request->file('file'))) {
$storedImage = new \Illuminate\Http\File(Storage::disk('local')->path(app()->file->server_path));
$validated = Validator::make(['file' => $storedImage], ["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_file_size,
"min:" . app()->collection->min_file_size
]])->validate();
}
if (!is_null(app()->collection->process)) {
$imageProcessor->process($request->file->path(), $request->file->path(), app()->collection->process);
}
DB::beginTransaction();
$urlStorage = '/' . date('y') . '/' . date('m') . app()->getFileUuid;
DB::transaction(function () use ($request) {
try {
$file = File::create([
'uuid' => app()->getFileUuid,
$uuid = app()->uuid;
$request->resourceFile = File::create([
'uuid' => $uuid,
'original_name' => $request->name,
'public' => $request->public,
'ext' => $request->file->extension(),
@ -64,25 +101,19 @@ 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') . '/' . $uuid . '.' . app()->collection->ext,
]);
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);
$storedImage = $request->file->storeAs($request->resourceFile->server_path, app()->uuid . '.' . app()->collection->ext, app()->collection->disk);
if (app()->collection->public) {
Storage::setVisibility($file, 'public');
Storage::setVisibility($storedImage, 'public');
}
DB::commit();
} catch (\Exception $e) {
DB::rollback();
}
FileConversionQueue::dispatch($file, app()->collection);
return new FileResource($file);
});
return new FileResource($request->resourceFile);
}

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

@ -0,0 +1,15 @@
<?php
namespace App\Http\Controllers\Traits;
trait FileTrait
{
public function isImage($file)
{
if(@is_array(getimagesize($file))){
return true;
} else {
return false;
}
}
}

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,
],
];

12
app/Http/Middleware/BindCollectionModelMiddleware.php

@ -19,18 +19,14 @@ 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();
app()->singleton('collection', function () use ($request) {
return Collection::where('name', $request->route('collection_name'))->get()->first();
});
}
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'))->get()->first();
});
}

31
app/Http/Middleware/BindFileModelMiddleware.php

@ -0,0 +1,31 @@
<?php
namespace App\Http\Middleware;
use App\Models\File;
use Closure;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
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.file.store') {
if(is_null($request->file('file'))){
app()->bind('file', function () use ($request) {
return File::find($request->file);
});
}
}
return $next($request);
}
}

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,

110
app/Image/ImageProcessor.php

@ -0,0 +1,110 @@
<?php
namespace App\Image;
use Jcupitt\Vips\Image;
use Jcupitt\Vips\Interpretation;
class ImageProcessor
{
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 process(string $filename, string $target, array $options = ["w" => null, "h" => null, "r" => null,"flip" => null , "canv" => null,"rotation" => null],$saveOptions = ['Q' => 100])
{
if (!isset($options['w']) && !isset($options['h'])) {
$image = Image::thumbnail($filename, getimagesize($filename)[0]);
}
if (isset($options['r'])) {
$rArray = explode(':', $options['r']);
if (isset($options['w']) && !isset($options['h'])) {
$options['h'] = $options['w'] * $rArray[1];
$options['w'] = $options['w'] * $rArray[0];
}
if (!isset($options['w']) && isset($options['h'])) {
$options['h'] = $options['h'] * $rArray[1];
$options['w'] = $options['h'] * $rArray[0];
}
if (!isset($options['w']) && !isset($options['h'])) {
$options['h'] = getimagesize($filename)[0] * $rArray[1];
$options['w'] = getimagesize($filename)[0] * $rArray[0];
}
}
if (isset($options['w']) && !isset($options['h'])) {
$image = Image::thumbnail($filename, $options['w'], ['height' => getimagesize($filename)[1]]);
}
if (!isset($options['w']) && isset($options['h'])) {
$image = Image::thumbnail($filename, getimagesize($filename)[0], ['height' => $options['h']]);
}
if (isset($options['w']) && isset($options['h']) && !($options['canv'] == true)) {
$image = Image::thumbnail($filename, $options['w'], ['height' => $options['h'], 'crop' => 'centre']);
}
if (isset($options['w']) && isset($options['h']) && $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' => 1024]
);
}
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 (isset($options['rotation'])) {
$image = $image->rotate($options['rotation']);
}
if (isset($options['flip'])) {
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 $target;
}
}

28
app/Image/Processor.php

@ -1,28 +0,0 @@
<?php
namespace App\Image;
use Jcupitt\Vips\Interpretation;
class Processor {
public static 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);
}
}

10
app/Models/Collection.php

@ -28,7 +28,7 @@ class Collection extends Model
"alt_required",
"description_required",
"exts",
"avalible_exts",
"ext",
"mimetypes",
"model",
"expire_date",
@ -36,7 +36,6 @@ class Collection extends Model
protected $casts = [
'exts' => 'array',
'avalible_exts' => 'array',
'mimetypes' => 'array',
];
@ -46,12 +45,7 @@ class Collection extends Model
set: fn ($value) => json_encode($value),
);
}
protected function avalible_exts(): Attribute
{
return Attribute::make(
set: fn ($value) => json_encode($value),
);
}
protected function mimetypes(): Attribute
{
return Attribute::make(

1
app/Models/File.php

@ -29,6 +29,7 @@ class File extends Model
"description",
"user_id",
"ip",
"model_id",
"collection_id",
"published_at",
];

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
{

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'),
];
}
}

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
],
/*

13
database/factories/BaseFactory.php

@ -3,7 +3,7 @@
namespace Database\Factories;
use App\Image\ImageProcessor;
use Illuminate\Support\Str;
trait BaseFactory
@ -70,4 +70,15 @@ trait BaseFactory
}
public function withImage()
{
return null;
}
public function createImage($path, $saveOptions = ['Q' => 100],$options = ["w" => null, "h" => null, "r" => null,"flip" => null , "canv" => null,"rotation" => null])
{
$imageProcessor = new ImageProcessor;
$image = $imageProcessor->process(storage_path('stub') . '/image.png',$path,$options,$saveOptions);
return $this;
}
}

15
database/factories/CollectionFactory.php

@ -38,17 +38,12 @@ class CollectionFactory extends Factory
"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"

16
database/factories/FileFactory.php

@ -4,12 +4,15 @@ namespace Database\Factories;
use App\Models\Collection;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Str;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\File>
*/
class FileFactory extends Factory
{
use BaseFactory;
/**
* Define the model's default state.
*
@ -18,14 +21,13 @@ class FileFactory extends Factory
public function definition()
{
return [
"uuid" => 1,
"uuid" => app()->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),
"alts" => [
'hello wroldswdfouiwref iuwrhgf ow rgfaw ghfawej',
@ -34,8 +36,16 @@ class FileFactory extends Factory
"description" => 'ajsfoisahjfoaspf asduf safsafjsh lh',
"user_id" => rand(43724, 382348),
"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()
{
return [
'collection_id' => Collection::factory()->createQuietly()
];
}
}

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();

3
database/migrations/2022_07_27_073906_create_collections_table.php

@ -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();

23
database/seeders/DatabaseSeeder.php

@ -21,7 +21,28 @@ class DatabaseSeeder extends Seeder
// 'email' => 'test@example.com',
// ]);
\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(1)->create();
\App\Models\File::factory()->create();
}
}

1
php.ini

@ -1 +0,0 @@
ffi.enable = "true"

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>

2
routes/api.php

@ -24,5 +24,5 @@ Route::group(['as' => 'api.'], function () {
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::post('{collection_name}/{model_id?}', [FileController::class, 'store'])->name('file.store');
});

28
routes/web.php

@ -1,7 +1,9 @@
<?php
use App\Http\Controllers\FileController;
use App\Image\ImageProcessor;
use App\Image\Processor;
use App\Models\Collection;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Jcupitt\Vips\Config;
@ -19,11 +21,21 @@ use Jcupitt\Vips\Utils;
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('/fuck.webp',function(Request $request,ImageProcessor $imageProcessor)
{
$option = [
'w' => $request->w,
'h' => $request->h,
'r' => $request->r,
'canv' => $request->canv,
];
$imageProcessor->process(public_path('image.png'),public_path('image-modified.webp'),$option);
return response()->file(public_path('image-modified.webp'));
});
Route::get('/', function (Request $request) {
// // $request->dddd = 'tesst';
// dump($request->);
Collection::find(1)->getExts();
if (!isset($request->w) && !isset($request->h)) {
$image = Image::thumbnail('../public/image.png', getimagesize('../public/image.png')[0]);
@ -95,7 +107,7 @@ Route::get('/', function (Request $request) {
}
$image->writeToFile('image-modified.webp', [
$image->writeToFile(storage_path('image-modified.webp'), [
'Q' => isset($request->q) ? $request->q : 100
]);
@ -103,8 +115,14 @@ Route::get('/', function (Request $request) {
});
Route::get('/upload',function(){
return view('welcome');
});
Route::post('/upload',[FileController::class,'store']);
Route::post('/fuck',function(Request $request)
{
$request->all();
$storedImage = $request->file->storeAs('/addifsfsfsffuck', app()->uuid . '.' . 'webp', 'local');
});

BIN
storage/image-modified.webp

Binary file not shown.

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

Before

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

After

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

2
tests/Base/FactoryMethodsTrait.php

@ -21,7 +21,7 @@ trait FactoryMethodsTrait
public function one($class, $attributes = [])
{
return $class::factory()->createQuietly($attributes);
return $class::factory()->withDependency()->createQuietly($attributes);
}
public function randomOne($class)

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;
}

138
tests/Feature/FileStoreTest.php

@ -0,0 +1,138 @@
<?php
namespace Tests\Feature;
use App\Http\Controllers\Traits\FileTrait;
use App\Models\Collection;
use App\Models\File;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Http\UploadedFile;
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()->create([
'tmp_support' => false
]);
$response = $this->loginAs('dick')->postJson(route('api.file.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(),
'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('dick')->postJson(route('api.file.store', ['collection_name' => $collection->name]), $data);
$response->assertForbidden();
}
public function test_file_is_not_isset_forbidden()
{
$collection = Collection::factory()->create([
'tmp_support' => true
]);
$data = [
"file" => app()->uuid,
"alts" => ['1', '2', '3'],
"description" => 'lfjdsklfslfsdlfasdfsfhgsfgsdf',
"public" => 1
];
$response = $this->loginAs('dick')->postJson(route('api.file.store', ['collection_name' => $collection->name]), $data);
$response->assertForbidden();
}
/**
* @dataProvider storeValidationTestProvider
*/
public function test_store_dynamic_validation_unprocessable($collectionFields, $dataFields)
{
$collection = Collection::factory()->create($collectionFields);
$response = $this->loginAs('dick')->postJson(route('api.file.store', ['collection_name' => $collection->name]), $dataFields);
$response->assertUnprocessable();
}
// /**
// * @testWith
// * ['email:gt']
// * ['email:gt']
// * ['email:gt']
// * ['email:gt']
// * ['email:gt']
// */
// public function test_store_static_validation_unprocessable($key)
// {
// File::factory()->smash($key);
// }
public function test_store_dynamic_validation_stored_file_unprocessable()
{
$collection = Collection::factory()->create([
'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()->createImage(storage_path('app/' . date('y') . '/' . date('m') . '/' . $uuid . '.' . $collection->ext))->create([
'uuid' => $uuid,
'server_path' => '/' . date('y') . '/' . date('m') . '/',
'collection_id' => $collection->id
]);
$data = [
"file" => $file->uuid,
"alts" => ['1', '2', '3'],
"description" => 'lfjdsklfslfsdlfasdfsfhgsfgsdf',
"public" => 1
];
$response = $this->loginAs('dick')->postJson(route('api.file.store', ['collection_name' => $collection->name]), $data);
$response->assertUnprocessable();
}
}

63
tests/Feature/Traits/FileTraits.php

@ -0,0 +1,63 @@
<?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 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;
}
}
Loading…
Cancel
Save