composed project, added packages, models, controllers, seeders, mirgations etc.
This commit is contained in:
27
app/ConsoleStyleInterfaceType.php
Normal file
27
app/ConsoleStyleInterfaceType.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package: events-venues-task
|
||||
* @author: Yevhen Odynets
|
||||
* @date: 2024-07-28
|
||||
* @time: 16:01
|
||||
*/
|
||||
|
||||
//phpcs:ignore
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace App;
|
||||
|
||||
enum ConsoleStyleInterfaceType
|
||||
{
|
||||
case title;
|
||||
case section;
|
||||
//case listing;
|
||||
case text;
|
||||
case success;
|
||||
case error;
|
||||
case warning;
|
||||
case note;
|
||||
case caution;
|
||||
//case $table;
|
||||
}
|
||||
34
app/Facades/Image.php
Normal file
34
app/Facades/Image.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package: events-venues-task
|
||||
* @author: Yevhen Odynets
|
||||
* @date: 2024-07-27
|
||||
* @time: 07:29
|
||||
*/
|
||||
|
||||
//phpcs:ignore
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace App\Facades;
|
||||
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
|
||||
/**
|
||||
* @method static string|null localPath(string $imagePath)
|
||||
* @method static array|string meta(string $fullPath, ?string $property = null)
|
||||
* phpcs:ignore
|
||||
* @method static string cropAlign(string $srcPath, string $destPath, ?int $cropWidth = null, ?int $cropHeight = null, ?string $horizontalAlign = 'center', ?string $verticalAlign = 'middle')
|
||||
*
|
||||
* @uses \App\Services\Image\Image
|
||||
*/
|
||||
class Image extends Facade
|
||||
{
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected static function getFacadeAccessor(): string
|
||||
{
|
||||
return 'image';
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ use App\Http\Requests\Auth\LoginRequest;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class AuthenticatedSessionController extends Controller
|
||||
@@ -21,6 +22,8 @@ class AuthenticatedSessionController extends Controller
|
||||
|
||||
/**
|
||||
* Handle an incoming authentication request.
|
||||
*
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function store(LoginRequest $request): RedirectResponse
|
||||
{
|
||||
@@ -28,7 +31,8 @@ class AuthenticatedSessionController extends Controller
|
||||
|
||||
$request->session()->regenerate();
|
||||
|
||||
return redirect()->intended(route('dashboard', absolute: false));
|
||||
//return redirect()->intended(route('dashboard', absolute: false));
|
||||
return redirect(route('dashboard', absolute: false));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,10 +24,12 @@ class ConfirmablePasswordController extends Controller
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
if (! Auth::guard('web')->validate([
|
||||
if (
|
||||
! Auth::guard('web')->validate([
|
||||
'email' => $request->user()->email,
|
||||
'password' => $request->password,
|
||||
])) {
|
||||
])
|
||||
) {
|
||||
throw ValidationException::withMessages([
|
||||
'password' => __('auth.password'),
|
||||
]);
|
||||
|
||||
@@ -31,7 +31,7 @@ class RegisteredUserController extends Controller
|
||||
{
|
||||
$request->validate([
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:'.User::class],
|
||||
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:' . User::class],
|
||||
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
||||
]);
|
||||
|
||||
|
||||
@@ -15,13 +15,13 @@ class VerifyEmailController extends Controller
|
||||
public function __invoke(EmailVerificationRequest $request): RedirectResponse
|
||||
{
|
||||
if ($request->user()->hasVerifiedEmail()) {
|
||||
return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
|
||||
return redirect()->intended(route('dashboard', absolute: false) . '?verified=1');
|
||||
}
|
||||
|
||||
if ($request->user()->markEmailAsVerified()) {
|
||||
event(new Verified($request->user()));
|
||||
}
|
||||
|
||||
return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
|
||||
return redirect()->intended(route('dashboard', absolute: false) . '?verified=1');
|
||||
}
|
||||
}
|
||||
|
||||
68
app/Http/Controllers/EventController.php
Normal file
68
app/Http/Controllers/EventController.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Event;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class EventController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
return view('model.event', ['events' => Event::with('venue')->get()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
22
app/Http/Controllers/FallbackController.php
Normal file
22
app/Http/Controllers/FallbackController.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package: events-venues-task
|
||||
* @author: Yevhen Odynets
|
||||
* @date: 2024-07-24
|
||||
* @time: 22:07
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
class FallbackController
|
||||
{
|
||||
public function __invoke(): Response
|
||||
{
|
||||
return response()->view('errors.404', [], 404);
|
||||
}
|
||||
}
|
||||
67
app/Http/Controllers/VenueController.php
Normal file
67
app/Http/Controllers/VenueController.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Venue;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class VenueController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
return Venue::with('events')->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
@@ -80,6 +80,6 @@ class LoginRequest extends FormRequest
|
||||
*/
|
||||
public function throttleKey(): string
|
||||
{
|
||||
return Str::transliterate(Str::lower($this->string('email')).'|'.$this->ip());
|
||||
return Str::transliterate(Str::lower($this->string('email')) . '|' . $this->ip());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,14 @@ class ProfileUpdateRequest extends FormRequest
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', Rule::unique(User::class)->ignore($this->user()->id)],
|
||||
'email' => [
|
||||
'required',
|
||||
'string',
|
||||
'lowercase',
|
||||
'email',
|
||||
'max:255',
|
||||
Rule::unique(User::class)->ignore($this->user()->id),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
56
app/LoggingSeverityLevelsType.php
Normal file
56
app/LoggingSeverityLevelsType.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package: events-venues-task
|
||||
* @author: Yevhen Odynets
|
||||
* @date: 2024-07-27
|
||||
* @time: 11:57
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App;
|
||||
|
||||
enum LoggingSeverityLevelsType
|
||||
{
|
||||
/**
|
||||
* indicates that the system is unusable and requires immediate attention
|
||||
*/
|
||||
case emergency;
|
||||
|
||||
/**
|
||||
* indicates that immediate action is necessary to resolve a critical issue immediately
|
||||
* (such as a corrupted system database)
|
||||
*/
|
||||
case alert;
|
||||
|
||||
/**
|
||||
* signifies critical conditions in the program that demand intervention to prevent system failure
|
||||
*/
|
||||
case critical;
|
||||
|
||||
/**
|
||||
* indicates error conditions that impair some operation but are less severe than critical situations
|
||||
*/
|
||||
case error;
|
||||
|
||||
/**
|
||||
* signifies potential issues that may lead to errors or unexpected behavior in the future if not addressed
|
||||
*/
|
||||
case warning;
|
||||
|
||||
/**
|
||||
* applies to normal but significant conditions that may require monitoring
|
||||
*/
|
||||
case notice;
|
||||
|
||||
/**
|
||||
* includes messages that provide a record of the normal operation of the system
|
||||
*/
|
||||
case info;
|
||||
|
||||
/**
|
||||
* intended for logging detailed information about the system for debugging purposes
|
||||
*/
|
||||
case debug;
|
||||
}
|
||||
37
app/Models/Event.php
Normal file
37
app/Models/Event.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\{Builder, Factories\HasFactory, Model, Relations\BelongsTo};
|
||||
|
||||
class Event extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = false;
|
||||
|
||||
public function venue(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Venue::class);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Builder $query
|
||||
* @param string $column
|
||||
* @param string $direction
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function scopeSortBy(Builder $query, string $column = 'id', string $direction = 'asc'): void
|
||||
{
|
||||
$query->orderBy($column, validateOrderDirection($direction));
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,15 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
|
||||
class User extends Authenticatable
|
||||
class User extends Authenticatable implements MustVerifyEmail
|
||||
{
|
||||
use HasFactory, Notifiable;
|
||||
use HasFactory;
|
||||
use Notifiable;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
|
||||
40
app/Models/Venue.php
Normal file
40
app/Models/Venue.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\{Builder, Factories\HasFactory, Model, Relations\HasMany};
|
||||
|
||||
class Venue extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = false;
|
||||
|
||||
|
||||
/**
|
||||
* @return HasMany
|
||||
*/
|
||||
public function events(): HasMany
|
||||
{
|
||||
return $this->hasMany(Event::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Builder $query
|
||||
* @param string $column
|
||||
* @param string $direction
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function scopeSortBy(Builder $query, string $column = 'id', string $direction = 'asc'): void
|
||||
{
|
||||
$query->orderBy($column, validateOrderDirection($direction));
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Services\Image\Image;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
@@ -11,7 +12,6 @@ class AppServiceProvider extends ServiceProvider
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
26
app/Providers/ImageServiceProvider.php
Normal file
26
app/Providers/ImageServiceProvider.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Services\Image\Image;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class ImageServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$this->app->singleton('image', Image::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
class_alias(Image::class, 'Image');
|
||||
}
|
||||
}
|
||||
25
app/Providers/JournalServiceProvider.php
Normal file
25
app/Providers/JournalServiceProvider.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Services\Journal\Journal;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class JournalServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$this->app->singleton('journal', Journal::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
class_alias(Journal::class, 'Journal');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package: events-venues-task
|
||||
* @author: Yevhen Odynets
|
||||
* @date: 2024-07-26
|
||||
* @time: 19:44
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Faker\Image\Contracts;
|
||||
|
||||
enum FakerImageProviderType
|
||||
{
|
||||
case LoremFlickr;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package: events-venues-task
|
||||
* @author: Yevhen Odynets
|
||||
* @date: 2024-07-26
|
||||
* @time: 19:51
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Faker\Image\Contracts;
|
||||
|
||||
interface ProviderFactoryInterface
|
||||
{
|
||||
public static function apply(): ProviderInterface;
|
||||
}
|
||||
57
app/Services/Faker/Image/Contracts/ProviderInterface.php
Normal file
57
app/Services/Faker/Image/Contracts/ProviderInterface.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package: events-venues-task
|
||||
* @author: Yevhen Odynets
|
||||
* @date: 2024-07-26
|
||||
* @time: 19:48
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Faker\Image\Contracts;
|
||||
|
||||
use App\Services\Faker\Image\ImageStorage;
|
||||
|
||||
interface ProviderInterface
|
||||
{
|
||||
public const array KEYWORDS
|
||||
= [
|
||||
'tournament',
|
||||
'match',
|
||||
'game',
|
||||
'fight',
|
||||
'championship',
|
||||
'date',
|
||||
'contest',
|
||||
'competition',
|
||||
'event',
|
||||
'advent',
|
||||
'meeting',
|
||||
'adventure',
|
||||
'convention',
|
||||
'gathering',
|
||||
'council',
|
||||
'committee',
|
||||
'seminar',
|
||||
'congregation',
|
||||
'symposium',
|
||||
'congress',
|
||||
'assembly',
|
||||
'forum',
|
||||
'colloquium',
|
||||
'convocation',
|
||||
'marvel',
|
||||
'conference',
|
||||
'celebration',
|
||||
'holiday',
|
||||
'ceremony',
|
||||
];
|
||||
|
||||
/**
|
||||
* @param ImageStorage $storage
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getImage(ImageStorage $storage): ?string;
|
||||
}
|
||||
32
app/Services/Faker/Image/FakerImageProvider.php
Normal file
32
app/Services/Faker/Image/FakerImageProvider.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package: events-venues-task
|
||||
* @author: Yevhen Odynets
|
||||
* @date: 2024-07-26
|
||||
* @time: 19:56
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Faker\Image;
|
||||
|
||||
use App\Services\Faker\Image\Providers\LoremFlickrFactory;
|
||||
use App\Services\Faker\Image\Contracts\{ProviderFactoryInterface, FakerImageProviderType};
|
||||
use RuntimeException;
|
||||
|
||||
class FakerImageProvider
|
||||
{
|
||||
public static function use(FakerImageProviderType $type): ProviderFactoryInterface
|
||||
{
|
||||
if ($type === FakerImageProviderType::LoremFlickr) {
|
||||
{
|
||||
return new LoremFlickrFactory();
|
||||
}
|
||||
} else {
|
||||
{
|
||||
throw new RuntimeException("Unknown image provider: " . $type->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
70
app/Services/Faker/Image/ImageStorage.php
Normal file
70
app/Services/Faker/Image/ImageStorage.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package: events-venues-task
|
||||
* @author: Yevhen Odynets
|
||||
* @date: 2024-07-26
|
||||
* @time: 19:20
|
||||
*/
|
||||
|
||||
//phpcs:ignore
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace App\Services\Faker\Image;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class ImageStorage
|
||||
{
|
||||
private string $destFolder;
|
||||
private int $width;
|
||||
private int $height;
|
||||
private array $keywords;
|
||||
|
||||
public function __construct(string $destFolder, int $width, int $height, array $keywords = [])
|
||||
{
|
||||
$this->destFolder = $destFolder;
|
||||
$this->width = $width;
|
||||
$this->height = $height;
|
||||
$this->keywords = $keywords;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFolder(): string
|
||||
{
|
||||
return $this->destFolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getWidth(): int
|
||||
{
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getHeight(): int
|
||||
{
|
||||
return $this->height;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $prefix
|
||||
* @param string $suffix
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getKeyword(string $prefix = '', string $suffix = ''): ?string
|
||||
{
|
||||
if (count($this->keywords) === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $prefix . Arr::random($this->keywords) . $suffix;
|
||||
}
|
||||
}
|
||||
69
app/Services/Faker/Image/Provider.php
Normal file
69
app/Services/Faker/Image/Provider.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package: events-venues-task
|
||||
* @author: Yevhen Odynets
|
||||
* @date: 2024-07-26
|
||||
* @time: 22:54
|
||||
*/
|
||||
|
||||
//phpcs:ignore
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Faker\Image;
|
||||
|
||||
use App\Facades\Image;
|
||||
use App\Services\Faker\Image\{Contracts\ProviderInterface};
|
||||
use Error;
|
||||
use Illuminate\Support\{Facades\Http, Facades\Storage, Str};
|
||||
|
||||
abstract class Provider implements ProviderInterface
|
||||
{
|
||||
public ?string $url = null;
|
||||
|
||||
abstract public function getImage(ImageStorage $storage): ?string;
|
||||
|
||||
/**
|
||||
* @param ImageStorage $storage
|
||||
* @param string|null $url
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function download(ImageStorage $storage, ?string $url): ?string
|
||||
{
|
||||
$storagePath = Str::of($storage->getFolder()) . '/' . Str::uuid()->toString() . '.jpg';
|
||||
|
||||
$response = Http::get($url);
|
||||
if ($response->successful() === false) {
|
||||
appNotice("Couldn't download image from $url");
|
||||
return null;
|
||||
}
|
||||
|
||||
$result = Storage::disk("local")->put($storagePath, $response->body());
|
||||
$verify = $this->verify($storagePath, config('image.faker.cropper'));
|
||||
|
||||
return $result && $verify !== "NULL" ? $verify : "NULL";
|
||||
}
|
||||
|
||||
private function verify(string $storagePath, ?array $cropper = []): string
|
||||
{
|
||||
$fullPath = Image::localPath($storagePath);
|
||||
if (is_null($fullPath)) {
|
||||
appNotice("Couldn't locate image from $storagePath");
|
||||
return "NULL";
|
||||
}
|
||||
|
||||
$meta = Image::meta($fullPath);
|
||||
if (empty($meta)) {
|
||||
appNotice("Couldn't retrieve image meta from $fullPath");
|
||||
return "NULL";
|
||||
}
|
||||
|
||||
try {
|
||||
return Image::cropAlign($fullPath, $storagePath, ...$cropper);
|
||||
} catch (Error $err) {
|
||||
//Image::log($err, LoggingSeverityLevelsType::emergency);
|
||||
return "NULL";
|
||||
}
|
||||
}
|
||||
}
|
||||
25
app/Services/Faker/Image/Providers/LoremFlickr.php
Normal file
25
app/Services/Faker/Image/Providers/LoremFlickr.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package: events-venues-task
|
||||
* @author: Yevhen Odynets
|
||||
* @date: 2024-07-26
|
||||
* @time: 19:48
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Faker\Image\Providers;
|
||||
|
||||
use App\Services\Faker\Image\{ImageStorage, Provider};
|
||||
|
||||
final class LoremFlickr extends Provider
|
||||
{
|
||||
public function getImage(ImageStorage $storage): ?string
|
||||
{
|
||||
$this->url = config('image.faker.provider.loremflickr.host') .
|
||||
$storage->getWidth() . "/" . $storage->getHeight() . $storage->getKeyword('/');
|
||||
|
||||
return $this->download($storage, $this->url);
|
||||
}
|
||||
}
|
||||
22
app/Services/Faker/Image/Providers/LoremFlickrFactory.php
Normal file
22
app/Services/Faker/Image/Providers/LoremFlickrFactory.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package: events-venues-task
|
||||
* @author: Yevhen Odynets
|
||||
* @date: 2024-07-26
|
||||
* @time: 20:03
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Faker\Image\Providers;
|
||||
|
||||
use App\Services\Faker\Image\Contracts\{ProviderFactoryInterface, ProviderInterface};
|
||||
|
||||
class LoremFlickrFactory implements ProviderFactoryInterface
|
||||
{
|
||||
public static function apply(): ProviderInterface
|
||||
{
|
||||
return new LoremFlickr();
|
||||
}
|
||||
}
|
||||
17
app/Services/Image/Contracts/ImageInterface.php
Normal file
17
app/Services/Image/Contracts/ImageInterface.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package: events-venues-task
|
||||
* @author: Yevhen Odynets
|
||||
* @date: 2024-07-27
|
||||
* @time: 12:58
|
||||
*/
|
||||
|
||||
//phpcs:ignore
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Image\Contracts;
|
||||
|
||||
interface ImageInterface
|
||||
{
|
||||
}
|
||||
77
app/Services/Image/Image.php
Normal file
77
app/Services/Image/Image.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package: events-venues-task
|
||||
* @author: Yevhen Odynets
|
||||
* @date: 2024-07-27
|
||||
* @time: 07:35
|
||||
*/
|
||||
|
||||
//phpcs:ignore
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace App\Services\Image;
|
||||
|
||||
use App\ConsoleStyleInterfaceType;
|
||||
use App\LoggingSeverityLevelsType;
|
||||
use App\Services\Image\Contracts\ImageInterface;
|
||||
use App\Services\Image\ImageCropperTrait as Cropper;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\{App, Log, Storage};
|
||||
use Symfony\Component\Console\{Input\ArgvInput, Output\ConsoleOutput, Style\SymfonyStyle};
|
||||
|
||||
final class Image implements ImageInterface
|
||||
{
|
||||
use Cropper;
|
||||
|
||||
public static mixed $faker = null;
|
||||
private SymfonyStyle $io;
|
||||
private array $config;
|
||||
private bool $isConsole;
|
||||
private bool $isVerbosity;
|
||||
private bool $isIO;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->config = config('image');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $imagePath
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function localPath(string $imagePath): ?string
|
||||
{
|
||||
$fullPath = realpath(Storage::path($imagePath));
|
||||
|
||||
if (! $fullPath || ! file_exists($fullPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $fullPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $fullPath
|
||||
* @param string|null $property
|
||||
*
|
||||
* @return array|string
|
||||
*/
|
||||
public function meta(string $fullPath, ?string $property = null): array|string
|
||||
{
|
||||
try {
|
||||
$meta = getimagesize($fullPath);
|
||||
|
||||
if (is_null($property) === false && array_key_exists($property, $this->config['meta'])) {
|
||||
return $meta[$this->config['meta'][$property]];
|
||||
}
|
||||
|
||||
return getimagesize($fullPath);
|
||||
} catch (Exception $e) {
|
||||
appWarning($e);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
154
app/Services/Image/ImageCropperTrait.php
Normal file
154
app/Services/Image/ImageCropperTrait.php
Normal file
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package: events-venues-task
|
||||
* @author: Yevhen Odynets
|
||||
* @date: 2024-07-27
|
||||
* @time: 12:42
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Image;
|
||||
|
||||
use Illuminate\Support\Facades\File;
|
||||
use ReflectionException;
|
||||
use RuntimeException;
|
||||
|
||||
trait ImageCropperTrait
|
||||
{
|
||||
public static int $counter = 1;
|
||||
|
||||
/**
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
public function cropAlign(
|
||||
string $srcPath,
|
||||
string $dbPath,
|
||||
?int $cropWidth = null,
|
||||
?int $cropHeight = null,
|
||||
?string $horizontalAlign = 'center',
|
||||
?string $verticalAlign = 'middle'
|
||||
): string {
|
||||
$resource = $this->imageResource($srcPath);
|
||||
if ($resource === false) {
|
||||
appNotice("could not get image resource: $srcPath");
|
||||
|
||||
return $dbPath;
|
||||
}
|
||||
|
||||
[$image, $saveMethod, $quality] = $resource;
|
||||
|
||||
$width = imagesx($image);
|
||||
$height = imagesy($image);
|
||||
$cropWidth ??= $width;
|
||||
$cropHeight ??= $height;
|
||||
|
||||
if (($width > $cropWidth || $height > $cropHeight) === false) {
|
||||
$destPath = $this->destPath($srcPath, $dbPath, $width, $height);
|
||||
appInfo(self::$counter++ . ". skip cropping ($width x $height): " . basename($destPath[0]) . " saved");
|
||||
|
||||
return File::move($srcPath, $destPath[0]) ? $destPath[1] : $dbPath;
|
||||
}
|
||||
|
||||
$horizontalAlignPixels = $this->calculatePixelsForAlign($width, $cropWidth, $horizontalAlign);
|
||||
$verticalAlignPixels = $this->calculatePixelsForAlign($height, $cropHeight, $verticalAlign);
|
||||
$destPath = $this->destPath($srcPath, $dbPath, $horizontalAlignPixels[1], $verticalAlignPixels[1]);
|
||||
|
||||
$croppedImage = imagecrop($image, [
|
||||
'x' => $horizontalAlignPixels[0],
|
||||
'y' => $verticalAlignPixels[0],
|
||||
'width' => $horizontalAlignPixels[1],
|
||||
'height' => $verticalAlignPixels[1],
|
||||
]);
|
||||
|
||||
if ($croppedImage !== false) {
|
||||
$saveMethod($croppedImage, $destPath[0], $quality);
|
||||
imagedestroy($croppedImage);
|
||||
|
||||
appSuccess(
|
||||
self::$counter++ . ". cropped successfully from $width x $height: " . basename($destPath[0]) . " saved"
|
||||
);
|
||||
}
|
||||
|
||||
imagedestroy($image);
|
||||
|
||||
if (File::delete($srcPath) === false) {
|
||||
$e = new RuntimeException("could not delete image: $srcPath");
|
||||
|
||||
appWarning($e);
|
||||
|
||||
return $dbPath;
|
||||
}
|
||||
|
||||
return $destPath[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $srcPath
|
||||
*
|
||||
* @return false|array
|
||||
*/
|
||||
private function imageResource(string $srcPath): false|array
|
||||
{
|
||||
return match (mime_content_type($srcPath)) {
|
||||
'image/jpeg' => [imagecreatefromjpeg($srcPath), 'imagejpeg', 84],
|
||||
'image/png' => [imagecreatefrompng($srcPath), 'imagepng', 7],
|
||||
'image/gif' => [imagecreatefromgif($srcPath), 'imagegif', null],
|
||||
default => false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $srcPath
|
||||
* @param string $dbPath
|
||||
* @param int $width
|
||||
* @param int $height
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
private function destPath(string $srcPath, string $dbPath, int $width, int $height): array
|
||||
{
|
||||
$pi = pathinfo($srcPath);
|
||||
|
||||
$fs_path = sprintf(
|
||||
"%s\%s-%dx%d.%s",
|
||||
$pi['dirname'],
|
||||
$pi['filename'],
|
||||
$width,
|
||||
$height,
|
||||
$pi['extension']
|
||||
);
|
||||
|
||||
$db_path = sprintf(
|
||||
"%s/%s-%dx%d.%s",
|
||||
dirname($dbPath),
|
||||
$pi['filename'],
|
||||
$width,
|
||||
$height,
|
||||
$pi['extension']
|
||||
);
|
||||
|
||||
return [$fs_path, $db_path];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $imageSize
|
||||
* @param int $cropSize
|
||||
* @param string $align
|
||||
*
|
||||
* @return array|int[]
|
||||
*/
|
||||
private function calculatePixelsForAlign(int $imageSize, int $cropSize, string $align): array
|
||||
{
|
||||
return match ($align) {
|
||||
'left', 'top' => [0, min($cropSize, $imageSize)],
|
||||
'right', 'bottom' => [max(0, $imageSize - $cropSize), min($cropSize, $imageSize)],
|
||||
'center', 'middle' => [
|
||||
max(0, floor(($imageSize / 2) - ($cropSize / 2))),
|
||||
min($cropSize, $imageSize),
|
||||
],
|
||||
default => [0, $imageSize],
|
||||
};
|
||||
}
|
||||
}
|
||||
13
app/helpers.php
Normal file
13
app/helpers.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package: events-venues-task
|
||||
* @author: Yevhen Odynets
|
||||
* @date: 2024-07-24
|
||||
* @time: 21:26
|
||||
*/
|
||||
|
||||
// phpcs:ignore
|
||||
declare(strict_types = 1);
|
||||
|
||||
require_once 'helpers/logging.php';
|
||||
115
app/helpers/logging.php
Normal file
115
app/helpers/logging.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package: events-venues-task
|
||||
* @author: Yevhen Odynets
|
||||
* @date: 2024-07-28
|
||||
* @time: 16:12
|
||||
*/
|
||||
|
||||
//phpcs:ignore
|
||||
declare(strict_types = 1);
|
||||
|
||||
use App\{ConsoleStyleInterfaceType as Style, LoggingSeverityLevelsType as LogType};
|
||||
use Illuminate\Support\Facades\{App, Log};
|
||||
use Symfony\Component\Console\{Input\ArgvInput, Output\ConsoleOutput, Style\SymfonyStyle};
|
||||
|
||||
/**
|
||||
* @param mixed $e
|
||||
* @param LogType|null $severity
|
||||
* @param int|null $code
|
||||
* @param int|null $traceLine
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function appLog(Exception|string $e, ?LogType $severity = null, ?int $code = null, ?int $traceLine = null): void
|
||||
{
|
||||
$severity ??= LogType::notice;
|
||||
|
||||
if (is_string($e)) {
|
||||
$e = new RuntimeException($e);
|
||||
|
||||
if (is_null($traceLine)) {
|
||||
$traceLine = 2;
|
||||
}
|
||||
|
||||
$trace = $e->getTrace()[$traceLine];
|
||||
$context = [$code ?? $e->getCode(), $trace['file'], $trace['line'], $e->getTrace()[++$traceLine]['function']];
|
||||
} else {
|
||||
$context = [$code ?? $e->getCode(), $e->getFile(), $e->getLine(), $e->getTrace()[0]['function']];
|
||||
}
|
||||
|
||||
Log::channel('app')->{$severity->name}($e->getMessage(), $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Exception|string $e
|
||||
* @param Style $cmd
|
||||
*
|
||||
* @return void
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
function appConsole(Exception|string $e, Style $cmd): void
|
||||
{
|
||||
if (! is_string($e)) {
|
||||
$e = $e->getMessage();
|
||||
}
|
||||
|
||||
// $reflection = new ReflectionClass($e);
|
||||
// if (! $reflection->isSubclassOf(Exception::class)) {
|
||||
// $e = $e->getMessage();
|
||||
// }
|
||||
|
||||
if (App::runningInConsole()) {
|
||||
$io = new SymfonyStyle(new ArgvInput(), new ConsoleOutput());
|
||||
$io->{$cmd->name}($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Exception|string $e
|
||||
*
|
||||
* @return void
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
function appWarning(Exception|string $e): void
|
||||
{
|
||||
appLog($e, LogType::warning);
|
||||
appConsole($e, Style::warning);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $e
|
||||
*
|
||||
* @return void
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
function appSuccess(Exception|string $e): void
|
||||
{
|
||||
appLog($e, LogType::info);
|
||||
appConsole($e, Style::success);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $e
|
||||
*
|
||||
* @return void
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
function appInfo(Exception|string $e): void
|
||||
{
|
||||
appLog($e, LogType::info);
|
||||
appConsole($e, Style::note);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $e
|
||||
*
|
||||
* @return void
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
function appNotice(Exception|string $e): void
|
||||
{
|
||||
appLog($e, LogType::notice);
|
||||
appConsole($e, Style::note);
|
||||
}
|
||||
Reference in New Issue
Block a user