refactor plugins marketplace
This commit is contained in:
parent
078fcf47ec
commit
4c735aa8e4
|
|
@ -8,46 +8,28 @@ use App\Services\Unzip;
|
||||||
use Composer\CaBundle\CaBundle;
|
use Composer\CaBundle\CaBundle;
|
||||||
use Composer\Semver\Comparator;
|
use Composer\Semver\Comparator;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use GuzzleHttp\Client;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
class MarketController extends Controller
|
class MarketController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
public function marketData(PluginManager $manager, Client $client)
|
||||||
* Guzzle HTTP client.
|
|
||||||
*
|
|
||||||
* @var \GuzzleHttp\Client
|
|
||||||
*/
|
|
||||||
protected $guzzle;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cache for plugins registry.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $registryCache;
|
|
||||||
|
|
||||||
public function __construct(\GuzzleHttp\Client $guzzle)
|
|
||||||
{
|
{
|
||||||
$this->guzzle = $guzzle;
|
$plugins = $this->fetch($client)->map(function ($item) use ($manager) {
|
||||||
}
|
|
||||||
|
|
||||||
public function marketData(PluginManager $manager)
|
|
||||||
{
|
|
||||||
$plugins = collect($this->getAllAvailablePlugins())->map(function ($item) use ($manager) {
|
|
||||||
$plugin = $manager->get($item['name']);
|
$plugin = $manager->get($item['name']);
|
||||||
|
|
||||||
if ($plugin) {
|
if ($plugin) {
|
||||||
$item['enabled'] = $plugin->isEnabled();
|
$item['enabled'] = $plugin->isEnabled();
|
||||||
$item['installed'] = $plugin->version;
|
$item['installed'] = $plugin->version;
|
||||||
$item['update_available'] = Comparator::greaterThan($item['version'], $item['installed']);
|
$item['can_update'] = Comparator::greaterThan($item['version'], $item['installed']);
|
||||||
} else {
|
} else {
|
||||||
$item['installed'] = false;
|
$item['installed'] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$requirements = Arr::get($item, 'require', []);
|
$requirements = Arr::get($item, 'require', []);
|
||||||
unset($item['require']);
|
unset($item['require']);
|
||||||
|
|
||||||
$item['dependencies'] = [
|
$item['dependencies'] = [
|
||||||
'all' => $requirements,
|
'all' => $requirements,
|
||||||
'unsatisfied' => $manager->getUnsatisfied(new Plugin('', $item)),
|
'unsatisfied' => $manager->getUnsatisfied(new Plugin('', $item)),
|
||||||
|
|
@ -59,10 +41,15 @@ class MarketController extends Controller
|
||||||
return $plugins;
|
return $plugins;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function download(Request $request, PluginManager $manager, Unzip $unzip)
|
public function download(
|
||||||
{
|
Request $request,
|
||||||
$name = $request->get('name');
|
PluginManager $manager,
|
||||||
$metadata = $this->getPluginMetadata($name);
|
Client $client,
|
||||||
|
Unzip $unzip
|
||||||
|
) {
|
||||||
|
$name = $request->input('name');
|
||||||
|
$plugins = $this->fetch($client);
|
||||||
|
$metadata = $plugins->firstWhere('name', $name);
|
||||||
|
|
||||||
if (!$metadata) {
|
if (!$metadata) {
|
||||||
return json(trans('admin.plugins.market.non-existent', ['plugin' => $name]), 1);
|
return json(trans('admin.plugins.market.non-existent', ['plugin' => $name]), 1);
|
||||||
|
|
@ -77,56 +64,38 @@ class MarketController extends Controller
|
||||||
return json(trans('admin.plugins.market.unresolved'), 1, compact('reason'));
|
return json(trans('admin.plugins.market.unresolved'), 1, compact('reason'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$url = $metadata['dist']['url'];
|
$path = tempnam(sys_get_temp_dir(), $name);
|
||||||
$pluginsDir = $manager->getPluginsDirs()->first();
|
|
||||||
$path = storage_path("packages/$name".'_'.$metadata['version'].'.zip');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->guzzle->get($url, [
|
$client->get($metadata['dist']['url'], [
|
||||||
'sink' => $path,
|
'sink' => $path,
|
||||||
'verify' => CaBundle::getSystemCaRootBundlePath(),
|
'verify' => CaBundle::getSystemCaRootBundlePath(),
|
||||||
]);
|
]);
|
||||||
$unzip->extract($path, $pluginsDir);
|
$unzip->extract($path, $manager->getPluginsDirs()->first());
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
return json($e->getMessage(), 1);
|
report($e);
|
||||||
|
|
||||||
|
return json(trans('admin.download.errors.download', ['error' => $e->getMessage()]), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return json(trans('admin.plugins.market.install-success'), 0);
|
return json(trans('admin.plugins.market.install-success'), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getPluginMetadata($name)
|
protected function fetch(Client $client): Collection
|
||||||
{
|
{
|
||||||
return collect($this->getAllAvailablePlugins())->firstWhere('name', $name);
|
$plugins = collect(explode(',', config('plugins.registry')))
|
||||||
}
|
->map(function ($registry) use ($client) {
|
||||||
|
|
||||||
protected function getAllAvailablePlugins()
|
|
||||||
{
|
|
||||||
$registryVersion = 1;
|
|
||||||
if (app()->runningUnitTests() || !$this->registryCache) {
|
|
||||||
$registries = collect(explode(',', config('plugins.registry')));
|
|
||||||
$this->registryCache = $registries->map(function ($registry) use ($registryVersion) {
|
|
||||||
try {
|
try {
|
||||||
$pluginsJson = $this->guzzle->request(
|
$body = $client->get(trim($registry), [
|
||||||
'GET',
|
'verify' => CaBundle::getSystemCaRootBundlePath(),
|
||||||
trim($registry),
|
])->getBody();
|
||||||
['verify' => \Composer\CaBundle\CaBundle::getSystemCaRootBundlePath()]
|
|
||||||
)->getBody();
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
throw new Exception(trans('admin.plugins.market.connection-error', ['error' => htmlentities($e->getMessage())]));
|
throw new Exception(trans('admin.plugins.market.connection-error', ['error' => $e->getMessage()]));
|
||||||
}
|
}
|
||||||
|
|
||||||
$registryData = json_decode($pluginsJson, true);
|
return Arr::get(json_decode($body, true), 'packages', []);
|
||||||
$received = Arr::get($registryData, 'version');
|
})
|
||||||
abort_if(
|
->flatten(1);
|
||||||
is_int($received) && $received != $registryVersion,
|
|
||||||
500,
|
|
||||||
"Only version $registryVersion of market registry is accepted."
|
|
||||||
);
|
|
||||||
|
|
||||||
return Arr::get($registryData, 'packages', []);
|
return $plugins;
|
||||||
})->flatten(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->registryCache;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@
|
||||||
<span v-else-if="props.column.field === 'operations'">
|
<span v-else-if="props.column.field === 'operations'">
|
||||||
<template v-if="props.row.installed">
|
<template v-if="props.row.installed">
|
||||||
<button
|
<button
|
||||||
v-if="props.row.update_available"
|
v-if="props.row.can_update"
|
||||||
class="btn btn-success"
|
class="btn btn-success"
|
||||||
:disabled="installing === props.row.name"
|
:disabled="installing === props.row.name"
|
||||||
@click="updatePlugin(props.row)"
|
@click="updatePlugin(props.row)"
|
||||||
|
|
@ -145,7 +145,7 @@ export default {
|
||||||
)
|
)
|
||||||
if (code === 0) {
|
if (code === 0) {
|
||||||
toast.success(message)
|
toast.success(message)
|
||||||
this.plugins[originalIndex].update_available = false
|
this.plugins[originalIndex].can_update = false
|
||||||
this.plugins[originalIndex].installed = true
|
this.plugins[originalIndex].installed = true
|
||||||
} else if (data && data.reason) {
|
} else if (data && data.reason) {
|
||||||
alertUnresolvedPlugins(message, data.reason)
|
alertUnresolvedPlugins(message, data.reason)
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ test('render dependencies', async () => {
|
||||||
test('render operation buttons', async () => {
|
test('render operation buttons', async () => {
|
||||||
Vue.prototype.$http.get.mockResolvedValue([
|
Vue.prototype.$http.get.mockResolvedValue([
|
||||||
{
|
{
|
||||||
name: 'a', dependencies: { all: {}, unsatisfied: {} }, installed: true, update_available: true,
|
name: 'a', dependencies: { all: {}, unsatisfied: {} }, installed: true, can_update: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'b', dependencies: { all: {}, unsatisfied: {} }, installed: true, enabled: true,
|
name: 'b', dependencies: { all: {}, unsatisfied: {} }, installed: true, enabled: true,
|
||||||
|
|
@ -90,7 +90,7 @@ test('update plugin', async () => {
|
||||||
version: '2.0.0',
|
version: '2.0.0',
|
||||||
dependencies: { all: {}, unsatisfied: {} },
|
dependencies: { all: {}, unsatisfied: {} },
|
||||||
installed: '1.0.0',
|
installed: '1.0.0',
|
||||||
update_available: true,
|
can_update: true,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
Vue.prototype.$http.post
|
Vue.prototype.$http.post
|
||||||
|
|
|
||||||
|
|
@ -17,24 +17,22 @@ class MarketControllerTest extends TestCase
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
$this->actAs('superAdmin');
|
$this->actingAs(factory(\App\Models\User::class, 'superAdmin')->create());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDownload()
|
public function testDownload()
|
||||||
{
|
{
|
||||||
$this->setupGuzzleClientMock();
|
$this->setupGuzzleClientMock();
|
||||||
|
|
||||||
// Try to download a non-existent plugin
|
|
||||||
$this->appendToGuzzleQueue(200, [], json_encode([
|
$this->appendToGuzzleQueue(200, [], json_encode([
|
||||||
'version' => 1,
|
'version' => 1,
|
||||||
'packages' => [],
|
'packages' => [],
|
||||||
]));
|
]));
|
||||||
$this->postJson('/admin/plugins/market/download', [
|
$this->postJson('/admin/plugins/market/download', ['name' => 'nope'])
|
||||||
'name' => 'non-existent-plugin',
|
->assertJson([
|
||||||
])->assertJson([
|
'code' => 1,
|
||||||
'code' => 1,
|
'message' => trans('admin.plugins.market.non-existent', ['plugin' => 'nope']),
|
||||||
'message' => trans('admin.plugins.market.non-existent', ['plugin' => 'non-existent-plugin']),
|
]);
|
||||||
]);
|
|
||||||
|
|
||||||
// Unresolved plugin.
|
// Unresolved plugin.
|
||||||
$fakeRegistry = json_encode(['packages' => [
|
$fakeRegistry = json_encode(['packages' => [
|
||||||
|
|
@ -88,7 +86,7 @@ class MarketControllerTest extends TestCase
|
||||||
public function testMarketData()
|
public function testMarketData()
|
||||||
{
|
{
|
||||||
$this->setupGuzzleClientMock([
|
$this->setupGuzzleClientMock([
|
||||||
new RequestException('Connection Error', new Request('POST', 'whatever')),
|
new RequestException('Connection Error', new Request('POST', '')),
|
||||||
new Response(200, [], json_encode(['version' => 1, 'packages' => [
|
new Response(200, [], json_encode(['version' => 1, 'packages' => [
|
||||||
[
|
[
|
||||||
'name' => 'fake1',
|
'name' => 'fake1',
|
||||||
|
|
@ -109,11 +107,9 @@ class MarketControllerTest extends TestCase
|
||||||
'require' => [],
|
'require' => [],
|
||||||
],
|
],
|
||||||
]])),
|
]])),
|
||||||
new Response(200, [], json_encode(['version' => 0])),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Expected an exception, but unable to be asserted.
|
$this->getJson('/admin/plugins/market/list')->assertStatus(500);
|
||||||
$this->getJson('/admin/plugins/market/list');
|
|
||||||
|
|
||||||
$this->mock(PluginManager::class, function ($mock) {
|
$this->mock(PluginManager::class, function ($mock) {
|
||||||
$mock->shouldReceive('get')
|
$mock->shouldReceive('get')
|
||||||
|
|
@ -139,8 +135,5 @@ class MarketControllerTest extends TestCase
|
||||||
'dependencies',
|
'dependencies',
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->getJson('/admin/plugins/market/list')
|
|
||||||
->assertJson(['message' => 'Only version 1 of market registry is accepted.']);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user