04a2034975
- storefront 全站改为极简黑白配色:直角、细线分隔、卡片 hover 浅底、深色模式(跟随系统+手动切换) - 抽出 storefront/partials/card 复用卡片 - 一并提交此前暂存的客户端注册 API、站点设置、后台页面与测试 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
244 lines
8.3 KiB
PHP
244 lines
8.3 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\Category;
|
|
use App\Models\Package;
|
|
use App\Models\Version;
|
|
use Illuminate\Support\Arr;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\File;
|
|
use Illuminate\Support\Str;
|
|
|
|
class AdminPackageService
|
|
{
|
|
public function createPackage(array $data): Package
|
|
{
|
|
return DB::transaction(function () use ($data) {
|
|
$package = Package::query()->create([
|
|
'type' => $data['type'],
|
|
'slug' => $data['slug'],
|
|
'name' => $data['name'],
|
|
'summary' => $data['summary'] ?? '',
|
|
'description' => $data['description'] ?? '',
|
|
'author' => $data['author'] ?? '',
|
|
'homepage' => $data['homepage'] ?? '',
|
|
'icon_url' => $data['icon_url'] ?? '',
|
|
'license' => $data['license'] ?? '',
|
|
'status' => $data['status'] ?? 'draft',
|
|
'is_featured' => (bool) ($data['is_featured'] ?? false),
|
|
'sort_order' => (int) ($data['sort_order'] ?? 0),
|
|
'download_count' => 0,
|
|
'latest_version' => '',
|
|
]);
|
|
|
|
$this->syncCategories($package, Arr::get($data, 'categories', []));
|
|
|
|
return $package->load(['categories', 'latestStableVersion']);
|
|
});
|
|
}
|
|
|
|
public function updatePackage(Package $package, array $data): Package
|
|
{
|
|
return DB::transaction(function () use ($package, $data) {
|
|
$package->fill([
|
|
'name' => $data['name'] ?? $package->name,
|
|
'summary' => $data['summary'] ?? $package->summary,
|
|
'description' => $data['description'] ?? $package->description,
|
|
'author' => $data['author'] ?? $package->author,
|
|
'homepage' => $data['homepage'] ?? $package->homepage,
|
|
'icon_url' => $data['icon_url'] ?? $package->icon_url,
|
|
'license' => $data['license'] ?? $package->license,
|
|
'status' => $data['status'] ?? $package->status,
|
|
'is_featured' => array_key_exists('is_featured', $data) ? (bool) $data['is_featured'] : $package->is_featured,
|
|
'sort_order' => array_key_exists('sort_order', $data) ? (int) $data['sort_order'] : $package->sort_order,
|
|
]);
|
|
$package->save();
|
|
|
|
if (array_key_exists('categories', $data)) {
|
|
$this->syncCategories($package, Arr::get($data, 'categories', []));
|
|
}
|
|
|
|
$this->refreshLatestVersion($package->fresh());
|
|
|
|
return $package->fresh(['categories', 'latestStableVersion']);
|
|
});
|
|
}
|
|
|
|
public function updateStatus(Package $package, string $status): Package
|
|
{
|
|
$package->status = $status;
|
|
$package->save();
|
|
|
|
return $package->fresh(['categories', 'latestStableVersion']);
|
|
}
|
|
|
|
public function createVersion(Package $package, array $data): Version
|
|
{
|
|
return DB::transaction(function () use ($package, $data) {
|
|
$markAsLatest = array_key_exists('mark_as_latest', $data) ? (bool) $data['mark_as_latest'] : true;
|
|
|
|
if ($markAsLatest) {
|
|
$package->versions()->update(['is_latest' => false]);
|
|
}
|
|
|
|
$version = $package->versions()->create([
|
|
'version' => $data['version'],
|
|
'changelog' => $data['changelog'] ?? '',
|
|
'typecho_min' => $data['typecho_min'] ?? '1.2.0',
|
|
'typecho_max' => $data['typecho_max'] ?? '',
|
|
'php_min' => $data['php_min'] ?? '7.4',
|
|
'php_max' => $data['php_max'] ?? '',
|
|
'php_extensions' => json_encode($data['php_extensions'] ?? [], JSON_UNESCAPED_UNICODE),
|
|
'package_url' => $data['package_url'],
|
|
'package_size' => (int) ($data['package_size'] ?? 0),
|
|
'sha256' => strtolower($data['sha256']),
|
|
'is_stable' => array_key_exists('is_stable', $data) ? (bool) $data['is_stable'] : true,
|
|
'is_latest' => $markAsLatest,
|
|
'download_count' => 0,
|
|
'published_at' => $data['published_at'] ?? now(),
|
|
]);
|
|
|
|
$this->refreshLatestVersion($package->fresh());
|
|
|
|
return $version->fresh();
|
|
});
|
|
}
|
|
|
|
public function deleteVersion(Version $version): void
|
|
{
|
|
DB::transaction(function () use ($version) {
|
|
$package = $version->package;
|
|
$version->delete();
|
|
if ($package) {
|
|
$this->refreshLatestVersion($package->fresh());
|
|
}
|
|
});
|
|
}
|
|
|
|
public function deletePackage(Package $package): void
|
|
{
|
|
$storageDir = storage_path('app/packages/' . $package->type . '/' . $package->slug);
|
|
|
|
DB::transaction(function () use ($package) {
|
|
$package->delete();
|
|
});
|
|
|
|
if (is_dir($storageDir)) {
|
|
File::deleteDirectory($storageDir);
|
|
}
|
|
}
|
|
|
|
public function refreshLatestVersion(Package $package): void
|
|
{
|
|
$latestStable = $package->versions()
|
|
->where('is_stable', true)
|
|
->orderByDesc('is_latest')
|
|
->orderByDesc('published_at')
|
|
->orderByDesc('id')
|
|
->first();
|
|
|
|
if ($latestStable) {
|
|
$package->latest_version = $latestStable->version;
|
|
$package->save();
|
|
return;
|
|
}
|
|
|
|
$latestAny = $package->versions()
|
|
->orderByDesc('is_latest')
|
|
->orderByDesc('published_at')
|
|
->orderByDesc('id')
|
|
->first();
|
|
|
|
$package->latest_version = $latestAny?->version ?: '';
|
|
$package->save();
|
|
}
|
|
|
|
private function syncCategories(Package $package, array $categorySlugs): void
|
|
{
|
|
$categorySlugs = collect($categorySlugs)
|
|
->map(fn ($item) => trim((string) $item))
|
|
->filter()
|
|
->unique(fn (string $item) => mb_strtolower($item, 'UTF-8'))
|
|
->values()
|
|
->all();
|
|
|
|
if (empty($categorySlugs)) {
|
|
$package->categories()->sync([]);
|
|
return;
|
|
}
|
|
|
|
$categories = Category::query()
|
|
->where('type', $package->type)
|
|
->get(['id', 'type', 'slug', 'name']);
|
|
|
|
$categoryIds = [];
|
|
|
|
foreach ($categorySlugs as $token) {
|
|
$normalizedToken = mb_strtolower($token, 'UTF-8');
|
|
|
|
$existing = $categories->first(function (Category $category) use ($token, $normalizedToken) {
|
|
return $category->name === $token
|
|
|| mb_strtolower($category->slug, 'UTF-8') === $normalizedToken
|
|
|| mb_strtolower($category->name, 'UTF-8') === $normalizedToken;
|
|
});
|
|
|
|
if ($existing) {
|
|
$categoryIds[] = $existing->id;
|
|
continue;
|
|
}
|
|
|
|
$slug = $this->generateCategorySlug($token, $categories);
|
|
$category = Category::query()->create([
|
|
'type' => $package->type,
|
|
'slug' => $slug,
|
|
'name' => $this->generateCategoryName($token),
|
|
'description' => '',
|
|
'sort_order' => 0,
|
|
]);
|
|
|
|
$categories->push($category);
|
|
$categoryIds[] = $category->id;
|
|
}
|
|
|
|
$package->categories()->sync(array_values(array_unique($categoryIds)));
|
|
}
|
|
|
|
private function generateCategorySlug(string $token, $categories): string
|
|
{
|
|
$base = (string) Str::of($token)
|
|
->trim()
|
|
->replace('_', '-')
|
|
->replaceMatches('/\s+/u', '-')
|
|
->replaceMatches('/[^\pL\pN-]+/u', '-')
|
|
->replaceMatches('/-+/u', '-')
|
|
->lower()
|
|
->trim('-');
|
|
|
|
if ($base === '') {
|
|
$base = 'category';
|
|
}
|
|
|
|
$slug = $base;
|
|
$suffix = 2;
|
|
|
|
while ($categories->contains(fn (Category $category) => mb_strtolower($category->slug, 'UTF-8') === mb_strtolower($slug, 'UTF-8'))) {
|
|
$slug = $base . '-' . $suffix;
|
|
$suffix++;
|
|
}
|
|
|
|
return $slug;
|
|
}
|
|
|
|
private function generateCategoryName(string $token): string
|
|
{
|
|
if (preg_match('/^[a-z0-9_-]+$/i', $token) === 1) {
|
|
return (string) Str::of($token)
|
|
->replace(['-', '_'], ' ')
|
|
->title();
|
|
}
|
|
|
|
return trim($token);
|
|
}
|
|
}
|