Initial commit

This commit is contained in:
René Preuß
2019-08-31 13:21:54 +02:00
commit 7226975ac3
26 changed files with 6093 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\ApiOperations;
use GhostZero\BitinflowAccounts\Helpers\Paginator;
trait Delete
{
abstract public function delete(string $path = '', array $parameters = [], Paginator $paginator = null);
}

12
src/ApiOperations/Get.php Normal file
View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\ApiOperations;
use GhostZero\BitinflowAccounts\Helpers\Paginator;
trait Get
{
abstract public function get(string $path = '', array $parameters = [], Paginator $paginator = null);
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\ApiOperations;
use GhostZero\BitinflowAccounts\Helpers\Paginator;
trait Post
{
abstract public function post(string $path = '', array $parameters = [], Paginator $paginator = null);
}

12
src/ApiOperations/Put.php Normal file
View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\ApiOperations;
use GhostZero\BitinflowAccounts\Helpers\Paginator;
trait Put
{
abstract public function put(string $path = '', array $parameters = [], Paginator $paginator = null);
}

392
src/BitinflowAccounts.php Normal file
View File

@@ -0,0 +1,392 @@
<?php
namespace GhostZero\BitinflowAccounts;
use GhostZero\BitinflowAccounts\Exceptions\RequestRequiresAuthenticationException;
use GhostZero\BitinflowAccounts\Exceptions\RequestRequiresClientIdException;
use GhostZero\BitinflowAccounts\Exceptions\RequestRequiresRedirectUriException;
use GhostZero\BitinflowAccounts\Helpers\Paginator;
use GhostZero\BitinflowAccounts\Traits;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\RequestException;
/**
* @author René Preuß <rene@preuss.io>
*/
class BitinflowAccounts
{
use Traits\UsersTrait;
use Traits\SshKeysTrait;
const BASE_URI = 'https://accounts.bitinflow.com/api/';
const OAUTH_BASE_URI = 'https://accounts.bitinflow.com/api/';
/**
* Guzzle is used to make http requests.
* @var \GuzzleHttp\Client
*/
protected $client;
/**
* Paginator object.
* @var Paginator
*/
protected $paginator;
/**
* bitinflow Accounts OAuth token.
* @var string|null
*/
protected $token = null;
/**
* bitinflow Accounts client id.
* @var string|null
*/
protected $clientId = null;
/**
* bitinflow Accounts client secret.
* @var string|null
*/
protected $clientSecret = null;
/**
* bitinflow Accounts OAuth redirect url.
* @var string|null
*/
protected $redirectUri = null;
/**
* Constructor.
*/
public function __construct()
{
if ($clientId = config('bitinflow-accounts-api.client_id')) {
$this->setClientId($clientId);
}
if ($clientSecret = config('bitinflow-accounts-api.client_secret')) {
$this->setClientSecret($clientSecret);
}
if ($redirectUri = config('bitinflow-accounts-api.redirect_url')) {
$this->setRedirectUri($redirectUri);
}
$this->client = new Client([
'base_uri' => self::BASE_URI,
]);
}
/**
* Get client id.
* @return string
* @throws RequestRequiresClientIdException
*/
public function getClientId(): string
{
if (!$this->clientId) {
throw new RequestRequiresClientIdException;
}
return $this->clientId;
}
/**
* Set client id.
*
* @param string $clientId bitinflow Accounts client id
*
* @return void
*/
public function setClientId(string $clientId): void
{
$this->clientId = $clientId;
}
/**
* Fluid client id setter.
*
* @param string $clientId bitinflow Accounts client id.
*
* @return self
*/
public function withClientId(string $clientId): self
{
$this->setClientId($clientId);
return $this;
}
/**
* Get client secret.
* @return string
* @throws RequestRequiresClientIdException
*/
public function getClientSecret(): string
{
if (!$this->clientSecret) {
throw new RequestRequiresClientIdException;
}
return $this->clientSecret;
}
/**
* Set client secret.
*
* @param string $clientSecret bitinflow Accounts client secret
*
* @return void
*/
public function setClientSecret(string $clientSecret): void
{
$this->clientSecret = $clientSecret;
}
/**
* Fluid client secret setter.
*
* @param string $clientSecret bitinflow Accounts client secret
*
* @return self
*/
public function withClientSecret(string $clientSecret): self
{
$this->setClientSecret($clientSecret);
return $this;
}
/**
* Get redirect url.
* @return string
* @throws RequestRequiresRedirectUriException
*/
public function getRedirectUri(): string
{
if (!$this->redirectUri) {
throw new RequestRequiresRedirectUriException;
}
return $this->redirectUri;
}
/**
* Set redirect url.
*
* @param string $redirectUri
*
* @return void
*/
public function setRedirectUri(string $redirectUri): void
{
$this->redirectUri = $redirectUri;
}
/**
* Fluid redirect url setter.
*
* @param string $redirectUri
*
* @return self
*/
public function withRedirectUri(string $redirectUri): self
{
$this->setRedirectUri($redirectUri);
return $this;
}
/**
* Get OAuth token.
* @return string bitinflow Accounts token
* @return string|null
* @throws RequestRequiresAuthenticationException
*/
public function getToken()
{
if (!$this->token) {
throw new RequestRequiresAuthenticationException;
}
return $this->token;
}
/**
* Set OAuth token.
*
* @param string $token bitinflow Accounts OAuth token
*
* @return void
*/
public function setToken(string $token): void
{
$this->token = $token;
}
/**
* Fluid OAuth token setter.
*
* @param string $token bitinflow Accounts OAuth token
*
* @return self
*/
public function withToken(string $token): self
{
$this->setToken($token);
return $this;
}
/**
* @param string $path
* @param array $parameters
* @param Paginator|null $paginator
*
* @return Result
* @throws GuzzleException
* @throws RequestRequiresClientIdException
*/
public function get(string $path = '', array $parameters = [], Paginator $paginator = null)
{
return $this->query('GET', $path, $parameters, $paginator);
}
/**
* @param string $path
* @param array $parameters
* @param Paginator|null $paginator
*
* @return Result
* @throws GuzzleException
* @throws RequestRequiresClientIdException
*/
public function post(string $path = '', array $parameters = [], Paginator $paginator = null)
{
return $this->query('POST', $path, $parameters, $paginator);
}
/**
* @param string $path
* @param array $parameters
* @param Paginator|null $paginator
*
* @return Result
* @throws GuzzleException
* @throws RequestRequiresClientIdException
*/
public function delete(string $path = '', array $parameters = [], Paginator $paginator = null)
{
return $this->query('DELETE', $path, $parameters, $paginator);
}
/**
* @param string $path
* @param array $parameters
* @param Paginator|null $paginator
*
* @return Result
* @throws GuzzleException
* @throws RequestRequiresClientIdException
*/
public function put(string $path = '', array $parameters = [], Paginator $paginator = null)
{
return $this->query('PUT', $path, $parameters, $paginator);
}
/**
* @param string $method
* @param string $path
* @param array|null $body
*
* @return Result
* @throws GuzzleException
* @throws RequestRequiresClientIdException
*/
public function json(string $method, string $path = '', array $body = null)
{
if ($body) {
$body = json_encode(['data' => $body]);
}
return $this->query($method, $path, [], null, $body);
}
/**
* Build query & execute.
*
* @param string $method HTTP method
* @param string $path Query path
* @param array $parameters Query parameters
* @param Paginator $paginator Paginator object
* @param mixed|null $jsonBody JSON data
*
* @return Result Result object
* @throws GuzzleException
* @throws RequestRequiresClientIdException
*/
public function query(string $method = 'GET', string $path = '', array $parameters = [], Paginator $paginator = null, $jsonBody = null): Result
{
if ($paginator !== null) {
$parameters[$paginator->action] = $paginator->cursor();
}
try {
$response = $this->client->request($method, $path, [
'headers' => $this->buildHeaders($jsonBody ? true : false),
'query' => $this->buildQuery($parameters),
'json' => $jsonBody ? $jsonBody : null,
]);
$result = new Result($response, null, $paginator);
} catch (RequestException $exception) {
$result = new Result($exception->getResponse(), $exception, $paginator);
}
$result->bitinflow = $this;
return $result;
}
/**
* Build query with support for multiple smae first-dimension keys.
*
* @param array $query
*
* @return string
*/
public function buildQuery(array $query): string
{
$parts = [];
foreach ($query as $name => $value) {
$value = (array) $value;
array_walk_recursive($value, function ($value) use (&$parts, $name) {
$parts[] = urlencode($name) . '=' . urlencode($value);
});
}
return implode('&', $parts);
}
/**
* Build headers for request.
*
* @param bool $json Body is JSON
*
* @return array
* @throws RequestRequiresClientIdException
*/
private function buildHeaders(bool $json = false): array
{
$headers = [
'Client-ID' => $this->getClientId(),
];
if ($this->token) {
$headers['Authorization'] = 'Bearer ' . $this->token;
}
if ($json) {
$headers['Content-Type'] = 'application/json';
$headers['Accept'] = 'application/json';
}
return $headers;
}
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\Exceptions;
use Exception;
/**
* @author René Preuß <rene@preuss.io>
*/
class RateLimitException extends Exception
{
public function __construct($message = 'Rate Limit exceeded', $code = 0, Exception $previous = null)
{
parent::__construct($message, $code, $previous);
}
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\Exceptions;
use Exception;
/**
* @author René Preuß <rene@preuss.io>
*/
class RequestRequiresAuthenticationException extends Exception
{
public function __construct($message = 'Request requires authentication', $code = 0, Exception $previous = null)
{
parent::__construct($message, $code, $previous);
}
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\Exceptions;
use Exception;
/**
* @author René Preuß <rene@preuss.io>
*/
class RequestRequiresClientIdException extends Exception
{
public function __construct($message = 'Request requires Client-ID', $code = 0, Exception $previous = null)
{
parent::__construct($message, $code, $previous);
}
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\Exceptions;
use Exception;
/**
* @author René Preuß <rene@preuss.io>
*/
class RequestRequiresParameter extends Exception
{
public function __construct($message = 'Request requires parameters', $code = 0, Exception $previous = null)
{
parent::__construct($message, $code, $previous);
}
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\Exceptions;
use Exception;
/**
* @author René Preuß <rene@preuss.io>
*/
class RequestRequiresRedirectUriException extends Exception
{
public function __construct($message = 'Request requires redirect uri', $code = 0, Exception $previous = null)
{
parent::__construct($message, $code, $previous);
}
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\Facades;
use GhostZero\BitinflowAccounts\BitinflowAccounts as BitinflowAccountsService;
use Illuminate\Support\Facades\Facade;
/**
* @author René Preuß <rene@preuss.io>
*/
class BitinflowAccounts extends Facade
{
protected static function getFacadeAccessor()
{
return BitinflowAccountsService::class;
}
}

91
src/Helpers/Paginator.php Normal file
View File

@@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\Helpers;
use GhostZero\BitinflowAccounts\Result;
use stdClass;
/**
* @author René Preuß <rene@preuss.io>
*/
class Paginator
{
/**
* bitinflow Accounts response pagination cursor.
* @var null|stdClass
*/
private $pagination;
/**
* Next desired action (first, after, before).
* @var null|string
*/
public $action = null;
/**
* Constructor.
*
* @param null|stdClass $pagination bitinflow Accounts response pagination cursor
*/
public function __construct(stdClass $pagination = null)
{
$this->pagination = $pagination;
}
/**
* Create Paginator from Result object.
*
* @param Result $result Result object
*
* @return self Paginator object
*/
public static function from(Result $result): self
{
return new self($result->pagination);
}
/**
* Return the current active cursor.
* @return string bitinflow Accounts cursor
*/
public function cursor(): string
{
return $this->pagination->cursor;
}
/**
* Set the Paginator to fetch the next set of results.
* @return self
*/
public function first(): self
{
$this->action = 'first';
return $this;
}
/**
* Set the Paginator to fetch the first set of results.
* @return self
*/
public function next(): self
{
$this->action = 'after';
return $this;
}
/**
* Set the Paginator to fetch the last set of results.
* @return self
*/
public function back(): self
{
$this->action = 'before';
return $this;
}
}

View File

@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\Providers;
use GhostZero\BitinflowAccounts\BitinflowAccounts;
use Illuminate\Support\ServiceProvider;
class BitinflowAccountsServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
* @return void
*/
public function boot()
{
$this->publishes([
dirname(__DIR__) . '/../config/bitinflow-accounts-api.php' => config_path('bitinflow-accounts-api.php'),
], 'config');
}
/**
* Register the application services.
* @return void
*/
public function register()
{
$this->mergeConfigFrom(
dirname(__DIR__) . '/../config/bitinflow-accounts-api.php', 'bitinflow-accounts-api'
);
$this->app->singleton(BitinflowAccounts::class, function () {
return new BitinflowAccounts;
});
}
/**
* Get the services provided by the provider.
* @return array
*/
public function provides()
{
return [BitinflowAccounts::class];
}
}

251
src/Result.php Normal file
View File

@@ -0,0 +1,251 @@
<?php
declare(strict_types=1);
namespace GhostZero\BitinflowAccounts;
use Exception;
use GhostZero\BitinflowAccounts\Helpers\Paginator;
use GuzzleHttp\Psr7\Response;
use stdClass;
/**
* @author René Preuß <rene@preuss.io>
*/
class Result
{
/**
* Query successfull.
* @var boolean
*/
public $success = false;
/**
* Guzzle exception, if present.
* @var null|mixed
*/
public $exception = null;
/**
* Query result data.
* @var array
*/
public $data = [];
/**
* Total amount of result data.
* @var integer
*/
public $total = 0;
/**
* Status Code.
* @var integer
*/
public $status = 0;
/**
* bitinflow Accounts response pagination cursor.
* @var null|\stdClass
*/
public $pagination;
/**
* Internal paginator.
* @var null|Paginator
*/
public $paginator;
/**
* Original Guzzle HTTP Response.
* @var Response
*/
public $response;
/**
* Original bitinflow Accounts instance.
* @var BitinflowAccounts
*/
public $bitinflow;
/**
* Constructor,
*
* @param Response $response HTTP response
* @param Exception|mixed $exception Exception, if present
* @param null|Paginator $paginator Paginator, if present
*/
public function __construct(Response $response, Exception $exception = null, Paginator $paginator = null)
{
$this->response = $response;
$this->success = $exception === null;
$this->exception = $exception;
$this->status = $response->getStatusCode();
$jsonResponse = @json_decode($response->getBody()->getContents());
if ($jsonResponse !== null) {
$this->setProperty($jsonResponse, 'data');
$this->setProperty($jsonResponse, 'total');
$this->setProperty($jsonResponse, 'pagination');
$this->paginator = Paginator::from($this);
}
}
/**
* Sets a class attribute by given JSON Response Body.
*
* @param stdClass $jsonResponse Response Body
* @param string $responseProperty Response property name
* @param string|null $attribute Class property name
*/
private function setProperty(stdClass $jsonResponse, string $responseProperty, string $attribute = null)
{
$classAttribute = $attribute ?? $responseProperty;
if (!empty($jsonResponse) && property_exists($jsonResponse, $responseProperty)) {
$this->{$classAttribute} = $jsonResponse->{$responseProperty};
} elseif ($responseProperty === 'data') {
$this->{$classAttribute} = $jsonResponse;
}
}
/**
* Returns wether the query was successfull.
* @return bool Success state
*/
public function success(): bool
{
return $this->success;
}
/**
* Get the response data, also available as public attribute.
* @return mixed
*/
public function data()
{
return $this->data;
}
/**
* Returns the last HTTP or API error.
* @return string Error message
*/
public function error(): string
{
// TODO Switch Exception response parsing to this->data
if ($this->exception === null || !$this->exception->hasResponse()) {
return 'bitinflow Accounts API Unavailable';
}
$exception = (string) $this->exception->getResponse()->getBody();
$exception = @json_decode($exception);
if (property_exists($exception, 'message') && !empty($exception->message)) {
return $exception->message;
}
return $this->exception->getMessage();
}
/**
* Shifts the current result (Use for single user/video etc. query).
* @return mixed Shifted data
*/
public function shift()
{
if (!empty($this->data)) {
$data = $this->data;
return array_shift($data);
}
return null;
}
/**
* Return the current count of items in dataset.
* @return int Count
*/
public function count(): int
{
return count((array) $this->data);
}
/**
* Set the Paginator to fetch the first set of results.
* @return null|Paginator
*/
public function first()
{
return $this->paginator !== null ? $this->paginator->first() : null;
}
/**
* Set the Paginator to fetch the next set of results.
* @return null|Paginator
*/
public function next()
{
return $this->paginator !== null ? $this->paginator->next() : null;
}
/**
* Set the Paginator to fetch the last set of results.
* @return null|Paginator
*/
public function back()
{
return $this->paginator !== null ? $this->paginator->back() : null;
}
/**
* Get rate limit information.
*
* @param string|null $key Get defined index
*
* @return string|array|null
*/
public function rateLimit(string $key = null)
{
if (!$this->response) {
return null;
}
$rateLimit = [
'limit' => (int) $this->response->getHeaderLine('X-RateLimit-Limit'),
'remaining' => (int) $this->response->getHeaderLine('X-RateLimit-Remaining'),
'reset' => (int) $this->response->getHeaderLine('Retry-After'),
];
if ($key === null) {
return $rateLimit;
}
return $rateLimit[$key];
}
/**
* Insert users in data response.
*
* @param string $identifierAttribute Attribute to identify the users
* @param string $insertTo Data index to insert user data
*
* @return self
*/
public function insertUsers(string $identifierAttribute = 'user_id', string $insertTo = 'user'): self
{
$data = $this->data;
$userIds = collect($data)->map(function ($item) use ($identifierAttribute) {
return $item->{$identifierAttribute};
})->toArray();
if (count($userIds) == 0) {
return $this;
}
$users = collect($this->bitinflow->getUsersByIds($userIds)->data);
$dataWithUsers = collect($data)->map(function ($item) use ($users, $identifierAttribute, $insertTo) {
$item->$insertTo = $users->where('id', $item->{$identifierAttribute})->first();
return $item;
});
$this->data = $dataWithUsers->toArray();
return $this;
}
}

View File

@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\Traits;
use GhostZero\BitinflowAccounts\ApiOperations\Delete;
use GhostZero\BitinflowAccounts\ApiOperations\Get;
use GhostZero\BitinflowAccounts\ApiOperations\Post;
use GhostZero\BitinflowAccounts\Result;
trait SshKeysTrait
{
use Get, Post, Delete;
/**
* Get currently authed user with Bearer Token
*
* @param int $id
*
* @return Result Result object
*/
public function getSshKeysByUserId(int $id): Result
{
return $this->get("users/$id/keys/json", [], null);
}
/**
* Get currently authed user with Bearer Token
*
* @param string $publicKey
* @param string|null $name
*
* @return Result Result object
*/
public function createSshKey(string $publicKey, string $name = null): Result
{
return $this->post('ssh-keys', [
'public_key' => $publicKey,
'name' => $name,
], null);
}
/**
* Get currently authed user with Bearer Token
*
* @param int $id
*
* @return Result Result object
*/
public function deleteSshKey(int $id): Result
{
return $this->delete("ssh-keys/$id", [], null);
}
}

23
src/Traits/UsersTrait.php Normal file
View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\Traits;
use GhostZero\BitinflowAccounts\ApiOperations\Get;
use GhostZero\BitinflowAccounts\Result;
trait UsersTrait
{
use Get;
/**
* Get currently authed user with Bearer Token
* @return Result Result object
*/
public function getAuthedUser(): Result
{
return $this->get('users/me', [], null);
}
}