2026-04-04 02:14:57 +03:00
<? php
namespace App\Services ;
use App\Models\Job ;
use App\Models\JobCategory ;
use Illuminate\Support\Str ;
class SeoService
{
/**
* Generate meta tags for a job
*/
public function getJobMetaTags ( Job $job ) : array
{
$title = $job -> title . ' at ' . $job -> company . ' - JobifyGarden' ;
$description = Str :: limit ( strip_tags ( $job -> description ), 155 );
2026-04-04 18:19:34 +03:00
$url = route ( 'jobs.show' , $job );
2026-04-04 02:14:57 +03:00
return [
'title' => $title ,
'description' => $description ,
'keywords' => $this -> generateJobKeywords ( $job ),
'canonical' => $url ,
'og_title' => $job -> title . ' at ' . $job -> company ,
'og_description' => $description ,
'og_url' => $url ,
'og_type' => 'website' ,
'og_image' => asset ( 'images/og-default.jpg' ),
'twitter_card' => 'summary_large_image' ,
'twitter_title' => $job -> title . ' at ' . $job -> company ,
'twitter_description' => $description ,
'twitter_image' => asset ( 'images/twitter-card-default.jpg' ),
];
}
/**
* Generate meta tags for category page
*/
public function getCategoryMetaTags ( JobCategory $category , int $jobCount ) : array
{
$title = $category -> name . ' Jobs - JobifyGarden' ;
$description = "Browse { $jobCount } { $category -> name } job opportunities. " . ( $category -> description ?? 'Find your next career in ' . $category -> name . '.' );
$url = route ( 'jobs.category' , $category -> slug );
return [
'title' => $title ,
'description' => Str :: limit ( $description , 155 ),
'keywords' => $this -> generateCategoryKeywords ( $category ),
'canonical' => $url ,
'og_title' => $category -> name . ' Jobs' ,
'og_description' => Str :: limit ( $description , 155 ),
'og_url' => $url ,
'og_type' => 'website' ,
'og_image' => asset ( 'images/og-default.jpg' ),
'twitter_card' => 'summary' ,
'twitter_title' => $category -> name . ' Jobs' ,
'twitter_description' => Str :: limit ( $description , 155 ),
];
}
/**
* Generate meta tags for homepage
*/
public function getHomeMetaTags ( int $jobCount ) : array
{
return [
'title' => 'JobifyGarden - Find Your Dream Job | ' . number_format ( $jobCount ) . '+ Jobs' ,
'description' => 'Discover thousands of job opportunities from multiple sources. Search remote jobs, full-time positions, and more. Your next career starts here.' ,
'keywords' => 'jobs, careers, employment, job search, remote jobs, full-time jobs, part-time jobs, find jobs' ,
'canonical' => route ( 'home' ),
'og_title' => 'JobifyGarden - Find Your Dream Job' ,
'og_description' => 'Discover thousands of job opportunities from multiple sources. Your next career starts here.' ,
'og_url' => route ( 'home' ),
'og_type' => 'website' ,
'og_image' => asset ( 'images/og-home.jpg' ),
'twitter_card' => 'summary_large_image' ,
'twitter_title' => 'JobifyGarden - Find Your Dream Job' ,
'twitter_description' => 'Discover thousands of job opportunities from multiple sources.' ,
'twitter_image' => asset ( 'images/twitter-card-home.jpg' ),
];
}
/**
* Generate keywords for a job
*/
private function generateJobKeywords ( Job $job ) : string
{
$keywords = [
$job -> title ,
$job -> company ,
$job -> job_type ,
$job -> experience_level ,
];
if ( $job -> category ) {
$keywords [] = $job -> category -> name ;
}
if ( $job -> location ) {
$keywords [] = $job -> location ;
}
if ( $job -> remote_allowed ) {
$keywords [] = 'remote' ;
$keywords [] = 'work from home' ;
}
return implode ( ', ' , array_filter ( $keywords ));
}
/**
* Generate keywords for a category
*/
private function generateCategoryKeywords ( JobCategory $category ) : string
{
return implode ( ', ' , [
$category -> name . ' jobs' ,
$category -> name . ' careers' ,
$category -> name . ' employment' ,
'find ' . $category -> name . ' jobs' ,
]);
}
/**
* Generate JSON-LD structured data for a job
*/
public function generateJobStructuredData ( Job $job ) : array
{
$data = [
'@context' => 'https://schema.org/' ,
'@type' => 'JobPosting' ,
'title' => $job -> title ,
'description' => strip_tags ( $job -> description ),
'datePosted' => $job -> posted_at ?-> toIso8601String () ?? $job -> created_at -> toIso8601String (),
'hiringOrganization' => [
'@type' => 'Organization' ,
'name' => $job -> company ,
],
'jobLocation' => $this -> getJobLocationData ( $job ),
'employmentType' => $this -> mapEmploymentType ( $job -> job_type ),
];
if ( $job -> expires_at ) {
$data [ 'validThrough' ] = $job -> expires_at -> toIso8601String ();
}
if ( $job -> salary_min || $job -> salary_max ) {
$data [ 'baseSalary' ] = $this -> getSalaryData ( $job );
}
if ( $job -> experience_level ) {
$data [ 'experienceRequirements' ] = [
'@type' => 'OccupationalExperienceRequirements' ,
'monthsOfExperience' => $this -> mapExperienceLevel ( $job -> experience_level ),
];
}
if ( $job -> apply_url || $job -> source_url ) {
$data [ 'url' ] = $job -> apply_url ?? $job -> source_url ;
}
return $data ;
}
/**
* Get job location data for structured data
*/
private function getJobLocationData ( Job $job ) : array
{
if ( $job -> remote_allowed ) {
return [
'@type' => 'Place' ,
'address' => [
'@type' => 'PostalAddress' ,
'addressCountry' => 'Remote' ,
],
];
}
$address = [
'@type' => 'PostalAddress' ,
];
if ( $job -> jobLocation ) {
$address [ 'addressLocality' ] = $job -> jobLocation -> city ;
$address [ 'addressRegion' ] = $job -> jobLocation -> state ;
$address [ 'addressCountry' ] = $job -> jobLocation -> country ;
} elseif ( $job -> location ) {
$address [ 'name' ] = $job -> location ;
}
return [
'@type' => 'Place' ,
'address' => $address ,
];
}
/**
* Get salary data for structured data
*/
private function getSalaryData ( Job $job ) : array
{
$currency = $job -> salary_currency ?? 'USD' ;
if ( $job -> salary_min && $job -> salary_max ) {
return [
'@type' => 'MonetaryAmount' ,
'currency' => $currency ,
'value' => [
'@type' => 'QuantitativeValue' ,
'minValue' => $job -> salary_min ,
'maxValue' => $job -> salary_max ,
'unitText' => 'YEAR' ,
],
];
}
$value = $job -> salary_min ?? $job -> salary_max ;
return [
'@type' => 'MonetaryAmount' ,
'currency' => $currency ,
'value' => [
'@type' => 'QuantitativeValue' ,
'value' => $value ,
'unitText' => 'YEAR' ,
],
];
}
/**
* Map job type to schema.org employment type
*/
private function mapEmploymentType ( ? string $jobType ) : string
{
return match ( $jobType ) {
'full-time' , 'full_time' => 'FULL_TIME' ,
'part-time' , 'part_time' => 'PART_TIME' ,
'contract' => 'CONTRACTOR' ,
'temporary' => 'TEMPORARY' ,
'internship' => 'INTERN' ,
'freelance' => 'CONTRACTOR' ,
default => 'FULL_TIME' ,
};
}
/**
* Map experience level to months
*/
private function mapExperienceLevel ( ? string $level ) : int
{
return match ( $level ) {
'entry' => 0 ,
'mid' => 24 ,
'senior' => 60 ,
'lead' => 84 ,
'executive' => 120 ,
default => 0 ,
};
}
/**
* Generate breadcrumb structured data
*/
public function generateBreadcrumbStructuredData ( array $items ) : array
{
$listItems = [];
foreach ( $items as $index => $item ) {
$listItems [] = [
'@type' => 'ListItem' ,
'position' => $index + 1 ,
'name' => $item [ 'name' ],
'item' => $item [ 'url' ],
];
}
return [
'@context' => 'https://schema.org' ,
'@type' => 'BreadcrumbList' ,
'itemListElement' => $listItems ,
];
}
}