From 7c09846bd628e84534aaffb42c78aba50dbd03e2 Mon Sep 17 00:00:00 2001 From: Doar Mihai Date: Sat, 4 Apr 2026 18:19:34 +0300 Subject: [PATCH] fixes --- README.md | 396 ++++++++++++++++-- app/Http/Controllers/Web/HomeController.php | 15 +- app/Http/Controllers/Web/JobController.php | 6 +- app/Http/Controllers/Web/PageController.php | 29 ++ app/Models/Job.php | 55 +++ .../JobApi/Transformers/AdzunaTransformer.php | 11 +- .../Transformers/RemotiveTransformer.php | 29 +- app/Services/SeoService.php | 2 +- app/Services/SitemapService.php | 8 +- ...26_04_04_094449_add_slug_to_jobs_table.php | 30 ++ resources/views/components/job-card.blade.php | 4 +- resources/views/home.blade.php | 120 +++++- resources/views/jobs/category.blade.php | 4 +- resources/views/jobs/index.blade.php | 4 +- resources/views/jobs/remote.blade.php | 4 +- resources/views/jobs/show.blade.php | 112 ++++- resources/views/layouts/app.blade.php | 13 +- resources/views/pages/about.blade.php | 76 ++++ resources/views/pages/contact.blade.php | 122 ++++++ resources/views/pages/privacy.blade.php | 120 ++++++ resources/views/pages/terms.blade.php | 154 +++++++ routes/web.php | 9 +- 22 files changed, 1229 insertions(+), 94 deletions(-) create mode 100644 app/Http/Controllers/Web/PageController.php create mode 100644 database/migrations/2026_04_04_094449_add_slug_to_jobs_table.php create mode 100644 resources/views/pages/about.blade.php create mode 100644 resources/views/pages/contact.blade.php create mode 100644 resources/views/pages/privacy.blade.php create mode 100644 resources/views/pages/terms.blade.php diff --git a/README.md b/README.md index 4c679e4..a8ad4dd 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,34 @@ A comprehensive job aggregator platform built with Laravel 13 that fetches, stores, and displays job listings from multiple free job APIs. +## Quick Start + +```bash +# Clone repository +git clone && cd jobifygarden + +# Install dependencies +composer install + +# Start Docker environment (includes MySQL, Redis) +./vendor/bin/sail up -d + +# Setup application +./vendor/bin/sail artisan key:generate +./vendor/bin/sail artisan migrate +./vendor/bin/sail artisan db:seed + +# Install & build frontend assets +./vendor/bin/sail npm install && ./vendor/bin/sail npm run dev + +# Start queue worker (in a separate terminal) +./vendor/bin/sail artisan queue:work + +# Visit http://localhost +``` + +**Important:** After setup, configure the cron job (see [Cron Jobs](#cron-jobs-required) section) to enable automatic job fetching. + ## Requirements - **PHP**: 8.3.x or higher @@ -40,7 +68,81 @@ A comprehensive job aggregator platform built with Laravel 13 that fetches, stor ## Installation -### Development Setup +### Development Setup (Laravel Sail - Recommended) + +1. **Clone the repository** +```bash +git clone +cd jobifygarden +``` + +2. **Install PHP dependencies** +```bash +composer install +``` + +3. **Copy environment file** +```bash +cp .env.example .env +``` + +4. **Configure environment variables** +Edit `.env` and update the following: +```env +APP_NAME=JobifyGarden +APP_ENV=local +APP_DEBUG=true +APP_URL=http://localhost + +# Database (Sail uses these defaults) +DB_CONNECTION=mysql +DB_HOST=mysql +DB_PORT=3306 +DB_DATABASE=jobifygarden +DB_USERNAME=sail +DB_PASSWORD=password + +# Cache & Sessions (Redis) +CACHE_STORE=redis +SESSION_DRIVER=redis +QUEUE_CONNECTION=redis + +# Redis (Sail default) +REDIS_HOST=redis +REDIS_PASSWORD=null +REDIS_PORT=6379 +``` + +5. **Start Docker containers** +```bash +./vendor/bin/sail up -d +``` + +6. **Generate application key** +```bash +./vendor/bin/sail artisan key:generate +``` + +7. **Run database migrations** +```bash +./vendor/bin/sail artisan migrate +``` + +8. **Seed initial data (optional)** +```bash +./vendor/bin/sail artisan db:seed +``` + +9. **Install frontend dependencies and build assets** +```bash +./vendor/bin/sail npm install +./vendor/bin/sail npm run dev +``` + +10. **Visit the application** +Open `http://localhost` in your browser. + +### Production Setup (Traditional Server) 1. **Clone the repository** ```bash @@ -50,8 +152,9 @@ cd jobifygarden 2. **Install dependencies** ```bash -composer install +composer install --optimize-autoloader --no-dev npm install +npm run build ``` 3. **Configure environment** @@ -60,72 +163,141 @@ cp .env.example .env php artisan key:generate ``` -4. **Update `.env` with your database credentials** +4. **Update `.env` for production** ```env +APP_NAME=JobifyGarden +APP_ENV=production +APP_DEBUG=false +APP_URL=https://yourdomain.com + DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=jobifygarden DB_USERNAME=your_username -DB_PASSWORD=your_password +DB_PASSWORD=your_secure_password + +CACHE_STORE=redis +SESSION_DRIVER=redis +QUEUE_CONNECTION=redis + +REDIS_HOST=127.0.0.1 +REDIS_PASSWORD=your_redis_password +REDIS_PORT=6379 ``` 5. **Run migrations** ```bash -php artisan migrate +php artisan migrate --force ``` -6. **Seed the database (optional)** +6. **Optimize for production** ```bash -php artisan db:seed +php artisan config:cache +php artisan route:cache +php artisan view:cache +php artisan optimize ``` -7. **Build frontend assets** +7. **Set proper permissions** ```bash -npm run dev -``` - -8. **Start the development server** -```bash -php artisan serve -``` - -Visit `http://localhost:8000` to view the application. - -### Docker Setup (Laravel Sail) - -```bash -# Install dependencies -composer install - -# Start Docker containers -./vendor/bin/sail up -d - -# Run migrations -./vendor/bin/sail artisan migrate - -# Build assets -./vendor/bin/sail npm install -./vendor/bin/sail npm run dev +chown -R www-data:www-data storage bootstrap/cache +chmod -R 775 storage bootstrap/cache ``` ## Production Deployment See [DEPLOYMENT.md](DEPLOYMENT.md) for detailed production deployment instructions. +## Required Services + +### Queue Worker (Required) + +The application uses queues for background job processing. You must run a queue worker: + +**Using Laravel Sail (Development):** +```bash +./vendor/bin/sail artisan queue:work --sleep=3 --tries=3 --timeout=90 +``` + +**Using Supervisor (Production):** +Create `/etc/supervisor/conf.d/jobifygarden-worker.conf`: +```ini +[program:jobifygarden-worker] +process_name=%(program_name)s_%(process_num)02d +command=php /path/to/jobifygarden/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600 +autostart=true +autorestart=true +stopasgroup=true +killasgroup=true +user=www-data +numprocs=2 +redirect_stderr=true +stdout_logfile=/path/to/jobifygarden/storage/logs/worker.log +stopwaitsecs=3600 +``` + +Then reload supervisor: +```bash +sudo supervisorctl reread +sudo supervisorctl update +sudo supervisorctl start jobifygarden-worker:* +``` + +### Cron Jobs (Required) + +Laravel's task scheduler must run every minute. Add this to your crontab: + +**Edit crontab:** +```bash +crontab -e +``` + +**Add this line:** +```bash +* * * * * cd /path/to/jobifygarden && php artisan schedule:run >> /dev/null 2>&1 +``` + +**For Laravel Sail (Development):** +```bash +* * * * * cd /path/to/jobifygarden && ./vendor/bin/sail artisan schedule:run >> /dev/null 2>&1 +``` + +### Scheduled Tasks + +The following tasks are automatically scheduled and will run via the cron job above: + +| Task | Schedule | Description | +|------|----------|-------------| +| `jobs:fetch` | Every 2 hours | Fetch new jobs from all API sources | +| `jobs:clean` | Daily at 3:00 AM | Remove expired and old jobs | +| `jobs:deduplicate` | Daily at 4:00 AM | Remove duplicate job listings | +| `cache:prune-stale-tags` | Hourly | Clean up stale cache tags | + +**Note:** Job fetching intervals can be customized in `app/Console/Kernel.php` + ## Available Commands ### Job Management ```bash -# Fetch jobs from all sources +# Fetch jobs from all sources (automatically scheduled) php artisan jobs:fetch -# Clean expired jobs +# Fetch from a specific source +php artisan jobs:fetch --source=indeed + +# Clean expired jobs (automatically scheduled) php artisan jobs:clean -# Remove duplicate jobs +# Clean jobs older than specific days +php artisan jobs:clean --days=60 + +# Remove duplicate jobs (automatically scheduled) php artisan jobs:deduplicate +# Force dedupe even with different sources +php artisan jobs:deduplicate --force + # Enrich job data (categories, experience levels) php artisan jobs:enrich @@ -133,24 +305,55 @@ php artisan jobs:enrich php artisan jobs:test-apis ``` -### Database Optimization +### Database Management ```bash -# Optimize database tables and rebuild indexes +# Run migrations +php artisan migrate + +# Rollback last migration +php artisan migrate:rollback + +# Fresh migration with seeding +php artisan migrate:fresh --seed + +# Optimize database tables php artisan db:optimize ``` ### Cache Management ```bash # Clear all caches +php artisan optimize:clear + +# Clear specific caches php artisan cache:clear php artisan config:clear php artisan route:clear php artisan view:clear # Build caches for production -php artisan config:cache -php artisan route:cache -php artisan view:cache +php artisan optimize +``` + +### Queue Management +```bash +# Start queue worker +php artisan queue:work + +# Process jobs with options +php artisan queue:work --queue=high,default --sleep=3 --tries=3 + +# List failed jobs +php artisan queue:failed + +# Retry all failed jobs +php artisan queue:retry all + +# Retry specific failed job +php artisan queue:retry {id} + +# Clear all failed jobs +php artisan queue:flush ``` ## Project Structure @@ -194,8 +397,9 @@ GET /api/jobs/autocomplete/companies # Company autocomplete ### Backend - **Laravel 13**: PHP framework +- **PHP 8.3+**: Programming language - **MySQL 8**: Primary database -- **Redis**: Caching and queues +- **Redis**: Caching, sessions, and queues - **Guzzle**: HTTP client for API requests ### Frontend @@ -204,6 +408,114 @@ GET /api/jobs/autocomplete/companies # Company autocomplete - **TailwindCSS**: Utility-first CSS framework - **Vite**: Frontend build tool +### DevOps +- **Laravel Sail**: Docker development environment +- **Supervisor**: Process manager for queue workers +- **Nginx/Apache**: Web server +- **Certbot**: SSL certificate management + +## Troubleshooting + +### Common Issues + +**1. Queue jobs not processing** +```bash +# Check if queue worker is running +ps aux | grep "queue:work" + +# Restart queue worker (Supervisor) +sudo supervisorctl restart jobifygarden-worker:* + +# Or start manually for debugging +php artisan queue:work --verbose +``` + +**2. Jobs not being fetched automatically** +```bash +# Verify cron is set up +crontab -l + +# Test schedule manually +php artisan schedule:run + +# Check schedule list +php artisan schedule:list +``` + +**3. Redis connection errors** +```bash +# Check Redis is running +redis-cli ping + +# For Sail +./vendor/bin/sail redis redis-cli ping + +# Restart Redis (Sail) +./vendor/bin/sail down +./vendor/bin/sail up -d +``` + +**4. Permission errors** +```bash +# Fix storage permissions +sudo chown -R www-data:www-data storage bootstrap/cache +sudo chmod -R 775 storage bootstrap/cache + +# For development (use your user) +sudo chown -R $USER:$USER storage bootstrap/cache +chmod -R 775 storage bootstrap/cache +``` + +**5. Slugs not generating for new jobs** +```bash +# Regenerate slugs for existing jobs +php artisan tinker +# Then run: +# \App\Models\Job::whereNull('slug')->chunk(100, function($jobs) { +# foreach($jobs as $job) { +# $job->slug = \App\Models\Job::generateUniqueSlug($job->title, $job->company); +# $job->save(); +# } +# }); +``` + +**6. Cache issues** +```bash +# Clear all caches +php artisan optimize:clear + +# Rebuild for production +php artisan optimize +``` + +### Performance Tips + +1. **Enable OPcache in production** (add to `php.ini`): +```ini +opcache.enable=1 +opcache.memory_consumption=256 +opcache.interned_strings_buffer=16 +opcache.max_accelerated_files=20000 +opcache.validate_timestamps=0 +``` + +2. **Optimize database queries:** +```bash +php artisan db:optimize +``` + +3. **Use queue workers with multiple processes:** +```bash +# Update supervisor config to use more workers +numprocs=4 +``` + +4. **Monitor slow queries:** +```bash +# Enable query logging in .env +DB_LOG_QUERIES=true +``` + ## License This project is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT). diff --git a/app/Http/Controllers/Web/HomeController.php b/app/Http/Controllers/Web/HomeController.php index 48f3831..bb8512d 100644 --- a/app/Http/Controllers/Web/HomeController.php +++ b/app/Http/Controllers/Web/HomeController.php @@ -25,6 +25,19 @@ class HomeController extends Controller ->orderBy('name') ->get(); - return view('home', compact('featuredJobs', 'recentJobs', 'stats', 'categories')); + // Get filter options for search form + $jobTypes = \App\Models\Job::select('job_type') + ->whereNotNull('job_type') + ->distinct() + ->pluck('job_type') + ->toArray(); + + $experienceLevels = \App\Models\Job::select('experience_level') + ->whereNotNull('experience_level') + ->distinct() + ->pluck('experience_level') + ->toArray(); + + return view('home', compact('featuredJobs', 'recentJobs', 'stats', 'categories', 'jobTypes', 'experienceLevels')); } } diff --git a/app/Http/Controllers/Web/JobController.php b/app/Http/Controllers/Web/JobController.php index 7e1fcc5..cf66676 100644 --- a/app/Http/Controllers/Web/JobController.php +++ b/app/Http/Controllers/Web/JobController.php @@ -53,10 +53,10 @@ class JobController extends Controller /** * Display the specified job */ - public function show(int $id) + public function show(Job $job) { - $job = Job::with(['source', 'category', 'jobLocation']) - ->findOrFail($id); + // Load relationships + $job->load(['source', 'category', 'jobLocation']); // Track job view event AnalyticsEvent::logEvent(AnalyticsEvent::EVENT_JOB_VIEW, $job->id); diff --git a/app/Http/Controllers/Web/PageController.php b/app/Http/Controllers/Web/PageController.php new file mode 100644 index 0000000..ce8ece9 --- /dev/null +++ b/app/Http/Controllers/Web/PageController.php @@ -0,0 +1,29 @@ +slug)) { + $job->slug = static::generateUniqueSlug($job->title, $job->company); + } + }); + + static::updating(function ($job) { + if ($job->isDirty('title') && empty($job->slug)) { + $job->slug = static::generateUniqueSlug($job->title, $job->company); + } + }); + } + protected $fillable = [ 'external_id', + 'slug', 'title', 'company', 'description', @@ -187,4 +206,40 @@ class Job extends Model { return $query->where('company', 'LIKE', "%{$company}%"); } + + /** + * Generate a unique slug for the job. + */ + public static function generateUniqueSlug(string $title, ?string $company = null): string + { + // Create base slug from title + $baseSlug = Str::slug($title); + + // Add company to slug if provided for better uniqueness + if ($company) { + $baseSlug .= '-' . Str::slug(Str::limit($company, 30, '')); + } + + // Limit slug length to avoid database issues + $baseSlug = Str::limit($baseSlug, 200, ''); + + $slug = $baseSlug; + $counter = 1; + + // Check if slug exists and append counter if needed + while (static::withTrashed()->where('slug', $slug)->exists()) { + $slug = $baseSlug . '-' . $counter; + $counter++; + } + + return $slug; + } + + /** + * Get the route key for the model. + */ + public function getRouteKeyName(): string + { + return 'slug'; + } } diff --git a/app/Services/JobApi/Transformers/AdzunaTransformer.php b/app/Services/JobApi/Transformers/AdzunaTransformer.php index cac2007..2d7355c 100644 --- a/app/Services/JobApi/Transformers/AdzunaTransformer.php +++ b/app/Services/JobApi/Transformers/AdzunaTransformer.php @@ -46,9 +46,16 @@ class AdzunaTransformer implements JobTransformer private function sanitizeDescription(string $description): string { - $clean = strip_tags($description); + // Adzuna returns truncated descriptions with ellipsis (...) + // Keep the description as-is but strip excessive HTML while preserving basic formatting + $clean = strip_tags($description, '