first commit

This commit is contained in:
Maxime 2024-07-09 11:31:28 +02:00
commit cc835b3692
9 changed files with 461 additions and 0 deletions

35
composer.json Normal file
View File

@ -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
}

42
src/Entity/Action.php Normal file
View File

@ -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,
],
];
}
}

16
src/Entity/Entity.php Normal file
View File

@ -0,0 +1,16 @@
<?php
namespace Bluesquare\Pilot\Entity;
abstract class Entity
{
public function __construct(protected $slug = null)
{
}
public function slug()
{
return $this->slug;
}
}

116
src/Entity/Metric.php Normal file
View File

@ -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,
];
}
}

8
src/Entity/Widget.php Normal file
View File

@ -0,0 +1,8 @@
<?php
namespace Bluesquare\Pilot\Entity;
abstract class Widget extends Entity
{
//
}

View File

@ -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();
}
}

View File

@ -0,0 +1,7 @@
<?php
return [
'key' => env('PILOT_KEY', null),
];

98
src/Pilot.php Normal file
View File

@ -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'];
}
}
}

42
src/Response.php Normal file
View File

@ -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'),
];
}
}