1 Commits

Author SHA1 Message Date
Frank John Begornia
88912ee8e3 Add Docker and Nginx configuration for Cloud Run deployment 2025-08-12 00:28:17 +08:00
51 changed files with 551 additions and 1545 deletions

18
.dockerignore Normal file
View File

@@ -0,0 +1,18 @@
.git
.gitignore
.env
.env.*
.editorconfig
.idea
.vscode
*.md
docker-compose.yml
Dockerfile
vendor/
node_modules/
storage/framework/cache/**
storage/framework/sessions/**
storage/framework/views/**
storage/logs/**
tests/
phpunit.xml

View File

@@ -1,36 +0,0 @@
APP_ENV=local
APP_DEBUG=true
APP_KEY=base64:YOUR_APP_KEY_HERE
DB_HOST=db
DB_DATABASE=merchbay
DB_USERNAME=merchbay
DB_PASSWORD=secret
DB_PORT=3306
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=sync
# Local mail (logs to storage/logs/laravel.log)
MAIL_DRIVER=log
MAIL_HOST=localhost
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
# Local URLs
APP_URL=http://localhost:8080
PROD_PRIVATE=http://localhost:8080
IMAGES_URL=http://localhost:8080
UPLOAD_URL=http://localhost:8080/uploads/
# Test Captcha (for local dev)
CAPTCHA_SITE_KEY=test_key
CAPTCHA_SECRET_KEY=test_secret
# Analytics (optional for local)
ANALYTICS_SITE_ID=
ANALYTICS_CLIENT_ID=
ANALYTICS_SERVICE_EMAIL=

View File

@@ -1,57 +0,0 @@
name: Build and Push Docker Image
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
tag:
description: 'Docker image tag (e.g., v1.0.0, latest)'
required: false
default: 'latest'
push_to_registry:
description: 'Push to registry?'
required: false
default: 'true'
jobs:
build-and-push:
runs-on: ubuntu-latest
container:
image: catthehacker/ubuntu:act-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Docker Registry
uses: docker/login-action@v2
with:
registry: ${{ secrets.DOCKER_REGISTRY_URL }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ secrets.DOCKER_REGISTRY_URL }}/merchbay
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value=latest
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=${{ secrets.DOCKER_REGISTRY_URL }}/merchbay:buildcache
cache-to: type=registry,ref=${{ secrets.DOCKER_REGISTRY_URL }}/merchbay:buildcache,mode=max

View File

@@ -1,161 +0,0 @@
name: Deploy Development
on:
push:
branches:
- dev
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
container:
image: catthehacker/ubuntu:act-latest
steps:
# 1⃣ Checkout code
- name: Checkout code
shell: sh
run: |
git clone $GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git /workspace/repo
cd /workspace/repo
git checkout $GITHUB_REF_NAME
# 2⃣ Build image
- name: Build Docker image
shell: sh
run: |
cd /workspace/repo
docker build -t merchbay:dev .
docker save merchbay:dev | gzip > merchbay_dev.tar.gz
# 3⃣ Setup SSH
- name: Setup SSH
shell: sh
env:
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "$DEPLOY_SSH_KEY" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts
# 4⃣ Upload artifacts
- name: Upload image and compose
shell: sh
env:
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
run: |
scp -i ~/.ssh/id_ed25519 \
/workspace/repo/merchbay_dev.tar.gz \
/workspace/repo/docker-compose.dev.yml \
${DEPLOY_USER}@${DEPLOY_HOST}:/tmp/
# 5⃣ Deploy on server
- name: Deploy on server
shell: sh
env:
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
run: |
ssh -i ~/.ssh/id_ed25519 $DEPLOY_USER@$DEPLOY_HOST << 'EOF'
set -e
DEPLOY_DIR="/var/www/apps/merchbay_dev"
mkdir -p "$DEPLOY_DIR"
echo "📦 Loading image"
docker load < /tmp/merchbay_dev.tar.gz
echo "🧹 Removing old merchbay images"
docker images | grep merchbay | grep -v "$(docker images merchbay:dev -q)" | awk '{print $3}' | xargs -r docker rmi -f || true
echo "📄 Updating compose file"
cp /tmp/docker-compose.dev.yml "$DEPLOY_DIR/docker-compose.yml"
cd "$DEPLOY_DIR"
echo "🔍 Checking .env file"
if [ ! -f .env ]; then
echo "❌ .env file not found at $DEPLOY_DIR/.env"
echo "Please create it first with required variables:"
echo " - DB_*, PROD_PRIVATE, IMAGES_URL, UPLOAD_URL"
echo " - MAIL_*, CAPTCHA_*, ANALYTICS_*"
exit 1
fi
echo "🔧 Fixing .env permissions"
sudo chown $USER:$USER .env
sudo chmod 600 .env
echo "🌐 Ensure networks"
docker network inspect traefik-public >/dev/null 2>&1 || \
docker network create traefik-public
docker network inspect crew-app-net >/dev/null 2>&1 || \
docker network create crew-app-net
echo "🚀 Starting containers (env vars from .env file)"
docker compose up -d
echo "⏳ Waiting for app container"
sleep 15
if docker ps --format '{{.Names}}' | grep -q merchbay_app; then
echo "🧹 Clearing and rebuilding config cache"
docker compose exec -T app php artisan config:clear
docker compose exec -T app php artisan config:cache
else
echo "❌ App container not running"
docker compose logs
exit 1
fi
echo "🧹 Cleanup"
rm -f /tmp/merchbay_dev.tar.gz /tmp/docker-compose.dev.yml
echo "🧹 Aggressive Docker cleanup to reclaim space"
docker image prune -af --filter "until=24h" || true
docker container prune -f || true
docker volume prune -f || true
docker builder prune -af --filter "until=48h" || true
echo "📊 Docker space usage:"
docker system df
echo "✅ Deployment completed"
EOF
# 6⃣ Health check
- name: Health check
shell: sh
run: |
echo "⏳ Waiting for app to be ready..."
sleep 20
echo "🔍 Testing health check (ignoring SSL cert for now)..."
HTTP_CODE=$(curl -k -s -o /dev/null -w "%{http_code}" --max-time 30 https://dev.merchbay.app || echo "000")
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "302" ] || [ "$HTTP_CODE" = "301" ]; then
echo "✅ Health check passed! (HTTP $HTTP_CODE)"
echo "⚠️ Note: Using -k to ignore SSL cert. Check Traefik logs if cert not ready."
else
echo "❌ Health check failed! (HTTP $HTTP_CODE)"
echo ""
echo "💡 Troubleshooting:"
echo " 1. Check if container is running:"
echo " docker ps | grep merchbay_app"
echo ""
echo " 2. Check app logs:"
echo " docker logs merchbay_app"
echo ""
echo " 3. Check Traefik logs:"
echo " docker logs traefik"
echo ""
echo " 4. Test manually:"
echo " curl -Ik https://dev.merchbay.app"
exit 1
fi

View File

@@ -1,181 +0,0 @@
name: Deploy Production (merchbay.com)
on:
push:
branches:
- main
- master
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
container:
image: catthehacker/ubuntu:act-latest
steps:
# 1⃣ Checkout code
- name: Checkout code
shell: sh
run: |
git clone $GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git /workspace/repo
cd /workspace/repo
git checkout $GITHUB_REF_NAME
# 2⃣ Build image
- name: Build Docker image
shell: sh
run: |
cd /workspace/repo
docker build -t merchbay:latest .
docker save merchbay:latest | gzip > merchbay.tar.gz
# 3⃣ Setup SSH
- name: Setup SSH
shell: sh
env:
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "$DEPLOY_SSH_KEY" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts
# 4⃣ Upload artifacts
- name: Upload image and compose
shell: sh
env:
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
run: |
scp -i ~/.ssh/id_ed25519 \
/workspace/repo/merchbay.tar.gz \
/workspace/repo/docker-compose.prod.yml \
${DEPLOY_USER}@${DEPLOY_HOST}:/tmp/
# 5⃣ Deploy on server
- name: Deploy on server
shell: sh
env:
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
run: |
ssh -i ~/.ssh/id_ed25519 $DEPLOY_USER@$DEPLOY_HOST << 'EOF'
set -e
DEPLOY_DIR="/var/www/apps/merchbay"
sudo mkdir -p "$DEPLOY_DIR"
sudo chown $USER:$USER "$DEPLOY_DIR"
echo "<22> Stopping dev environment"
DEV_DIR="/var/www/apps/merchbay_dev"
if [ -d "$DEV_DIR" ]; then
cd "$DEV_DIR"
docker compose down || true
cd -
echo "✅ Dev environment stopped"
echo "📋 Copying .env from dev to production"
if [ -f "$DEV_DIR/.env" ]; then
cp "$DEV_DIR/.env" "$DEPLOY_DIR/.env"
echo "✅ .env file copied from dev to production"
else
echo "⚠️ No .env file found in dev directory"
fi else
echo " No dev environment found"
fi
echo "📦 Loading image"
docker load < /tmp/merchbay.tar.gz
echo "🧹 Removing dev images and old merchbay images"
docker images | grep "merchbay:dev" | awk '{print $3}' | xargs -r docker rmi -f || true
docker images | grep merchbay | grep -v "$(docker images merchbay:latest -q)" | awk '{print $3}' | xargs -r docker rmi -f || true
echo "📄 Updating compose file"
cp /tmp/docker-compose.prod.yml "$DEPLOY_DIR/docker-compose.yml"
cd "$DEPLOY_DIR"
echo "🔍 Checking .env file"
if [ ! -f .env ]; then
echo "❌ .env file not found at $DEPLOY_DIR/.env"
echo "Please create it first with required variables:"
echo " - DB_*, PROD_PRIVATE, IMAGES_URL, UPLOAD_URL"
echo " - MAIL_*, CAPTCHA_*, ANALYTICS_*"
exit 1
fi
echo "🔧 Fixing .env permissions"
sudo chown $USER:$USER .env
sudo chmod 600 .env
echo "🌐 Ensure networks"
docker network inspect traefik-public >/dev/null 2>&1 || \
docker network create traefik-public
docker network inspect crew-app-net >/dev/null 2>&1 || \
docker network create crew-app-net
echo "🚀 Starting containers (env vars from .env file)"
docker compose up -d
echo "⏳ Waiting for app container"
sleep 15
if docker ps --format '{{.Names}}' | grep -q merchbay_app; then
echo "🧹 Clearing and rebuilding config cache"
docker compose exec -T app php artisan config:clear
docker compose exec -T app php artisan config:cache
else
echo "❌ App container not running"
docker compose logs
exit 1
fi
echo "🧹 Cleanup"
rm -f /tmp/merchbay.tar.gz /tmp/docker-compose.prod.yml
echo "🧹 Aggressive Docker cleanup to reclaim space"
docker image prune -af --filter "until=24h" || true
docker container prune -f || true
docker volume prune -f || true
docker builder prune -af --filter "until=48h" || true
echo "📊 Docker space usage:"
docker system df
echo "✅ Production deployment completed!"
echo "🌐 Application available at: https://merchbay.com"
EOF
# 6⃣ Health check
- name: Health check
shell: sh
run: |
echo "⏳ Waiting for app to be ready..."
sleep 20
echo "🔍 Testing health check..."
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 30 https://merchbay.com || echo "000")
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "302" ] || [ "$HTTP_CODE" = "301" ]; then
echo "✅ Health check passed! (HTTP $HTTP_CODE)"
echo "🎉 Production deployment successful!"
else
echo "❌ Health check failed! (HTTP $HTTP_CODE)"
echo ""
echo "💡 Troubleshooting:"
echo " 1. Check if container is running:"
echo " docker ps | grep merchbay_app"
echo ""
echo " 2. Check app logs:"
echo " docker logs merchbay_app"
echo ""
echo " 3. Check Traefik logs:"
echo " docker logs traefik"
echo ""
echo " 4. Test manually:"
echo " curl -Ik https://merchbay.com"
exit 1
fi

View File

@@ -1,125 +0,0 @@
# DigiCert SSL Certificate Setup for Production
## Certificate Files Required
From DigiCert, you'll receive these files:
- `merchbay_com.crt` - Your domain certificate
- `merchbay_com.key` - Private key (generated during CSR creation)
- `DigiCertCA.crt` - Intermediate certificate
- `TrustedRoot.crt` - Root certificate (optional)
## Step 1: Combine Certificate Chain (on your local machine)
```bash
# Create full chain certificate
cat merchbay_com.crt DigiCertCA.crt > merchbay.com.crt
# Copy private key
cp merchbay_com.key merchbay.com.key
```
## Step 2: Upload to Production Server
```bash
# SSH to production server
ssh PROD_DEPLOY_USER@PROD_DEPLOY_HOST
# Create certificates directory
sudo mkdir -p /srv/certs
sudo chmod 700 /srv/certs
# Exit SSH, then upload from local machine
scp merchbay.com.crt PROD_DEPLOY_USER@PROD_DEPLOY_HOST:/tmp/
scp merchbay.com.key PROD_DEPLOY_USER@PROD_DEPLOY_HOST:/tmp/
# SSH back to server and move files
ssh PROD_DEPLOY_USER@PROD_DEPLOY_HOST
sudo mv /tmp/merchbay.com.crt /srv/certs/
sudo mv /tmp/merchbay.com.key /srv/certs/
sudo chmod 600 /srv/certs/*
sudo chown root:root /srv/certs/*
```
## Step 3: Upload Traefik Configuration
```bash
# From local machine
scp traefik-certs.yml PROD_DEPLOY_USER@PROD_DEPLOY_HOST:/tmp/
# SSH to server
ssh PROD_DEPLOY_USER@PROD_DEPLOY_HOST
sudo mkdir -p /srv/traefik
sudo mv /tmp/traefik-certs.yml /srv/traefik/dynamic-certs.yml
sudo chmod 644 /srv/traefik/dynamic-certs.yml
```
## Step 4: Update Traefik Container
Ensure your Traefik docker-compose.yml includes:
```yaml
services:
traefik:
image: traefik:v2.10
command:
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --providers.file.filename=/dynamic-certs.yml
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --entrypoints.web.http.redirections.entrypoint.to=websecure
- --entrypoints.web.http.redirections.entrypoint.scheme=https
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /srv/certs:/srv/certs:ro
- /srv/traefik/dynamic-certs.yml:/dynamic-certs.yml:ro
networks:
- traefik-public
restart: unless-stopped
```
## Step 5: Restart Traefik
```bash
cd /opt/traefik # or wherever your traefik docker-compose.yml is
docker compose restart traefik
# Verify certificate is loaded
docker compose logs traefik | grep -i certificate
```
## Step 6: Deploy merchbay Application
Once Traefik is configured, deploy merchbay:
```bash
cd /var/www/merchbay
docker compose up -d
```
## Verification
```bash
# Check certificate
openssl s_client -connect merchbay.com:443 -servername merchbay.com < /dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates
# Should show:
# subject=CN = merchbay.com
# issuer=O = DigiCert Inc, CN = DigiCert TLS RSA SHA256 2020 CA1
# notBefore=...
# notAfter=...
```
## Certificate Renewal
DigiCert certificates typically last 1-2 years. Set a reminder to renew 30 days before expiration and repeat Steps 1-3 and 5.
## Security Notes
- Never commit `.key` files to git
- Keep private keys secure (600 permissions)
- Use strong encryption for private key storage
- Consider using a certificate management system for automatic renewal

View File

@@ -1,86 +1,81 @@
# Use PHP 7.0 with Apache (has native mcrypt support for Laravel 5.0) # Build stage
FROM php:7.0-apache FROM php:5.6-fpm-alpine as composer
# Update to use archived Debian repositories # Install system dependencies and PHP extensions required for Composer
RUN sed -i 's|deb.debian.org|archive.debian.org|g' /etc/apt/sources.list \ RUN apk add --no-cache \
&& sed -i 's|security.debian.org|archive.debian.org|g' /etc/apt/sources.list \
&& sed -i '/stretch-updates/d' /etc/apt/sources.list
# Install system dependencies
RUN apt-get update && apt-get install -y --allow-unauthenticated \
git \ git \
curl \ curl \
libpng-dev \
libxml2-dev \
libmcrypt-dev \
zip \ zip \
unzip \ unzip \
libfreetype6-dev \ libmcrypt \
libjpeg62-turbo-dev \ libmcrypt-dev \
openssh-client \ zlib-dev \
libzip-dev \
autoconf \
make \
gcc \
g++ \
&& docker-php-ext-install mcrypt mbstring zip \
&& curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \
&& composer self-update --1
ENV COMPOSER_ALLOW_SUPERUSER=1
WORKDIR /app
COPY composer.* ./
# Install dependencies with Composer (optimize later after full copy)
RUN composer config platform.php 5.6.40 \
&& composer install --prefer-dist --no-dev --no-scripts --no-autoloader
# Copy the rest of the application and optimize autoload
COPY . .
RUN composer dump-autoload --optimize --no-dev --classmap-authoritative
# Production stage
FROM php:5.6-fpm-alpine
# Install runtime dependencies & build PHP extensions
RUN apk add --no-cache \
nginx \
curl \
libpng \
libjpeg-turbo \
freetype \
libzip \
libmcrypt \
zlib \
&& apk add --no-cache --virtual .build-deps \
autoconf make gcc g++ \
libpng-dev libjpeg-turbo-dev freetype-dev libzip-dev libmcrypt-dev zlib-dev \
&& docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \ && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
&& docker-php-ext-install -j$(nproc) gd && docker-php-ext-install -j"$(nproc)" gd pdo pdo_mysql zip mcrypt mbstring opcache \
&& docker-php-ext-enable mcrypt \
&& apk del .build-deps \
&& php -m | grep -i mcrypt
# Install PHP extensions (mcrypt is built-in for PHP 7.0) # Configure PHP for production
RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath mcrypt tokenizer zip RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" \
&& echo "opcache.enable=1" >> /usr/local/etc/php/conf.d/opcache.ini \
&& echo "opcache.memory_consumption=128" >> /usr/local/etc/php/conf.d/opcache.ini \
&& echo "opcache.interned_strings_buffer=8" >> /usr/local/etc/php/conf.d/opcache.ini \
&& echo "opcache.max_accelerated_files=4000" >> /usr/local/etc/php/conf.d/opcache.ini \
&& echo "opcache.revalidate_freq=60" >> /usr/local/etc/php/conf.d/opcache.ini \
&& echo "opcache.fast_shutdown=1" >> /usr/local/etc/php/conf.d/opcache.ini
# Enable Apache mod_rewrite WORKDIR /var/www
RUN a2enmod rewrite
# Install Composer (version 1.x for better compatibility with Laravel 5.0) # Copy vendor & app code from build stage
COPY --from=composer:1.10 /usr/bin/composer /usr/bin/composer COPY --from=composer /app/vendor ./vendor
COPY . .
# Set working directory # Set appropriate permissions and create required directories
WORKDIR /var/www/html RUN chown -R www-data:www-data storage bootstrap \
&& mkdir -p /run/php \
&& chown www-data:www-data /run/php \
&& php artisan key:generate || true
# Copy existing application directory contents # Healthcheck (FPM listens on 9000; adjust as needed if behind nginx)
COPY . /var/www/html HEALTHCHECK --interval=30s --timeout=5s CMD php -m > /dev/null || exit 1
# Create storage directories and set permissions EXPOSE 9000
RUN mkdir -p storage/framework/views \ CMD ["php-fpm"]
storage/framework/cache \
storage/framework/sessions \
storage/logs \
bootstrap/cache
# Set proper ownership and permissions
RUN chown -R www-data:www-data /var/www/html \
&& chmod -R 775 /var/www/html/storage \
&& chmod -R 775 /var/www/html/bootstrap/cache
# Create .env file if it doesn't exist
RUN if [ ! -f .env ]; then cp .env.example .env; fi
# Install PHP dependencies (Laravel 5.0 compatible)
RUN composer install --no-dev --no-interaction --prefer-dist
# Generate application key
RUN php artisan key:generate || true
# Run Laravel 5.0 optimization
RUN php artisan clear-compiled && php artisan optimize
# Note: yakpro-po obfuscation requires PHP 7.1+, incompatible with PHP 7.0
# For code protection with PHP 7.0, consider:
# 1. ionCube Encoder (commercial, most secure)
# 2. Keeping source code private and using proper access controls
# 3. Using --optimize flag in composer (already done above)
# Configure Apache DocumentRoot to point to Laravel's public directory
ENV APACHE_DOCUMENT_ROOT=/var/www/html/public
RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf
RUN sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf
# Suppress Apache ServerName warning
RUN echo "ServerName localhost" >> /etc/apache2/apache2.conf
# Copy entrypoint script
COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
# Expose port 80
EXPOSE 80
# Use entrypoint to set up permissions before starting Apache
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["apache2-foreground"]

View File

@@ -1,133 +0,0 @@
# Local Development Setup for Merchbay
## Quick Start
1. **Copy local environment file:**
```bash
cp .env.local .env
```
2. **Build and start containers:**
```bash
docker-compose -f docker-compose.local.yml up -d --build
```
3. **Generate application key:**
```bash
docker exec merchbay_app_local php artisan key:generate
```
4. **Run migrations:**
```bash
docker exec merchbay_app_local php artisan migrate
```
5. **Access the application:**
- **App:** http://localhost:8080
- **phpMyAdmin:** http://localhost:8081
- Username: `merchbay`
- Password: `secret`
## Development Commands
### View logs
```bash
# Application logs
docker exec merchbay_app_local tail -f storage/logs/laravel.log
# Apache logs
docker logs -f merchbay_app_local
```
### Run artisan commands
```bash
docker exec merchbay_app_local php artisan [command]
```
### Access container shell
```bash
docker exec -it merchbay_app_local bash
```
### Database access
```bash
docker exec -it merchbay_db_local mysql -u merchbay -psecret merchbay
```
### Clear caches
```bash
docker exec merchbay_app_local php artisan cache:clear
docker exec merchbay_app_local php artisan config:clear
docker exec merchbay_app_local php artisan view:clear
```
### Stop containers
```bash
docker-compose -f docker-compose.local.yml down
```
### Stop and remove volumes (clean slate)
```bash
docker-compose -f docker-compose.local.yml down -v
```
## Debugging
### Enable Xdebug (if needed)
Add to Dockerfile:
```dockerfile
RUN pecl install xdebug-2.9.8 && docker-php-ext-enable xdebug
```
### Check container status
```bash
docker-compose -f docker-compose.local.yml ps
```
### View all logs
```bash
docker-compose -f docker-compose.local.yml logs -f
```
## Hot Reload Development
For live code changes without rebuilding:
- Code changes in `.php` files are reflected immediately (via volume mount)
- View changes are reflected immediately
- Config/route changes require cache clearing
## Database Management
### Import SQL dump
```bash
docker exec -i merchbay_db_local mysql -u merchbay -psecret merchbay < dump.sql
```
### Export database
```bash
docker exec merchbay_db_local mysqldump -u merchbay -psecret merchbay > dump.sql
```
## Troubleshooting
### Permission issues
```bash
docker exec merchbay_app_local chown -R www-data:www-data storage bootstrap/cache
docker exec merchbay_app_local chmod -R 775 storage bootstrap/cache
```
### Reset everything
```bash
docker-compose -f docker-compose.local.yml down -v
docker rmi merchbay_app:local
rm .env
cp .env.local .env
docker-compose -f docker-compose.local.yml up -d --build
```
## Notes
- The local setup uses a separate MySQL container
- All code changes are live-mounted for easy development
- Mail is logged to `storage/logs/laravel.log` instead of being sent
- phpMyAdmin is available for easy database management

View File

@@ -3,7 +3,6 @@
namespace App\Exceptions; namespace App\Exceptions;
use Exception; use Exception;
use Throwable;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
@@ -24,16 +23,11 @@ class Handler extends ExceptionHandler
* *
* This is a great spot to send exceptions to Sentry, Bugsnag, etc. * This is a great spot to send exceptions to Sentry, Bugsnag, etc.
* *
* @param \Throwable $e * @param \Exception $e
* @return void * @return void
*/ */
public function report(Throwable $e) public function report(Exception $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); return parent::report($e);
} }
@@ -49,13 +43,11 @@ class Handler extends ExceptionHandler
// return parent::render($request, $e); // return parent::render($request, $e);
// } // }
public function render($request, Throwable $e) public function render($request, Exception $e)
{ {
if ($e instanceof MethodNotAllowedHttpException) { if ($e instanceof MethodNotAllowedHttpException) {
abort(404); abort(404);
} }
// Wrap non-Exception Throwables for Laravel 5.0 parent compatibility. return parent::render($request, $e);
$exception = $e instanceof Exception ? $e : new \RuntimeException($e->getMessage(), $e->getCode());
return parent::render($request, $exception);
} }
} }

View File

@@ -34,10 +34,10 @@ class UserController extends Controller
$post_data = array( $post_data = array(
'isStoreOwner' => true, 'isStoreOwner' => true,
'store_order' => isset($countStoreOrder[0]->count_order) ? $countStoreOrder[0]->count_order : 0, 'store_order' => $countStoreOrder[0]->count_order,
'store_income' => isset($storeIncome[0]->store_income) ? $storeIncome[0]->store_income : 0, 'store_income' => $storeIncome[0]->store_income,
'store_product_count' => isset($countStoreProduct[0]->store_product_count) ? $countStoreProduct[0]->store_product_count : 0, 'store_product_count' => $countStoreProduct[0]->store_product_count,
'store_published_product' => isset($countStorePublishedProduct[0]->store_published_product) ? $countStorePublishedProduct[0]->store_published_product : 0 'store_published_product' => $countStorePublishedProduct[0]->store_published_product
); );
} else { } else {
$post_data = array( $post_data = array(
@@ -765,7 +765,8 @@ class UserController extends Controller
$u = $UserModel->insertNewProductThumbnails($thumbs); $u = $UserModel->insertNewProductThumbnails($thumbs);
// var_dump($thumbs); // var_dump($thumbs);
Storage::disk('minio_crewsportswear')->put('images/' . $thumbnail, fopen($request->file('imgupload')[$i], 'r+')); Storage::disk('sftp')->put($thumbnail, fopen($request->file('imgupload')[$i], 'r+')); //live
//Storage::disk('localdir')->put($thumbnail, fopen($request->file('imgupload')[$i], 'r+'));
// var_dump($s); // var_dump($s);
} }
@@ -937,7 +938,8 @@ class UserController extends Controller
); );
$u = $UserModel->insertNewProductThumbnails($thumbs); $u = $UserModel->insertNewProductThumbnails($thumbs);
Storage::disk('minio_crewsportswear')->put('images/' . $thumbnail, fopen($request->file('upload_images')[$i], 'r+')); 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+'));
} }
@@ -959,8 +961,6 @@ class UserController extends Controller
unlink($storagePath . $file); unlink($storagePath . $file);
} }
Storage::disk('minio_crewsportswear')->delete('images/' . $file);
$i = $UserModel->deleteImageThumb('Id', $id); $i = $UserModel->deleteImageThumb('Id', $id);
return response()->json(array( return response()->json(array(

View File

@@ -4,8 +4,6 @@ use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use League\Flysystem\Filesystem; use League\Flysystem\Filesystem;
use League\Flysystem\Sftp\SftpAdapter; use League\Flysystem\Sftp\SftpAdapter;
use League\Flysystem\AwsS3v3\AwsS3Adapter as AwsS3v3Adapter;
use Aws\S3\S3Client;
use Illuminate\Support\Facades\Blade; use Illuminate\Support\Facades\Blade;
class AppServiceProvider extends ServiceProvider { class AppServiceProvider extends ServiceProvider {
@@ -17,11 +15,7 @@ class AppServiceProvider extends ServiceProvider {
*/ */
public function boot() public function boot()
{ {
// Force HTTPS URLs when behind a proxy (Traefik) //
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
\URL::forceSchema('https');
}
Blade::extend(function($value) { Blade::extend(function($value) {
return preg_replace('/\@define(.+)/', '<?php ${1}; ?>', $value); return preg_replace('/\@define(.+)/', '<?php ${1}; ?>', $value);
}); });
@@ -29,21 +23,6 @@ class AppServiceProvider extends ServiceProvider {
Storage::extend('sftp', function ($app, $config) { Storage::extend('sftp', function ($app, $config) {
return new Filesystem(new SftpAdapter($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);
});
} }
/** /**

View File

@@ -1,33 +0,0 @@
<?php
if (!function_exists('minio_url')) {
/**
* Generate MinIO URL for a file.
*
* @param string $path File path relative to bucket
* @return string Full MinIO URL
*/
function minio_url($path)
{
$bucket = env('MINIO_BUCKET', 'merchbay');
$baseUrl = env('MINIO_URL', 'https://minio.crewsportswear.app');
// Remove leading slash if present
$path = ltrim($path, '/');
return $baseUrl . '/' . $bucket . '/' . $path;
}
}
if (!function_exists('minio_image_url')) {
/**
* Generate MinIO URL for an image in uploads/images/.
*
* @param string $filename Image filename
* @return string Full MinIO URL
*/
function minio_image_url($filename)
{
return minio_url('uploads/images/' . $filename);
}
}

View File

@@ -0,0 +1,72 @@
# Combined image for Cloud Run: nginx + php-fpm (PHP 5.6) for legacy Laravel 5
# NOTE: PHP 5.6 is EOL; use only for legacy maintenance. Consider upgrading.
FROM php:5.6-fpm
# Set build args/env
ARG APP_ENV=production
ENV APP_ENV=${APP_ENV} \
APP_DEBUG=false \
OPCACHE_VALIDATE_TIMESTAMPS=0 \
COMPOSER_ALLOW_SUPERUSER=1 \
PORT=8080 \
PATH="/var/www/artisan:$PATH"
WORKDIR /var/www
# Install system deps (Debian variant easier than alpine for mixed services)
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
git curl unzip zip supervisor nginx \
libmcrypt4 libmcrypt-dev \
libpng-dev libjpeg62-turbo-dev libfreetype6-dev libzip-dev zlib1g-dev \
&& docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
&& docker-php-ext-install gd mcrypt mbstring pdo pdo_mysql zip opcache \
&& rm -rf /var/lib/apt/lists/*
# Install Composer (v1)
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \
&& composer self-update --1
# Copy composer files & install deps first (cache layer)
COPY composer.* ./
RUN composer config platform.php 5.6.40 \
&& composer install --no-dev --no-scripts --no-autoloader --prefer-dist
# Copy application code
COPY . .
RUN composer dump-autoload --optimize --no-dev --classmap-authoritative || true
# Nginx config
COPY cloudrun/nginx.conf /etc/nginx/nginx.conf
# Supervisord config
COPY cloudrun/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
# Remove default nginx site configs if present
RUN rm -f /etc/nginx/sites-enabled/default /etc/nginx/conf.d/default.conf || true
# Create runtime dirs
RUN mkdir -p /run/php /var/log/supervisor /var/www/storage /var/www/bootstrap/cache \
&& chown -R www-data:www-data /var/www/storage /var/www/bootstrap/cache
# PHP production ini tweaks
RUN { \
echo "opcache.enable=1"; \
echo "opcache.memory_consumption=128"; \
echo "opcache.interned_strings_buffer=8"; \
echo "opcache.max_accelerated_files=4000"; \
echo "opcache.revalidate_freq=60"; \
echo "opcache.fast_shutdown=1"; \
echo "date.timezone=UTC"; \
} > /usr/local/etc/php/conf.d/zz-custom.ini
# Generate app key if missing (non-fatal if artisan fails early)
RUN php artisan key:generate || true
# Cloud Run listens on $PORT
EXPOSE 8080
# Health check path suggestion: /healthz (configure in Cloud Run if desired)
# Start supervisor (manages php-fpm + nginx)
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]

42
cloudrun/nginx.conf Normal file
View File

@@ -0,0 +1,42 @@
user nginx;
worker_processes auto;
error_log /dev/stderr warn;
pid /var/run/nginx.pid;
events { worker_connections 1024; }
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /dev/stdout main;
sendfile on;
keepalive_timeout 65;
server_tokens off;
server {
listen 8080 default_server;
listen [::]:8080 default_server;
root /var/www/public;
index index.php index.html;
location /healthz { return 200 'ok'; add_header Content-Type text/plain; }
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass 127.0.0.1:9000; # php-fpm
fastcgi_index index.php;
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 30d;
access_log off;
}
}
}

24
cloudrun/supervisord.conf Normal file
View File

@@ -0,0 +1,24 @@
[supervisord]
nodaemon=true
logfile=/dev/stdout
logfile_maxbytes=0
[program:php-fpm]
command=/usr/sbin/php-fpm5.6 -F
priority=10
autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:nginx]
command=/usr/sbin/nginx -g 'daemon off;'
priority=20
autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

View File

@@ -11,26 +11,20 @@
"guzzlehttp/guzzle": "~5.0", "guzzlehttp/guzzle": "~5.0",
"google/recaptcha": "~1.1", "google/recaptcha": "~1.1",
"spatie/laravel-analytics": "^1.4", "spatie/laravel-analytics": "^1.4",
"league/flysystem-sftp": "^1.0", "league/flysystem-sftp": "^1.0"
"league/flysystem-aws-s3-v3": "~1.0",
"aws/aws-sdk-php": "~3.0",
"psr/http-message": "^1.0",
"guzzlehttp/psr7": "^1.4"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "~4.0", "phpunit/phpunit": "~4.0",
"phpspec/phpspec": "~2.1" "phpspec/phpspec": "~2.1"
}, },
"autoload": { "autoload": {
"classmap": [ "classmap": [
"database" "database"
], ],
"psr-4": { "psr-4": {
"App\\": "app/" "App\\": "app/"
}, }
"files": [
"app/helpers.php"
]
}, },
"autoload-dev": { "autoload-dev": {
"classmap": [ "classmap": [
@@ -52,6 +46,9 @@
] ]
}, },
"config": { "config": {
"preferred-install": "dist" "preferred-install": "dist",
"allow-plugins": {
"kylekatarnls/update-helper": true
}
} }
} }

View File

@@ -108,28 +108,6 @@ return [
'driver' => 'local', 'driver' => 'local',
'root' => 'C:/wamp64/www/uploads', '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'),
],
], ],
]; ];

View File

@@ -20,7 +20,6 @@ return [
'prod_private_server_ip' => env('PROD_PRIVATE'), 'prod_private_server_ip' => env('PROD_PRIVATE'),
// 'images_url' => env('https://crewsportswear.app:5955', 'https://crewsportswear.app:5955'), // 'images_url' => env('https://crewsportswear.app:5955', 'https://crewsportswear.app:5955'),
'images_url' => env('IMAGES_URL'), '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('https://crewsportswear.app:5955/merchbay/', 'https://crewsportswear.com/uploads/images/'), // local
'uploads' => env('UPLOAD_URL'), 'uploads' => env('UPLOAD_URL'),

View File

@@ -1,58 +0,0 @@
services:
app:
image: merchbay:dev
container_name: merchbay_app_dev
restart: unless-stopped
environment:
- APP_ENV=${APP_ENV:-development}
- APP_DEBUG=${APP_DEBUG:-true}
- APP_URL=${APP_URL:-https://dev.merchbay.app}
- DB_CONNECTION=mysql
- DB_HOST=${DB_HOST}
- DB_PORT=${DB_PORT:-3306}
- DB_DATABASE=${DB_DATABASE}
- DB_USERNAME=${DB_USERNAME}
- DB_PASSWORD=${DB_PASSWORD}
- PROD_PRIVATE=${PROD_PRIVATE}
- IMAGES_URL=${IMAGES_URL}
- UPLOAD_URL=${UPLOAD_URL}
- FORCE_HTTPS=true
- MAIL_DRIVER=${MAIL_DRIVER}
- MAIL_HOST=${MAIL_HOST}
- MAIL_PORT=${MAIL_PORT}
- MAIL_USERNAME=${MAIL_USERNAME}
- MAIL_PASSWORD=${MAIL_PASSWORD}
- MAIL_ENCRYPTION=${MAIL_ENCRYPTION}
- CAPTCHA_SITE_KEY=${CAPTCHA_SITE_KEY}
- CAPTCHA_SECRET_KEY=${CAPTCHA_SECRET_KEY}
- ANALYTICS_SITE_ID=${ANALYTICS_SITE_ID}
- ANALYTICS_CLIENT_ID=${ANALYTICS_CLIENT_ID}
- ANALYTICS_SERVICE_EMAIL=${ANALYTICS_SERVICE_EMAIL}
volumes:
- ./storage:/var/www/html/storage
- ./public/uploads:/var/www/html/public/uploads
labels:
- "traefik.enable=true"
# Development environment (dev.merchbay.app)
- "traefik.http.routers.merchbay-dev.rule=Host(`dev.merchbay.app`)"
- "traefik.http.routers.merchbay-dev.entrypoints=websecure"
- "traefik.http.routers.merchbay-dev.tls=true"
- "traefik.http.routers.merchbay-dev.tls.certresolver=le"
- "traefik.http.services.merchbay-dev.loadbalancer.server.port=80"
# HTTP to HTTPS redirect
- "traefik.http.routers.merchbay-dev-http.rule=Host(`dev.merchbay.app`)"
- "traefik.http.routers.merchbay-dev-http.entrypoints=web"
- "traefik.http.routers.merchbay-dev-http.middlewares=https-redirect"
- "traefik.http.middlewares.https-redirect.redirectscheme.scheme=https"
networks:
- traefik-public
- crew-app-net
- default
networks:
traefik-public:
external: true
crew-app-net:
external: true
default:
driver: bridge

View File

@@ -1,150 +0,0 @@
# ─────────────────────────────────────────────────────────────────────────────
# 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
platform: linux/arm64
container_name: merchbay_db_local
restart: unless-stopped
environment:
MYSQL_DATABASE: ${DB_DATABASE:-merchbay_db}
MYSQL_ROOT_PASSWORD: root
MYSQL_USER: ${DB_USERNAME:-merchbay}
MYSQL_PASSWORD: ${DB_PASSWORD:-secret}
ports:
- "3306:3306"
volumes:
- db_data:/var/lib/mysql
networks:
- merchbay-local
app:
build:
context: .
dockerfile: Dockerfile
container_name: merchbay_app_local
restart: unless-stopped
ports:
- "8080:80"
environment:
- APP_ENV=local
- APP_DEBUG=true
- APP_URL=http://localhost:8080
- DB_CONNECTION=mysql
- 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=${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
- MAIL_USERNAME=null
- MAIL_PASSWORD=null
- MAIL_ENCRYPTION=null
- CAPTCHA_SITE_KEY=test_key
- CAPTCHA_SECRET_KEY=test_secret
- 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
container_name: merchbay_phpmyadmin
restart: unless-stopped
ports:
- "8081:80"
environment:
PMA_HOST: db
PMA_PORT: 3306
MYSQL_ROOT_PASSWORD: root
depends_on:
- db
networks:
- merchbay-local
networks:
merchbay-local:
driver: bridge
volumes:
db_data:
vendor_local:

View File

@@ -1,57 +0,0 @@
services:
app:
image: merchbay:latest
container_name: merchbay_app_prod
restart: unless-stopped
environment:
- APP_ENV=${APP_ENV:-production}
- APP_DEBUG=${APP_DEBUG:-false}
- APP_URL=${APP_URL:-https://merchbay.com}
- DB_CONNECTION=mysql
- DB_HOST=${DB_HOST}
- DB_PORT=${DB_PORT:-3306}
- DB_DATABASE=${DB_DATABASE}
- DB_USERNAME=${DB_USERNAME}
- DB_PASSWORD=${DB_PASSWORD}
- PROD_PRIVATE=${PROD_PRIVATE}
- IMAGES_URL=${IMAGES_URL}
- UPLOAD_URL=${UPLOAD_URL}
- FORCE_HTTPS=true
- MAIL_DRIVER=${MAIL_DRIVER}
- MAIL_HOST=${MAIL_HOST}
- MAIL_PORT=${MAIL_PORT}
- MAIL_USERNAME=${MAIL_USERNAME}
- MAIL_PASSWORD=${MAIL_PASSWORD}
- MAIL_ENCRYPTION=${MAIL_ENCRYPTION}
- CAPTCHA_SITE_KEY=${CAPTCHA_SITE_KEY}
- CAPTCHA_SECRET_KEY=${CAPTCHA_SECRET_KEY}
- ANALYTICS_SITE_ID=${ANALYTICS_SITE_ID}
- ANALYTICS_CLIENT_ID=${ANALYTICS_CLIENT_ID}
- ANALYTICS_SERVICE_EMAIL=${ANALYTICS_SERVICE_EMAIL}
volumes:
- ./storage:/var/www/html/storage
- ./public/uploads:/var/www/html/public/uploads
labels:
- "traefik.enable=true"
# Production environment (merchbay.com) - Uses DigiCert SSL
- "traefik.http.routers.merchbay-prod.rule=Host(`merchbay.com`) || Host(`www.merchbay.com`)"
- "traefik.http.routers.merchbay-prod.entrypoints=websecure"
- "traefik.http.routers.merchbay-prod.tls=true"
- "traefik.http.services.merchbay-prod.loadbalancer.server.port=80"
# HTTP to HTTPS redirect
- "traefik.http.routers.merchbay-prod-http.rule=Host(`merchbay.com`) || Host(`www.merchbay.com`)"
- "traefik.http.routers.merchbay-prod-http.entrypoints=web"
- "traefik.http.routers.merchbay-prod-http.middlewares=https-redirect"
- "traefik.http.middlewares.https-redirect.redirectscheme.scheme=https"
networks:
- traefik-public
- crew-app-net
- default
networks:
traefik-public:
external: true
crew-app-net:
external: true
default:
driver: bridge

View File

@@ -1,74 +1,74 @@
# ⚠️ DEPRECATED: Use docker-compose.dev.yml or docker-compose.prod.yml instead
# This file is kept for reference only
#
# For development: docker-compose.dev.yml (dev.merchbay.app)
# For production: docker-compose.prod.yml (merchbay.com)
# For local dev: docker-compose.local.yml (localhost:8080)
services: services:
#PHP Service
app: app:
image: merchbay:latest build:
container_name: merchbay_app context: .
dockerfile: Dockerfile
args:
- APP_ENV=production
image: digitalocean.com/php
container_name: app
restart: unless-stopped restart: unless-stopped
deploy:
resources:
limits:
cpus: '0.50'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
environment: environment:
- APP_ENV=${APP_ENV:-production} APP_ENV: production
- APP_DEBUG=${APP_DEBUG:-false} APP_DEBUG: 'false'
- APP_URL=${APP_URL:-http://localhost} PHP_OPCACHE_VALIDATE_TIMESTAMPS: 0
- DB_CONNECTION=mysql working_dir: /var/www
- DB_HOST=${DB_HOST}
- DB_PORT=${DB_PORT:-3306}
- DB_DATABASE=${DB_DATABASE}
- DB_USERNAME=${DB_USERNAME}
- DB_PASSWORD=${DB_PASSWORD}
- PROD_PRIVATE=${PROD_PRIVATE}
- IMAGES_URL=${IMAGES_URL}
- UPLOAD_URL=${UPLOAD_URL}
- FORCE_HTTPS=true
- MAIL_DRIVER=${MAIL_DRIVER}
- MAIL_HOST=${MAIL_HOST}
- MAIL_PORT=${MAIL_PORT}
- MAIL_USERNAME=${MAIL_USERNAME}
- MAIL_PASSWORD=${MAIL_PASSWORD}
- MAIL_ENCRYPTION=${MAIL_ENCRYPTION}
- CAPTCHA_SITE_KEY=${CAPTCHA_SITE_KEY}
- CAPTCHA_SECRET_KEY=${CAPTCHA_SECRET_KEY}
- ANALYTICS_SITE_ID=${ANALYTICS_SITE_ID}
- ANALYTICS_CLIENT_ID=${ANALYTICS_CLIENT_ID}
- ANALYTICS_SERVICE_EMAIL=${ANALYTICS_SERVICE_EMAIL}
volumes: volumes:
- ./storage:/var/www/html/storage - ./storage:/var/www/storage
- ./public/uploads:/var/www/html/public/uploads - ./php/local.ini:/usr/local/etc/php/conf.d/local.ini
labels:
- "traefik.enable=true"
# Development environment (dev.merchbay.app)
- "traefik.http.routers.merchbay-dev.rule=Host(`dev.merchbay.app`)"
- "traefik.http.routers.merchbay-dev.entrypoints=websecure"
- "traefik.http.routers.merchbay-dev.tls=true"
- "traefik.http.routers.merchbay-dev.tls.certresolver=letsencrypt"
- "traefik.http.services.merchbay-dev.loadbalancer.server.port=80"
# Production environment (merchbay.com) - Uses DigiCert SSL
- "traefik.http.routers.merchbay-prod.rule=Host(`merchbay.com`) || Host(`www.merchbay.com`)"
- "traefik.http.routers.merchbay-prod.entrypoints=websecure"
- "traefik.http.routers.merchbay-prod.tls=true"
- "traefik.http.services.merchbay-prod.loadbalancer.server.port=80"
# HTTP to HTTPS redirect - Development
- "traefik.http.routers.merchbay-dev-http.rule=Host(`dev.merchbay.app`)"
- "traefik.http.routers.merchbay-dev-http.entrypoints=web"
- "traefik.http.routers.merchbay-dev-http.middlewares=https-redirect"
# HTTP to HTTPS redirect - Production
- "traefik.http.routers.merchbay-prod-http.rule=Host(`merchbay.com`) || Host(`www.merchbay.com`)"
- "traefik.http.routers.merchbay-prod-http.entrypoints=web"
- "traefik.http.routers.merchbay-prod-http.middlewares=https-redirect"
- "traefik.http.middlewares.https-redirect.redirectscheme.scheme=https"
networks: networks:
- traefik-public - app-network
- crew-app-net healthcheck:
- default test: ["CMD", "curl", "-f", "http://localhost:9000/"]
interval: 30s
timeout: 3s
retries: 3
#Nginx Service
webserver:
image: nginx:alpine
container_name: webserver
restart: unless-stopped
deploy:
resources:
limits:
cpus: '0.30'
memory: 256M
reservations:
cpus: '0.10'
memory: 128M
ports:
- "10003:80"
- "10443:443"
volumes:
- ./public:/var/www/public
- ./nginx/conf.d/:/etc/nginx/conf.d/
networks:
- app-network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80/"]
interval: 30s
timeout: 3s
retries: 3
depends_on:
app:
condition: service_healthy
#Docker Networks
networks: networks:
traefik-public: app-network:
external: true driver: bridge
crew-app-net: #Volumes
external: true volumes:
default: dbdata:
driver: bridge driver: local

View File

@@ -1,17 +0,0 @@
#!/bin/bash
set -e
# Create storage directory structure if it doesn't exist
mkdir -p storage/framework/views
mkdir -p storage/framework/cache
mkdir -p storage/framework/sessions
mkdir -p storage/logs
mkdir -p storage/app/public
mkdir -p bootstrap/cache
# Set proper permissions
chown -R www-data:www-data storage bootstrap/cache
chmod -R 775 storage bootstrap/cache
# Execute the main command
exec "$@"

View File

@@ -1,9 +1,30 @@
server { server {
listen 80; listen 80;
index index.php index.html; server_tokens off;
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log; # Compression
root /var/www; gzip on;
gzip_comp_level 5;
gzip_min_length 256;
gzip_proxied any;
gzip_vary on;
gzip_types
application/javascript
application/json
application/x-javascript
application/xml
text/css
text/javascript
text/plain
text/xml;
client_max_body_size 100M;
fastcgi_read_timeout 1800;
error_log /var/log/nginx/error.log warn;
access_log /var/log/nginx/access.log combined buffer=512k flush=1m;
root /var/www/public;
index index.php index.html; index index.php index.html;
location / { location / {

176
readme.md
View File

@@ -1,171 +1,23 @@
# MerchBay ## Laravel PHP Framework
A custom merchandise and apparel design platform built with Laravel 5.0, enabling users to create, customize, and order personalized products. [![Build Status](https://travis-ci.org/laravel/framework.svg)](https://travis-ci.org/laravel/framework)
[![Total Downloads](https://poser.pugx.org/laravel/framework/downloads.svg)](https://packagist.org/packages/laravel/framework)
[![Latest Stable Version](https://poser.pugx.org/laravel/framework/v/stable.svg)](https://packagist.org/packages/laravel/framework)
[![Latest Unstable Version](https://poser.pugx.org/laravel/framework/v/unstable.svg)](https://packagist.org/packages/laravel/framework)
[![License](https://poser.pugx.org/laravel/framework/license.svg)](https://packagist.org/packages/laravel/framework)
## 🚀 Tech Stack Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable, creative experience to be truly fulfilling. Laravel attempts to take the pain out of development by easing common tasks used in the majority of web projects, such as authentication, routing, sessions, queueing, and caching.
- **Framework:** Laravel 5.0 Laravel is accessible, yet powerful, providing powerful tools needed for large, robust applications. A superb inversion of control container, expressive migration system, and tightly integrated unit testing support give you the tools you need to build any application with which you are tasked.
- **PHP:** 7.0 with native mcrypt support
- **Web Server:** Apache 2.4
- **Database:** MySQL
- **Container:** Docker with Apache
- **Reverse Proxy:** Traefik (for SSL/TLS and routing)
## 📋 Prerequisites ## Official Documentation
- Docker and Docker Compose Documentation for the framework can be found on the [Laravel website](http://laravel.com/docs).
- Git
- Access to deployment server (for production/dev deployments)
## 🛠️ Local Development ## Contributing
### Building the Docker Image Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](http://laravel.com/docs/contributions).
```bash ### License
docker build -t merchbay:dev .
```
### Running Locally The Laravel framework is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT)
```bash
# Create .env file from example
cp .env.example .env
# Update .env with your local configuration
# Set database credentials, mail settings, etc.
# Run with docker-compose (customize docker-compose.yml for local setup)
docker-compose up -d
```
### Environment Variables
The following environment variables are required:
#### Database Configuration
- `DB_HOST` - Database host
- `DB_PORT` - Database port (default: 3306)
- `DB_DATABASE` - Database name
- `DB_USERNAME` - Database username
- `DB_PASSWORD` - Database password
#### Application URLs
- `APP_URL` - Application base URL
- `PROD_PRIVATE` - Production private server URL
- `IMAGES_URL` - Images server URL
- `UPLOAD_URL` - Upload directory URL
#### Mail Configuration
- `MAIL_DRIVER` - Mail driver (smtp)
- `MAIL_HOST` - SMTP host
- `MAIL_PORT` - SMTP port
- `MAIL_USERNAME` - SMTP username
- `MAIL_PASSWORD` - SMTP password
- `MAIL_ENCRYPTION` - Encryption type (tls/ssl)
#### Third-Party Services
- `CAPTCHA_SITE_KEY` - reCAPTCHA site key
- `CAPTCHA_SECRET_KEY` - reCAPTCHA secret key
- `ANALYTICS_SITE_ID` - Google Analytics site ID
- `ANALYTICS_CLIENT_ID` - Google Analytics client ID
- `ANALYTICS_SERVICE_EMAIL` - Google Analytics service email
## 🚢 Deployment
### Automated CI/CD with Gitea Actions
This project includes automated deployment workflows using Gitea Actions:
#### Development Deployment
Push to the `dev` branch to automatically deploy to dev environment:
```bash
git push origin dev
```
- Deploys to: `https://dev.merchbay.app`
#### Production Deployment
Push to the `main` or `master` branch to automatically deploy to production:
```bash
git push origin main
```
- Deploys to: `https://merchbay.app`
#### Docker Registry
Create version tags to build and push to Docker registry:
```bash
git tag -a v1.0.0 -m "Release v1.0.0"
git push origin v1.0.0
```
### Workflow Files
- `.gitea/workflows/deploy-dev.yml` - Development deployment
- `.gitea/workflows/deploy.yml` - Production deployment
- `.gitea/workflows/build-push.yml` - Docker image build and push
## 📁 Project Structure
```
merchbay/
├── app/ # Application core
│ ├── Http/ # Controllers, middleware, routes
│ ├── Models/ # Database models
│ └── Services/ # Business logic services
├── config/ # Configuration files
├── database/ # Migrations and seeds
├── public/ # Public assets (images, CSS, JS)
├── resources/ # Views and frontend assets
├── storage/ # Application storage
├── docker-compose.yml # Docker compose configuration
├── Dockerfile # Docker image definition
└── docker-entrypoint.sh # Container startup script
```
## 🔧 Development Notes
### Storage Permissions
The Docker entrypoint automatically creates and sets proper permissions for:
- `storage/framework/views`
- `storage/framework/cache`
- `storage/framework/sessions`
- `storage/logs`
- `bootstrap/cache`
### PHP Extensions
The following PHP extensions are installed:
- pdo_mysql
- mbstring
- exif
- pcntl
- bcmath
- mcrypt (native in PHP 7.0)
- tokenizer
- zip
- gd (with freetype and jpeg support)
## 🐛 Troubleshooting
### 500 Internal Server Error
1. Check container logs: `docker logs merchbay_app`
2. Verify storage permissions are set correctly
3. Ensure all environment variables are configured in `.env`
4. Check database connectivity
### Storage Permission Issues
The entrypoint script automatically fixes permissions on container start. If issues persist:
```bash
docker exec merchbay_app chown -R www-data:www-data storage bootstrap/cache
docker exec merchbay_app chmod -R 775 storage bootstrap/cache
```
## 📄 License
Proprietary - All rights reserved
## 🤝 Support
For support and questions, contact the development team.

View File

@@ -450,7 +450,7 @@
@foreach ($pattern_arrays as $i => $val) @foreach ($pattern_arrays as $i => $val)
<div class="item @if ($i == 0) active @endif"> <div class="item @if ($i == 0) active @endif">
<div class="btn-group patternBox "> <div class="btn-group patternBox ">
<button type="button" data-pattern-path="{{ $val[0]->SVGPath }}" class="patternThumbs btn" data-id="{{ $val[0]->PatternId }}" style="background-image:url('{{ minio_url($val[0]->PatternThumbnail) }}'); background-size:cover; background-repeat: no-repeat;" @if ($i == 0) disabled @endif> @if ($i == 0) <i class="fa fa-2 fa-check" aria-hidden="true"></i> @endif</button> <button type="button" data-pattern-path="{{ $val[0]->SVGPath }}" class="patternThumbs btn" data-id="{{ $val[0]->PatternId }}" style="background-image:url('{{ config('site_config.uploads') }}{{ $val[0]->PatternThumbnail }}'); background-size:cover; background-repeat: no-repeat;" @if ($i == 0) disabled @endif> @if ($i == 0) <i class="fa fa-2 fa-check" aria-hidden="true"></i> @endif</button>
</div> </div>
</div> </div>
@endforeach @endforeach
@@ -550,7 +550,7 @@
@foreach ($pattern_arrays as $r => $val) @foreach ($pattern_arrays as $r => $val)
<div class="item @if ($r == 0) active @endif"> <div class="item @if ($r == 0) active @endif">
<div class="btn-group patternBox "> <div class="btn-group patternBox ">
<button type="button" data-pattern-path="{{ $val[0]->SVGPath }}" class="patternTrimThumbs patternTrim{{ $i }} btn" data-id="{{ $val[0]->PatternId }}" data-trim="{{ $i }}" style="background-image:url('{{ minio_url($val[0]->PatternThumbnail) }}'); background-size:cover; background-repeat: no-repeat;" @if ($r == 0) disabled @endif> @if ($r == 0) <i class="fa fa-2 fa-check" aria-hidden="true"></i> @endif</button> <button type="button" data-pattern-path="{{ $val[0]->SVGPath }}" class="patternTrimThumbs patternTrim{{ $i }} btn" data-id="{{ $val[0]->PatternId }}" data-trim="{{ $i }}" style="background-image:url('{{ config('site_config.uploads') }}{{ $val[0]->PatternThumbnail }}'); background-size:cover; background-repeat: no-repeat;" @if ($r == 0) disabled @endif> @if ($r == 0) <i class="fa fa-2 fa-check" aria-hidden="true"></i> @endif</button>
</div> </div>
</div> </div>
@endforeach @endforeach
@@ -1356,7 +1356,7 @@
var patternSVGPath = $(this).attr('data-pattern-path'); var patternSVGPath = $(this).attr('data-pattern-path');
var patternPath = "{{ minio_url('') }}" + patternSVGPath; var patternPath = "{{ config('site_config.uploads') }}" + patternSVGPath;
var SideAndPath = {!! json_encode($templatepaths_arrays) !!}; var SideAndPath = {!! json_encode($templatepaths_arrays) !!};
@@ -1433,7 +1433,7 @@
$(document).on('button click', '.patternTrimThumbs', function(){ $(document).on('button click', '.patternTrimThumbs', function(){
var patternSVGPath = $(this).attr('data-pattern-path'); var patternSVGPath = $(this).attr('data-pattern-path');
var patternPath = "{{ minio_url('') }}" + patternSVGPath; var patternPath = "{{ config('site_config.uploads') }}" + patternSVGPath;
var getTrimId = $(this).attr('data-trim'); var getTrimId = $(this).attr('data-trim');
var SideAndPath = {!! json_encode($templatepaths_arrays) !!}; var SideAndPath = {!! json_encode($templatepaths_arrays) !!};
@@ -1577,7 +1577,7 @@
var gradientIds = sideName+"_"+type+"_Gradients"; var gradientIds = sideName+"_"+type+"_Gradients";
var gradientPrefix = sideName+"_"+type+"_"; var gradientPrefix = sideName+"_"+type+"_";
var tempPath = "{{ minio_url('') }}" + pathLocation; var tempPath = "{{ config('site_config.uploads') }}" + pathLocation;
console.log(tempPath) console.log(tempPath)
if(!document.getElementById(objectId)) if(!document.getElementById(objectId))
return false; return false;
@@ -1761,7 +1761,7 @@
var type = SideAndPath[i]['Type']; var type = SideAndPath[i]['Type'];
var pathLocation = SideAndPath[i]['Path']; var pathLocation = SideAndPath[i]['Path'];
var canvasName = "canvas_" + type + "_" + sideName; var canvasName = "canvas_" + type + "_" + sideName;
var tempPath = "{{ minio_url('') }}" + pathLocation; var tempPath = "{{ config('site_config.uploads') }}" + pathLocation;
window['canvas_' + type + '_' + sideName] = new fabric.Canvas(canvasName); window['canvas_' + type + '_' + sideName] = new fabric.Canvas(canvasName);
var templateFormat = SideAndPath[i]['TemplateFormat']; var templateFormat = SideAndPath[i]['TemplateFormat'];
@@ -2093,9 +2093,9 @@
if(objType == "curvedText"){ if(objType == "curvedText"){
if(obj.effect == "curved"){ if(obj.effect == "curved"){
$('#teamname_text_shape').html('Text Shape: <br><img src="{{ minio_url('text-shapes-logo/curve-logo.png') }}" height="30px">'); $('#teamname_text_shape').html('Text Shape: <br><img src="{{ config('site_config.uploads') }}text-shapes-logo/curve-logo.png" height="30px">');
}else if(obj.effect == "arc"){ }else if(obj.effect == "arc"){
$('#teamname_text_shape').html('Text Shape: <br><img src="{{ minio_url('text-shapes-logo/arch-logo.png') }}" height="30px">'); $('#teamname_text_shape').html('Text Shape: <br><img src="{{ config('site_config.uploads') }}text-shapes-logo/arch-logo.png" height="30px">');
}else{ }else{
$('#teamname_text_shape').html('Add text Shape'); $('#teamname_text_shape').html('Add text Shape');
} }
@@ -3395,7 +3395,7 @@
function loadSVGClipart(dataUrl){ function loadSVGClipart(dataUrl){
var k = 0; var k = 0;
var arrayPathId = []; var arrayPathId = [];
var svgUrl = "{{ minio_url('cliparts/') }}" + dataUrl; var svgUrl = "{{ config('site_config.uploads') }}cliparts/" + dataUrl;
fabric.loadSVGFromURL(svgUrl, function(objects, options) { fabric.loadSVGFromURL(svgUrl, function(objects, options) {
var clipart = fabric.util.groupSVGElements(objects, options ); var clipart = fabric.util.groupSVGElements(objects, options );
clipart.set({ clipart.set({

View File

@@ -455,7 +455,7 @@
@foreach ($img_thumb as $img) @foreach ($img_thumb as $img)
@if ($img->ProductId == $item->ProductId) @if ($img->ProductId == $item->ProductId)
<img style="height: 200px; overflow: hidden; object-fit: contain;" <img style="height: 200px; overflow: hidden; object-fit: contain;"
src="{{ config('filesystems.disks.minio.url') }}/crewsportswear/images/{{ $img->Image }}"> src="{{ config('site_config.images_url') }}/{{ $img->Image }}">
@endif @endif
@endforeach @endforeach
</td> </td>

View File

@@ -63,7 +63,7 @@
<div class="col-md-8 col-md-pull-4"> <div class="col-md-8 col-md-pull-4">
<div style="border: 1px solid #e2e2e2; padding: 10px;"> <div style="border: 1px solid #e2e2e2; padding: 10px;">
<h6><img height="30px" class="cart-store-logo" <h6><img height="30px" class="cart-store-logo"
src="{{ minio_url('teamstore/' . $store_array[0]->ImageFolder . '/' . $store_array[0]->StoreLogo) }}"> src="{{ config('site_config.uploads') .'teamstore/' .$store_array[0]->ImageFolder .'/' .$store_array[0]->StoreLogo }}">
{{ $store_array[0]->StoreName }} {{ $store_array[0]->StoreName }}
</h6> </h6>
</div> </div>
@@ -76,7 +76,7 @@
@foreach ($img_thumb as $img) @foreach ($img_thumb as $img)
@if ($img->ProductId == $item->ProductId) @if ($img->ProductId == $item->ProductId)
<img class="previewImage" <img class="previewImage"
src="{{ config('filesystems.disks.minio.url') }}/crewsportswear/images/{{ $img->Image }}"> src="{{ config('site_config.images_url') }}/{{ $img->Image }}">
@endif @endif
@endforeach @endforeach
</div> </div>

View File

@@ -73,9 +73,9 @@
<a href="{{ url('store') }}/{{ $carousel->StoreUrl }}"> <a href="{{ url('store') }}/{{ $carousel->StoreUrl }}">
<div class="top-image"> <div class="top-image">
<img <img
src="{{ minio_url('teamstore/' . $carousel->ImageFolder . '/' . $carousel->StoreBanner) }}" /> src="{{ config('site_config.uploads') . 'teamstore/' . $carousel->ImageFolder . '/' . $carousel->StoreBanner }}" />
</div> </div>
<img src="{{ minio_url('teamstore/' . $carousel->ImageFolder . '/' . $carousel->StoreBanner) }}" <img src="{{ config('site_config.uploads') . 'teamstore/' . $carousel->ImageFolder . '/' . $carousel->StoreBanner }}"
class="blurred" /> class="blurred" />
</a> </a>
</div> </div>
@@ -84,9 +84,9 @@
<a href="{{ url('store') }}/{{ $carousel->StoreUrl }}"> <a href="{{ url('store') }}/{{ $carousel->StoreUrl }}">
<div class="top-image"> <div class="top-image">
<img <img
src="{{ minio_url('teamstore/' . $carousel->ImageFolder . '/' . $carousel->StoreBanner) }}" /> src="{{ config('site_config.uploads') . 'teamstore/' . $carousel->ImageFolder . '/' . $carousel->StoreBanner }}" />
</div> </div>
<img src="{{ minio_url('teamstore/' . $carousel->ImageFolder . '/' . $carousel->StoreBanner) }}" <img src="{{ config('site_config.uploads') . 'teamstore/' . $carousel->ImageFolder . '/' . $carousel->StoreBanner }}"
class="blurred" /> class="blurred" />
</a> </a>
</div> </div>
@@ -167,7 +167,7 @@
<div class="text-center p-3"> <div class="text-center p-3">
<a href="{{ url('store') . '/' . $product->StoreUrl . '/product/' . $product->ProductURL }}"> <a href="{{ url('store') . '/' . $product->StoreUrl . '/product/' . $product->ProductURL }}">
<div class="store-logo"> <div class="store-logo">
<img src="{{ config('filesystems.disks.minio.url') }}/crewsportswear/images/{{ $product->Image }}" <img src="{{ config('site_config.images_url') . '/' . $product->Image }}"
alt="{{ $product->ProductName }}" class="d-block border shadow-sm"> alt="{{ $product->ProductName }}" class="d-block border shadow-sm">
</div> </div>
<div class="store-name text-truncate">{{ $product->ProductName }}</div> <div class="store-name text-truncate">{{ $product->ProductName }}</div>

View File

@@ -10,13 +10,13 @@
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<title>Merchbay</title> <title>Merchbay</title>
<link rel="icon" href="{{ asset('/favicon.ico') }}"> <link rel="icon" href="{{ asset('public/favicon.ico') }}">
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"> <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@100;200;400;500;600;700;800;900&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@100;200;400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="{{ asset('/assets/css/merchbay/styles.css') }}" rel="stylesheet"> <link href="{{ asset('public/assets/css/merchbay/styles.css') }}" rel="stylesheet">
<!-- <link href="{{ asset('/assets/login/css/style.css') }}" rel="stylesheet"> <!-- <link href="{{ asset('public/assets/login/css/style.css') }}" rel="stylesheet">
<link href="{{ asset('/assets/login/css/form-elements.css') }}" rel="stylesheet"> --> <link href="{{ asset('public/assets/login/css/form-elements.css') }}" rel="stylesheet"> -->
<script src='https://www.google.com/recaptcha/api.js'></script> <script src='https://www.google.com/recaptcha/api.js'></script>
<!-- Global site tag (gtag.js) - Google Analytics --> <!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-SB3QK6BR1N"></script> <script async src="https://www.googletagmanager.com/gtag/js?id=G-SB3QK6BR1N"></script>
@@ -258,7 +258,7 @@
} }
function fetchCanada() { function fetchCanada() {
$.getJSON("{{ asset('//api/canada.json') }}", function(items) { $.getJSON("{{ asset('/public/api/canada.json') }}", function(items) {
var states = []; var states = [];
Object.keys(items).forEach(function(state) { Object.keys(items).forEach(function(state) {
@@ -307,7 +307,7 @@
} }
function fetchUSA() { function fetchUSA() {
$.getJSON("{{ asset('//api/usaCities.json') }}", function(data) { $.getJSON("{{ asset('/public/api/usaCities.json') }}", function(data) {
var states = []; var states = [];
for (i = 0; i < data.length; i++) { for (i = 0; i < data.length; i++) {

View File

@@ -64,7 +64,7 @@
<table class="table"> <table class="table">
<tr> <tr>
<td rowspan="2" class="text-center"><img class="previewImage" src="{{ config('filesystems.disks.minio.url') }}/crewsportswear/images/{{ $item->Image }}"></td> <td rowspan="2" class="text-center"><img class="previewImage" src="http://{{ config('site_config.images_url') }}/{{ $item->Image }} "></td>
<td colspan="5"> <td colspan="5">
<h4>{{ $item->ProductName }} {{ $itemOrder }} <br>Price: ${{ $item->Price }}</h4> <h4>{{ $item->ProductName }} {{ $itemOrder }} <br>Price: ${{ $item->Price }}</h4>
</td> </td>

View File

@@ -12,7 +12,7 @@
<div class="row"> <div class="row">
<div class="col-lg-12 text-center"> <div class="col-lg-12 text-center">
<div class="store-banner border-top"> <div class="store-banner border-top">
<img src="{{ minio_url('teamstore/' . $store_array[0]->ImageFolder . '/' . $store_array[0]->StoreBanner) }}" <img src="{{ config('site_config.uploads') .'teamstore/' .$store_array[0]->ImageFolder .'/' .$store_array[0]->StoreBanner }}"
id="storeBanner" class="shadow-sm img-fluid w-100" alt="{{ $store_array[0]->StoreName }}" /> id="storeBanner" class="shadow-sm img-fluid w-100" alt="{{ $store_array[0]->StoreName }}" />
</div> </div>
</div> </div>
@@ -130,7 +130,7 @@
<a <a
href="{{ url('store') }}/{{ $store_array[0]->StoreUrl }}/product/{{ $product->ProductURL }}"> href="{{ url('store') }}/{{ $store_array[0]->StoreUrl }}/product/{{ $product->ProductURL }}">
<div class="product-image"> <div class="product-image">
<img src="{{ config('filesystems.disks.minio.url') }}/crewsportswear/images/{{ $filename }}" <img src="{{ config('site_config.images_url') }}/{{ $filename }}"
class="d-block border shadow-sm" alt="{{ $product->ProductName }}" /> class="d-block border shadow-sm" alt="{{ $product->ProductName }}" />
</div> </div>
</a> </a>

View File

@@ -270,7 +270,7 @@
<div class="row"> <div class="row">
<div class="col-lg-12 text-center"> <div class="col-lg-12 text-center">
<div class="store-banner border-top"> <div class="store-banner border-top">
<img src="{{ minio_url('teamstore/' . $store_array[0]->ImageFolder . '/' . $store_array[0]->StoreBanner) }}" <img src="{{ config('site_config.uploads') .'teamstore/' .$store_array[0]->ImageFolder .'/' .$store_array[0]->StoreBanner }}"
id="storeBanner" class="shadow-sm img-fluid w-100" alt="..." /> id="storeBanner" class="shadow-sm img-fluid w-100" alt="..." />
</div> </div>
</div> </div>
@@ -308,13 +308,13 @@
<div data-bs-target="#demo" data-bs-slide-to="{{ $i }}" <div data-bs-target="#demo" data-bs-slide-to="{{ $i }}"
class="item active"> class="item active">
<img <img
src="{{ config('filesystems.disks.minio.url') }}/crewsportswear/images/{{ $thumbnail->Image }}" /> src="{{ config('site_config.images_url') }}/{{ $thumbnail->Image }}" />
</div> </div>
@else @else
<div data-bs-target="#demo" data-bs-slide-to="{{ $i }}" <div data-bs-target="#demo" data-bs-slide-to="{{ $i }}"
class="item"> class="item">
<img <img
src="{{ config('filesystems.disks.minio.url') }}/crewsportswear/images/{{ $thumbnail->Image }}" /> src="{{ config('site_config.images_url') }}/{{ $thumbnail->Image }}" />
</div> </div>
@endif @endif
@define $i++ @define $i++
@@ -330,12 +330,12 @@
@foreach ($thumbnails_array as $thumbnail) @foreach ($thumbnails_array as $thumbnail)
@if ($j == 0) @if ($j == 0)
<div class="carousel-item active"> <div class="carousel-item active">
<img src="{{ config('filesystems.disks.minio.url') }}/crewsportswear/images/{{ $thumbnail->Image }}" <img src="{{ config('site_config.images_url') }}/{{ $thumbnail->Image }}"
class="img-fluid" /> class="img-fluid" />
</div> </div>
@else @else
<div class="carousel-item"> <div class="carousel-item">
<img src="{{ config('filesystems.disks.minio.url') }}/crewsportswear/images/{{ $thumbnail->Image }}" <img src="{{ config('site_config.images_url') }}/{{ $thumbnail->Image }}"
class="img-fluid" /> class="img-fluid" />
</div> </div>
@endif @endif
@@ -394,7 +394,7 @@
<a <a
href="{{ url('store') . '/' . $product->StoreUrl . '/product/' . $product->ProductURL }}"> href="{{ url('store') . '/' . $product->StoreUrl . '/product/' . $product->ProductURL }}">
<div class="store-logo"> <div class="store-logo">
<img src="{{ config('filesystems.disks.minio.url') }}/crewsportswear/images/{{ $product->Image }}" <img src="{{ config('site_config.images_url') . '/' . $product->Image }}"
alt="{{ $product->ProductName }}" class="d-block border shadow-sm"> alt="{{ $product->ProductName }}" class="d-block border shadow-sm">
</div> </div>
<div class="store-name text-truncate">{{ $product->ProductName }}</div> <div class="store-name text-truncate">{{ $product->ProductName }}</div>

View File

@@ -17,7 +17,7 @@
<a href="#"> <a href="#">
<div class="store-logo password-protected" data-store-id="{{ $store->Id }}" <div class="store-logo password-protected" data-store-id="{{ $store->Id }}"
data-store-url="{{ $store->StoreUrl }}"> data-store-url="{{ $store->StoreUrl }}">
<img src="{{ minio_url('teamstore/' . $store->ImageFolder . '/' . $store->StoreLogo) }}" <img src="{{ config('site_config.uploads') . 'teamstore/' . $store->ImageFolder . '/' . $store->StoreLogo }}"
class="d-block" alt="{{ $store->StoreName }}" /> class="d-block" alt="{{ $store->StoreName }}" />
<div class="store-locked"> <div class="store-locked">
<i class="fa fa-lock"></i> <i class="fa fa-lock"></i>
@@ -29,7 +29,7 @@
@else @else
<a href="{{ url('store') . '/' . $store->StoreUrl }}"> <a href="{{ url('store') . '/' . $store->StoreUrl }}">
<div class="store-logo"> <div class="store-logo">
<img src="{{ minio_url('teamstore/' . $store->ImageFolder . '/' . $store->StoreLogo) }}" <img src="{{ config('site_config.uploads') . 'teamstore/' . $store->ImageFolder . '/' . $store->StoreLogo }}"
class="d-block" alt="{{ $store->StoreName }}" /> class="d-block" alt="{{ $store->StoreName }}" />
</div> </div>
<div class="store-name">{{ $store->StoreName }}</div> <div class="store-name">{{ $store->StoreName }}</div>

View File

@@ -42,7 +42,7 @@
@foreach($array_client_designs as $row) @foreach($array_client_designs as $row)
@foreach($array_template_paths as $key => $row1) @foreach($array_template_paths as $key => $row1)
@if($key == 0) @if($key == 0)
<img src="{{ config('filesystems.disks.minio.url') }}/crewsportswear/images/{{ $row->DesignCode }}-front-thumbnail.png" alt="{{ $row->DesignName }}" id="main-thumbnail" class="img img-responsive"> <img src="{{ config('site_config.images_url') }}/{{ $row->DesignCode }}-front-thumbnail.png" alt="{{ $row->DesignName }}" id="main-thumbnail" class="img img-responsive">
@endif @endif
@endforeach @endforeach
@endforeach @endforeach
@@ -56,7 +56,7 @@
@if($key == 0) @if($key == 0)
<li class="col-sm-3 col-xs-3"> <li class="col-sm-3 col-xs-3">
<a class="thumbnail a_thumbnail active"> <a class="thumbnail a_thumbnail active">
<img class="img img-responsive product-center image-thumbnails" style="height: 59.45px;" src="{{ config('filesystems.disks.minio.url') }}/crewsportswear/images/{{ $row->DesignCode }}-front-thumbnail.png"/> <img class="img img-responsive product-center image-thumbnails" style="height: 59.45px;" src="{{ config('site_config.images_url') }}/{{ $row->DesignCode }}-front-thumbnail.png"/>
</a> </a>
<!-- <p>Select Default Thumbnail:</p> <!-- <p>Select Default Thumbnail:</p>
<div class="text-center"> <div class="text-center">
@@ -66,7 +66,7 @@
@else @else
<li class="col-sm-3 col-xs-3"> <li class="col-sm-3 col-xs-3">
<a class="thumbnail a_thumbnail"> <a class="thumbnail a_thumbnail">
<img class="img img-responsive product-center image-thumbnails" style="height: 59.45px;" src="{{ config('filesystems.disks.minio.url') }}/crewsportswear/images/{{ $row->DesignCode }}-{{ strtolower($row1->Side) }}-thumbnail.png"/> <img class="img img-responsive product-center image-thumbnails" style="height: 59.45px;" src="{{ config('site_config.images_url') }}/{{ $row->DesignCode }}-{{ strtolower($row1->Side) }}-thumbnail.png"/>
</a> </a>
<!-- <p>&nbsp;</p> <!-- <p>&nbsp;</p>
<div class="text-center"> <div class="text-center">

View File

@@ -18,7 +18,7 @@
<!-- Control Sidebar Toggle Button --> <!-- Control Sidebar Toggle Button -->
<li class="user user-menu"> <li class="user user-menu">
<a href="#"> <a href="#">
<img src="{{ minio_url('user/default-user.png') }}" class="user-image" alt="User Image"> <img src="{{ config('site_config.uploads') . 'user/default-user.png' }}" class="user-image" alt="User Image">
<span class="hidden-xs">{{ Auth::user()->username }}</span> <span class="hidden-xs">{{ Auth::user()->username }}</span>
</a> </a>
</li> </li>

View File

@@ -86,6 +86,54 @@
</div> </div>
<!-- ./col --> <!-- ./col -->
</div> </div>
{{-- <div class="row">
<div class="col-lg-12">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">Total Sales</h3>
<div class="box-tools">
<button type="button" class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i>
</button>
<button type="button" class="btn btn-box-tool" data-widget="remove"><i class="fa fa-times"></i></button>
</div>
</div>
<div class="box-body">
<div class="well">
<div class="form-group col-md-3">
<label class="control-label">Select Year: <span class="required">*</span></label>
<select type="text" class="form-control" placeholder="Select Select Year">
<option value="2020">2020</option>
<option value="2019">2019</option>
</select>
</div>
<div class="form-group col-md-3">
<label class="control-label">Select Month <span class="required">*</span></label>
<select class="form-control" placeholder="Select Date">
<option value="">January</option>
<option value="">February</option>
<option value="">March</option>
<option value="">April</option>
<option value="">May</option>
<option value="">June</option>
<option value="">July</option>
<option value="">August</option>
<option value="">September</option>
<option value="">October</option>
<option value="">November</option>
<option value="">December</option>
</select>
</div>
<div class="clearfix"></div>
</div>
<div class="chart">
<canvas id="myChart" style="height:350px"></canvas>
</div>
</div>
<!-- /.box-body -->
</div>
</div>
</div> --}}
@else @else
<div class="text-center" id="homepage-logo"> <div class="text-center" id="homepage-logo">

View File

@@ -32,7 +32,7 @@
@foreach($array_client_designs as $row) @foreach($array_client_designs as $row)
<div class="col-md-3 col-sm-6"> <div class="col-md-3 col-sm-6">
<span class="thumbnail"> <span class="thumbnail">
<img src="{{ config('filesystems.disks.minio.url') }}/crewsportswear/images/{{ $row->DesignCode }}-front-thumbnail.png" alt="{{ $row->DesignName }}"> <img src="{{ config('site_config.images_url') }}/{{ $row->DesignCode }}-front-thumbnail.png" alt="{{ $row->DesignName }}">
<h4 class="design-name-holder">{{ $row->DesignName }}</h4> <h4 class="design-name-holder">{{ $row->DesignName }}</h4>
<small>{{ date('F j, Y g:i a', strtotime($row->DateCreated)) }}</small> <small>{{ date('F j, Y g:i a', strtotime($row->DateCreated)) }}</small>
<hr class="line"> <hr class="line">

View File

@@ -33,7 +33,7 @@
<div class="row"> <div class="row">
<div class="col-md-2"> <div class="col-md-2">
<div class="text-center"> <div class="text-center">
<img class="previewImage" id="active_thumbnail" src="{{ config('filesystems.disks.minio.url') }}/crewsportswear/images/{{ $array_thumbnail_display[0]->Image }}"> <img class="previewImage" id="active_thumbnail" src="{{ config('site_config.images_url') . '/' . $array_thumbnail_display[0]->Image }}">
</div> </div>
</div> </div>
<div class="col-md-10"> <div class="col-md-10">

View File

@@ -93,7 +93,7 @@
<div class="text-center"> <div class="text-center">
@foreach($img_thumb as $img) @foreach($img_thumb as $img)
@if($img->ProductId == $item->ProductId) @if($img->ProductId == $item->ProductId)
<img class="previewImage" src="{{ config('filesystems.disks.minio.url') }}/crewsportswear/images/{{ $img->Image }}"> <img class="previewImage" src="{{ config('site_config.images_url') }}/{{ $img->Image }}">
@endif @endif
@endforeach @endforeach
</div> </div>

View File

@@ -42,7 +42,7 @@
@foreach($array_client_designs as $row) @foreach($array_client_designs as $row)
@foreach($array_template_paths as $key => $row1) @foreach($array_template_paths as $key => $row1)
@if($key == 0) @if($key == 0)
<img src="{{ config('filesystems.disks.minio.url') }}/crewsportswear/images/{{ $row->DesignCode }}-front-thumbnail.png" alt="{{ $row->DesignName }}" id="main-thumbnail" class="img img-responsive"> <img src="{{ config('site_config.images_url') }}/{{ $row->DesignCode }}-front-thumbnail.png" alt="{{ $row->DesignName }}" id="main-thumbnail" class="img img-responsive">
@endif @endif
@endforeach @endforeach
@endforeach @endforeach
@@ -56,7 +56,7 @@
@if($key == 0) @if($key == 0)
<li class="col-sm-3 col-xs-3"> <li class="col-sm-3 col-xs-3">
<a class="thumbnail a_thumbnail active"> <a class="thumbnail a_thumbnail active">
<img class="img img-responsive product-center image-thumbnails" style="height: 59.45px;" src="{{ config('filesystems.disks.minio.url') }}/crewsportswear/images/{{ $row->DesignCode }}-front-thumbnail.png"/> <img class="img img-responsive product-center image-thumbnails" style="height: 59.45px;" src="{{ config('site_config.images_url') }}/{{ $row->DesignCode }}-front-thumbnail.png"/>
</a> </a>
<!-- <p>Select Default Thumbnail:</p> <!-- <p>Select Default Thumbnail:</p>
<div class="text-center"> <div class="text-center">
@@ -66,7 +66,7 @@
@else @else
<li class="col-sm-3 col-xs-3"> <li class="col-sm-3 col-xs-3">
<a class="thumbnail a_thumbnail"> <a class="thumbnail a_thumbnail">
<img class="img img-responsive product-center image-thumbnails" style="height: 59.45px;" src="{{ config('filesystems.disks.minio.url') }}/crewsportswear/images/{{ $row->DesignCode }}-{{ strtolower($row1->Side) }}-thumbnail.png"/> <img class="img img-responsive product-center image-thumbnails" style="height: 59.45px;" src="{{ config('site_config.images_url') }}/{{ $row->DesignCode }}-{{ strtolower($row1->Side) }}-thumbnail.png"/>
</a> </a>
<!-- <p>&nbsp;</p> <!-- <p>&nbsp;</p>
<div class="text-center"> <div class="text-center">

View File

@@ -4,7 +4,7 @@
<!-- Sidebar user panel --> <!-- Sidebar user panel -->
<div class="user-panel"> <div class="user-panel">
<div class="pull-left image"> <div class="pull-left image">
<img src="{{ minio_url('user/default-user.png') }}" class="img-circle" alt="User Image"> <img src="{{ config('site_config.uploads') . 'user/default-user.png' }}" class="img-circle" alt="User Image">
</div> </div>
<div class="pull-left info"> <div class="pull-left info">
<p>{{ Auth::user()->name }}</p> <p>{{ Auth::user()->name }}</p>

View File

@@ -59,7 +59,7 @@
<div class="col-md-3 col-sm-6"> <div class="col-md-3 col-sm-6">
<div class="thumbnail" > <div class="thumbnail" >
<a href="{{ url('user/store-items/item') }}/{{ $product->ProductURL }}"> <a href="{{ url('user/store-items/item') }}/{{ $product->ProductURL }}">
<img style="height:200px" src="{{ config('filesystems.disks.minio.url') }}/crewsportswear/images/{{ $filename . '?t=' . time() }}" alt="{{ $product->ProductName }}" > <img style="height:200px" src="{{ config('site_config.images_url') }}/{{ $filename . '?t=' . time() }}" alt="{{ $product->ProductName }}" >
</a> </a>
<hr class="line"> <hr class="line">
<div class="pull-right"> <div class="pull-right">

View File

@@ -63,7 +63,7 @@
<div id="{{ 'order_number_' . $product->Id }}"> <div id="{{ 'order_number_' . $product->Id }}">
<div class="thumbnail" > <div class="thumbnail" >
<a href="#"> <a href="#">
<img style="height:200px" src="{{ config('filesystems.disks.minio.url') }}/crewsportswear/images/{{ $filename . '?t=' . time() }}" alt="{{ $product->ProductName }}" > <img style="height:200px" src="{{ config('site_config.images_url') }}/{{ $filename . '?t=' . time() }}" alt="{{ $product->ProductName }}" >
</a> </a>
<hr class="line"> <hr class="line">
<div class="pull-right"> <div class="pull-right">

View File

@@ -63,8 +63,8 @@
<div class="col-sm-8"> <div class="col-sm-8">
<p>Store Logo Preview:</p> <p>Store Logo Preview:</p>
<div class="store-logo-holder"> <div class="store-logo-holder">
<a href="{{ minio_url('teamstore/' . $store_array[0]->ImageFolder . '/' . $store_array[0]->StoreLogo) }}?v={{ time() }}" class="img_store_logo_href" data-toggle="lightbox"> <a href="{{ config('site_config.uploads') . 'teamstore/' . $store_array[0]->ImageFolder . '/' . $store_array[0]->StoreLogo }}?v={{ time() }}" class="img_store_logo_href" data-toggle="lightbox">
<img class="img_store_logo_img" id="img_store_logo" src="{{ minio_url('teamstore/' . $store_array[0]->ImageFolder . '/' . $store_array[0]->StoreLogo) }}?v={{ time() }}" style="max-width: 100%; max-height: 100%; "> <img class="img_store_logo_img" id="img_store_logo" src="{{ config('site_config.uploads') . 'teamstore/' . $store_array[0]->ImageFolder . '/' . $store_array[0]->StoreLogo }}?v={{ time() }}" style="max-width: 100%; max-height: 100%; ">
</a> </a>
</div> </div>
</div> </div>
@@ -82,8 +82,8 @@
<div class="col-sm-8"> <div class="col-sm-8">
<p>Store Banner Preview:</p> <p>Store Banner Preview:</p>
<div class="store-banner-holder"> <div class="store-banner-holder">
<a href="{{ minio_url('teamstore/' . $store_array[0]->ImageFolder . '/' . $store_array[0]->StoreBanner) }}?v={{ time() }}" class="img_store_banner_href" data-toggle="lightbox"> <a href="{{ config('site_config.uploads') . 'teamstore/' . $store_array[0]->ImageFolder . '/' . $store_array[0]->StoreBanner }}?v={{ time() }}" class="img_store_banner_href" data-toggle="lightbox">
<img class="img_store_banner_img" id="img_store_banner" src="{{ minio_url('teamstore/' . $store_array[0]->ImageFolder . '/' . $store_array[0]->StoreBanner) }}?v={{ time() }}" style="max-width: 100%; max-height: 100%;"> <img class="img_store_banner_img" id="img_store_banner" src="{{ config('site_config.uploads') . 'teamstore/' . $store_array[0]->ImageFolder . '/' . $store_array[0]->StoreBanner }}?v={{ time() }}" style="max-width: 100%; max-height: 100%;">
</a> </a>
</div> </div>
</div> </div>

View File

@@ -13,37 +13,37 @@
<meta http-equiv="Pragma" content="no-cache" /> <meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" /> <meta http-equiv="Expires" content="0" />
<!-- Bootstrap 3.3.6 --> <!-- Bootstrap 3.3.6 -->
<link rel="stylesheet" href="{{asset('/bower_components/AdminLTE/bootstrap/css/bootstrap.min.css')}}"> <link rel="stylesheet" href="{{asset('/public/bower_components/AdminLTE/bootstrap/css/bootstrap.min.css')}}">
<!-- Font Awesome --> <!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css">
<!-- Ionicons --> <!-- Ionicons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/ionicons/2.0.1/css/ionicons.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/ionicons/2.0.1/css/ionicons.min.css">
<!-- Theme style --> <!-- Theme style -->
<link rel="stylesheet" href="{{asset('/bower_components/AdminLTE/dist/css/AdminLTE.min.css')}}"> <link rel="stylesheet" href="{{asset('/public/bower_components/AdminLTE/dist/css/AdminLTE.min.css')}}">
<!-- AdminLTE Skins. Choose a skin from the css/skins <!-- AdminLTE Skins. Choose a skin from the css/skins
folder instead of downloading all of them to reduce the load. --> folder instead of downloading all of them to reduce the load. -->
<link rel="stylesheet" href="{{asset('/bower_components/AdminLTE/dist/css/skins/_all-skins.min.css')}}"> <link rel="stylesheet" href="{{asset('/public/bower_components/AdminLTE/dist/css/skins/_all-skins.min.css')}}">
<!-- iCheck --> <!-- iCheck -->
<link rel="stylesheet" href="{{asset('/bower_components/AdminLTE/plugins/iCheck/flat/blue.css')}}"> <link rel="stylesheet" href="{{asset('/public/bower_components/AdminLTE/plugins/iCheck/flat/blue.css')}}">
<!-- Date Picker --> <!-- Date Picker -->
<link rel="stylesheet" href="{{asset('/bower_components/AdminLTE/plugins/datepicker/datepicker3.css')}}"> <link rel="stylesheet" href="{{asset('/public/bower_components/AdminLTE/plugins/datepicker/datepicker3.css')}}">
<!-- Daterange picker --> <!-- Daterange picker -->
<link rel="stylesheet" href="{{asset('/bower_components/AdminLTE/plugins/daterangepicker/daterangepicker.css')}}"> <link rel="stylesheet" href="{{asset('/public/bower_components/AdminLTE/plugins/daterangepicker/daterangepicker.css')}}">
<!-- bootstrap wysihtml5 - text editor --> <!-- bootstrap wysihtml5 - text editor -->
<link rel="stylesheet" href="{{asset('/bower_components/AdminLTE/plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.min.css')}}"> <link rel="stylesheet" href="{{asset('/public/bower_components/AdminLTE/plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.min.css')}}">
<!-- Select2 --> <!-- Select2 -->
<link href="{{asset('/bower_components/AdminLTE/plugins/select2/select2.min.css')}}" rel="stylesheet" /> <link href="{{asset('/public/bower_components/AdminLTE/plugins/select2/select2.min.css')}}" rel="stylesheet" />
<!-- ekko-lightbox --> <!-- ekko-lightbox -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/ekko-lightbox/5.3.0/ekko-lightbox.css" rel="stylesheet"> <link href="https://cdnjs.cloudflare.com/ajax/libs/ekko-lightbox/5.3.0/ekko-lightbox.css" rel="stylesheet">
<!-- bootstrap-toggle --> <!-- bootstrap-toggle -->
<link href="https://gitcdn.github.io/bootstrap-toggle/2.2.2/css/bootstrap-toggle.min.css" rel="stylesheet"> <link href="https://gitcdn.github.io/bootstrap-toggle/2.2.2/css/bootstrap-toggle.min.css" rel="stylesheet">
<!-- DataTables --> <!-- DataTables -->
<link rel="stylesheet" href="{{ asset('/bower_components/AdminLTE/plugins/datatables.net-bs/css/dataTables.bootstrap.min.css') }}"> <link rel="stylesheet" href="{{ asset('/public/bower_components/AdminLTE/plugins/datatables.net-bs/css/dataTables.bootstrap.min.css') }}">
<!-- datepicker --> <!-- datepicker -->
<link rel="stylesheet" href="{{ asset('/bower_components/AdminLTE/plugins/datepicker/datepicker3.css') }}"> <link rel="stylesheet" href="{{ asset('/public/bower_components/AdminLTE/plugins/datepicker/datepicker3.css') }}">
<!-- jquery-ui --> <!-- jquery-ui -->
<link href="{{ asset('/assets/css/jquery-ui.css') }}" rel="stylesheet"> <link href="{{ asset('/public/assets/css/jquery-ui.css') }}" rel="stylesheet">
<link href="{{asset('/designer/css/build.css')}}" rel="stylesheet"> <link href="{{asset('/public/designer/css/build.css')}}" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14"></script> <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14"></script>
@@ -628,41 +628,41 @@
<!-- ./wrapper --> <!-- ./wrapper -->
<!-- jQuery 2.2.3 --> <!-- jQuery 2.2.3 -->
<script src="{{asset('/bower_components/AdminLTE/plugins/jQuery/jquery-2.2.3.min.js')}}"></script> <script src="{{asset('/public/bower_components/AdminLTE/plugins/jQuery/jquery-2.2.3.min.js')}}"></script>
<!-- Bootstrap 3.3.6 --> <!-- Bootstrap 3.3.6 -->
<script src="{{asset('/bower_components/AdminLTE/bootstrap/js/bootstrap.min.js')}}"></script> <script src="{{asset('/public/bower_components/AdminLTE/bootstrap/js/bootstrap.min.js')}}"></script>
<!-- daterangepicker --> <!-- daterangepicker -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.11.2/moment.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.11.2/moment.min.js"></script>
<script src="{{asset('/bower_components/AdminLTE/plugins/daterangepicker/daterangepicker.js')}}"></script> <script src="{{asset('/public/bower_components/AdminLTE/plugins/daterangepicker/daterangepicker.js')}}"></script>
<!-- datepicker --> <!-- datepicker -->
<script src="{{asset('/bower_components/AdminLTE/plugins/datepicker/bootstrap-datepicker.js')}}"></script> <script src="{{asset('/public/bower_components/AdminLTE/plugins/datepicker/bootstrap-datepicker.js')}}"></script>
<!-- Bootstrap WYSIHTML5 --> <!-- Bootstrap WYSIHTML5 -->
<script src="{{asset('/bower_components/AdminLTE/plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.all.min.js')}}"></script> <script src="{{asset('/public/bower_components/AdminLTE/plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.all.min.js')}}"></script>
<!-- Slimscroll --> <!-- Slimscroll -->
<script src="{{asset('/bower_components/AdminLTE/plugins/slimScroll/jquery.slimscroll.min.js')}}"></script> <script src="{{asset('/public/bower_components/AdminLTE/plugins/slimScroll/jquery.slimscroll.min.js')}}"></script>
<!-- FastClick --> <!-- FastClick -->
<script src="{{asset('/bower_components/AdminLTE/plugins/fastclick/fastclick.js')}}"></script> <script src="{{asset('/public/bower_components/AdminLTE/plugins/fastclick/fastclick.js')}}"></script>
<!-- AdminLTE App --> <!-- AdminLTE App -->
<script src="{{asset('/bower_components/AdminLTE/dist/js/app.min.js')}}"></script> <script src="{{asset('/public/bower_components/AdminLTE/dist/js/app.min.js')}}"></script>
<!-- AdminLTE dashboard demo (This is only for demo purposes) <!-- AdminLTE dashboard demo (This is only for demo purposes)
<script src="{{asset('/bower_components/AdminLTE/dist/js/pages/dashboard.js')}}"></script>--> <script src="{{asset('/bower_components/AdminLTE/dist/js/pages/dashboard.js')}}"></script>-->
<!-- AdminLTE for demo purposes --> <!-- AdminLTE for demo purposes -->
<script src="{{asset('/bower_components/AdminLTE/dist/js/demo.js')}}"></script> <script src="{{asset('/public/bower_components/AdminLTE/dist/js/demo.js')}}"></script>
<!-- Select2 --> <!-- Select2 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.min.js"></script>
<!-- validate jquery --> <!-- validate jquery -->
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.11.1/jquery.validate.min.js"></script> <script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.11.1/jquery.validate.min.js"></script>
<!-- \priceformat --> <!-- \priceformat -->
<script src="{{asset('/designer/js/jquery.priceformat.min.js')}}"></script> <script src="{{asset('/public/designer/js/jquery.priceformat.min.js')}}"></script>
<!-- ekko-lightbox --> <!-- ekko-lightbox -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/ekko-lightbox/5.3.0/ekko-lightbox.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/ekko-lightbox/5.3.0/ekko-lightbox.js"></script>
<!-- bootstrap-toggle --> <!-- bootstrap-toggle -->
<script src="https://gitcdn.github.io/bootstrap-toggle/2.2.2/js/bootstrap-toggle.min.js"></script> <script src="https://gitcdn.github.io/bootstrap-toggle/2.2.2/js/bootstrap-toggle.min.js"></script>
<!-- DataTables --> <!-- DataTables -->
<script src="{{ asset('/bower_components/AdminLTE/plugins/datatables.net/js/jquery.dataTables.min.js') }}"></script> <script src="{{ asset('/public/bower_components/AdminLTE/plugins/datatables.net/js/jquery.dataTables.min.js') }}"></script>
<script src="{{ asset('/bower_components/AdminLTE/plugins/datatables.net-bs/js/dataTables.bootstrap.min.js') }}"></script> <script src="{{ asset('/public/bower_components/AdminLTE/plugins/datatables.net-bs/js/dataTables.bootstrap.min.js') }}"></script>
<script src="https://cdn.datatables.net/buttons/1.5.6/js/dataTables.buttons.min.js"></script> <script src="https://cdn.datatables.net/buttons/1.5.6/js/dataTables.buttons.min.js"></script>
<script src="https://cdn.datatables.net/buttons/1.5.6/js/buttons.flash.min.js"></script> <script src="https://cdn.datatables.net/buttons/1.5.6/js/buttons.flash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.3/jszip.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.3/jszip.min.js"></script>
@@ -671,9 +671,9 @@
<script src="https://cdn.datatables.net/buttons/1.5.6/js/buttons.html5.min.js"></script> <script src="https://cdn.datatables.net/buttons/1.5.6/js/buttons.html5.min.js"></script>
<script src="https://cdn.datatables.net/buttons/1.5.6/js/buttons.print.min.js"></script> <script src="https://cdn.datatables.net/buttons/1.5.6/js/buttons.print.min.js"></script>
<!-- datepicker --> <!-- datepicker -->
<script src="{{asset('/bower_components/AdminLTE/plugins/datepicker/bootstrap-datepicker.js')}}"></script> <script src="{{asset('/public/bower_components/AdminLTE/plugins/datepicker/bootstrap-datepicker.js')}}"></script>
<!-- jquery-ui --> <!-- jquery-ui -->
<script src="{{ asset('/assets/js/jquery-ui.js') }}"></script> <script src="{{ asset('/public/assets/js/jquery-ui.js') }}"></script>
{{-- Chartjs --}} {{-- Chartjs --}}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js"></script>
@@ -1791,7 +1791,7 @@
} }
function fetchCanada() { function fetchCanada() {
$.getJSON("{{ asset('/api/canada.json') }}", function(items) { $.getJSON("{{ asset('/public/api/canada.json') }}", function(items) {
var states = []; var states = [];
Object.keys(items).forEach(function(state) { Object.keys(items).forEach(function(state) {
@@ -1840,7 +1840,7 @@
} }
function fetchUSA() { function fetchUSA() {
$.getJSON("{{ asset('/api/usaCities.json') }}", function(data) { $.getJSON("{{ asset('/public/api/usaCities.json') }}", function(data) {
var states = []; var states = [];
for (i = 0; i < data.length; i++) { for (i = 0; i < data.length; i++) {

View File

@@ -39,7 +39,7 @@
@foreach($array_client_designs as $row) @foreach($array_client_designs as $row)
@foreach($array_template_paths as $key => $row1) @foreach($array_template_paths as $key => $row1)
@if($key == 0) @if($key == 0)
<img src="{{ config('filesystems.disks.minio.url') }}/crewsportswear/images/{{ $row->DesignCode }}-front-thumbnail.png" alt="{{ $row->DesignName }}" id="main-thumbnail" class="img img-responsive"> <img src="{{ config('site_config.images_url') }}/{{ $row->DesignCode }}-front-thumbnail.png" alt="{{ $row->DesignName }}" id="main-thumbnail" class="img img-responsive">
@endif @endif
@endforeach @endforeach
@endforeach @endforeach
@@ -53,13 +53,13 @@
@if($key == 0) @if($key == 0)
<li class="col-sm-3 col-xs-3"> <li class="col-sm-3 col-xs-3">
<a class="thumbnail a_thumbnail active"> <a class="thumbnail a_thumbnail active">
<img class="img img-responsive product-center image-thumbnails" style="height: 59.45px;" src="{{ config('filesystems.disks.minio.url') }}/crewsportswear/images/{{ $row->DesignCode }}-front-thumbnail.png"/> <img class="img img-responsive product-center image-thumbnails" style="height: 59.45px;" src="{{ config('site_config.images_url') }}/{{ $row->DesignCode }}-front-thumbnail.png"/>
</a> </a>
</li> </li>
@else @else
<li class="col-sm-3 col-xs-3"> <li class="col-sm-3 col-xs-3">
<a class="thumbnail a_thumbnail"> <a class="thumbnail a_thumbnail">
<img class="img img-responsive product-center image-thumbnails" style="height: 59.45px;" src="{{ config('filesystems.disks.minio.url') }}/crewsportswear/images/{{ $row->DesignCode }}-{{ strtolower($row1->Side) }}-thumbnail.png"/> <img class="img img-responsive product-center image-thumbnails" style="height: 59.45px;" src="{{ config('site_config.images_url') }}/{{ $row->DesignCode }}-{{ strtolower($row1->Side) }}-thumbnail.png"/>
</a> </a>
</li> </li>
@endif @endif

View File

@@ -42,7 +42,7 @@
@foreach ($thumbnails_array as $thumbnail) @foreach ($thumbnails_array as $thumbnail)
@if ($thumbnail->ImageClass == 'active') @if ($thumbnail->ImageClass == 'active')
<img style="height:400px" <img style="height:400px"
src="{{ config('filesystems.disks.minio.url') }}/crewsportswear/images/{{ $thumbnail->Image . '?t=' . time() }}" src="{{ config('site_config.images_url') }}/{{ $thumbnail->Image . '?t=' . time() }}"
id="main-thumbnail"> id="main-thumbnail">
@endif @endif
@endforeach @endforeach
@@ -71,7 +71,7 @@
<!-- <span class="close">&times;</span> --> <!-- <span class="close">&times;</span> -->
<img class="img img-responsive product-center image-thumbnails" <img class="img img-responsive product-center image-thumbnails"
style="height: 59.45px;" style="height: 59.45px;"
src="{{ config('filesystems.disks.minio.url') }}/crewsportswear/images/{{ $thumbnail->Image . '?t=' . time() }}" /> src="{{ config('site_config.images_url') }}/{{ $thumbnail->Image . '?t=' . time() }}" />
</a> </a>
<div class="funkyradio"> <div class="funkyradio">
<div class="funkyradio-primary"> <div class="funkyradio-primary">
@@ -284,7 +284,7 @@
<tr id="{{ 'item-' . $thumbnail->Id }}"> <tr id="{{ 'item-' . $thumbnail->Id }}">
<td class="text-center" style="width: 50px"><i class="fa fa-bars"></i></td> <td class="text-center" style="width: 50px"><i class="fa fa-bars"></i></td>
<td><img class="img img-responsive product-center" style="height: 59.45px;" <td><img class="img img-responsive product-center" style="height: 59.45px;"
src="{{ config('filesystems.disks.minio.url') }}/crewsportswear/images/{{ $thumbnail->Image . '?t=' . time() }}" /> src="{{ config('site_config.images_url') }}/{{ $thumbnail->Image . '?t=' . time() }}" />
</td> </td>
<td class="text-center"> <td class="text-center">
<!-- <button class="btn btn-default btn-xs btn-edit-clipart" data-id="#"><i class="fa fa-edit"></i></button> --> <!-- <button class="btn btn-default btn-xs btn-edit-clipart" data-id="#"><i class="fa fa-edit"></i></button> -->

68
run.sh Executable file
View File

@@ -0,0 +1,68 @@
#!/usr/bin/env bash
set -euo pipefail
# Helper script for docker compose workflow
# Usage examples:
# ./run.sh up # build (if needed) and start in background
# ./run.sh rebuild # force full rebuild (no cache) and start
# ./run.sh down # stop and remove containers
# ./run.sh logs # follow logs
# ./run.sh artisan migrate
# ./run.sh sh # shell into PHP app container
# ./run.sh restart # restart containers
# ./run.sh build # build images (uses cache)
CMD=${1:-up}
shift || true
case "$CMD" in
build)
echo "[build] Building images (with cache)..."
docker compose build
;;
up)
echo "[up] Starting services (build if needed)..."
docker compose up -d --build
;;
rebuild)
echo "[rebuild] Full rebuild without cache..."
docker compose down || true
docker compose build --no-cache
docker compose up -d
;;
down)
echo "[down] Stopping and removing services..."
docker compose down
;;
restart)
echo "[restart] Restarting services..."
docker compose restart
;;
logs)
echo "[logs] Following logs (Ctrl+C to exit)..."
docker compose logs -f --tail=200 "$@"
;;
artisan)
echo "[artisan] php artisan $*"
docker compose exec app php artisan "$@"
;;
sh)
echo "[sh] Opening shell in app container..."
docker compose exec app sh
;;
*)
cat <<EOF
Usage: ./run.sh <command> [args]
Commands:
up Build (if needed) and start containers
build Build images using cache
rebuild Rebuild images without cache then start
down Stop and remove containers
restart Restart running containers
logs Follow logs (pass service names optionally)
artisan Run php artisan <args>
sh Shell into app container
EOF
exit 1
;;
esac

View File

@@ -1,17 +0,0 @@
# Traefik Dynamic Configuration for Custom SSL Certificates
# This file should be uploaded to /srv/traefik/dynamic/traefik-certs.yml on production server
tls:
certificates:
# Production SSL Certificate (DigiCert)
# Note: merchbay.com.crt should be a bundle containing www_merchbay_com.crt + DigiCertCA.crt
- certFile: /srv/certs/merchbay.com.crt
keyFile: /srv/certs/merchbay.com.key
stores:
- default
stores:
default:
defaultCertificate:
certFile: /srv/certs/merchbay.com.crt
keyFile: /srv/certs/merchbay.com.key

View File

@@ -1,54 +0,0 @@
<?php
// yakpro-po configuration for Laravel 5.0
$conf = new StdClass;
// Directories to obfuscate (relative to project root)
$conf->t_directories = [
'app'
];
// Directories/files to skip
$conf->t_skip = [
'vendor',
'storage',
'bootstrap',
'config',
'database',
'public',
'resources',
'tests',
'.env',
'.env.example',
'artisan',
'server.php'
];
// Obfuscation options
$conf->obfuscate_string_literal = false; // Don't obfuscate strings (can break Laravel)
$conf->obfuscate_function_name = true; // Obfuscate function names
$conf->obfuscate_class_name = true; // Obfuscate class names (except Laravel core)
$conf->obfuscate_variable_name = true; // Obfuscate variable names
$conf->obfuscate_property_name = true; // Obfuscate property names
$conf->obfuscate_class_constant_name = true;
$conf->obfuscate_constant_name = true;
$conf->obfuscate_namespace_name = false; // Keep namespaces readable
$conf->obfuscate_label_name = true;
// Keep Laravel framework methods/classes readable
$conf->t_ignore_constants = ['APP_ENV', 'APP_DEBUG', 'APP_URL', 'APP_KEY'];
$conf->t_ignore_methods = [
'__construct', '__destruct', '__call', '__get', '__set',
'boot', 'register', 'handle', 'middleware', 'authorize'
];
// Scrambler mode
$conf->scrambler = true;
// Allow multiple PHP versions
$conf->allow_and_operator = true;
// Output directory (will be overridden in command)
$conf->t_dir = null;
return $conf;