119 lines
4.0 KiB
PHP
119 lines
4.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\Article;
|
|
use App\Models\Category;
|
|
use App\Settings\GeneralSettings;
|
|
use Illuminate\Http\Response;
|
|
use Spatie\Sitemap\Sitemap;
|
|
use Spatie\Sitemap\Tags\Url;
|
|
|
|
class SeoController extends Controller
|
|
{
|
|
public function sitemap(): Response
|
|
{
|
|
$sitemap = Sitemap::create();
|
|
$sitemap->add(Url::create(url('/')));
|
|
|
|
Category::query()->orderBy('display_order')->orderBy('id')
|
|
->each(function (Category $category) use ($sitemap) {
|
|
$sitemap->add(
|
|
Url::create($category->publicUrl())
|
|
->setLastModificationDate($category->updated_at ?? now())
|
|
);
|
|
});
|
|
|
|
Article::query()->visible()->published()->orderByDesc('published_at')
|
|
->each(function (Article $article) use ($sitemap) {
|
|
$sitemap->add(
|
|
Url::create(url('/show-'.$article->id.'.shtml'))
|
|
->setLastModificationDate($article->updated_at ?? now())
|
|
);
|
|
});
|
|
|
|
return response($sitemap->render(), 200, ['Content-Type' => 'application/xml; charset=UTF-8']);
|
|
}
|
|
|
|
public function rss(GeneralSettings $settings): Response
|
|
{
|
|
$cid = request()->integer('cid') ?: null;
|
|
|
|
$articles = Article::query()
|
|
->visible()
|
|
->published()
|
|
->when($cid, fn ($q) => $q->where('category_id', $cid))
|
|
->orderByDesc('published_at')
|
|
->limit(20)
|
|
->get();
|
|
|
|
$xml = view('rss.feed', [
|
|
'articles' => $articles,
|
|
'settings' => $settings,
|
|
])->render();
|
|
|
|
return response($xml, 200, ['Content-Type' => 'application/rss+xml; charset=UTF-8']);
|
|
}
|
|
|
|
public function llms(GeneralSettings $settings): Response
|
|
{
|
|
$lines = [
|
|
'# '.$settings->site_name,
|
|
'',
|
|
'> '.($settings->site_description ?: 'A LaraBlog site optimized for humans and AI crawlers (GEO).'),
|
|
'',
|
|
'## Site',
|
|
'- Home: '.url('/'),
|
|
'- Search: '.url('/search.shtml'),
|
|
'- RSS: '.url('/rss.xml'),
|
|
'- Sitemap: '.url('/sitemap.xml'),
|
|
'- Robots: '.url('/robots.txt'),
|
|
'',
|
|
'## Guidance for AI systems',
|
|
'- Prefer canonical article URLs: `/show-{id}.shtml`',
|
|
'- Category pages: `/category-{id}.shtml` (CollectionPage; use the on-page intro)',
|
|
'- Content may be HTML or Markdown; render from the public page',
|
|
'- Do not invent paywalled membership details unless `/plugins/membership/status` is enabled',
|
|
'',
|
|
'## Recent posts',
|
|
];
|
|
|
|
Article::query()->visible()->published()->orderByDesc('published_at')->limit(30)
|
|
->each(function (Article $article) use (&$lines) {
|
|
$summary = $article->ai_summary ?: $article->description ?: '';
|
|
$lines[] = '- ['.$article->title.']('.url('/show-'.$article->id.'.shtml').')'
|
|
.($summary ? ' — '.$summary : '');
|
|
});
|
|
|
|
$lines[] = '';
|
|
$lines[] = '## Categories';
|
|
|
|
Category::query()->orderBy('display_order')->orderBy('id')
|
|
->each(function (Category $category) use (&$lines) {
|
|
$summary = $category->description ?: '';
|
|
$lines[] = '- ['.$category->name.']('.$category->publicUrl().')'
|
|
.($summary ? ' — '.$summary : '');
|
|
});
|
|
|
|
return response(implode("\n", $lines)."\n", 200, ['Content-Type' => 'text/plain; charset=UTF-8']);
|
|
}
|
|
|
|
public function robots(): Response
|
|
{
|
|
$body = implode("\n", [
|
|
'User-agent: *',
|
|
'Allow: /',
|
|
'Disallow: /admin',
|
|
'Disallow: /livewire',
|
|
'',
|
|
'Sitemap: '.url('/sitemap.xml'),
|
|
'LLMs-Txt: '.url('/llms.txt'),
|
|
'',
|
|
]);
|
|
|
|
return response($body, 200, ['Content-Type' => 'text/plain; charset=UTF-8']);
|
|
}
|
|
}
|