wip: article AI polish, category SEO fields, cover generator, membership plan seeder
CI / PHPUnit (PHP 8.3) (push) Failing after 4s
CI / PHPUnit (PHP 8.2) (push) Failing after 1m9s
CI / Deploy (manual gate) (push) Skipped

This commit is contained in:
2026-09-07 18:48:37 +00:00
parent 263b98b218
commit 3cec4c5e18
121 changed files with 4701 additions and 313 deletions
+14
View File
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Http\Controllers;
use App\Domain\Plugin\PluginManager;
use App\Models\Category;
use App\Models\User;
use App\Settings\GeneralSettings;
@@ -12,6 +13,7 @@ use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\View\View;
use Plugins\Larablog\Membership\Domain\MembershipService;
use Spatie\Permission\Models\Role;
class AuthController extends Controller
@@ -40,10 +42,22 @@ class AuthController extends Controller
return redirect('/login.shtml');
}
$membership = null;
try {
if (class_exists(MembershipService::class)
&& app(PluginManager::class)->isEnabled('larablog/membership')) {
$membership = app(MembershipService::class)
->statusFor(Auth::user());
}
} catch (\Throwable) {
$membership = null;
}
return view('theme::profile', [
'user' => Auth::user(),
'settings' => $settings,
'categories' => Category::query()->orderBy('display_order')->get(),
'membership' => $membership,
'seo' => ['title' => '资料 - '.$settings->site_name],
]);
}
+37 -6
View File
@@ -14,12 +14,13 @@ use App\Models\Link;
use App\Models\Tag;
use App\Settings\BlogSettings;
use App\Settings\GeneralSettings;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class BlogController extends Controller
{
public function index(Request $request, GeneralSettings $settings, SeoPresenter $seo): View|\Illuminate\Http\RedirectResponse
public function index(Request $request, GeneralSettings $settings, SeoPresenter $seo): View|RedirectResponse
{
// /index.php is often rewritten to / by PHP built-in server / nginx try_files.
$action = $request->query('action');
@@ -70,6 +71,19 @@ class BlogController extends Controller
$articles = $query->paginate($perPage)->withQueryString();
if ($cid) {
$category = Category::query()->find($cid);
if ($category !== null) {
return view('theme::category', [
'category' => $category,
'articles' => $articles,
'categories' => Category::query()->orderBy('display_order')->get(),
'settings' => $settings,
'seo' => $seo->forCategory($category),
]);
}
}
return view('theme::home', [
'articles' => $articles,
'categories' => Category::query()->orderBy('display_order')->get(),
@@ -78,7 +92,7 @@ class BlogController extends Controller
]);
}
public function bySlug(string $slug, GeneralSettings $settings, SeoPresenter $seo): View|\Illuminate\Http\RedirectResponse
public function bySlug(string $slug, GeneralSettings $settings, SeoPresenter $seo): View|RedirectResponse
{
$article = Article::query()
->visible()
@@ -89,7 +103,7 @@ class BlogController extends Controller
return redirect('/show-'.$article->id.'.shtml', 301);
}
public function show(Request $request, int $id, GeneralSettings $settings, SeoPresenter $seo, ArticleAccess $access): View|\Illuminate\Http\RedirectResponse
public function show(Request $request, int $id, GeneralSettings $settings, SeoPresenter $seo, ArticleAccess $access): View|RedirectResponse
{
$article = Article::query()
->with(['category', 'user', 'tags', 'comments' => fn ($q) => $q->visible()->orderBy('published_at')])
@@ -151,11 +165,28 @@ class BlogController extends Controller
return $this->index($request, app(GeneralSettings::class), app(SeoPresenter::class));
}
public function category(Request $request, int $cid): View
public function category(Request $request, int $cid, GeneralSettings $settings, SeoPresenter $seo): View
{
$request->merge(['cid' => $cid]);
$category = Category::query()->findOrFail($cid);
$perPage = max(1, (int) app(BlogSettings::class)->posts_per_page);
return $this->index($request, app(GeneralSettings::class), app(SeoPresenter::class));
$articles = Article::query()
->with(['category', 'user', 'tags'])
->visible()
->published()
->where('category_id', $category->id)
->orderByDesc('stick')
->orderByDesc('published_at')
->paginate($perPage)
->withQueryString();
return view('theme::category', [
'category' => $category,
'articles' => $articles,
'categories' => Category::query()->orderBy('display_order')->get(),
'settings' => $settings,
'seo' => $seo->forCategory($category),
]);
}
public function tagsList(): View
+20
View File
@@ -5,6 +5,7 @@ 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;
@@ -17,6 +18,14 @@ class SeoController extends Controller
$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(
@@ -64,6 +73,7 @@ class SeoController extends Controller
'',
'## 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',
'',
@@ -77,6 +87,16 @@ class SeoController extends Controller
.($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']);
}