first commit
This commit is contained in:
commit
cc835b3692
|
@ -0,0 +1,35 @@
|
||||||
|
{
|
||||||
|
"name": "bluesquare/pilot-sdk",
|
||||||
|
"description": "Pilot peer implementation.",
|
||||||
|
"keywords": [
|
||||||
|
"package",
|
||||||
|
"bluesquare",
|
||||||
|
"pilot"
|
||||||
|
],
|
||||||
|
"homepage": "https://git.bluesquare.io/bluesquare/pilot-sdk",
|
||||||
|
"license": "proprietary",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Maxime Renou",
|
||||||
|
"email": "maxime@bluesquare.io",
|
||||||
|
"homepage": "https://bluesquare.io/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"minimum-stability": "dev",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Bluesquare\\Pilot\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"Bluesquare\\Pilot\\Laravel\\PilotServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.3|^8.0"
|
||||||
|
},
|
||||||
|
"prefer-stable": true
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Bluesquare\Pilot\Entity;
|
||||||
|
|
||||||
|
abstract class Action extends Entity
|
||||||
|
{
|
||||||
|
public function action(
|
||||||
|
$type = 'info',
|
||||||
|
$title = null,
|
||||||
|
$message = null,
|
||||||
|
$button = null,
|
||||||
|
$url = null,
|
||||||
|
$files = [],
|
||||||
|
) {
|
||||||
|
return [
|
||||||
|
'json' => [
|
||||||
|
'type' => 'action',
|
||||||
|
'action' => compact('type', 'title', 'message', 'button', 'url', 'files'),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function file(
|
||||||
|
$contents,
|
||||||
|
$name = '',
|
||||||
|
$type = ''
|
||||||
|
) {
|
||||||
|
return [
|
||||||
|
'file' => compact('contents', 'name', 'type'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function error($message)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'json' => [
|
||||||
|
'type' => 'error',
|
||||||
|
'error' => $message,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Bluesquare\Pilot\Entity;
|
||||||
|
|
||||||
|
abstract class Entity
|
||||||
|
{
|
||||||
|
public function __construct(protected $slug = null)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function slug()
|
||||||
|
{
|
||||||
|
return $this->slug;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,116 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Bluesquare\Pilot\Entity;
|
||||||
|
|
||||||
|
abstract class Metric extends Widget
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var bool|int $cache Cache duration (in seconds) or FALSE to disable caching
|
||||||
|
*/
|
||||||
|
protected $cache = false;
|
||||||
|
|
||||||
|
protected $data = null;
|
||||||
|
|
||||||
|
abstract public function compute($entry = null);
|
||||||
|
|
||||||
|
public function handle($data)
|
||||||
|
{
|
||||||
|
$this->data = $data;
|
||||||
|
|
||||||
|
$cache_key = 'pilot-metric-'.$this->slug.'-'.json_encode($data);
|
||||||
|
|
||||||
|
if ($this->cache !== false && cache()->has($cache_key)) {
|
||||||
|
$computed = cache()->get($cache_key);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$computed = $this->compute(
|
||||||
|
$data['entry'] ?? null
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($this->cache !== false) {
|
||||||
|
cache()->put($cache_key, $computed, $this->cache);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->output($computed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function output($data)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'json' => [
|
||||||
|
'metric' => $data
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function resolveFilter($filters)
|
||||||
|
{
|
||||||
|
$filter = collect($filters)->firstWhere('value', $this->data['filter'] ?? null) ?? collect($filters)->first();
|
||||||
|
|
||||||
|
$computed = is_array($filter) ? $filter['callback']($this->data) : [];
|
||||||
|
|
||||||
|
return [
|
||||||
|
'filter' => $filter['value'],
|
||||||
|
'filters' => collect($filters)->map(function ($filter) {
|
||||||
|
return [
|
||||||
|
'label' => $filter['label'],
|
||||||
|
'value' => $filter['value'],
|
||||||
|
];
|
||||||
|
})->values()->all(),
|
||||||
|
...$computed
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function filter($label, $value, callable $callback)
|
||||||
|
{
|
||||||
|
return ['label' => $label, 'value' => $value, 'callback' => $callback];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function filters($filters)
|
||||||
|
{
|
||||||
|
return $this->resolveFilter($filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function progress($current, $target)
|
||||||
|
{
|
||||||
|
return $this->output([
|
||||||
|
'type' => 'progress',
|
||||||
|
'value' => $current,
|
||||||
|
'target' => $target,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function trend($value, $previous = null)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'type' => 'trend',
|
||||||
|
'value' => $value,
|
||||||
|
'previous' => $previous,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function partition($values)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'type' => 'partition',
|
||||||
|
'values' => $values,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function value($value)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'type' => 'value',
|
||||||
|
'value' => $value,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function monitor($available)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'type' => 'monitor',
|
||||||
|
'value' => $available,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Bluesquare\Pilot\Entity;
|
||||||
|
|
||||||
|
abstract class Widget extends Entity
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Bluesquare\Pilot\Laravel;
|
||||||
|
|
||||||
|
use Bluesquare\Pilot\Pilot;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
|
||||||
|
class PilotServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
protected $pilot;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register services.
|
||||||
|
*/
|
||||||
|
public function register(): void
|
||||||
|
{
|
||||||
|
$this->mergeConfigFrom(
|
||||||
|
__DIR__.'/config/pilot.php', 'pilot'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bootstrap services.
|
||||||
|
*/
|
||||||
|
public function boot(): void
|
||||||
|
{
|
||||||
|
$this->publishes([
|
||||||
|
__DIR__.'/config/pilot.php' => config_path('pilot.php'),
|
||||||
|
], 'config');
|
||||||
|
|
||||||
|
$this->registerPilot();
|
||||||
|
|
||||||
|
Route::any('api/pilot', function (Request $request) {
|
||||||
|
return $this->handleRequest($request);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function registerPilot()
|
||||||
|
{
|
||||||
|
$this->pilot = new Pilot;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$actions = app('files')->allFiles(app_path('Pilot/Actions'));
|
||||||
|
|
||||||
|
foreach ($actions as $action) {
|
||||||
|
$class = 'App\\Pilot\\Actions\\' . $action->getBasename('.php');
|
||||||
|
|
||||||
|
if (class_exists($class)) {
|
||||||
|
$action = new $class;
|
||||||
|
$this->pilot->action($action->slug(), [$action, 'handle']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (\Exception $e) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$metrics = app('files')->allFiles(app_path('Pilot/Metrics'));
|
||||||
|
|
||||||
|
foreach ($metrics as $metric) {
|
||||||
|
$class = 'App\\Pilot\\Metrics\\' . $metric->getBasename('.php');
|
||||||
|
|
||||||
|
if (class_exists($class)) {
|
||||||
|
$metric = new $class;
|
||||||
|
$this->pilot->metric($metric->slug(), [$metric, 'handle']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (\Exception $e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function handleRequest(Request $request)
|
||||||
|
{
|
||||||
|
if (empty(config('pilot.key')) || config('pilot.key') != $request->bearerToken()) {
|
||||||
|
abort(403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$output = $this->pilot->handle($request->all(), $request->files->all());
|
||||||
|
|
||||||
|
if (isset($output['json'])) {
|
||||||
|
return response()->json($output['json']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($output['file'])) {
|
||||||
|
return response()->download(
|
||||||
|
$output['file']['content'],
|
||||||
|
$output['file']['name'],
|
||||||
|
[
|
||||||
|
'Content-Type' => $output['file']['type'],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'key' => env('PILOT_KEY', null),
|
||||||
|
|
||||||
|
];
|
|
@ -0,0 +1,98 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Bluesquare\Pilot;
|
||||||
|
|
||||||
|
class Pilot
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var callable[]
|
||||||
|
*/
|
||||||
|
protected $registry = [
|
||||||
|
'action' => [],
|
||||||
|
'metric' => [],
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function authorize($token, $user_token)
|
||||||
|
{
|
||||||
|
if ($token !== $user_token) {
|
||||||
|
throw new \Exception('Unauthorized Pilot request');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function action($slug, callable $function)
|
||||||
|
{
|
||||||
|
$this->register('action', $slug, $function);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function metric($slug, callable $function)
|
||||||
|
{
|
||||||
|
$this->register('metric', $slug, $function);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function register($type, $slug, callable $function)
|
||||||
|
{
|
||||||
|
$this->registry[$type][$slug] = $function;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle($data = [], $files = [])
|
||||||
|
{
|
||||||
|
$type = $data['type'] ?? null;
|
||||||
|
$slug = $data['slug'] ?? null;
|
||||||
|
|
||||||
|
if (isset($this->registry[$type]) && isset($this->registry[$type][$slug])) {
|
||||||
|
$params = $this->resolveDependencies($slug, $this->registry[$type][$slug], $data, $files);
|
||||||
|
|
||||||
|
return call_user_func_array($this->registry[$type][$slug], $params);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response::error("Unknown {$type} {$slug}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resolveDependencies($slug, $function, $data, $files)
|
||||||
|
{
|
||||||
|
$entries = isset($data['entries']) && is_array($data['entries']) ? $data['entries'] : [];
|
||||||
|
|
||||||
|
$input = [
|
||||||
|
'data' => $data,
|
||||||
|
'filter' => $data['filter'] ?? null,
|
||||||
|
'files' => $files,
|
||||||
|
'entries' => $entries,
|
||||||
|
'entry' => $entries[0] ?? null,
|
||||||
|
];
|
||||||
|
|
||||||
|
$output = [];
|
||||||
|
|
||||||
|
$ref = is_array($function) ? new \ReflectionMethod($function[0], $function[1]) : new \ReflectionFunction($function);
|
||||||
|
|
||||||
|
foreach ($ref->getParameters() as $parameter) {
|
||||||
|
if (isset($input[$parameter->getName()])) {
|
||||||
|
$output[$parameter->getName()] = $input[$parameter->getName()];
|
||||||
|
} elseif (! $parameter->isOptional()) {
|
||||||
|
throw new \Exception("Pilot[$slug]: unknown required parameter '{$parameter->getName()}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function serve($data)
|
||||||
|
{
|
||||||
|
if (isset($data['json'])) {
|
||||||
|
header('content-type: application/json');
|
||||||
|
echo json_encode($data['json']);
|
||||||
|
} elseif (isset($data['file'])) {
|
||||||
|
header('content-type: '.$data['file']['type']);
|
||||||
|
|
||||||
|
if (! empty($data['file']['name'])) {
|
||||||
|
header('content-disposition: attachment; filename="'.$data['file']['name'].'"');
|
||||||
|
}
|
||||||
|
|
||||||
|
echo $data['file']['content'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Bluesquare\Pilot;
|
||||||
|
|
||||||
|
class Response
|
||||||
|
{
|
||||||
|
public static function action(
|
||||||
|
$type = 'info',
|
||||||
|
$title = null,
|
||||||
|
$message = null,
|
||||||
|
$button = null,
|
||||||
|
$url = null,
|
||||||
|
$files = [],
|
||||||
|
) {
|
||||||
|
return [
|
||||||
|
'json' => [
|
||||||
|
'type' => 'action',
|
||||||
|
'action' => compact('type', 'title', 'message', 'button', 'url', 'files'),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function error($message)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'json' => [
|
||||||
|
'type' => 'error',
|
||||||
|
'error' => $message,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function file(
|
||||||
|
$contents,
|
||||||
|
$name = '',
|
||||||
|
$type = ''
|
||||||
|
) {
|
||||||
|
return [
|
||||||
|
'file' => compact('contents', 'name', 'type'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue