Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 203 additions & 0 deletions app/Http/Controllers/Api/Application/Plugins/PluginController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
<?php

namespace App\Http\Controllers\Api\Application\Plugins;

use App\Enums\PluginStatus;
use App\Exceptions\PanelException;
use App\Http\Controllers\Api\Application\ApplicationApiController;
use App\Http\Requests\Api\Application\Plugins\ImportFilePluginRequest;
use App\Http\Requests\Api\Application\Plugins\ReadPluginRequest;
use App\Http\Requests\Api\Application\Plugins\UninstallPluginRequest;
use App\Http\Requests\Api\Application\Plugins\WritePluginRequest;
use App\Models\Plugin;
use App\Services\Helpers\PluginService;
use App\Transformers\Api\Application\PluginTransformer;
use Exception;
use Illuminate\Http\Response;
use Spatie\QueryBuilder\QueryBuilder;

class PluginController extends ApplicationApiController
{
/**
* PluginController constructor.
*/
public function __construct(private readonly PluginService $pluginService)
{
parent::__construct();
}

/**
* List plugins
*
* Return all plugins on the Panel.
*
* @return array<array-key, mixed>
*/
public function index(ReadPluginRequest $request): array
{
$plugins = QueryBuilder::for(Plugin::class)
->allowedFilters(['id', 'name', 'author', 'category'])
->allowedSorts(['id', 'name', 'author', 'category'])
->paginate($request->query('per_page') ?? 10);

return $this->fractal->collection($plugins)
->transformWith($this->getTransformer(PluginTransformer::class))
->toArray();
}

/**
* View plugin
*
* Return a single plugin.
*
* @return array<array-key, mixed>
*/
public function view(ReadPluginRequest $request, Plugin $plugin): array
{
return $this->fractal->item($plugin)
->transformWith($this->getTransformer(PluginTransformer::class))
->toArray();
}

/**
* Import plugin (file)
*
* Imports a new plugin file.
*
* @throws Exception
*/
public function importFile(WritePluginRequest $request): Response
{
if (!$request->hasFile('plugin')) {
throw new PanelException("No 'plugin' file in request");
}

$this->pluginService->downloadPluginFromFile($request->file('plugin'));

return new Response('', Response::HTTP_CREATED);
}

/**
* Import plugin (url)
*
* Imports a new plugin from an url.
*
* @throws Exception
*/
public function importUrl(ImportFilePluginRequest $request): Response
{
$this->pluginService->downloadPluginFromUrl($request->input('url'));

return new Response('', Response::HTTP_CREATED);
}

/**
* Install plugin
*
* Installs and enables a plugin.
*
* @return array<array-key, mixed>
*
* @throws Exception
*/
public function install(WritePluginRequest $request, Plugin $plugin): array
{
if ($plugin->status !== PluginStatus::NotInstalled) {
throw new PanelException('Plugin is already installed');
}

$this->pluginService->installPlugin($plugin);

return $this->fractal->item($plugin)
->transformWith($this->getTransformer(PluginTransformer::class))
->toArray();
}

/**
* Update plugin
*
* Downloads and installs an update for a plugin. Will throw if no update is available.
*
* @return array<array-key, mixed>
*
* @throws Exception
*/
public function update(WritePluginRequest $request, Plugin $plugin): array
{
if (!$plugin->isUpdateAvailable()) {
throw new PanelException("Plugin doesn't need updating");
}

$this->pluginService->updatePlugin($plugin);

return $this->fractal->item($plugin)
->transformWith($this->getTransformer(PluginTransformer::class))
->toArray();
}

/**
* Uninstall plugin
*
* Uninstalls a plugin. Optionally it will delete the plugin folder too.
*
* @return array<array-key, mixed>
*
* @throws Exception
*/
public function uninstall(UninstallPluginRequest $request, Plugin $plugin): array
{
if ($plugin->status === PluginStatus::NotInstalled) {
throw new PanelException('Plugin is not installed');
}

$this->pluginService->uninstallPlugin($plugin, $request->boolean('delete'));

return $this->fractal->item($plugin)
->transformWith($this->getTransformer(PluginTransformer::class))
->toArray();
}

/**
* Enable plugin
*
* Enables a plugin.
*
* @return array<array-key, mixed>
*
* @throws Exception
*/
public function enable(WritePluginRequest $request, Plugin $plugin): array
{
if (!$plugin->canEnable()) {
throw new PanelException("Plugin can't be enabled");
}

$this->pluginService->enablePlugin($plugin);

return $this->fractal->item($plugin)
->transformWith($this->getTransformer(PluginTransformer::class))
->toArray();
}

/**
* Disable plugin
*
* Disables a plugin.
*
* @return array<array-key, mixed>
*
* @throws Exception
*/
public function disable(WritePluginRequest $request, Plugin $plugin): array
{
if (!$plugin->canDisable()) {
throw new PanelException("Plugin can't be disabled");
}

$this->pluginService->disablePlugin($plugin);

return $this->fractal->item($plugin)
->transformWith($this->getTransformer(PluginTransformer::class))
->toArray();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace App\Http\Requests\Api\Application\Plugins;

class ImportFilePluginRequest extends WritePluginRequest
{
public function rules(): array
{
return [
'url' => 'required|string',
];
}
}
14 changes: 14 additions & 0 deletions app/Http/Requests/Api/Application/Plugins/ReadPluginRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace App\Http\Requests\Api\Application\Plugins;

use App\Http\Requests\Api\Application\ApplicationApiRequest;
use App\Models\Plugin;
use App\Services\Acl\Api\AdminAcl;

class ReadPluginRequest extends ApplicationApiRequest
{
protected ?string $resource = Plugin::RESOURCE_NAME;

protected int $permission = AdminAcl::READ;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace App\Http\Requests\Api\Application\Plugins;

class UninstallPluginRequest extends WritePluginRequest
{
/**
* @param array<array-key, string|string[]>|null $rules
* @return array<array-key, string|string[]>
*/
public function rules(?array $rules = null): array
{
return [
'delete' => 'boolean',
];
}
}
14 changes: 14 additions & 0 deletions app/Http/Requests/Api/Application/Plugins/WritePluginRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace App\Http\Requests\Api\Application\Plugins;

use App\Http\Requests\Api\Application\ApplicationApiRequest;
use App\Models\Plugin;
use App\Services\Acl\Api\AdminAcl;

class WritePluginRequest extends ApplicationApiRequest
{
protected ?string $resource = Plugin::RESOURCE_NAME;

protected int $permission = AdminAcl::WRITE;
}
1 change: 1 addition & 0 deletions app/Models/ApiKey.php
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ public function getPermission(string $resource): int
Database::RESOURCE_NAME,
Mount::RESOURCE_NAME,
Role::RESOURCE_NAME,
Plugin::RESOURCE_NAME,
];

/** @var string[] */
Expand Down
2 changes: 2 additions & 0 deletions app/Models/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ class Plugin extends Model implements HasPluginSettings
{
use Sushi;

public const RESOURCE_NAME = 'plugin';

protected $primaryKey = 'id';

protected $keyType = 'string';
Expand Down
47 changes: 47 additions & 0 deletions app/Transformers/Api/Application/PluginTransformer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

namespace App\Transformers\Api\Application;

use App\Models\Plugin;

class PluginTransformer extends BaseTransformer
{
/**
* Return the resource name for the JSONAPI output.
*/
public function getResourceName(): string
{
return Plugin::RESOURCE_NAME;
}

/**
* @param Plugin $model
*/
public function transform($model): array
{
return [
'id' => $model->id,
'name' => $model->name,
'author' => $model->author,
'version' => $model->version,
'description' => $model->description,
'category' => $model->category,
'url' => $model->url,
'update_url' => $model->update_url,
'namespace' => $model->namespace,
'class' => $model->class,
'panels' => $model->panels ? explode(',', $model->panels) : null,
'panel_version' => $model->panel_version,
'composer_packages' => $model->composer_packages ? json_decode($model->composer_packages, true, 512, JSON_THROW_ON_ERROR) : null,
'meta' => [
'status' => $model->status,
'status_message' => $model->status_message,
'load_order' => $model->load_order,
'is_compatible' => $model->isCompatible(),
'update_available' => $model->isUpdateAvailable(),
'can_enable' => $model->canEnable(),
'can_disable' => $model->canDisable(),
],
];
}
}
23 changes: 23 additions & 0 deletions routes/api-application.php
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,26 @@

Route::delete('/{role:id}', [Application\Roles\RoleController::class, 'delete']);
});

/*
|--------------------------------------------------------------------------
| Plugin Controller Routes
|--------------------------------------------------------------------------
|
| Endpoint: /api/application/plugins
|
*/
Route::prefix('/plugins')->group(function () {
Route::get('/', [Application\Plugins\PluginController::class, 'index'])->name('api.application.plugins');
Route::get('/{plugin:id}', [Application\Plugins\PluginController::class, 'view'])->name('api.application.plugins.view');

Route::post('/import/file', [Application\Plugins\PluginController::class, 'importFile']);
Route::post('/import/url', [Application\Plugins\PluginController::class, 'importUrl']);

Route::post('/{plugin:id}/install', [Application\Plugins\PluginController::class, 'install']);
Route::post('/{plugin:id}/update', [Application\Plugins\PluginController::class, 'update']);
Route::post('/{plugin:id}/uninstall', [Application\Plugins\PluginController::class, 'uninstall']);

Route::post('/{plugin:id}/enable', [Application\Plugins\PluginController::class, 'enable']);
Route::post('/{plugin:id}/disable', [Application\Plugins\PluginController::class, 'disable']);
});