This commit is contained in:
Doar Mihai
2026-04-04 18:19:34 +03:00
parent 5fb17f717d
commit 7c09846bd6
22 changed files with 1229 additions and 94 deletions
+14 -1
View File
@@ -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'));
}
}
+3 -3
View File
@@ -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);
@@ -0,0 +1,29 @@
<?php
namespace App\Http\Controllers\Web;
use App\Http\Controllers\Controller;
use Illuminate\View\View;
class PageController extends Controller
{
public function about(): View
{
return view('pages.about');
}
public function contact(): View
{
return view('pages.contact');
}
public function privacy(): View
{
return view('pages.privacy');
}
public function terms(): View
{
return view('pages.terms');
}
}
+55
View File
@@ -6,13 +6,32 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Str;
class Job extends Model
{
use SoftDeletes;
protected static function boot()
{
parent::boot();
static::creating(function ($job) {
if (empty($job->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';
}
}
@@ -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, '<p><br><ul><ol><li><strong><b><em><i><h1><h2><h3><h4>');
$clean = html_entity_decode($clean, ENT_QUOTES | ENT_HTML5, 'UTF-8');
$clean = preg_replace('/\s+/', ' ', $clean);
// Convert multiple spaces to single space but preserve newlines
$clean = preg_replace('/[^\S\n]+/', ' ', $clean);
// Note: Adzuna API only provides truncated descriptions in search results.
// Full descriptions would require individual job detail API calls.
return trim($clean);
}
@@ -70,9 +70,32 @@ class RemotiveTransformer implements JobTransformer
return null;
}
// Try to extract numbers from salary string
if (preg_match_all('/[\d,]+/', $salary, $matches)) {
$numbers = array_map(fn($n) => (float) str_replace(',', '', $n), $matches[0]);
// Normalize the salary string
$salary = strtolower($salary);
// Check if it's hourly rate - if so, convert to annual (assuming 40hr/week * 52 weeks = 2080 hours/year)
$isHourly = str_contains($salary, '/hour') || str_contains($salary, 'per hour') || str_contains($salary, '/hr');
// Try to extract numbers with optional k/K suffix
// Pattern: number followed by optional comma-separated digits, then optional 'k'
if (preg_match_all('/\$?\s*([\d,]+)\s*([kK])?/', $salary, $matches, PREG_SET_ORDER)) {
$numbers = [];
foreach ($matches as $match) {
$number = (float) str_replace(',', '', $match[1]);
// If there's a 'k' suffix, multiply by 1000
if (!empty($match[2]) && strtolower($match[2]) === 'k') {
$number *= 1000;
}
// If it's hourly, convert to annual
if ($isHourly) {
$number *= 2080; // 40 hours/week * 52 weeks
}
$numbers[] = $number;
}
if (count($numbers) >= 2) {
return $type === 'min' ? min($numbers) : max($numbers);
+1 -1
View File
@@ -15,7 +15,7 @@ class SeoService
{
$title = $job->title . ' at ' . $job->company . ' - JobifyGarden';
$description = Str::limit(strip_tags($job->description), 155);
$url = route('jobs.show', $job->id);
$url = route('jobs.show', $job);
return [
'title' => $title,
+4 -4
View File
@@ -43,12 +43,12 @@ class SitemapService
})
->orderBy('posted_at', 'desc')
->limit(10000) // Limit to 10k most recent jobs
->get(['id', 'posted_at', 'updated_at']);
->get(['id', 'slug', 'posted_at', 'updated_at']);
foreach ($jobs as $job) {
$this->addUrl(
$xml,
route('jobs.show', $job->id),
route('jobs.show', $job),
$job->updated_at ?? $job->posted_at,
'weekly',
'0.7'
@@ -150,12 +150,12 @@ class SitemapService
})
->orderBy('posted_at', 'desc')
->limit(50000) // Sitemap limit
->get(['id', 'posted_at', 'updated_at']);
->get(['id', 'slug', 'posted_at', 'updated_at']);
foreach ($jobs as $job) {
$this->addUrl(
$xml,
route('jobs.show', $job->id),
route('jobs.show', $job),
$job->updated_at ?? $job->posted_at,
'weekly',
'0.7'