diff --git a/CRON_SETUP.md b/CRON_SETUP.md new file mode 100644 index 0000000..3054522 --- /dev/null +++ b/CRON_SETUP.md @@ -0,0 +1,260 @@ +# Cron Job Setup for JobifyGarden + +Since your hosting provider doesn't have Supervisor, you'll use cron jobs to handle scheduled tasks and queue processing. + +## Prerequisites + +1. SSH access to your server +2. Access to crontab configuration +3. Verify your application path (likely `/home/jobifyg/public_html` or similar) + +## Setup Instructions + +### Step 1: Make Queue Worker Script Executable + +```bash +cd /home/jobifyg/public_html +chmod +x queue-worker.sh +``` + +### Step 2: Configure Crontab + +Edit your crontab: +```bash +crontab -e +``` + +Add these lines (replace `/home/jobifyg/public_html` with your actual path): + +```bash +# ============================================================================= +# JobifyGarden Cron Jobs +# ============================================================================= + +# Laravel Scheduler - REQUIRED +# Runs every minute to process scheduled commands +* * * * * cd /home/jobifyg/public_html && php artisan schedule:run >> /dev/null 2>&1 + +# Queue Worker - REQUIRED +# Option 1: Using the helper script (recommended) +* * * * * /home/jobifyg/public_html/queue-worker.sh + +# Queue Worker - Option 2: Direct command (if script doesn't work) +# * * * * * cd /home/jobifyg/public_html && php artisan queue:work --max-time=50 --stop-when-empty --sleep=3 --tries=3 >> storage/logs/queue.log 2>&1 + +# Queue Restart - OPTIONAL but RECOMMENDED +# Restarts queue workers every hour to prevent memory leaks +0 * * * * cd /home/jobifyg/public_html && php artisan queue:restart >> /dev/null 2>&1 + +# Clear old failed jobs - OPTIONAL +# Clears failed jobs older than 7 days, runs daily at 4 AM +0 4 * * * cd /home/jobifyg/public_html && php artisan queue:flush >> /dev/null 2>&1 +``` + +### Step 3: Verify Cron Jobs Are Running + +After saving your crontab, verify it's active: + +```bash +# List your cron jobs +crontab -l + +# Check if cron jobs are running (wait a few minutes) +tail -f /home/jobifyg/public_html/storage/logs/laravel.log + +# Check queue worker logs +tail -f /home/jobifyg/public_html/storage/logs/queue-worker.log +``` + +## What Each Cron Job Does + +### 1. Laravel Scheduler (`* * * * * ... schedule:run`) +**Frequency:** Every minute +**Purpose:** Checks and runs scheduled tasks defined in `bootstrap/app.php` + +**Scheduled Tasks:** +- `jobs:fetch` - Fetches new jobs from APIs every 2 hours +- `jobs:enrich` - Auto-categorizes and enhances jobs every 6 hours +- `jobs:clean` - Removes old jobs (45+ days) daily at 2 AM +- `jobs:deduplicate` - Removes duplicate jobs daily at 3 AM +- `cache:prune-stale-tags` - Cleans cache hourly + +For detailed explanations and customization options, see [SCHEDULED_TASKS.md](SCHEDULED_TASKS.md) + +### 2. Queue Worker (`* * * * * ... queue:work`) +**Frequency:** Every minute +**Purpose:** Processes background jobs from the queue + +**Jobs Processed:** +- Fetching jobs from external APIs +- Processing individual job listings +- Email notifications (if configured) +- Cache warming +- Other background tasks + +**Options Explained:** +- `--max-time=50` - Worker runs for max 50 seconds then exits +- `--stop-when-empty` - Exits if no jobs are in queue +- `--sleep=3` - Waits 3 seconds between jobs +- `--tries=3` - Retries failed jobs up to 3 times + +### 3. Queue Restart (`0 * * * * ... queue:restart`) +**Frequency:** Every hour +**Purpose:** Gracefully restarts queue workers to prevent memory leaks + +### 4. Queue Flush (`0 4 * * * ... queue:flush`) +**Frequency:** Daily at 4 AM +**Purpose:** Clears old failed jobs from the database + +## Troubleshooting + +### Cron jobs not running? + +1. **Check crontab is saved:** + ```bash + crontab -l + ``` + +2. **Check PHP path:** + ```bash + which php + # Use the full path in crontab if needed: /usr/bin/php artisan ... + ``` + +3. **Check application path:** + ```bash + pwd + # Make sure you're using the correct path in crontab + ``` + +4. **Check file permissions:** + ```bash + ls -la /home/jobifyg/public_html/artisan + chmod +x /home/jobifyg/public_html/artisan + ``` + +### Jobs not being processed? + +1. **Check queue worker logs:** + ```bash + tail -f storage/logs/queue-worker.log + ``` + +2. **Check Laravel logs:** + ```bash + tail -f storage/logs/laravel.log + ``` + +3. **Manually run queue worker to see errors:** + ```bash + php artisan queue:work --verbose + ``` + +4. **Check failed jobs:** + ```bash + php artisan queue:failed + ``` + +5. **Retry failed jobs:** + ```bash + php artisan queue:retry all + ``` + +### Jobs are fetched but not appearing? + +1. **Check if job fetch is scheduled:** + ```bash + php artisan schedule:list + ``` + +2. **Manually test job fetching:** + ```bash + php artisan jobs:fetch --limit=5 + ``` + +3. **Check queue:** + ```bash + php artisan queue:work --once --verbose + ``` + +## Performance Tuning + +### Low Traffic Site +If your site has low traffic, you can reduce frequency: + +```bash +# Run queue worker every 5 minutes instead +*/5 * * * * cd /home/jobifyg/public_html && php artisan queue:work --max-time=240 --stop-when-empty --sleep=3 --tries=3 >> storage/logs/queue.log 2>&1 +``` + +### High Traffic Site +If you need faster job processing: + +```bash +# Run multiple queue workers +* * * * * cd /home/jobifyg/public_html && php artisan queue:work --max-time=50 --stop-when-empty --queue=high,default --sleep=1 --tries=3 >> storage/logs/queue-worker-1.log 2>&1 +* * * * * cd /home/jobifyg/public_html && php artisan queue:work --max-time=50 --stop-when-empty --queue=default --sleep=1 --tries=3 >> storage/logs/queue-worker-2.log 2>&1 +``` + +## Monitoring + +### Check Cron Execution +Your hosting control panel may have a cron job log. Check there for execution history. + +### Check Application Health +```bash +# Visit this URL in your browser (Laravel health check) +https://jobifygarden.com/up + +# Check recent jobs +php artisan tinker +>>> \App\Models\Job::latest()->take(5)->get(['id', 'title', 'created_at']); +``` + +### Monitor Logs +```bash +# Watch all logs in real-time +tail -f storage/logs/*.log + +# Check for errors +grep -i error storage/logs/laravel.log | tail -20 +``` + +## Important Notes + +1. **Queue Worker Must Keep Running**: The queue worker cron ensures jobs are processed continuously. Without it, jobs will queue but never execute. + +2. **Schedule Must Run Every Minute**: The scheduler cron must run every minute. Laravel internally determines which scheduled tasks should run. + +3. **Time Zone**: Make sure your server's timezone matches your Laravel app timezone (check `config/app.php`). + +4. **Testing**: After setup, wait a few minutes and check logs to verify everything is working. + +5. **Production**: Make sure `APP_ENV=production` and `APP_DEBUG=false` in your `.env` file. + +## Quick Test + +After setting up cron, test immediately: + +```bash +# 1. Manually run scheduler +php artisan schedule:run + +# 2. Queue a test job +php artisan jobs:fetch Remotive --limit=2 + +# 3. Process the queue +php artisan queue:work --once --verbose + +# 4. Check if jobs were created +php artisan tinker +>>> \App\Models\Job::count(); +``` + +## Support + +If you encounter issues: +1. Check the troubleshooting section above +2. Review logs in `storage/logs/` +3. Ensure Redis is working: `php artisan tinker` then `Cache::get('test')` +4. Verify database connection works diff --git a/README.md b/README.md index a8ad4dd..e0b875e 100644 --- a/README.md +++ b/README.md @@ -220,7 +220,7 @@ The application uses queues for background job processing. You must run a queue ./vendor/bin/sail artisan queue:work --sleep=3 --tries=3 --timeout=90 ``` -**Using Supervisor (Production):** +**Using Supervisor (Production with Supervisor):** Create `/etc/supervisor/conf.d/jobifygarden-worker.conf`: ```ini [program:jobifygarden-worker] @@ -244,6 +244,9 @@ sudo supervisorctl update sudo supervisorctl start jobifygarden-worker:* ``` +**Using Cron (Production without Supervisor):** +If your hosting provider doesn't have Supervisor, see [CRON_SETUP.md](CRON_SETUP.md) for detailed cron-based setup instructions. + ### Cron Jobs (Required) Laravel's task scheduler must run every minute. Add this to your crontab: diff --git a/SCHEDULED_TASKS.md b/SCHEDULED_TASKS.md new file mode 100644 index 0000000..8d47936 --- /dev/null +++ b/SCHEDULED_TASKS.md @@ -0,0 +1,367 @@ +# Scheduled Tasks Guide for JobifyGarden + +This document explains the optimal scheduling intervals for all automated tasks in JobifyGarden and the reasoning behind them. + +## Overview + +All scheduled tasks are configured in `bootstrap/app.php` and run automatically via the Laravel scheduler (cron job running every minute). + +## Scheduled Tasks + +### 1. Job Fetching (`jobs:fetch`) + +**Interval:** Every 2 hours +**Command:** `php artisan jobs:fetch --limit=50` + +**Why this interval:** +- **Freshness:** Most job boards update their listings every few hours +- **API Rate Limits:** Spacing out requests prevents hitting rate limits +- **Resource Management:** Prevents overwhelming your queue and database +- **User Experience:** 2-hour intervals ensure users see fresh content without excessive API calls + +**Customization Options:** +```php +// More frequent (hourly) - for high-traffic sites +->hourly() + +// Less frequent (every 4 hours) - for lower traffic or to save resources +->everyFourHours() + +// Peak hours only (every 2 hours from 6 AM to 6 PM) +->everyTwoHours()->between('6:00', '18:00') +``` + +**Limit Parameter:** +- `--limit=50` fetches up to 50 jobs per source +- Adjust based on your API quotas and database capacity +- Recommended: 20-100 jobs per fetch cycle + +--- + +### 2. Job Enrichment (`jobs:enrich`) + +**Interval:** Every 6 hours +**Command:** `php artisan jobs:enrich` + +**Why this interval:** +- **Processing Time:** Enrichment can be resource-intensive (AI/ML for categorization) +- **Batch Efficiency:** Allows multiple fetch cycles to accumulate jobs before enriching +- **Priority:** Less urgent than fetching; can run in background +- **Resource Balance:** Doesn't compete with peak fetch times + +**What it does:** +- Auto-categorizes jobs based on title/description +- Detects experience levels (entry, mid, senior, etc.) +- Normalizes job data +- Enhances job metadata + +**Customization Options:** +```php +// More frequent (every 4 hours) - if categorization is critical +->everyFourHours() + +// Less frequent (twice daily) - to save resources +->twiceDaily(1, 13) // 1 AM and 1 PM + +// Only during off-peak hours +->dailyAt('03:00') +``` + +--- + +### 3. Job Deduplication (`jobs:deduplicate`) + +**Interval:** Daily at 3:00 AM +**Command:** `php artisan jobs:deduplicate` + +**Why this interval:** +- **Daily Cleanup:** Removes duplicates that accumulate from multiple sources +- **Off-Peak Hours:** Runs at 3 AM when traffic is lowest +- **Post-Fetch:** Runs after fetch cycles have completed +- **Database Health:** Keeps database clean without running too frequently + +**What it does:** +- Identifies duplicate jobs based on title, company, and location +- Removes or merges duplicate entries +- Updates job fingerprints +- Maintains data integrity + +**Customization Options:** +```php +// More frequent (every 12 hours) - if duplicates are a major issue +->twiceDaily(3, 15) // 3 AM and 3 PM + +// Less frequent (weekly) - if duplicates are rare +->weeklyOn(0, '03:00') // Sunday at 3 AM + +// After major fetch cycles +->dailyAt('04:00') // After enrichment completes +``` + +--- + +### 4. Job Cleanup (`jobs:clean`) + +**Interval:** Daily at 2:00 AM +**Command:** `php artisan jobs:clean --days=45` + +**Why this interval:** +- **Daily Maintenance:** Removes expired jobs every day +- **Off-Peak Hours:** 2 AM has minimal user activity +- **Before Deduplication:** Runs before deduplication to remove stale data first +- **Database Performance:** Prevents database bloat + +**What it does:** +- Removes jobs older than 45 days +- Deletes expired job listings +- Cleans up soft-deleted jobs +- Optimizes database performance + +**Retention Period Options:** +```php +// Shorter retention (30 days) - for high-volume sites +'jobs:clean --days=30' + +// Longer retention (60-90 days) - for SEO or archival +'jobs:clean --days=60' + +// Remove only expired jobs +'jobs:clean --expired-only' +``` + +**Customization Options:** +```php +// Weekly cleanup instead of daily +->weeklyOn(0, '02:00') + +// Twice daily (aggressive cleanup) +->twiceDaily(2, 14) + +// Monthly cleanup (for archival sites) +->monthlyOn(1, '02:00') +``` + +--- + +### 5. Cache Pruning (`cache:prune-stale-tags`) + +**Interval:** Hourly +**Command:** `php artisan cache:prune-stale-tags` + +**Why this interval:** +- **Cache Health:** Removes stale cache entries regularly +- **Memory Management:** Prevents Redis/cache bloat +- **Frequent Cleanup:** Cache tags can accumulate quickly +- **Low Impact:** Fast operation, doesn't affect performance + +--- + +## Recommended Schedules by Site Size + +### Small Site (<1,000 jobs, <1,000 visitors/day) + +```php +$schedule->command('jobs:fetch --limit=30') + ->everyFourHours() + ->withoutOverlapping(); + +$schedule->command('jobs:enrich') + ->twiceDaily(2, 14); + +$schedule->command('jobs:deduplicate') + ->weeklyOn(0, '03:00'); + +$schedule->command('jobs:clean --days=60') + ->weekly(); +``` + +### Medium Site (1,000-10,000 jobs, 1,000-10,000 visitors/day) + +```php +// Current default configuration in bootstrap/app.php +$schedule->command('jobs:fetch --limit=50') + ->everyTwoHours() + ->withoutOverlapping(); + +$schedule->command('jobs:enrich') + ->everySixHours(); + +$schedule->command('jobs:deduplicate') + ->dailyAt('03:00'); + +$schedule->command('jobs:clean --days=45') + ->dailyAt('02:00'); +``` + +### Large Site (10,000+ jobs, 10,000+ visitors/day) + +```php +$schedule->command('jobs:fetch --limit=100') + ->hourly() + ->withoutOverlapping() + ->onOneServer() + ->runInBackground(); + +$schedule->command('jobs:enrich') + ->everyFourHours() + ->runInBackground(); + +$schedule->command('jobs:deduplicate') + ->twiceDaily(3, 15) + ->runInBackground(); + +$schedule->command('jobs:clean --days=30') + ->dailyAt('02:00') + ->runInBackground(); + +// Add sitemap regeneration +$schedule->command('sitemap:generate') + ->dailyAt('04:00') + ->runInBackground(); +``` + +--- + +## Best Practices + +### 1. Monitoring + +Always monitor your scheduled tasks: + +```bash +# View scheduled tasks +php artisan schedule:list + +# Test schedule (simulates next minute) +php artisan schedule:test + +# Run schedule manually +php artisan schedule:run + +# Check logs +tail -f storage/logs/laravel.log | grep -i "schedule\|fetch\|enrich\|deduplicate\|clean" +``` + +### 2. Resource Management + +- Use `->runInBackground()` for heavy tasks to prevent blocking +- Use `->withoutOverlapping()` to prevent concurrent runs +- Use `->onOneServer()` if running multiple app servers + +### 3. Error Handling + +Add notifications for failures: + +```php +$schedule->command('jobs:fetch') + ->everyTwoHours() + ->onFailure(function () { + // Log error, send notification, etc. + Log::error('Job fetch failed!'); + }); +``` + +### 4. Performance Optimization + +```php +// Run during off-peak hours only +$schedule->command('jobs:fetch') + ->everyTwoHours() + ->between('6:00', '22:00'); // Only 6 AM to 10 PM + +// Avoid peak hours +$schedule->command('jobs:deduplicate') + ->dailyAt('03:00') + ->unlessBetween('08:00', '18:00'); // Not during business hours +``` + +--- + +## Task Dependencies + +The order matters! Run tasks in this sequence: + +``` +1. jobs:fetch (2 AM, 4 AM, 6 AM, ...) + ↓ +2. jobs:clean (2 AM) - Remove old/expired first + ↓ +3. jobs:deduplicate (3 AM) - Then remove duplicates + ↓ +4. jobs:enrich (6 AM, 12 PM, 6 PM, 12 AM) - Finally enrich +``` + +This ensures: +- Old jobs are removed before deduplication +- Deduplication happens before enrichment +- Enrichment works on clean, deduplicated data + +--- + +## Troubleshooting + +### Jobs not being fetched? + +1. Check if scheduler is running: + ```bash + php artisan schedule:list + ``` + +2. Verify cron job is active: + ```bash + crontab -l + ``` + +3. Test manual fetch: + ```bash + php artisan jobs:fetch --limit=5 + ``` + +4. Check API credentials in `.env` + +### Duplicates still appearing? + +1. Run deduplication manually: + ```bash + php artisan jobs:deduplicate --force + ``` + +2. Check fingerprint generation logic + +3. Consider running deduplication more frequently + +### Database growing too large? + +1. Reduce retention period: + ```php + 'jobs:clean --days=30' + ``` + +2. Run cleanup more frequently: + ```php + ->twiceDaily(2, 14) + ``` + +3. Check for failed deletions: + ```bash + php artisan jobs:clean --verbose + ``` + +--- + +## Summary + +**Optimal Schedule (Default):** +- **Fetch:** Every 2 hours +- **Enrich:** Every 6 hours +- **Deduplicate:** Daily at 3 AM +- **Clean:** Daily at 2 AM (45-day retention) + +This provides a good balance of: +✅ Fresh content +✅ Resource efficiency +✅ Database health +✅ User experience +✅ API compliance + +Adjust based on your specific needs, traffic patterns, and resource constraints. diff --git a/TAGS.md b/TAGS.md new file mode 100644 index 0000000..0d65c20 --- /dev/null +++ b/TAGS.md @@ -0,0 +1,5 @@ +We need a tags system for the jobs. +Also, on the jobs table we need a is_curated bool column. +a command that gets jobs that were not curated, does an POST api call that i will give later to you with the job name and description and the response will be an array of tags. +we should save those tags and associate it with the job. +we should also display the tags in the job page, diff --git a/bootstrap/app.php b/bootstrap/app.php index 02e4321..4eaae9b 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -16,20 +16,59 @@ return Application::configure(basePath: dirname(__DIR__)) // }) ->withSchedule(function (Schedule $schedule): void { - // Fetch jobs from all sources hourly - $schedule->command('jobs:fetch --limit=100') - ->hourly() + // ========================================================================= + // Job Fetching - Fetch fresh jobs from external APIs + // ========================================================================= + + // Fetch jobs every 2 hours to keep listings fresh + // Limit per source to avoid rate limits and keep queue manageable + $schedule->command('jobs:fetch --limit=50') + ->everyTwoHours() ->withoutOverlapping() - ->onOneServer(); + ->onOneServer() + ->runInBackground(); - // Clean up old jobs daily at 2 AM - $schedule->command('jobs:clean --days=30') - ->dailyAt('02:00') - ->onOneServer(); + // ========================================================================= + // Job Enrichment - Auto-categorize and enhance job data + // ========================================================================= - // Deduplicate jobs weekly on Sunday at 3 AM + // Enrich new jobs every 6 hours (categorization, experience level detection) + // This runs after jobs have been fetched to categorize uncategorized jobs + $schedule->command('jobs:enrich') + ->everySixHours() + ->withoutOverlapping() + ->onOneServer() + ->runInBackground(); + + // ========================================================================= + // Job Deduplication - Remove duplicate job listings + // ========================================================================= + + // Deduplicate jobs daily at 3 AM (after fetch cycles have completed) + // Daily is better than weekly to keep listings clean $schedule->command('jobs:deduplicate') - ->weeklyOn(0, '03:00') + ->dailyAt('03:00') + ->onOneServer() + ->runInBackground(); + + // ========================================================================= + // Job Cleanup - Remove expired and old jobs + // ========================================================================= + + // Clean up expired and old jobs daily at 2 AM + // Keeps database lean and removes stale listings + $schedule->command('jobs:clean --days=45') + ->dailyAt('02:00') + ->onOneServer() + ->runInBackground(); + + // ========================================================================= + // Cache Management - Keep caches fresh + // ========================================================================= + + // Prune stale cache tags hourly + $schedule->command('cache:prune-stale-tags') + ->hourly() ->onOneServer(); }) ->withExceptions(function (Exceptions $exceptions): void { diff --git a/composer.lock b/composer.lock index c709ef2..65e89b9 100644 --- a/composer.lock +++ b/composer.lock @@ -9221,12 +9221,12 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": true, "prefer-lowest": false, "platform": { "php": "^8.3" }, - "platform-dev": [], - "plugin-api-version": "2.2.0" + "platform-dev": {}, + "plugin-api-version": "2.9.0" } diff --git a/public/favicon_io.zip b/public/favicon_io.zip new file mode 100644 index 0000000..c530477 Binary files /dev/null and b/public/favicon_io.zip differ diff --git a/public/favicon_io/about.txt b/public/favicon_io/about.txt new file mode 100644 index 0000000..f90ec40 --- /dev/null +++ b/public/favicon_io/about.txt @@ -0,0 +1,6 @@ +This favicon was generated using the following font: + +- Font Title: Leckerli One +- Font Author: undefined +- Font Source: https://fonts.gstatic.com/s/leckerlione/v21/V8mCoQH8VCsNttEnxnGQ-1itLZxcBtItFw.ttf +- Font License: undefined) diff --git a/public/favicon_io/android-chrome-192x192.png b/public/favicon_io/android-chrome-192x192.png new file mode 100644 index 0000000..08b0fa2 Binary files /dev/null and b/public/favicon_io/android-chrome-192x192.png differ diff --git a/public/favicon_io/android-chrome-512x512.png b/public/favicon_io/android-chrome-512x512.png new file mode 100644 index 0000000..8b74418 Binary files /dev/null and b/public/favicon_io/android-chrome-512x512.png differ diff --git a/public/favicon_io/apple-touch-icon.png b/public/favicon_io/apple-touch-icon.png new file mode 100644 index 0000000..6e50ecb Binary files /dev/null and b/public/favicon_io/apple-touch-icon.png differ diff --git a/public/favicon_io/favicon-16x16.png b/public/favicon_io/favicon-16x16.png new file mode 100644 index 0000000..5757f4d Binary files /dev/null and b/public/favicon_io/favicon-16x16.png differ diff --git a/public/favicon_io/favicon-32x32.png b/public/favicon_io/favicon-32x32.png new file mode 100644 index 0000000..0ad660b Binary files /dev/null and b/public/favicon_io/favicon-32x32.png differ diff --git a/public/favicon_io/favicon.ico b/public/favicon_io/favicon.ico new file mode 100644 index 0000000..d479f40 Binary files /dev/null and b/public/favicon_io/favicon.ico differ diff --git a/public/favicon_io/site.webmanifest b/public/favicon_io/site.webmanifest new file mode 100644 index 0000000..45dc8a2 --- /dev/null +++ b/public/favicon_io/site.webmanifest @@ -0,0 +1 @@ +{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file diff --git a/queue-worker.sh b/queue-worker.sh new file mode 100644 index 0000000..5d113aa --- /dev/null +++ b/queue-worker.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# +# Queue Worker Script for JobifyGarden +# This script runs the queue worker and ensures it doesn't run if already running +# + +# Get the directory where this script is located +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd "$DIR" + +# Check if queue worker is already running +if pgrep -f "artisan queue:work" > /dev/null; then + echo "Queue worker already running, skipping..." + exit 0 +fi + +# Run the queue worker +# --max-time=50 ensures it runs for max 50 seconds +# --stop-when-empty exits when queue is empty +# --sleep=3 waits 3 seconds between jobs +# --tries=3 retries failed jobs 3 times +php artisan queue:work --max-time=50 --stop-when-empty --sleep=3 --tries=3 >> storage/logs/queue-worker.log 2>&1