Initial baseline: LaraBlog core with plugin commerce surface.
Captures the current working tree after theme slots, ArticleAccess, and the payment / paid-content plugins so subsequent work has a reviewable git history.
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Article;
|
||||
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('/')));
|
||||
|
||||
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`',
|
||||
'- 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 : '');
|
||||
});
|
||||
|
||||
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']);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user