318 lines
11 KiB
PHP
318 lines
11 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\DownloadLog;
|
|
use App\Models\Package;
|
|
use App\Services\RepoFormatter;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Http\Request;
|
|
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
|
|
|
class RepoController extends Controller
|
|
{
|
|
public function index(Request $request): JsonResponse
|
|
{
|
|
$allowPrerelease = (string) $request->query('allow_prerelease', '0') === '1';
|
|
|
|
$relation = $allowPrerelease ? 'latestVersion' : 'latestStableVersion';
|
|
$query = Package::query()
|
|
->with([$relation, 'categories'])
|
|
->where('status', 'published');
|
|
|
|
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('summary', 'like', '%' . $keyword . '%')
|
|
->orWhere('slug', 'like', '%' . $keyword . '%');
|
|
});
|
|
}
|
|
|
|
if ($category = $request->query('category')) {
|
|
$query->whereHas('categories', function ($q) use ($category) {
|
|
$q->where('slug', $category);
|
|
});
|
|
}
|
|
|
|
$sort = $request->query('sort', 'latest');
|
|
if ($sort === 'downloads') {
|
|
$query->orderByDesc('download_count')->orderByDesc('sort_order');
|
|
} elseif ($sort === 'name') {
|
|
$query->orderBy('name');
|
|
} else {
|
|
$query->orderByDesc('sort_order')->orderByDesc('updated_at');
|
|
}
|
|
|
|
$size = min(max((int) $request->query('size', 20), 1), 100);
|
|
$page = max((int) $request->query('page', 1), 1);
|
|
$result = $query->paginate($size, ['*'], 'page', $page);
|
|
|
|
return response()->json([
|
|
'code' => 0,
|
|
'message' => 'ok',
|
|
'data' => [
|
|
'page' => $result->currentPage(),
|
|
'size' => $result->perPage(),
|
|
'total' => $result->total(),
|
|
'items' => collect($result->items())->map(function ($package) use ($allowPrerelease) {
|
|
return RepoFormatter::packageListItem($package, $allowPrerelease);
|
|
})->values()->all(),
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function detail(string $type, string $slug): JsonResponse
|
|
{
|
|
$package = Package::query()
|
|
->with([
|
|
'categories',
|
|
'screenshots',
|
|
'versions' => function ($q) {
|
|
$q->orderByDesc('published_at')->orderByDesc('id');
|
|
},
|
|
])
|
|
->where('status', 'published')
|
|
->where('type', $type)
|
|
->where('slug', $slug)
|
|
->first();
|
|
|
|
if (!$package) {
|
|
return response()->json([
|
|
'code' => 404,
|
|
'message' => 'package not found',
|
|
'data' => null,
|
|
], 404);
|
|
}
|
|
|
|
return response()->json([
|
|
'code' => 0,
|
|
'message' => 'ok',
|
|
'data' => RepoFormatter::packageDetail($package, false),
|
|
]);
|
|
}
|
|
|
|
public function checkUpdates(Request $request): JsonResponse
|
|
{
|
|
$installed = $request->input('installed', []);
|
|
$typechoVersion = (string) $request->input('typecho_version', '');
|
|
$phpVersion = (string) $request->input('php_version', '');
|
|
$allowPrerelease = (bool) $request->input('allow_prerelease', false);
|
|
|
|
$updates = [];
|
|
$upToDate = [];
|
|
$incompatible = [];
|
|
$unknown = [];
|
|
|
|
foreach ($installed as $item) {
|
|
$type = $item['type'] ?? '';
|
|
$slug = $item['slug'] ?? '';
|
|
$currentVersion = $item['version'] ?? '';
|
|
|
|
$package = Package::query()
|
|
->with(['latestStableVersion', 'latestVersion'])
|
|
->where('status', 'published')
|
|
->where('type', $type)
|
|
->where('slug', $slug)
|
|
->first();
|
|
|
|
$latest = $allowPrerelease
|
|
? ($package?->latestVersion ?: $package?->latestStableVersion)
|
|
: $package?->latestStableVersion;
|
|
|
|
if (!$package || !$latest) {
|
|
$unknown[] = [
|
|
'type' => $type,
|
|
'slug' => $slug,
|
|
'current_version' => $currentVersion,
|
|
];
|
|
continue;
|
|
}
|
|
|
|
if (version_compare($currentVersion, $latest->version, '>=')) {
|
|
$upToDate[] = [
|
|
'type' => $type,
|
|
'slug' => $slug,
|
|
'current_version' => $currentVersion,
|
|
'latest_version' => $latest->version,
|
|
];
|
|
continue;
|
|
}
|
|
|
|
$compatibilityOk = $this->isCompatible($latest, $typechoVersion, $phpVersion);
|
|
if (!$compatibilityOk) {
|
|
$incompatible[] = [
|
|
'type' => $type,
|
|
'slug' => $slug,
|
|
'current_version' => $currentVersion,
|
|
'latest_version' => $latest->version,
|
|
];
|
|
continue;
|
|
}
|
|
|
|
$updates[] = [
|
|
'type' => $type,
|
|
'slug' => $slug,
|
|
'name' => $package->name,
|
|
'current_version' => $currentVersion,
|
|
'latest_version' => $latest->version,
|
|
'changelog' => $latest->changelog,
|
|
'compatibility_ok' => true,
|
|
'package' => [
|
|
'size' => (int) $latest->package_size,
|
|
'sha256' => $latest->sha256,
|
|
'download_url' => $latest->package_url,
|
|
],
|
|
];
|
|
}
|
|
|
|
return response()->json([
|
|
'code' => 0,
|
|
'message' => 'ok',
|
|
'data' => [
|
|
'updates' => $updates,
|
|
'up_to_date' => $upToDate,
|
|
'incompatible' => $incompatible,
|
|
'unknown' => $unknown,
|
|
'store_plugin_update' => null,
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function download(Request $request, string $type, string $slug, string $version): JsonResponse|BinaryFileResponse|RedirectResponse
|
|
{
|
|
$pluginToken = (string) config('store.plugin_access_token', '');
|
|
$requestToken = (string) ($request->header('X-Store-Plugin-Token') ?: $request->query('access_token', ''));
|
|
|
|
if ($pluginToken !== '' && $requestToken !== $pluginToken) {
|
|
return response()->json([
|
|
'code' => 403,
|
|
'message' => 'download forbidden',
|
|
'data' => null,
|
|
], 403);
|
|
}
|
|
|
|
$package = Package::query()
|
|
->where('status', 'published')
|
|
->where('type', $type)
|
|
->where('slug', $slug)
|
|
->first();
|
|
|
|
if (!$package) {
|
|
return response()->json([
|
|
'code' => 404,
|
|
'message' => 'package not found',
|
|
'data' => null,
|
|
], 404);
|
|
}
|
|
|
|
$versionModel = $version === 'latest'
|
|
? $package->versions()->where('is_stable', 1)->where('is_latest', 1)->first()
|
|
: $package->versions()->where('version', $version)->first();
|
|
|
|
if (!$versionModel) {
|
|
return response()->json([
|
|
'code' => 404,
|
|
'message' => 'version not found',
|
|
'data' => null,
|
|
], 404);
|
|
}
|
|
|
|
$filePath = storage_path('app/packages/' . $type . '/' . $slug . '/' . $versionModel->version . '.zip');
|
|
$hasLocalFile = is_file($filePath);
|
|
$redirect = (string) $request->query('redirect', '0') === '1';
|
|
|
|
if ($redirect) {
|
|
DownloadLog::create([
|
|
'package_id' => $package->id,
|
|
'version_id' => $versionModel->id,
|
|
'site_url' => (string) $request->query('site_url', ''),
|
|
'typecho_version' => (string) $request->query('typecho_version', ''),
|
|
'php_version' => (string) $request->query('php_version', ''),
|
|
'ip' => (string) $request->ip(),
|
|
'user_agent' => (string) $request->userAgent(),
|
|
'created_at' => now(),
|
|
]);
|
|
|
|
$package->increment('download_count');
|
|
$versionModel->increment('download_count');
|
|
|
|
if ($hasLocalFile) {
|
|
return response()->download($filePath, $slug . '-' . $versionModel->version . '.zip', [
|
|
'Content-Type' => 'application/zip',
|
|
]);
|
|
}
|
|
|
|
if (!empty($versionModel->package_url)) {
|
|
return redirect()->away($versionModel->package_url);
|
|
}
|
|
|
|
return response()->json([
|
|
'code' => 404,
|
|
'message' => 'package file not found',
|
|
'data' => null,
|
|
], 404);
|
|
}
|
|
|
|
return response()->json([
|
|
'code' => 0,
|
|
'message' => 'ok',
|
|
'data' => [
|
|
'type' => $type,
|
|
'slug' => $slug,
|
|
'version' => $versionModel->version,
|
|
'package' => [
|
|
'download_url' => $this->buildDownloadUrl($request, $type, $slug, $versionModel->version),
|
|
'size' => (int) $versionModel->package_size,
|
|
'sha256' => $versionModel->sha256,
|
|
'filename' => $slug . '-' . $versionModel->version . '.zip',
|
|
'storage' => [
|
|
'driver' => $hasLocalFile ? 'local' : 'remote',
|
|
'exists' => $hasLocalFile,
|
|
],
|
|
],
|
|
],
|
|
]);
|
|
}
|
|
|
|
private function isCompatible($version, string $typechoVersion, string $phpVersion): bool
|
|
{
|
|
if ($typechoVersion !== '' && $version->typecho_min !== '' && version_compare($typechoVersion, $version->typecho_min, '<')) {
|
|
return false;
|
|
}
|
|
if ($typechoVersion !== '' && $version->typecho_max !== '') {
|
|
$max = str_replace('*', '999', $version->typecho_max);
|
|
if (version_compare($typechoVersion, $max, '>')) {
|
|
return false;
|
|
}
|
|
}
|
|
if ($phpVersion !== '' && $version->php_min !== '' && version_compare($phpVersion, $version->php_min, '<')) {
|
|
return false;
|
|
}
|
|
if ($phpVersion !== '' && $version->php_max !== '') {
|
|
$max = str_replace('*', '999', $version->php_max);
|
|
if (version_compare($phpVersion, $max, '>')) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private function buildDownloadUrl(Request $request, string $type, string $slug, string $version): string
|
|
{
|
|
$path = '/api/v1/repo/download/' . $type . '/' . $slug . '/' . $version . '?redirect=1';
|
|
$appUrl = rtrim((string) config('app.url', ''), '/');
|
|
|
|
if ($appUrl !== '') {
|
|
return $appUrl . $path;
|
|
}
|
|
|
|
return $request->getSchemeAndHttpHost() . $path;
|
|
}
|
|
}
|