first commit

This commit is contained in:
浪子
2026-03-19 16:44:38 +08:00
commit ff2af385b9
100 changed files with 16826 additions and 0 deletions
@@ -0,0 +1,276 @@
<?php
namespace App\Http\Controllers\WebAdmin;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\StoreCategoryRequest;
use App\Http\Requests\Admin\StorePackageRequest;
use App\Models\Category;
use App\Models\Package;
use App\Models\Version;
use App\Services\AdminPackageService;
use App\Services\VersionPublishService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class DashboardController extends Controller
{
public function __construct(
private readonly AdminPackageService $service,
private readonly VersionPublishService $publishService,
) {
}
public function home(): View
{
$stats = [
'packages' => Package::count(),
'plugins' => Package::where('type', 'plugin')->count(),
'themes' => Package::where('type', 'theme')->count(),
'versions' => Version::count(),
'downloads' => (int) Package::sum('download_count'),
'categories' => Category::count(),
];
$recentPackages = Package::query()
->with(['categories', 'latestStableVersion'])
->latest('updated_at')
->limit(6)
->get();
$recentVersions = Version::query()
->with('package')
->latest('published_at')
->limit(8)
->get();
return view('admin.dashboard', compact('stats', 'recentPackages', 'recentVersions'));
}
public function packages(Request $request): View
{
$query = Package::query()->with(['categories', 'latestStableVersion']);
if ($type = $request->query('type')) {
$query->where('type', $type);
}
if ($status = $request->query('status')) {
$query->where('status', $status);
}
if ($keyword = trim((string) $request->query('keyword', ''))) {
$query->where(function ($q) use ($keyword) {
$q->where('name', 'like', '%' . $keyword . '%')
->orWhere('slug', 'like', '%' . $keyword . '%')
->orWhere('summary', 'like', '%' . $keyword . '%');
});
}
$packages = $query->orderByDesc('sort_order')->orderByDesc('updated_at')->paginate(15)->withQueryString();
return view('admin.packages.index', [
'packages' => $packages,
'filters' => [
'type' => (string) $request->query('type', ''),
'status' => (string) $request->query('status', ''),
'keyword' => (string) $request->query('keyword', ''),
],
]);
}
public function storePackage(StorePackageRequest $request): RedirectResponse
{
$data = $request->validated();
$data['categories'] = $this->normalizeCategories($request);
$package = $this->service->createPackage($data);
return redirect()->route('webadmin.packages.show', [$package->type, $package->slug])->with('success', '扩展已创建');
}
public function updatePackage(StorePackageRequest $request, string $type, string $slug): RedirectResponse
{
$package = $this->findPackage($type, $slug);
$data = $request->validated();
$data['categories'] = $this->normalizeCategories($request);
$this->service->updatePackage($package, $data);
return redirect()->route('webadmin.packages.show', [$type, $slug])->with('success', '扩展已更新');
}
public function updatePackageStatus(Request $request, string $type, string $slug): RedirectResponse
{
$validated = $request->validate([
'status' => ['required', 'in:draft,published,hidden,deprecated'],
]);
$package = $this->findPackage($type, $slug);
$this->service->updateStatus($package, $validated['status']);
return redirect()->route('webadmin.packages')->with('success', '状态已更新');
}
public function categories(Request $request): View
{
$query = Category::query()->withCount('packages');
if ($type = $request->query('type')) {
$query->where('type', $type);
}
if ($keyword = trim((string) $request->query('keyword', ''))) {
$query->where(function ($q) use ($keyword) {
$q->where('name', 'like', '%' . $keyword . '%')
->orWhere('slug', 'like', '%' . $keyword . '%')
->orWhere('description', 'like', '%' . $keyword . '%');
});
}
$categories = $query->orderBy('type')->orderByDesc('sort_order')->paginate(20)->withQueryString();
return view('admin.categories.index', [
'categories' => $categories,
'filters' => [
'type' => (string) $request->query('type', ''),
'keyword' => (string) $request->query('keyword', ''),
],
]);
}
public function storeCategory(StoreCategoryRequest $request): RedirectResponse
{
Category::create($request->validated());
return redirect()->route('webadmin.categories')->with('success', '分类已创建');
}
public function updateCategory(StoreCategoryRequest $request, int $id): RedirectResponse
{
$category = Category::findOrFail($id);
$category->update($request->validated());
return redirect()->route('webadmin.categories')->with('success', '分类已更新');
}
public function destroyCategory(int $id): RedirectResponse
{
$category = Category::findOrFail($id);
$category->packages()->detach();
$category->delete();
return redirect()->route('webadmin.categories')->with('success', '分类已删除');
}
public function showPackage(string $type, string $slug): View
{
$package = Package::query()
->with(['categories', 'screenshots', 'versions' => fn ($q) => $q->latest('published_at')])
->where('type', $type)
->where('slug', $slug)
->firstOrFail();
return view('admin.packages.show', [
'package' => $package,
]);
}
public function storeVersion(Request $request, string $type, string $slug): RedirectResponse
{
$package = $this->findPackage($type, $slug);
$validated = $request->validate([
'version' => ['required', 'string', 'max:32'],
'changelog' => ['nullable', 'string'],
'typecho_min' => ['nullable', 'string', 'max:32'],
'typecho_max' => ['nullable', 'string', 'max:32'],
'php_min' => ['nullable', 'string', 'max:32'],
'php_max' => ['nullable', 'string', 'max:32'],
'package_url' => ['nullable', 'url', 'max:512'],
'package_size' => ['nullable', 'integer', 'min:0'],
'sha256' => ['nullable', 'string', 'max:64'],
'is_stable' => ['nullable', 'boolean'],
'is_latest' => ['nullable', 'boolean'],
'published_at' => ['nullable', 'date'],
'php_extensions' => ['nullable', 'string'],
]);
$payload = [
'version' => $validated['version'],
'changelog' => $validated['changelog'] ?? '',
'typecho_min' => $validated['typecho_min'] ?? '1.2.0',
'typecho_max' => $validated['typecho_max'] ?? '',
'php_min' => $validated['php_min'] ?? '7.4',
'php_max' => $validated['php_max'] ?? '',
'php_extensions' => collect(explode(',', (string) ($validated['php_extensions'] ?? '')))
->map(fn ($item) => trim($item))
->filter()
->values()
->all(),
'package_url' => $validated['package_url'] ?? '',
'package_size' => (int) ($validated['package_size'] ?? 0),
'sha256' => $validated['sha256'] ?? str_repeat('0', 64),
'is_stable' => (bool) ($validated['is_stable'] ?? false),
'mark_as_latest' => (bool) ($validated['is_latest'] ?? false),
'published_at' => $validated['published_at'] ?? now(),
];
$version = $this->service->createVersion($package, $payload);
return redirect()->route('webadmin.packages.show', [$type, $slug])->with('success', '版本已添加:v' . $version->version);
}
public function publishVersion(Request $request, string $type, string $slug): RedirectResponse
{
$package = $this->findPackage($type, $slug);
$validated = $request->validate([
'package_file' => ['required', 'file', 'mimes:zip', 'max:51200'],
'is_stable' => ['nullable', 'boolean'],
'mark_as_latest' => ['nullable', 'boolean'],
'changelog' => ['nullable', 'string'],
'published_at' => ['nullable', 'date'],
]);
try {
$version = $this->publishService->publishFromZip($package, $request->file('package_file'), $validated);
} catch (\Throwable $e) {
return back()->withInput()->with('error', 'zip 发布失败:' . $e->getMessage());
}
$msg = 'zip 发布成功:v' . $version->version;
$msg .= $version->is_stable ? ' · stable' : ' · prerelease';
$msg .= $version->is_latest ? ' · latest' : '';
return redirect()->route('webadmin.packages.show', [$type, $slug])->with('success', $msg);
}
public function destroyVersion(string $type, string $slug, int $id): RedirectResponse
{
$package = $this->findPackage($type, $slug);
$version = $package->versions()->where('id', $id)->firstOrFail();
$label = $version->version;
$this->service->deleteVersion($version);
return redirect()->route('webadmin.packages.show', [$type, $slug])->with('success', '版本已删除:v' . $label);
}
private function findPackage(string $type, string $slug): Package
{
return Package::query()
->where('type', $type)
->where('slug', $slug)
->firstOrFail();
}
private function normalizeCategories(Request $request): array
{
$raw = (string) $request->input('categories_text', '');
return collect(preg_split('/[,\s]+/u', $raw))
->map(fn ($item) => trim((string) $item))
->filter()
->values()
->all();
}
}