Merge branch '164-add-api-bundle' into 'master'
Resolve "Add API bundle" Closes #164 See merge request jankstudio/resq!198
This commit is contained in:
commit
2a24567c4c
23 changed files with 2126 additions and 397 deletions
9
catalyst/api-bundle/CatalystAPIBundle.php
Normal file
9
catalyst/api-bundle/CatalystAPIBundle.php
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Catalyst\APIBundle;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||||
|
|
||||||
|
class CatalystAPIBundle extends Bundle
|
||||||
|
{
|
||||||
|
}
|
||||||
67
catalyst/api-bundle/Command/TestCommand.php
Normal file
67
catalyst/api-bundle/Command/TestCommand.php
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Catalyst\APIBundle\Command;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
|
||||||
|
|
||||||
|
use Catalyst\APIBundle\Connector\Client as APIClient;
|
||||||
|
|
||||||
|
|
||||||
|
class TestCommand extends Command
|
||||||
|
{
|
||||||
|
protected function configure()
|
||||||
|
{
|
||||||
|
$this->setName('api:test-connector')
|
||||||
|
->setDescription('Test API connector.')
|
||||||
|
->setHelp('Test API Connector.')
|
||||||
|
->addArgument('protocol', InputArgument::REQUIRED, 'protocol')
|
||||||
|
->addArgument('server', InputArgument::REQUIRED, 'server')
|
||||||
|
->addArgument('api_key', InputArgument::REQUIRED, 'api_key')
|
||||||
|
->addArgument('secret_key', InputArgument::REQUIRED, 'secret_key');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output)
|
||||||
|
{
|
||||||
|
$protocol = $input->getArgument('protocol');
|
||||||
|
$server = $input->getArgument('server');
|
||||||
|
$api_key = $input->getArgument('api_key');
|
||||||
|
$secret_key = $input->getArgument('secret_key');
|
||||||
|
|
||||||
|
// api client
|
||||||
|
$api = new APIClient($server, $api_key, $secret_key);
|
||||||
|
$api->setProtocol($protocol);
|
||||||
|
|
||||||
|
// test
|
||||||
|
$api->get('/capi/test');
|
||||||
|
|
||||||
|
// TODO: shift this out of the bundle, since it's project specific
|
||||||
|
// warranty register
|
||||||
|
$params = [
|
||||||
|
'serial' => 'LJ34LJADR12SDLKJL',
|
||||||
|
'plate_number' => 'XEN918',
|
||||||
|
'warranty_class' => 'private',
|
||||||
|
'date_purchase' => '20181001',
|
||||||
|
'date_expire' => '20191001',
|
||||||
|
];
|
||||||
|
$api->post('/capi/warranty', $params);
|
||||||
|
|
||||||
|
// warranty find
|
||||||
|
$api->get('/capi/warranty/LJ34LJADR12SDLKJL');
|
||||||
|
|
||||||
|
// warranty claim
|
||||||
|
$api->post('/capi/warranty/LJ34LJADR12SDLKJL/claim');
|
||||||
|
|
||||||
|
// battery
|
||||||
|
// $api->get('/capi/battery_models');
|
||||||
|
// $api->get('/capi/battery_sizes');
|
||||||
|
|
||||||
|
// vehicle
|
||||||
|
// $api->get('/capi/vehicle_manufacturers');
|
||||||
|
// $api->get('/capi/vehicles');
|
||||||
|
}
|
||||||
|
}
|
||||||
49
catalyst/api-bundle/Command/UserCreateCommand.php
Normal file
49
catalyst/api-bundle/Command/UserCreateCommand.php
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Catalyst\APIBundle\Command;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
|
||||||
|
use Catalyst\APIBundle\Entity\User;
|
||||||
|
|
||||||
|
class UserCreateCommand extends Command
|
||||||
|
{
|
||||||
|
protected $em;
|
||||||
|
|
||||||
|
public function __construct(EntityManagerInterface $em)
|
||||||
|
{
|
||||||
|
$this->em = $em;
|
||||||
|
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configure()
|
||||||
|
{
|
||||||
|
$this->setName('api:user-create')
|
||||||
|
->setDescription('Create new API user.')
|
||||||
|
->setHelp('Creates new API user and saves to database.')
|
||||||
|
->addArgument('name', InputArgument::REQUIRED, 'name');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output)
|
||||||
|
{
|
||||||
|
$name = $input->getArgument('name');
|
||||||
|
|
||||||
|
$user = new User();
|
||||||
|
$user->setName($name);
|
||||||
|
|
||||||
|
$this->em->persist($user);
|
||||||
|
$this->em->flush();
|
||||||
|
|
||||||
|
$output->write('API Key - ' . $user->getAPIKey() . "\n");
|
||||||
|
$output->write('Secret Key - ' . $user->getSecretKey() . "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
151
catalyst/api-bundle/Connector/Client.php
Normal file
151
catalyst/api-bundle/Connector/Client.php
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Catalyst\APIBundle\Connector;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
|
||||||
|
class Client
|
||||||
|
{
|
||||||
|
const HEADER_API_KEY = 'X-Cata-API-Key';
|
||||||
|
const HEADER_SIGNATURE = 'X-Cata-Signature';
|
||||||
|
const HEADER_DATE = 'X-Cata-Date';
|
||||||
|
|
||||||
|
const DATE_FORMAT = 'D, d M Y H:i:s T';
|
||||||
|
|
||||||
|
const USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36';
|
||||||
|
|
||||||
|
protected $protocol;
|
||||||
|
protected $server;
|
||||||
|
protected $port;
|
||||||
|
protected $api_key;
|
||||||
|
protected $secret_key;
|
||||||
|
|
||||||
|
protected $curl;
|
||||||
|
|
||||||
|
public function __construct($server, $api_key, $secret_key)
|
||||||
|
{
|
||||||
|
$this->protocol = 'https';
|
||||||
|
$this->port = null;
|
||||||
|
|
||||||
|
$this->server = $server;
|
||||||
|
$this->api_key = $api_key;
|
||||||
|
$this->secret_key = $secret_key;
|
||||||
|
$this->curl = curl_init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
curl_close($this->curl);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setProtocol($protocol)
|
||||||
|
{
|
||||||
|
if ($protocol != 'http' && $protocol != 'https')
|
||||||
|
return $this;
|
||||||
|
|
||||||
|
$this->protocol = $protocol;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getDateString()
|
||||||
|
{
|
||||||
|
$date = new DateTime();
|
||||||
|
return $date->format(self::DATE_FORMAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get($url, $params = [])
|
||||||
|
{
|
||||||
|
curl_reset($this->curl);
|
||||||
|
|
||||||
|
$date_string = $this->getDateString();
|
||||||
|
$headers = $this->generateHeaders('GET', $url, $date_string);
|
||||||
|
|
||||||
|
// build query string
|
||||||
|
if (count($params) > 0)
|
||||||
|
$query_string = '?' . http_build_query($params);
|
||||||
|
else
|
||||||
|
$query_string = '';
|
||||||
|
|
||||||
|
// build url
|
||||||
|
if ($this->port == null)
|
||||||
|
$full_url = $this->protocol . '://' . $this->server . $url . $query_string;
|
||||||
|
else
|
||||||
|
$full_url = $this->protocol . '://' . $this->server . ':' . $this->port . $url . $query_string;
|
||||||
|
|
||||||
|
error_log($full_url);
|
||||||
|
|
||||||
|
// curl
|
||||||
|
// curl_setopt($this->curl, CURLOPT_VERBOSE, true);
|
||||||
|
curl_setopt($this->curl, CURLOPT_URL, $full_url);
|
||||||
|
curl_setopt($this->curl, CURLOPT_HTTPHEADER, $headers);
|
||||||
|
curl_setopt($this->curl, CURLOPT_USERAGENT, self::USER_AGENT);
|
||||||
|
curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($this->curl, CURLOPT_TIMEOUT, 0);
|
||||||
|
|
||||||
|
$res = curl_exec($this->curl);
|
||||||
|
error_log($res);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function post($url, $params = [])
|
||||||
|
{
|
||||||
|
curl_reset($this->curl);
|
||||||
|
|
||||||
|
$date_string = $this->getDateString();
|
||||||
|
$headers = $this->generateHeaders('POST', $url, $date_string);
|
||||||
|
|
||||||
|
// build query string
|
||||||
|
$query_string = http_build_query($params);
|
||||||
|
|
||||||
|
// build url
|
||||||
|
if ($this->port == null)
|
||||||
|
$full_url = $this->protocol . '://' . $this->server . $url;
|
||||||
|
else
|
||||||
|
$full_url = $this->protocol . '://' . $this->server . ':' . $this->port . $url;
|
||||||
|
|
||||||
|
error_log($full_url);
|
||||||
|
|
||||||
|
// curl
|
||||||
|
// curl_setopt($this->curl, CURLOPT_VERBOSE, true);
|
||||||
|
curl_setopt($this->curl, CURLOPT_URL, $full_url);
|
||||||
|
curl_setopt($this->curl, CURLOPT_POST, true);
|
||||||
|
curl_setopt($this->curl, CURLOPT_POSTFIELDS, $query_string);
|
||||||
|
curl_setopt($this->curl, CURLOPT_HTTPHEADER, $headers);
|
||||||
|
curl_setopt($this->curl, CURLOPT_USERAGENT, self::USER_AGENT);
|
||||||
|
curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($this->curl, CURLOPT_TIMEOUT, 0);
|
||||||
|
|
||||||
|
$res = curl_exec($this->curl);
|
||||||
|
error_log($res);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function generateSignature($method, $url, $date_string)
|
||||||
|
{
|
||||||
|
$creds = [
|
||||||
|
$method,
|
||||||
|
$url,
|
||||||
|
$date_string,
|
||||||
|
$this->secret_key,
|
||||||
|
];
|
||||||
|
$sig_source = implode('|', $creds);
|
||||||
|
|
||||||
|
error_log('SIG SOURCE - ' . $sig_source);
|
||||||
|
|
||||||
|
$raw_sig = hash_hmac('sha1', $sig_source, $this->secret_key, true);
|
||||||
|
$enc_sig = base64_encode($raw_sig);
|
||||||
|
|
||||||
|
return $enc_sig;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function generateHeaders($method, $url, $date_string)
|
||||||
|
{
|
||||||
|
$sig = $this->generateSignature($method, $url, $date_string);
|
||||||
|
|
||||||
|
$headers = [
|
||||||
|
self::HEADER_API_KEY . ': ' . $this->api_key,
|
||||||
|
self::HEADER_SIGNATURE . ': ' . $sig,
|
||||||
|
self::HEADER_DATE . ': ' . $date_string,
|
||||||
|
];
|
||||||
|
|
||||||
|
return $headers;
|
||||||
|
}
|
||||||
|
}
|
||||||
7
catalyst/api-bundle/Controller/APIController.php
Normal file
7
catalyst/api-bundle/Controller/APIController.php
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Catalyst\APIBundle\Controller;
|
||||||
|
|
||||||
|
interface APIController
|
||||||
|
{
|
||||||
|
}
|
||||||
138
catalyst/api-bundle/Entity/User.php
Normal file
138
catalyst/api-bundle/Entity/User.php
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Catalyst\APIBundle\Entity;
|
||||||
|
|
||||||
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use DateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Entity
|
||||||
|
* @ORM\Table(name="api_user")
|
||||||
|
*/
|
||||||
|
class User implements UserInterface
|
||||||
|
{
|
||||||
|
// api key
|
||||||
|
/**
|
||||||
|
* @ORM\Id
|
||||||
|
* @ORM\Column(type="string", length=32)
|
||||||
|
*/
|
||||||
|
protected $api_key;
|
||||||
|
|
||||||
|
// secret key
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="string", length=32)
|
||||||
|
*/
|
||||||
|
protected $secret_key;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="string", length=80)
|
||||||
|
*/
|
||||||
|
protected $name;
|
||||||
|
|
||||||
|
// date created
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="datetime")
|
||||||
|
*/
|
||||||
|
protected $date_create;
|
||||||
|
|
||||||
|
// roles
|
||||||
|
// TODO: make this db loaded
|
||||||
|
protected $roles;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
// generate keys
|
||||||
|
$this->setAPIKey($this->generateAPIKey())
|
||||||
|
->setSecretKey($this->generateSecretKey());
|
||||||
|
|
||||||
|
// set date created
|
||||||
|
$this->date_create = new DateTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getID()
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setAPIKey($api_key)
|
||||||
|
{
|
||||||
|
$this->api_key = $api_key;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAPIKey()
|
||||||
|
{
|
||||||
|
return $this->api_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setSecretKey($key)
|
||||||
|
{
|
||||||
|
$this->secret_key = $key;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSecretKey()
|
||||||
|
{
|
||||||
|
return $this->secret_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setName($name)
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName()
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRoles()
|
||||||
|
{
|
||||||
|
return ['ROLE_API'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDateCreate()
|
||||||
|
{
|
||||||
|
return $this->date_create;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPassword()
|
||||||
|
{
|
||||||
|
// we don't need this for API
|
||||||
|
return 'notneeded';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSalt()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUsername()
|
||||||
|
{
|
||||||
|
// since it's an api, the api key IS the username
|
||||||
|
return $this->api_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function eraseCredentials()
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateAPIKey()
|
||||||
|
{
|
||||||
|
return $this->generateKey('api');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateSecretKey()
|
||||||
|
{
|
||||||
|
return $this->generateKey('secret');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function generateKey($prefix = '')
|
||||||
|
{
|
||||||
|
return md5(uniqid($prefix, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
20
catalyst/api-bundle/Response/APIResponse.php
Normal file
20
catalyst/api-bundle/Response/APIResponse.php
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Catalyst\APIBundle\Response;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
|
||||||
|
class APIResponse extends JsonResponse
|
||||||
|
{
|
||||||
|
public function __construct($success = true, $message = '', $data = null, $status = 200, $headers = [])
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'success' => (bool) $success,
|
||||||
|
'message' => (string) $message,
|
||||||
|
'data' => $data,
|
||||||
|
];
|
||||||
|
parent::__construct($data, $status, $headers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
154
catalyst/api-bundle/Security/APIKeyAuthenticator.php
Normal file
154
catalyst/api-bundle/Security/APIKeyAuthenticator.php
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Catalyst\APIBundle\Security;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||||
|
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||||
|
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
|
||||||
|
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||||
|
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||||
|
use Symfony\Component\Security\Http\Authentication\SimplePreAuthenticatorInterface;
|
||||||
|
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
|
||||||
|
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
|
||||||
|
class APIKeyAuthenticator implements SimplePreAuthenticatorInterface, AuthenticationFailureHandlerInterface
|
||||||
|
{
|
||||||
|
const HEADER_API_KEY = 'X-Cata-API-Key';
|
||||||
|
const HEADER_SIGNATURE = 'X-Cata-Signature';
|
||||||
|
const HEADER_DATE = 'X-Cata-Date';
|
||||||
|
|
||||||
|
const DATE_FORMAT = 'D, d M Y H:i:s T';
|
||||||
|
|
||||||
|
// 30 minute time limit
|
||||||
|
const TIME_LIMIT = 1800;
|
||||||
|
|
||||||
|
protected $em;
|
||||||
|
|
||||||
|
public function __construct(EntityManagerInterface $em)
|
||||||
|
{
|
||||||
|
$this->em = $em;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function validateSignature($creds, $secret_key)
|
||||||
|
{
|
||||||
|
$elements = [
|
||||||
|
$creds['method'],
|
||||||
|
$creds['uri'],
|
||||||
|
$creds['date'],
|
||||||
|
$secret_key,
|
||||||
|
];
|
||||||
|
$sig_source = implode('|', $elements);
|
||||||
|
|
||||||
|
error_log($sig_source);
|
||||||
|
|
||||||
|
// generate signature
|
||||||
|
$raw_sig = hash_hmac('sha1', $sig_source, $secret_key, true);
|
||||||
|
$enc_sig = base64_encode($raw_sig);
|
||||||
|
|
||||||
|
error_log($enc_sig);
|
||||||
|
|
||||||
|
if ($enc_sig != trim($creds['signature']))
|
||||||
|
throw new CustomUserMessageAuthenticationException('Invalid signature.');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createToken(Request $req, $provider_key)
|
||||||
|
{
|
||||||
|
// api key header
|
||||||
|
$api_key = $req->headers->get(self::HEADER_API_KEY);
|
||||||
|
if ($api_key == null)
|
||||||
|
throw new BadCredentialsException('No API key sent.');
|
||||||
|
|
||||||
|
// check date from headers
|
||||||
|
$hdate_string = $req->headers->get(self::HEADER_DATE);
|
||||||
|
if ($hdate_string == null)
|
||||||
|
throw new BadCredentialsException('No date specified.');
|
||||||
|
|
||||||
|
$hdate = DateTime::createFromFormat(self::DATE_FORMAT, $hdate_string);
|
||||||
|
if ($hdate == null)
|
||||||
|
throw new BadCredentialsException('Invalid date specified.');
|
||||||
|
|
||||||
|
// get number of seconds difference
|
||||||
|
$date_now = new DateTime();
|
||||||
|
$date_diff = abs($date_now->getTimestamp() - $hdate->getTimestamp());
|
||||||
|
|
||||||
|
// time difference is too much
|
||||||
|
if ($date_diff > self::TIME_LIMIT)
|
||||||
|
throw new BadCredentialsException('Clock synchronization error.');
|
||||||
|
|
||||||
|
// signature header
|
||||||
|
$sig = $req->headers->get(self::HEADER_SIGNATURE);
|
||||||
|
if ($sig == null)
|
||||||
|
throw new BadCredentialsException('No signature sent.');
|
||||||
|
|
||||||
|
// credentials
|
||||||
|
$creds = [
|
||||||
|
'api_key' => $api_key,
|
||||||
|
'date' => $hdate_string,
|
||||||
|
'signature' => $sig,
|
||||||
|
'method' => $req->getRealMethod(),
|
||||||
|
'uri' => $req->getPathInfo(),
|
||||||
|
];
|
||||||
|
|
||||||
|
return new PreAuthenticatedToken(
|
||||||
|
'anonymous',
|
||||||
|
$creds,
|
||||||
|
$provider_key
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsToken(TokenInterface $token, $provider_key)
|
||||||
|
{
|
||||||
|
return $token instanceof PreAuthenticatedToken && $token->getProviderKey() === $provider_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function authenticateToken(TokenInterface $token, UserProviderInterface $user_provider, $provider_key)
|
||||||
|
{
|
||||||
|
if (!$user_provider instanceof APIKeyUserProvider)
|
||||||
|
{
|
||||||
|
throw new \InvalidArgumentException(
|
||||||
|
sprintf(
|
||||||
|
'The user provider must be an instance of APIKeyUserProvider (%s was given).',
|
||||||
|
get_class($user_provider)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$creds = $token->getCredentials();
|
||||||
|
$api_key = $creds['api_key'];
|
||||||
|
$user = $user_provider->getUserByAPIKey($api_key);
|
||||||
|
|
||||||
|
// check if api key is valid
|
||||||
|
if (!$user)
|
||||||
|
throw new CustomUserMessageAuthenticationException('Invalid API Key');
|
||||||
|
|
||||||
|
// check if signature is valid
|
||||||
|
$this->validateSignature($creds, $user->getSecretKey());
|
||||||
|
|
||||||
|
// $user = $user_provider->loadUserByUsername($username);
|
||||||
|
|
||||||
|
return new PreAuthenticatedToken(
|
||||||
|
$user,
|
||||||
|
$api_key,
|
||||||
|
$provider_key,
|
||||||
|
$user->getRoles()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onAuthenticationFailure(Request $req, AuthenticationException $exception)
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'success' => false,
|
||||||
|
'error' => [
|
||||||
|
'message' => $exception->getMessage(),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
return new JsonResponse($data, 401);
|
||||||
|
}
|
||||||
|
}
|
||||||
62
catalyst/api-bundle/Security/APIKeyUserProvider.php
Normal file
62
catalyst/api-bundle/Security/APIKeyUserProvider.php
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Catalyst\APIBundle\Security;
|
||||||
|
|
||||||
|
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||||
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
|
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
|
||||||
|
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
|
||||||
|
use Catalyst\APIBundle\Entity\User;
|
||||||
|
|
||||||
|
class APIKeyUserProvider implements UserProviderInterface
|
||||||
|
{
|
||||||
|
protected $em;
|
||||||
|
|
||||||
|
public function __construct(EntityManagerInterface $em)
|
||||||
|
{
|
||||||
|
$this->em = $em;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserByAPIKey($api_key)
|
||||||
|
{
|
||||||
|
$user = $this->em->getRepository(User::class)->find($api_key);
|
||||||
|
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUsernameForAPIKey($apiKey)
|
||||||
|
{
|
||||||
|
// Look up the username based on the token in the database, via
|
||||||
|
// an API call, or do something entirely different
|
||||||
|
$username = 'test';
|
||||||
|
|
||||||
|
return $username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadUserByUsername($username)
|
||||||
|
{
|
||||||
|
return new User(
|
||||||
|
$username,
|
||||||
|
null,
|
||||||
|
// the roles for the user - you may choose to determine
|
||||||
|
// these dynamically somehow based on the user
|
||||||
|
array('ROLE_API')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function refreshUser(UserInterface $user)
|
||||||
|
{
|
||||||
|
// this is used for storing authentication in the session
|
||||||
|
// but in this example, the token is sent in each request,
|
||||||
|
// so authentication can be stateless. Throwing this exception
|
||||||
|
// is proper to make things stateless
|
||||||
|
throw new UnsupportedUserException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsClass($class)
|
||||||
|
{
|
||||||
|
return User::class === $class;
|
||||||
|
}
|
||||||
|
}
|
||||||
18
catalyst/api-bundle/Service/AccessDeniedHandler.php
Normal file
18
catalyst/api-bundle/Service/AccessDeniedHandler.php
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Catalyst\APIBundle\Service;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
|
||||||
|
use Symfony\Component\Security\Http\Authorization\AccessDeniedHandlerInterface;
|
||||||
|
|
||||||
|
class AccessDeniedHandler implements AccessDeniedHandlerInterface
|
||||||
|
{
|
||||||
|
public function handle(Request $req, AccessDeniedException $exception)
|
||||||
|
{
|
||||||
|
$content = $exception->getMessage();
|
||||||
|
|
||||||
|
return new Response($content, 403);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -38,7 +38,7 @@
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"App\\": "src/",
|
"App\\": "src/",
|
||||||
"Catalyst\\": "catalyst-libs/"
|
"Catalyst\\APIBundle\\": "catalyst/api-bundle/"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload-dev": {
|
"autoload-dev": {
|
||||||
|
|
|
||||||
1185
composer.lock
generated
1185
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -11,5 +11,7 @@ return [
|
||||||
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
|
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
|
||||||
Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true],
|
Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true],
|
||||||
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
|
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
|
||||||
|
|
||||||
|
Catalyst\APIBundle\CatalystAPIBundle::class => ['all' => true],
|
||||||
// DataDog\AuditBundle\DataDogAuditBundle::class => ['all' => true],
|
// DataDog\AuditBundle\DataDogAuditBundle::class => ['all' => true],
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,12 @@ security:
|
||||||
algorithm: bcrypt
|
algorithm: bcrypt
|
||||||
cost: 12
|
cost: 12
|
||||||
providers:
|
providers:
|
||||||
user:
|
user_provider:
|
||||||
entity:
|
entity:
|
||||||
class: App\Entity\User
|
class: App\Entity\User
|
||||||
property: username
|
property: username
|
||||||
|
api_key_user_provider:
|
||||||
|
id: Catalyst\APIBundle\Security\APIKeyUserProvider
|
||||||
firewalls:
|
firewalls:
|
||||||
dev:
|
dev:
|
||||||
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
||||||
|
|
@ -27,7 +29,15 @@ security:
|
||||||
pattern: ^\/rapi\/
|
pattern: ^\/rapi\/
|
||||||
security: false
|
security: false
|
||||||
|
|
||||||
|
warranty_api:
|
||||||
|
pattern: ^\/capi\/
|
||||||
|
stateless: true
|
||||||
|
simple_preauth:
|
||||||
|
authenticator: Catalyst\APIBundle\Security\APIKeyAuthenticator
|
||||||
|
provider: api_key_user_provider
|
||||||
|
|
||||||
main:
|
main:
|
||||||
|
provider: user_provider
|
||||||
form_login:
|
form_login:
|
||||||
login_path: login
|
login_path: login
|
||||||
check_path: login
|
check_path: login
|
||||||
|
|
|
||||||
75
config/routes/warranty_api.yaml
Normal file
75
config/routes/warranty_api.yaml
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
# api test
|
||||||
|
capi_test:
|
||||||
|
path: /capi/test
|
||||||
|
controller: App\Controller\CAPI\TestController::test
|
||||||
|
|
||||||
|
|
||||||
|
# battery api
|
||||||
|
|
||||||
|
# battery models
|
||||||
|
capi_battery_models:
|
||||||
|
path: /capi/battery_models
|
||||||
|
controller: App\Controller\CAPI\BatteryController::getModels
|
||||||
|
methods: [GET]
|
||||||
|
|
||||||
|
# battery sizes
|
||||||
|
capi_battery_sizes:
|
||||||
|
path: /capi/battery_sizes
|
||||||
|
controller: App\Controller\CAPI\BatteryController::getSizes
|
||||||
|
methods: [GET]
|
||||||
|
|
||||||
|
|
||||||
|
# vehicle api
|
||||||
|
|
||||||
|
capi_vehicle_mfgs:
|
||||||
|
path: /capi/vehicle_manufacturers
|
||||||
|
controller: App\Controller\CAPI\VehicleController::getManufacturers
|
||||||
|
methods: [GET]
|
||||||
|
|
||||||
|
capi_vehicle_list:
|
||||||
|
path: /capi/vehicles
|
||||||
|
controller: App\Controller\CAPI\VehicleController::list
|
||||||
|
methods: [GET]
|
||||||
|
|
||||||
|
|
||||||
|
# warranty api
|
||||||
|
|
||||||
|
# check warranty by serial
|
||||||
|
capi_warranty_find:
|
||||||
|
path: /capi/warranty/{serial}
|
||||||
|
controller: App\Controller\CAPI\WarrantyController::find
|
||||||
|
methods: [GET]
|
||||||
|
|
||||||
|
# register battery
|
||||||
|
capi_warranty_register:
|
||||||
|
path: /capi/warranty
|
||||||
|
controller: App\Controller\CAPI\WarrantyController::register
|
||||||
|
methods: [POST]
|
||||||
|
|
||||||
|
# claim warranty
|
||||||
|
capi_warranty_claim:
|
||||||
|
path: /capi/warranty/{serial}/claim
|
||||||
|
controller: App\Controller\CAPI\WarrantyController::claim
|
||||||
|
methods: [POST]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# customer vehicle api
|
||||||
|
|
||||||
|
# find customer vehicle by id
|
||||||
|
capi_cv_find_by_id:
|
||||||
|
path: /capi/customer_vehicle/id/{id}
|
||||||
|
controller: App\Controller\::findByID
|
||||||
|
methods: [GET]
|
||||||
|
|
||||||
|
# find customer vehicle by plate
|
||||||
|
capi_cv_find_by_plate:
|
||||||
|
path: /capi/customer_vehicle/plate/{plate}
|
||||||
|
controller: App\Controller\CAPI\CustomerVehicle::findByPlate
|
||||||
|
methods: [GET]
|
||||||
|
|
||||||
|
# register customer vehicle
|
||||||
|
capi_cv_register:
|
||||||
|
path: /capi/customer_vehicle
|
||||||
|
controller: App\Controller\CAPI\CustomerVehicle::register
|
||||||
|
methods: [POST]
|
||||||
|
|
@ -75,3 +75,20 @@ services:
|
||||||
arguments:
|
arguments:
|
||||||
$ip_address: "%env(APNS_REDIS_IP_ADDRESS)%"
|
$ip_address: "%env(APNS_REDIS_IP_ADDRESS)%"
|
||||||
$port: "%env(APNS_REDIS_PORT)%"
|
$port: "%env(APNS_REDIS_PORT)%"
|
||||||
|
|
||||||
|
Catalyst\APIBundle\Security\APIKeyUserProvider:
|
||||||
|
arguments:
|
||||||
|
$em: "@doctrine.orm.entity_manager"
|
||||||
|
|
||||||
|
Catalyst\APIBundle\Security\APIKeyAuthenticator:
|
||||||
|
arguments:
|
||||||
|
$em: "@doctrine.orm.entity_manager"
|
||||||
|
|
||||||
|
Catalyst\APIBundle\Command\UserCreateCommand:
|
||||||
|
arguments:
|
||||||
|
$em: "@doctrine.orm.entity_manager"
|
||||||
|
tags: ['console.command']
|
||||||
|
|
||||||
|
Catalyst\APIBundle\Command\TestCommand:
|
||||||
|
tags: ['console.command']
|
||||||
|
|
||||||
|
|
|
||||||
55
src/Controller/CAPI/BatteryController.php
Normal file
55
src/Controller/CAPI/BatteryController.php
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller\CAPI;
|
||||||
|
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Doctrine\ORM\Query;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Catalyst\APIBundle\Controller\APIController;
|
||||||
|
use Catalyst\APIBundle\Response\APIResponse;
|
||||||
|
use App\Entity\BatteryModel;
|
||||||
|
use App\Entity\BatterySize;
|
||||||
|
|
||||||
|
class BatteryController extends Controller implements APIController
|
||||||
|
{
|
||||||
|
public function getModels(EntityManagerInterface $em)
|
||||||
|
{
|
||||||
|
$models = $em->getRepository(BatteryModel::class)->findBy([], ['name' => 'ASC']);
|
||||||
|
|
||||||
|
$result = [];
|
||||||
|
foreach ($models as $model)
|
||||||
|
{
|
||||||
|
$result[] = [
|
||||||
|
'id' => $model->getID(),
|
||||||
|
'name' => $model->getName(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'models' => $result,
|
||||||
|
];
|
||||||
|
|
||||||
|
return new APIResponse(true, 'Battery models loaded.', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSizes(EntityManagerInterface $em)
|
||||||
|
{
|
||||||
|
$sizes = $em->getRepository(BatterySize::class)->findBy([], ['name' => 'ASC']);
|
||||||
|
|
||||||
|
$result = [];
|
||||||
|
foreach ($sizes as $size)
|
||||||
|
{
|
||||||
|
$result[] = [
|
||||||
|
'id' => $size->getID(),
|
||||||
|
'name' => $size->getName(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'sizes' => $result,
|
||||||
|
];
|
||||||
|
|
||||||
|
return new APIResponse(true, 'Battery sizes loaded.', $data);
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/Controller/CAPI/TestController.php
Normal file
20
src/Controller/CAPI/TestController.php
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller\CAPI;
|
||||||
|
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Doctrine\ORM\Query;
|
||||||
|
use Catalyst\APIBundle\Controller\APIController;
|
||||||
|
use Catalyst\APIBundle\Response\APIResponse;
|
||||||
|
|
||||||
|
class TestController extends Controller implements APIController
|
||||||
|
{
|
||||||
|
public function test()
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'status' => 'Test successful.',
|
||||||
|
];
|
||||||
|
return new APIResponse(true, 'Test successful.', $data);
|
||||||
|
}
|
||||||
|
}
|
||||||
55
src/Controller/CAPI/VehicleController.php
Normal file
55
src/Controller/CAPI/VehicleController.php
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller\CAPI;
|
||||||
|
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Doctrine\ORM\Query;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Catalyst\APIBundle\Controller\APIController;
|
||||||
|
use Catalyst\APIBundle\Response\APIResponse;
|
||||||
|
use App\Entity\Vehicle;
|
||||||
|
use App\Entity\VehicleManufacturer;
|
||||||
|
|
||||||
|
class VehicleController extends Controller implements APIController
|
||||||
|
{
|
||||||
|
public function getManufacturers(EntityManagerInterface $em)
|
||||||
|
{
|
||||||
|
$mfgs = $em->getRepository(VehicleManufacturer::class)->findBy([], ['name' => 'ASC']);
|
||||||
|
|
||||||
|
$result = [];
|
||||||
|
foreach ($mfgs as $mfg)
|
||||||
|
{
|
||||||
|
$result[] = [
|
||||||
|
'id' => $mfg->getID(),
|
||||||
|
'name' => $mfg->getName(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'manufacturers' => $result,
|
||||||
|
];
|
||||||
|
|
||||||
|
return new APIResponse(true, 'Vehicle manufacturers loaded.', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function list(EntityManagerInterface $em)
|
||||||
|
{
|
||||||
|
$vehicles = $em->getRepository(Vehicle::class)->findBy([], ['manufacturer' => 'ASC', 'make' => 'ASC']);
|
||||||
|
|
||||||
|
$result = [];
|
||||||
|
foreach ($vehicles as $v)
|
||||||
|
{
|
||||||
|
$result[] = [
|
||||||
|
'id' => $v->getID(),
|
||||||
|
'name' => $v->getMake() . ' ' . $v->getModelYearFormatted(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'vehicles' => $result,
|
||||||
|
];
|
||||||
|
|
||||||
|
return new APIResponse(true, 'Vehicles loaded.', $data);
|
||||||
|
}
|
||||||
|
}
|
||||||
185
src/Controller/CAPI/WarrantyController.php
Normal file
185
src/Controller/CAPI/WarrantyController.php
Normal file
|
|
@ -0,0 +1,185 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller\CAPI;
|
||||||
|
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Doctrine\ORM\Query;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
|
||||||
|
use Catalyst\APIBundle\Controller\APIController;
|
||||||
|
use Catalyst\APIBundle\Response\APIResponse;
|
||||||
|
use App\Entity\Warranty;
|
||||||
|
use App\Ramcar\NameValue;
|
||||||
|
use App\Ramcar\WarrantyClass;
|
||||||
|
use App\Ramcar\WarrantyStatus;
|
||||||
|
use DateTime;
|
||||||
|
|
||||||
|
class WarrantyController extends Controller implements APIController
|
||||||
|
{
|
||||||
|
protected function checkRequiredParameters(Request $req, $params = [])
|
||||||
|
{
|
||||||
|
$missing = [];
|
||||||
|
|
||||||
|
// check if parameters are there
|
||||||
|
foreach ($params as $param)
|
||||||
|
{
|
||||||
|
if ($req->getMethod() == 'GET')
|
||||||
|
{
|
||||||
|
$check = $req->query->get($param);
|
||||||
|
if (empty($check))
|
||||||
|
$missing[] = $param;
|
||||||
|
}
|
||||||
|
// else if ($req->getMethod() == 'POST')
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$check = $req->request->get($param);
|
||||||
|
if (empty($check))
|
||||||
|
$missing[] = $param;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check missing parameters
|
||||||
|
if (count($missing) > 0)
|
||||||
|
{
|
||||||
|
$miss_string = implode(', ', $missing);
|
||||||
|
return 'Missing required parameter(s): ' . $miss_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function cleanSerial($serial)
|
||||||
|
{
|
||||||
|
return trim(strtoupper($serial));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function generateWarrantyData(Warranty $warr)
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'id' => (int) $warr->getID(),
|
||||||
|
'serial' => (string) $warr->getSerial(),
|
||||||
|
'warranty_class' => (string) $warr->getWarrantyClass(),
|
||||||
|
'plate_number' => (string) $warr->getPlateNumber(),
|
||||||
|
'status' => (string) $warr->getStatus(),
|
||||||
|
'date_create' => (string) $warr->getDateCreate()->format('YmdHis'),
|
||||||
|
'date_purchase' => (string) $warr->getDatePurchase()->format('Ymd'),
|
||||||
|
'date_expire' => (string) $warr->getDateExpire()->format('Ymd'),
|
||||||
|
];
|
||||||
|
|
||||||
|
$date_claim = $warr->getDateClaim();
|
||||||
|
if ($date_claim == null)
|
||||||
|
$data['date_claim'] = null;
|
||||||
|
else
|
||||||
|
$data['date_claim'] = (string) $warr->getDateClaim()->format('Ymd');
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function find($serial, EntityManagerInterface $em)
|
||||||
|
{
|
||||||
|
$clean_serial = $this->cleanSerial($serial);
|
||||||
|
$warr = $em->getRepository(Warranty::class)->findOneBy(['serial' => $clean_serial]);
|
||||||
|
|
||||||
|
if ($warr == null)
|
||||||
|
return new APIResponse(false, 'No warranty found with that serial number.', null, 404);
|
||||||
|
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'warranty' => $this->generateWarrantyData($warr),
|
||||||
|
];
|
||||||
|
|
||||||
|
return new APIResponse(true, 'Warranty found.', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function register(Request $req, EntityManagerInterface $em)
|
||||||
|
{
|
||||||
|
// required parameters
|
||||||
|
$params = [
|
||||||
|
'serial',
|
||||||
|
'warranty_class',
|
||||||
|
'plate_number',
|
||||||
|
'date_expire',
|
||||||
|
'date_purchase',
|
||||||
|
];
|
||||||
|
$msg = $this->checkRequiredParameters($req, $params);
|
||||||
|
error_log('msg - ' . $msg);
|
||||||
|
if ($msg)
|
||||||
|
return new APIResponse(false, $msg);
|
||||||
|
|
||||||
|
$serial = $req->request->get('serial');
|
||||||
|
$date_expire_string = $req->request->get('date_expire');
|
||||||
|
$date_pur_string = $req->request->get('date_purchase');
|
||||||
|
$warr_class = $req->request->get('warranty_class');
|
||||||
|
$plate = $req->request->get('plate_number');
|
||||||
|
|
||||||
|
// wrong date expire format
|
||||||
|
$date_expire = DateTime::createFromFormat('Ymd', $date_expire_string);
|
||||||
|
if ($date_expire === false)
|
||||||
|
return new APIResponse(false, 'Wrong date format: date_expire.');
|
||||||
|
|
||||||
|
// wrong date purchase format
|
||||||
|
$date_pur = DateTime::createFromFormat('Ymd', $date_pur_string);
|
||||||
|
if ($date_pur === false)
|
||||||
|
return new APIResponse(false, 'Wrong date format: date_purchase.');
|
||||||
|
|
||||||
|
// valid warranty class
|
||||||
|
if (!WarrantyClass::validate($warr_class))
|
||||||
|
return new APIResponse(false, 'Invalid warranty class.');
|
||||||
|
|
||||||
|
// plate number
|
||||||
|
$plate = Warranty::cleanPlateNumber($plate);
|
||||||
|
if (!$plate)
|
||||||
|
return new APIResponse(false, 'Invalid plate number.');
|
||||||
|
|
||||||
|
// warranty
|
||||||
|
$warr = new Warranty();
|
||||||
|
$warr->setSerial($serial)
|
||||||
|
->setWarrantyClass($warr_class)
|
||||||
|
->setPlateNumber($plate)
|
||||||
|
->setDatePurchase($date_pur)
|
||||||
|
->setDateClaim(null)
|
||||||
|
->setDateExpire($date_expire);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$em->persist($warr);
|
||||||
|
$em->flush();
|
||||||
|
}
|
||||||
|
catch (UniqueConstraintViolationException $e)
|
||||||
|
{
|
||||||
|
return new APIResponse(false, 'Duplicate serial encountered.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// data
|
||||||
|
$data = [
|
||||||
|
'warranty' => $this->generateWarrantyData($warr),
|
||||||
|
];
|
||||||
|
|
||||||
|
return new APIResponse(true, 'Warranty registered.', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function claim(Request $req, EntityManagerInterface $em, $serial)
|
||||||
|
{
|
||||||
|
$clean_serial = $this->cleanSerial($serial);
|
||||||
|
$warr = $em->getRepository(Warranty::class)->findOneBy(['serial' => $clean_serial]);
|
||||||
|
|
||||||
|
// no warranty
|
||||||
|
if ($warr == null)
|
||||||
|
return new APIResponse(false, 'No warranty found with that serial number.', null, 404);
|
||||||
|
|
||||||
|
// warranty is not active
|
||||||
|
if (!$warr->canClaim())
|
||||||
|
return new APIResponse(false, 'Warranty is not active.');
|
||||||
|
|
||||||
|
// set status to claim
|
||||||
|
$warr->setStatus(WarrantyStatus::CLAIMED)
|
||||||
|
->setDateClaim(new DateTime());
|
||||||
|
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// TODO: claim log
|
||||||
|
|
||||||
|
return new APIResponse(true, 'Warranty claimed successfully.');
|
||||||
|
}
|
||||||
|
}
|
||||||
207
src/Entity/Warranty.php
Normal file
207
src/Entity/Warranty.php
Normal file
|
|
@ -0,0 +1,207 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use App\Ramcar\WarrantyClass;
|
||||||
|
use App\Ramcar\WarrantyStatus;
|
||||||
|
use DateTime;
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Entity
|
||||||
|
* @ORM\Table(
|
||||||
|
* name="warranty",
|
||||||
|
* uniqueConstraints={
|
||||||
|
* @ORM\UniqueConstraint(columns={"serial"})
|
||||||
|
* }
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
class Warranty
|
||||||
|
{
|
||||||
|
// unique id
|
||||||
|
/**
|
||||||
|
* @ORM\Id
|
||||||
|
* @ORM\Column(type="integer")
|
||||||
|
* @ORM\GeneratedValue(strategy="AUTO")
|
||||||
|
*/
|
||||||
|
protected $id;
|
||||||
|
|
||||||
|
// serial number
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="string", length=50)
|
||||||
|
*/
|
||||||
|
protected $serial;
|
||||||
|
|
||||||
|
// warranty class
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="string", length=25)
|
||||||
|
*/
|
||||||
|
protected $warranty_class;
|
||||||
|
|
||||||
|
// plate
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="string", length=10)
|
||||||
|
*/
|
||||||
|
protected $plate_number;
|
||||||
|
|
||||||
|
// status
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="string", length=25)
|
||||||
|
*/
|
||||||
|
protected $status;
|
||||||
|
|
||||||
|
// date created
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="datetime")
|
||||||
|
*/
|
||||||
|
protected $date_create;
|
||||||
|
|
||||||
|
// date purchased
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="date")
|
||||||
|
*/
|
||||||
|
protected $date_purchase;
|
||||||
|
|
||||||
|
// date expires
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="date")
|
||||||
|
*/
|
||||||
|
protected $date_expire;
|
||||||
|
|
||||||
|
// date claimed
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="date", nullable=true)
|
||||||
|
*/
|
||||||
|
protected $date_claim;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->date_create = new DateTime();
|
||||||
|
$this->warranty_class = WarrantyClass::WTY_PRIVATE;
|
||||||
|
$this->status = WarrantyStatus::ACTIVE;
|
||||||
|
$this->date_claim = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getID()
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setSerial($serial)
|
||||||
|
{
|
||||||
|
$this->serial = $serial;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSerial()
|
||||||
|
{
|
||||||
|
return $this->serial;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setWarrantyClass($class)
|
||||||
|
{
|
||||||
|
$this->warranty_class = $class;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getWarrantyClass()
|
||||||
|
{
|
||||||
|
return $this->warranty_class;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: use a service to handle plate number filtering
|
||||||
|
public static function cleanPlateNumber($plate)
|
||||||
|
{
|
||||||
|
// trim and make upper case
|
||||||
|
$clean_plate = strtoupper(trim($plate));
|
||||||
|
|
||||||
|
// remove invalid characters
|
||||||
|
$clean_plate = preg_replace("/[^A-Z0-9]/", '', $clean_plate);
|
||||||
|
|
||||||
|
// check if format is correct
|
||||||
|
// AAA123 or AAA1234
|
||||||
|
$res = preg_match("/^[A-Z]{3}[0-9]{3,4}$/", $clean_plate);
|
||||||
|
if ($res)
|
||||||
|
return $clean_plate;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPlateNumber($plate)
|
||||||
|
{
|
||||||
|
// TODO: custom exception
|
||||||
|
$plate_number = $this->cleanPlateNumber($plate);
|
||||||
|
if (!$plate_number)
|
||||||
|
throw new Exception('Invalid plate number.');
|
||||||
|
|
||||||
|
$this->plate_number = $plate_number;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPlateNumber()
|
||||||
|
{
|
||||||
|
return $this->plate_number;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setStatus($status)
|
||||||
|
{
|
||||||
|
$this->status = $status;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStatus()
|
||||||
|
{
|
||||||
|
return $this->status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDateCreate()
|
||||||
|
{
|
||||||
|
return $this->date_create;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDatePurchase(DateTime $date)
|
||||||
|
{
|
||||||
|
$this->date_purchase = $date;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDatePurchase()
|
||||||
|
{
|
||||||
|
return $this->date_purchase;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDateExpire(DateTime $date)
|
||||||
|
{
|
||||||
|
$this->date_expire = $date;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDateExpire()
|
||||||
|
{
|
||||||
|
return $this->date_expire;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDateClaim(DateTime $date = null)
|
||||||
|
{
|
||||||
|
$this->date_claim = $date;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDateClaim()
|
||||||
|
{
|
||||||
|
return $this->date_claim;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canClaim()
|
||||||
|
{
|
||||||
|
if ($this->status == WarrantyStatus::ACTIVE)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if ($this->status == WarrantyStatus::CLAIMED)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/Ramcar/WarrantyStatus.php
Normal file
18
src/Ramcar/WarrantyStatus.php
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Ramcar;
|
||||||
|
|
||||||
|
class WarrantyStatus extends NameValue
|
||||||
|
{
|
||||||
|
const ACTIVE = 'active';
|
||||||
|
const EXPIRED = 'expired';
|
||||||
|
const CLAIMED = 'claimed';
|
||||||
|
const CANCELLED = 'cancelled';
|
||||||
|
|
||||||
|
const COLLECTION = [
|
||||||
|
'active' => 'Active',
|
||||||
|
'expired' => 'Expired',
|
||||||
|
'claimed' => 'Claimed',
|
||||||
|
'cancelled' => 'Cancelled',
|
||||||
|
];
|
||||||
|
}
|
||||||
15
symfony.lock
15
symfony.lock
|
|
@ -56,6 +56,9 @@
|
||||||
"ref": "c1431086fec31f17fbcfe6d6d7e92059458facc1"
|
"ref": "c1431086fec31f17fbcfe6d6d7e92059458facc1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"doctrine/event-manager": {
|
||||||
|
"version": "v1.0.0"
|
||||||
|
},
|
||||||
"doctrine/inflector": {
|
"doctrine/inflector": {
|
||||||
"version": "v1.2.0"
|
"version": "v1.2.0"
|
||||||
},
|
},
|
||||||
|
|
@ -71,6 +74,12 @@
|
||||||
"doctrine/orm": {
|
"doctrine/orm": {
|
||||||
"version": "v2.6.0"
|
"version": "v2.6.0"
|
||||||
},
|
},
|
||||||
|
"doctrine/persistence": {
|
||||||
|
"version": "v1.0.1"
|
||||||
|
},
|
||||||
|
"doctrine/reflection": {
|
||||||
|
"version": "v1.0.0"
|
||||||
|
},
|
||||||
"guzzlehttp/guzzle": {
|
"guzzlehttp/guzzle": {
|
||||||
"version": "6.3.0"
|
"version": "6.3.0"
|
||||||
},
|
},
|
||||||
|
|
@ -83,6 +92,9 @@
|
||||||
"jdorn/sql-formatter": {
|
"jdorn/sql-formatter": {
|
||||||
"version": "v1.2.17"
|
"version": "v1.2.17"
|
||||||
},
|
},
|
||||||
|
"nikic/php-parser": {
|
||||||
|
"version": "v4.1.0"
|
||||||
|
},
|
||||||
"ocramius/package-versions": {
|
"ocramius/package-versions": {
|
||||||
"version": "1.2.0"
|
"version": "1.2.0"
|
||||||
},
|
},
|
||||||
|
|
@ -197,6 +209,9 @@
|
||||||
"symfony/orm-pack": {
|
"symfony/orm-pack": {
|
||||||
"version": "v1.0.5"
|
"version": "v1.0.5"
|
||||||
},
|
},
|
||||||
|
"symfony/polyfill-ctype": {
|
||||||
|
"version": "v1.9.0"
|
||||||
|
},
|
||||||
"symfony/polyfill-mbstring": {
|
"symfony/polyfill-mbstring": {
|
||||||
"version": "v1.6.0"
|
"version": "v1.6.0"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue