+ + {{ $article->title }} + +
+ @if($article->excerpt) +{{ $article->excerpt }}
+ @endif + + Read more + + +diff --git a/app/Http/Controllers/Web/BlogController.php b/app/Http/Controllers/Web/BlogController.php
new file mode 100644
index 0000000..916c277
--- /dev/null
+++ b/app/Http/Controllers/Web/BlogController.php
@@ -0,0 +1,25 @@
+orderByDesc('published_at')
+ ->paginate(12);
+
+ return view('blog.index', compact('articles'));
+ }
+
+ public function show(Article $article)
+ {
+ abort_unless($article->is_published, 404);
+
+ return view('blog.show', compact('article'));
+ }
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/Web/SitemapController.php b/app/Http/Controllers/Web/SitemapController.php
index 55d0942..58760ce 100644
--- a/app/Http/Controllers/Web/SitemapController.php
+++ b/app/Http/Controllers/Web/SitemapController.php
@@ -66,11 +66,26 @@ class SitemapController extends Controller
{
$xml = $this->cacheService->remember(
'sitemap:jobs',
- 1800, // 30 minutes cache (jobs change more frequently)
+ 1800,
fn() => $this->sitemapService->generateJobsSitemap()
);
return response($xml, 200)
->header('Content-Type', 'application/xml');
}
+
+ /**
+ * Generate and serve blog sitemap
+ */
+ public function blogSitemap(): Response
+ {
+ $xml = $this->cacheService->remember(
+ 'sitemap:blog',
+ 3600,
+ fn() => $this->sitemapService->generateBlogSitemap()
+ );
+
+ return response($xml, 200)
+ ->header('Content-Type', 'application/xml');
+ }
}
diff --git a/app/Models/Article.php b/app/Models/Article.php
new file mode 100644
index 0000000..fa04cba
--- /dev/null
+++ b/app/Models/Article.php
@@ -0,0 +1,71 @@
+ 'datetime',
+ ];
+ }
+
+ protected static function boot(): void
+ {
+ parent::boot();
+
+ static::creating(function (Article $article) {
+ if (empty($article->slug)) {
+ $article->slug = static::generateUniqueSlug($article->title);
+ }
+ });
+
+ static::updating(function (Article $article) {
+ if ($article->isDirty('title') && empty($article->slug)) {
+ $article->slug = static::generateUniqueSlug($article->title);
+ }
+ });
+ }
+
+ public static function generateUniqueSlug(string $title): string
+ {
+ $baseSlug = Str::limit(Str::slug($title), 200, '');
+ $slug = $baseSlug;
+ $counter = 1;
+
+ while (static::where('slug', $slug)->exists()) {
+ $slug = $baseSlug . '-' . $counter;
+ $counter++;
+ }
+
+ return $slug;
+ }
+
+ public function scopePublished(Builder $query): Builder
+ {
+ return $query->whereNotNull('published_at')->where('published_at', '<=', now());
+ }
+
+ public function getIsPublishedAttribute(): bool
+ {
+ return $this->published_at !== null && $this->published_at->isPast();
+ }
+
+ public function getRouteKeyName(): string
+ {
+ return 'slug';
+ }
+}
\ No newline at end of file
diff --git a/app/Services/SitemapService.php b/app/Services/SitemapService.php
index 3a0a2f5..7583371 100644
--- a/app/Services/SitemapService.php
+++ b/app/Services/SitemapService.php
@@ -2,6 +2,7 @@
namespace App\Services;
+use App\Models\Article;
use App\Models\Job;
use App\Models\JobCategory;
use Illuminate\Support\Facades\URL;
@@ -92,6 +93,9 @@ class SitemapService
// Add jobs sitemap
$this->addSitemapToIndex($xml, route('sitemap.jobs'), now());
+ // Add blog sitemap
+ $this->addSitemapToIndex($xml, route('sitemap.blog'), now());
+
return $xml->asXML();
}
@@ -105,6 +109,31 @@ class SitemapService
$sitemapElement->addChild('lastmod', $lastmod->format('Y-m-d'));
}
+ /**
+ * Generate blog sitemap
+ */
+ public function generateBlogSitemap(): string
+ {
+ $xml = new \SimpleXMLElement('
Career advice, job search tips, and hiring insights.
+No articles published yet. Check back soon.
+{{ $article->excerpt }}
+ @endif + + Read more + + +{{ $article->excerpt }}
+ @endif +