From ad8d8d75648e86d592fd0869d149b8dc76db6b76 Mon Sep 17 00:00:00 2001 From: Frank John Begornia Date: Wed, 13 May 2026 00:25:23 +0800 Subject: [PATCH] feat: Add MinIO storage support and update image URLs - Implemented MinIO storage driver in AppServiceProvider for S3-compatible storage. - Added helper functions to generate MinIO URLs for files and images. - Updated filesystem configuration to include MinIO settings. - Modified site configuration to include MinIO URL. - Enhanced Docker Compose configuration for local development with MinIO. - Updated various Blade templates to use MinIO URLs for images instead of local paths. - Ensured all image references in views are now pointing to MinIO storage. --- app/Exceptions/Handler.php | 16 +++- app/Http/Controllers/user/UserController.php | 8 +- app/Providers/AppServiceProvider.php | 17 ++++ app/helpers.php | 33 +++++++ composer.json | 17 ++-- config/filesystems.php | 22 +++++ config/site_config.php | 1 + docker-compose.local.yml | 89 ++++++++++++++++--- resources/views/designer/designer.blade.php | 18 ++-- resources/views/emails/orders.blade.php | 2 +- resources/views/merchbay/cart.blade.php | 4 +- resources/views/merchbay/index.blade.php | 10 +-- resources/views/sublayouts/checkout.blade.php | 2 +- .../teamstore-sublayouts/index.blade.php | 4 +- .../product-details.blade.php | 12 +-- .../teamstore-sublayouts/stores.blade.php | 4 +- .../views/user-layouts/buy_design.blade.php | 6 +- resources/views/user-layouts/header.blade.php | 2 +- .../views/user-layouts/my-design.blade.php | 2 +- .../user-layouts/order-details-body.blade.php | 2 +- .../user-layouts/order_details.blade.php | 2 +- .../views/user-layouts/sell_design.blade.php | 6 +- .../views/user-layouts/sidebar.blade.php | 2 +- .../views/user-layouts/store_items.blade.php | 2 +- .../store_items_arrange.blade.php | 2 +- .../user-layouts/store_setting.blade.php | 8 +- .../views/user-layouts/view-design.blade.php | 6 +- .../user-layouts/view-store-item.blade.php | 6 +- 28 files changed, 229 insertions(+), 76 deletions(-) create mode 100644 app/helpers.php diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 957952b..004bbcd 100755 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -3,6 +3,7 @@ namespace App\Exceptions; use Exception; +use Throwable; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; @@ -23,11 +24,16 @@ class Handler extends ExceptionHandler * * This is a great spot to send exceptions to Sentry, Bugsnag, etc. * - * @param \Exception $e + * @param \Throwable $e * @return void */ - public function report(Exception $e) + public function report(Throwable $e) { + // Laravel 5.0 parent expects an Exception; wrap Error instances so + // PHP 7+ fatal errors (TypeError, ParseError, etc.) are handled safely. + if (!$e instanceof Exception) { + $e = new \RuntimeException($e->getMessage(), $e->getCode()); + } return parent::report($e); } @@ -43,11 +49,13 @@ class Handler extends ExceptionHandler // return parent::render($request, $e); // } - public function render($request, Exception $e) + public function render($request, Throwable $e) { if ($e instanceof MethodNotAllowedHttpException) { abort(404); } - return parent::render($request, $e); + // Wrap non-Exception Throwables for Laravel 5.0 parent compatibility. + $exception = $e instanceof Exception ? $e : new \RuntimeException($e->getMessage(), $e->getCode()); + return parent::render($request, $exception); } } diff --git a/app/Http/Controllers/user/UserController.php b/app/Http/Controllers/user/UserController.php index c18fa7e..1a2deec 100755 --- a/app/Http/Controllers/user/UserController.php +++ b/app/Http/Controllers/user/UserController.php @@ -765,8 +765,7 @@ class UserController extends Controller $u = $UserModel->insertNewProductThumbnails($thumbs); // var_dump($thumbs); - Storage::disk('sftp')->put($thumbnail, fopen($request->file('imgupload')[$i], 'r+')); //live - //Storage::disk('localdir')->put($thumbnail, fopen($request->file('imgupload')[$i], 'r+')); + Storage::disk('minio_crewsportswear')->put('images/' . $thumbnail, fopen($request->file('imgupload')[$i], 'r+')); // var_dump($s); } @@ -938,8 +937,7 @@ class UserController extends Controller ); $u = $UserModel->insertNewProductThumbnails($thumbs); - Storage::disk('sftp')->put($thumbnail, fopen($request->file('upload_images')[$i], 'r+')); //live - //Storage::disk('localdir')->put($thumbnail, fopen($request->file('upload_images')[$i], 'r+')); + Storage::disk('minio_crewsportswear')->put('images/' . $thumbnail, fopen($request->file('upload_images')[$i], 'r+')); } @@ -961,6 +959,8 @@ class UserController extends Controller unlink($storagePath . $file); } + Storage::disk('minio_crewsportswear')->delete('images/' . $file); + $i = $UserModel->deleteImageThumb('Id', $id); return response()->json(array( diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 9e53a91..1912b9f 100755 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -4,6 +4,8 @@ use Illuminate\Support\ServiceProvider; use Illuminate\Support\Facades\Storage; use League\Flysystem\Filesystem; use League\Flysystem\Sftp\SftpAdapter; +use League\Flysystem\AwsS3v3\AwsS3Adapter as AwsS3v3Adapter; +use Aws\S3\S3Client; use Illuminate\Support\Facades\Blade; class AppServiceProvider extends ServiceProvider { @@ -27,6 +29,21 @@ class AppServiceProvider extends ServiceProvider { Storage::extend('sftp', function ($app, $config) { return new Filesystem(new SftpAdapter($config)); }); + + Storage::extend('minio', function ($app, $config) { + $client = new S3Client([ + 'credentials' => [ + 'key' => $config['key'], + 'secret' => $config['secret'], + ], + 'region' => $config['region'], + 'version' => 'latest', + 'endpoint' => $config['endpoint'], + 'use_path_style_endpoint' => filter_var($config['use_path_style_endpoint'] ?? true, FILTER_VALIDATE_BOOLEAN), + ]); + $adapter = new AwsS3v3Adapter($client, $config['bucket']); + return new Filesystem($adapter); + }); } /** diff --git a/app/helpers.php b/app/helpers.php new file mode 100644 index 0000000..2fa12d7 --- /dev/null +++ b/app/helpers.php @@ -0,0 +1,33 @@ + 'local', 'root' => 'C:/wamp64/www/uploads', ], + + 'minio' => [ + 'driver' => 'minio', + 'key' => env('MINIO_KEY'), + 'secret' => env('MINIO_SECRET'), + 'region' => env('MINIO_REGION', 'us-east-1'), + 'bucket' => env('MINIO_BUCKET', 'merchbay'), + 'endpoint' => env('MINIO_ENDPOINT'), + 'use_path_style_endpoint' => env('MINIO_USE_PATH_STYLE', true), + 'url' => env('MINIO_URL', 'https://minio.crewsportswear.app'), + ], + + 'minio_crewsportswear' => [ + 'driver' => 'minio', + 'key' => env('MINIO_KEY'), + 'secret' => env('MINIO_SECRET'), + 'region' => env('MINIO_REGION', 'us-east-1'), + 'bucket' => 'crewsportswear', + 'endpoint' => env('MINIO_ENDPOINT'), + 'use_path_style_endpoint' => env('MINIO_USE_PATH_STYLE', true), + 'url' => env('MINIO_URL', 'https://minio.crewsportswear.app'), + ], ], ]; diff --git a/config/site_config.php b/config/site_config.php index 34527b8..b01ddc2 100755 --- a/config/site_config.php +++ b/config/site_config.php @@ -20,6 +20,7 @@ return [ 'prod_private_server_ip' => env('PROD_PRIVATE'), // 'images_url' => env('https://crewsportswear.app:5955', 'https://crewsportswear.app:5955'), 'images_url' => env('IMAGES_URL'), + 'minio_url' => env('MINIO_URL', 'https://minio.crewsportswear.app/merchbay'), // 'uploads' => env('https://crewsportswear.app:5955/merchbay/', 'https://crewsportswear.com/uploads/images/'), // local 'uploads' => env('UPLOAD_URL'), diff --git a/docker-compose.local.yml b/docker-compose.local.yml index f49a4be..5a14346 100644 --- a/docker-compose.local.yml +++ b/docker-compose.local.yml @@ -1,3 +1,19 @@ +# ───────────────────────────────────────────────────────────────────────────── +# Local development stack +# +# Default (local MariaDB): +# docker compose --env-file .env.local -f docker-compose.local.yml up --build +# +# Remote DB via SSH private-key tunnel: +# 1. Set SSH_HOST, SSH_USER, SSH_KEY_PATH, SSH_DB_REMOTE_HOST, +# SSH_DB_REMOTE_PORT (and DB_* creds) in .env.local +# 2. docker compose --env-file .env.local -f docker-compose.local.yml --profile ssh-db up --build +# The app will talk to db-tunnel (port 3306) instead of the local db. +# +# App: http://localhost:8080 +# phpMyAdmin: http://localhost:8081 +# ───────────────────────────────────────────────────────────────────────────── + services: db: image: mariadb:10.6 @@ -5,10 +21,10 @@ services: container_name: merchbay_db_local restart: unless-stopped environment: - MYSQL_DATABASE: merchbay + MYSQL_DATABASE: ${DB_DATABASE:-merchbay_db} MYSQL_ROOT_PASSWORD: root - MYSQL_USER: merchbay - MYSQL_PASSWORD: secret + MYSQL_USER: ${DB_USERNAME:-merchbay} + MYSQL_PASSWORD: ${DB_PASSWORD:-secret} ports: - "3306:3306" volumes: @@ -29,14 +45,14 @@ services: - APP_DEBUG=true - APP_URL=http://localhost:8080 - DB_CONNECTION=mysql - - DB_HOST=db - - DB_PORT=3306 - - DB_DATABASE=merchbay - - DB_USERNAME=merchbay - - DB_PASSWORD=secret + - DB_HOST=${DB_HOST:-db} + - DB_PORT=${DB_PORT:-3306} + - DB_DATABASE=${DB_DATABASE:-merchbay_db} + - DB_USERNAME=${DB_USERNAME:-merchbay} + - DB_PASSWORD=${DB_PASSWORD:-secret} - PROD_PRIVATE=http://localhost:8080 - - IMAGES_URL=http://localhost:8080 - - UPLOAD_URL=http://localhost:8080/uploads/ + - IMAGES_URL=${IMAGES_URL:-https://minio.crewsportswear.app/merchbay} + - UPLOAD_URL=${UPLOAD_URL:-https://minio.crewsportswear.app/merchbay/uploads/} - MAIL_DRIVER=log - MAIL_HOST=localhost - MAIL_PORT=1025 @@ -48,15 +64,67 @@ services: - ANALYTICS_SITE_ID= - ANALYTICS_CLIENT_ID= - ANALYTICS_SERVICE_EMAIL= + # MinIO S3 Storage (connect to production MinIO for testing) + - MINIO_ENDPOINT=https://minio.crewsportswear.app + - MINIO_KEY=${MINIO_KEY:-minioadmin} + - MINIO_SECRET=${MINIO_SECRET:-minioadmin} + - MINIO_BUCKET=merchbay + - MINIO_REGION=us-east-1 + - MINIO_USE_PATH_STYLE=false + - MINIO_URL=https://minio.crewsportswear.app + env_file: + - path: .env.local + required: false volumes: - ./:/var/www/html - ./storage:/var/www/html/storage - ./public/uploads:/var/www/html/public/uploads + # Keep the vendor/ directory from the image — prevents the bind-mount + # from wiping out the composer install done during docker build. + - vendor_local:/var/www/html/vendor depends_on: - db networks: - merchbay-local + # ── SSH tunnel to a remote database ──────────────────────────────────────── + # Activated only with: --profile ssh-db + # Requires SSH_HOST, SSH_USER, SSH_KEY_PATH (and optionally SSH_PORT, + # SSH_DB_REMOTE_HOST, SSH_DB_REMOTE_PORT) set in .env.local. + db-tunnel: + profiles: + - ssh-db + image: alpine:3.19 + container_name: merchbay_db_tunnel + restart: unless-stopped + environment: + - SSH_HOST=${SSH_HOST} + - SSH_PORT=${SSH_PORT:-22} + - SSH_USER=${SSH_USER:-root} + - SSH_DB_REMOTE_HOST=${SSH_DB_REMOTE_HOST:-127.0.0.1} + - SSH_DB_REMOTE_PORT=${SSH_DB_REMOTE_PORT:-3306} + volumes: + - ${SSH_KEY_PATH:-~/.ssh/id_rsa}:/ssh/id_rsa:ro + command: + - sh + - -c + - | + apk add --no-cache openssh-client + cp /ssh/id_rsa /tmp/id_rsa + chmod 600 /tmp/id_rsa + echo "Starting SSH tunnel to $$SSH_HOST..." + exec ssh -N \ + -o StrictHostKeyChecking=no \ + -o ServerAliveInterval=30 \ + -o ServerAliveCountMax=3 \ + -o ExitOnForwardFailure=yes \ + -i /tmp/id_rsa \ + -L "0.0.0.0:3306:$$SSH_DB_REMOTE_HOST:$$SSH_DB_REMOTE_PORT" \ + -p "$$SSH_PORT" \ + "$$SSH_USER@$$SSH_HOST" + networks: + - merchbay-local + phpmyadmin: image: arm64v8/phpmyadmin platform: linux/arm64 @@ -79,3 +147,4 @@ networks: volumes: db_data: + vendor_local: diff --git a/resources/views/designer/designer.blade.php b/resources/views/designer/designer.blade.php index 349a62a..77da716 100755 --- a/resources/views/designer/designer.blade.php +++ b/resources/views/designer/designer.blade.php @@ -450,7 +450,7 @@ @foreach ($pattern_arrays as $i => $val)
- +
@endforeach @@ -550,7 +550,7 @@ @foreach ($pattern_arrays as $r => $val)
- +
@endforeach @@ -1356,7 +1356,7 @@ var patternSVGPath = $(this).attr('data-pattern-path'); - var patternPath = "{{ config('site_config.uploads') }}" + patternSVGPath; + var patternPath = "{{ minio_url('') }}" + patternSVGPath; var SideAndPath = {!! json_encode($templatepaths_arrays) !!}; @@ -1433,7 +1433,7 @@ $(document).on('button click', '.patternTrimThumbs', function(){ var patternSVGPath = $(this).attr('data-pattern-path'); - var patternPath = "{{ config('site_config.uploads') }}" + patternSVGPath; + var patternPath = "{{ minio_url('') }}" + patternSVGPath; var getTrimId = $(this).attr('data-trim'); var SideAndPath = {!! json_encode($templatepaths_arrays) !!}; @@ -1577,7 +1577,7 @@ var gradientIds = sideName+"_"+type+"_Gradients"; var gradientPrefix = sideName+"_"+type+"_"; - var tempPath = "{{ config('site_config.uploads') }}" + pathLocation; + var tempPath = "{{ minio_url('') }}" + pathLocation; console.log(tempPath) if(!document.getElementById(objectId)) return false; @@ -1761,7 +1761,7 @@ var type = SideAndPath[i]['Type']; var pathLocation = SideAndPath[i]['Path']; var canvasName = "canvas_" + type + "_" + sideName; - var tempPath = "{{ config('site_config.uploads') }}" + pathLocation; + var tempPath = "{{ minio_url('') }}" + pathLocation; window['canvas_' + type + '_' + sideName] = new fabric.Canvas(canvasName); var templateFormat = SideAndPath[i]['TemplateFormat']; @@ -2093,9 +2093,9 @@ if(objType == "curvedText"){ if(obj.effect == "curved"){ - $('#teamname_text_shape').html('Text Shape:
'); + $('#teamname_text_shape').html('Text Shape:
'); }else if(obj.effect == "arc"){ - $('#teamname_text_shape').html('Text Shape:
'); + $('#teamname_text_shape').html('Text Shape:
'); }else{ $('#teamname_text_shape').html('Add text Shape'); } @@ -3395,7 +3395,7 @@ function loadSVGClipart(dataUrl){ var k = 0; var arrayPathId = []; - var svgUrl = "{{ config('site_config.uploads') }}cliparts/" + dataUrl; + var svgUrl = "{{ minio_url('cliparts/') }}" + dataUrl; fabric.loadSVGFromURL(svgUrl, function(objects, options) { var clipart = fabric.util.groupSVGElements(objects, options ); clipart.set({ diff --git a/resources/views/emails/orders.blade.php b/resources/views/emails/orders.blade.php index 25c0d88..c8f58b2 100755 --- a/resources/views/emails/orders.blade.php +++ b/resources/views/emails/orders.blade.php @@ -455,7 +455,7 @@ @foreach ($img_thumb as $img) @if ($img->ProductId == $item->ProductId) + src="{{ config('filesystems.disks.minio.url') }}/crewsportswear/images/{{ $img->Image }}"> @endif @endforeach diff --git a/resources/views/merchbay/cart.blade.php b/resources/views/merchbay/cart.blade.php index e901282..aa384a5 100755 --- a/resources/views/merchbay/cart.blade.php +++ b/resources/views/merchbay/cart.blade.php @@ -63,7 +63,7 @@
+ src="{{ minio_url('teamstore/' . $store_array[0]->ImageFolder . '/' . $store_array[0]->StoreLogo) }}"> {{ $store_array[0]->StoreName }}
@@ -76,7 +76,7 @@ @foreach ($img_thumb as $img) @if ($img->ProductId == $item->ProductId) + src="{{ config('filesystems.disks.minio.url') }}/crewsportswear/images/{{ $img->Image }}"> @endif @endforeach
diff --git a/resources/views/merchbay/index.blade.php b/resources/views/merchbay/index.blade.php index 99b4537..96f2c4e 100755 --- a/resources/views/merchbay/index.blade.php +++ b/resources/views/merchbay/index.blade.php @@ -73,9 +73,9 @@
+ src="{{ minio_url('teamstore/' . $carousel->ImageFolder . '/' . $carousel->StoreBanner) }}" />
-
@@ -84,9 +84,9 @@
+ src="{{ minio_url('teamstore/' . $carousel->ImageFolder . '/' . $carousel->StoreBanner) }}" />
-
@@ -167,7 +167,7 @@
{{ $product->ProductName }}
diff --git a/resources/views/sublayouts/checkout.blade.php b/resources/views/sublayouts/checkout.blade.php index 009243e..703880d 100755 --- a/resources/views/sublayouts/checkout.blade.php +++ b/resources/views/sublayouts/checkout.blade.php @@ -64,7 +64,7 @@ - + diff --git a/resources/views/teamstore-sublayouts/index.blade.php b/resources/views/teamstore-sublayouts/index.blade.php index ba9a9b1..a5b9c40 100755 --- a/resources/views/teamstore-sublayouts/index.blade.php +++ b/resources/views/teamstore-sublayouts/index.blade.php @@ -12,7 +12,7 @@
- {{ $store_array[0]->StoreName }}
@@ -130,7 +130,7 @@
- {{ $product->ProductName }}
diff --git a/resources/views/teamstore-sublayouts/product-details.blade.php b/resources/views/teamstore-sublayouts/product-details.blade.php index cd7c4ba..f1b6192 100755 --- a/resources/views/teamstore-sublayouts/product-details.blade.php +++ b/resources/views/teamstore-sublayouts/product-details.blade.php @@ -270,7 +270,7 @@
- ...
@@ -308,13 +308,13 @@
+ src="{{ config('filesystems.disks.minio.url') }}/crewsportswear/images/{{ $thumbnail->Image }}" />
@else
+ src="{{ config('filesystems.disks.minio.url') }}/crewsportswear/images/{{ $thumbnail->Image }}" />
@endif @define $i++ @@ -330,12 +330,12 @@ @foreach ($thumbnails_array as $thumbnail) @if ($j == 0) @else @endif @@ -394,7 +394,7 @@
{{ $product->ProductName }}
diff --git a/resources/views/teamstore-sublayouts/stores.blade.php b/resources/views/teamstore-sublayouts/stores.blade.php index c723012..dd0d970 100755 --- a/resources/views/teamstore-sublayouts/stores.blade.php +++ b/resources/views/teamstore-sublayouts/stores.blade.php @@ -17,7 +17,7 @@

{{ $item->ProductName }} {{ $itemOrder }}
Price: ${{ $item->Price }}

+ src="{{ config('filesystems.disks.minio.url') }}/crewsportswear/images/{{ $thumbnail->Image . '?t=' . time() }}" />