resq/src/Controller/ResqAPI/CustomerController.php
2021-07-01 10:29:45 +00:00

663 lines
20 KiB
PHP

<?php
namespace App\Controller\ResqAPI;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Dotenv\Dotenv;
use Doctrine\ORM\Query;
use Doctrine\ORM\EntityManagerInterface;
use Catalyst\APIBundle\Controller\APIController;
use Catalyst\APIBundle\Response\APIResponse;
use App\Entity\MobileUser;
use App\Entity\Customer;
use App\Entity\PrivacyPolicy;
use App\Service\RisingTideGateway;
use App\Ramcar\CustomerSource;
use Catalyst\APIBundle\Access\Generator as ACLGenerator;
use DateTime;
class CustomerController extends APIController
{
protected $acl_gen;
public function __construct(ACLGenerator $acl_gen)
{
$this->acl_gen = $acl_gen;
}
public function register(Request $req, EntityManagerInterface $em)
{
$this->denyAccessUnlessGranted('mobile_user.register', null, 'No access.');
// confirm parameters
$required_params = [
'phone_model',
'os_type',
'os_version',
'phone_id'
];
// check required parameters
$msg = $this->checkRequiredParameters($req, $required_params);
if ($msg)
return new APIResponse(false, $msg);
// check if capi user already has a mobile user
$mobile_user = $this->findMobileUser($em);
if ($mobile_user != null)
return new APIResponse(false, 'User already registered');
// retry until we get a unique id
while (true)
{
try
{
// create mobile user
$mobile_user = new MobileUser();
$mobile_user->setPhoneModel($req->request->get('phone_model'))
->setOSType($req->request->get('os_type'))
->setOSVersion($req->request->get('os_version'))
->setPhoneID($req->request->get('phone_id'))
->setCapiUserId($user_id);
// reopen in case we get an exception
if (!$em->isOpen())
{
$em = $em->create(
$em->getConnection(),
$em->getConfiguration()
);
}
// save
$em->persist($mobile_user);
$em->flush();
}
catch (DBALException $e)
{
error_log($e->getMessage());
// delay one second and try again
sleep(1);
continue;
}
break;
}
// return data
// TODO: do we need to return the same names as before?
// right now, still usind the old names so we use session_id name
$data = [
'session_id' => $mobile_user->getID()
];
// response
return new APIResponse(true, 'Mobile user created.', $data);
}
public function confirmNumber(RisingTideGateway $rt, Request $req, EntityManagerInterface $em)
{
$this->denyAccessUnlessGranted('mobile_user.confirm.number', null, 'No access.');
// check parameters
$required_params = [
'phone_number',
];
// check required parameters
$msg = $this->checkRequiredParameters($req, $required_params);
if ($msg)
return new APIResponse(false, $msg);
// get mobile user
$mobile_user = $this->findMobileUser($em);
if ($mobile_user == null)
return new APIResponse(false, 'No mobile user found.');
// phone number
$phone_number = $req->request->get('phone_number');
// get otp_mode from .env
$dotenv = new Dotenv();
$dotenv->loadEnv(__DIR__.'/../../../.env');
$otp_mode = $_ENV['OTP_MODE'];
// check for hardcoded phone number for app store testing
if ($phone_number == '639221111111')
{
$code = '123456';
$mobile_user->setConfirmCode($code)
->setPhoneNumber($phone_number);
$em->flush();
return new APIResponse(true, 'Number confirmed.');
}
// check if otp_mode is test
if ($otp_mode == 'test')
{
$code = '123456';
$mobile_user->setConfirmCode($code)
->setPhoneNumber($phone_number);
$em->flush();
return new APIResponse(true, 'Number confirmed.');
}
// TODO: spam protection
// TODO: validate phone number
// generate code and save
$code = $this->generateConfirmCode();
$mobile_user->setConfirmCode($code)
->setPhoneNumber($phone_number);
$em->flush();
if ($otp_mode != 'test')
{
// send sms to number
$this->sendConfirmationCode($rt, $phone_number, $code);
}
// response
return new APIResponse(true, 'Number confirmed.');
}
public function validateCode(Request $req, EntityManagerInterface $em)
{
$this->denyAccessUnlessGranted('mobile_user.validate.code', null, 'No access.');
// check parameters
$required_params = [
'code',
];
// check required parameters
$msg = $this->checkRequiredParameters($req, $required_params);
if ($msg)
return new APIResponse(false, $msg);
// get mobile user
$mobile_user = $this->findMobileUser($em);
if ($mobile_user == null)
return new APIResponse(false, 'No mobile user found.');
// code is wrong
$code = $req->request->get('code');
if ($mobile_user->getConfirmCode() != $code)
return new APIResponse(false, 'Wrong confirm code');
// set confirm date
$date = new DateTime();
$mobile_user->setDateConfirmed($date)
->setConfirmed();
// TODO: check if we have the number registered before and merge
$dupe_user = $this->findNumberMobileUser($mobile_user->getPhoneNumber(), $em);
if ($dupe_user != null)
{
$dupe_cust = $dupe_user->getCustomer();
$mobile_user->setCustomer($dupe_cust);
}
// TODO: check if mobile matches mobile of customer
$customer = $this->findCustomerByNumber($mobile_user->getPhoneNumber(), $em);
if ($customer != null)
{
// TODO: if there is a dupe_sess, do we need to check if
// dupe_cust is the same as the customer we found?
$mobile_user->setCustomer($customer);
}
$em->flush();
// response
return new APIResponse(true, 'Code validated');
}
public function getInfo(Request $req, EntityManagerInterface $em)
{
$this->denyAccessUnlessGranted('mobile_user.get.info', null, 'No access.');
// get mobile user
$mobile_user = $this->findMobileUser($em);
if ($mobile_user == null)
return new APIResponse(false, 'No mobile user found.');
// if no customer found
$cust = $mobile_user->getCustomer();
if ($cust == null)
{
$data = [
'first_name' => '',
'last_name' => '',
'priv_third_party' => (bool) false,
'priv_promo' => (bool) false,
];
return new APIResponse(true, 'No customer info found', $data);
}
// send back customer details
$data = [
'first_name' => $cust->getFirstName(),
'last_name' => $cust->getLastName(),
'priv_third_party' => (bool) $cust->getPrivacyThirdParty(),
'priv_promo' => (bool) $cust->getPrivacyPromo(),
];
return new APIResponse(true, 'Customer info found', $data);
}
public function updateInfo(Request $req, EntityManagerInterface $em)
{
$this->denyAccessUnlessGranted('mobile_user.update.info', null, 'No access.');
// check required parameters
$required_params = [
'first_name',
'last_name',
];
// check required parameters
$msg = $this->checkRequiredParameters($req, $required_params);
if ($msg)
return new APIResponse(false, $msg);
// get mobile user
$mobile_user = $this->findMobileUser($em);
if ($mobile_user == null)
return new APIResponse(false, 'No mobile user found.');
$cust = $this->updateCustomerInfo($req, $em, $mobile_user);
// get privacy policy for mobile
$dotenv = new Dotenv();
$dotenv->loadEnv(__DIR__.'/../../../.env');
$policy_mobile_id = $_ENV['POLICY_MOBILE'];
$mobile_policy = $em->getRepository(PrivacyPolicy::class)->find($policy_mobile_id);
// set policy id
if ($mobile_policy != null)
{
$cust->setPrivacyPolicyMobile($mobile_policy);
}
$em->flush();
return new APIResponse(true, 'Customer info updated');
}
public function getStatus(Request $req, EntityManagerInterface $em)
{
$this->denyAccessUnlessGranted('mobile_user.get.status', null, 'No access.');
// get mobile user
$mobile_user = $this->findMobileUser($em);
if ($mobile_user == null)
return new APIResponse(false, 'No mobile user found.');
// set data
$data = [];
if ($mobile_user->isConfirmed())
$data['status'] = 'confirmed';
else
$data['status'] = 'unconfirmed';
return new APIResponse(true, 'Customer status', $data);
}
public function resendCode(Request $req, RisingTideGateway $rt, EntityManagerInterface $em)
{
$this->denyAccessUnlessGranted('mobile_user.resend.code', null, 'No access.');
// get mobile user
$mobile_user = $this->findMobileUser($em);
if ($mobile_user == null)
return new APIResponse(false, 'No mobile user found.');
// already confirmed
if ($mobile_user->isConfirmed())
return new APIResponse(true, 'User is already confirmed');
// have sent code before
if ($mobile_session->getDateCodeSent() != null)
return new APIResponse(true, 'Can only send confirm code every 5 mins');
// TODO: send via sms
$phone_number = $mobile_user->getPhoneNumber();
$code = $mobile_user->getConfirmCode();
$this->sendConfirmationCode($rt, $phone_number, $code);
return new APIResponse(true, 'Code re-sent');
}
public function versionCheck(Request $req, EntityManagerInterface $em)
{
$this->denyAccessUnlessGranted('mobile_user.version.check', null, 'No access.');
$required_params = [
'version',
];
// check required parameters
$msg = $this->checkRequiredParameters($req, $required_params);
if ($msg)
return new APIResponse(false, $msg);
// get mobile user
$mobile_user = $this->findMobileUser($em);
if ($mobile_user == null)
return new APIResponse(false, 'No mobile user found.');
$need_update = false;
$msg = 'Version is up to date.';
$api_version = $this->getParameter('api_version');
$app_version = $req->query->get('version');
$api_v = explode('.', $api_version);
$app_v = explode('.', $app_version);
if ($api_v[0] < $app_v[0])
return new APIResponse(false, 'Invalid application version: ' . $app_version);
if ($api_v[0] > $app_v[0])
{
$need_update = true;
$msg = 'Your version is outdated and needs an update to use the latest features RES-Q has to offer.';
}
$data = [
'need_update' => $need_update,
'message' => $msg,
];
return new APIResponse(true, 'Version checked', $data);
}
public function updateDeviceID(Request $req, EntityManagerInterface $em)
{
$this->denyAccessUnlessGranted('mobile_user.update.deviceid', null, 'No access.');
$required_params = [
'device_id',
];
// check required parameters
$msg = $this->checkRequiredParameters($req, $required_params);
if ($msg)
return new APIResponse(false, $msg);
// get mobile user
$mobile_user = $this->findMobileUser($em);
if ($mobile_user == null)
return new APIResponse(false, 'No mobile user found.');
$device_id = $req->request->get('device_id');
$mobile_user->setDevicePushID($device_id);
$em->flush();
// response
return new APIResponse(true, 'Device ID updated');
}
public function privacySettings(Request $req, EntityManagerInterface $em)
{
$this->denyAccessUnlessGranted('mobile_user.privacy.settings', null, 'No access.');
$required_params = [
'priv_third_party',
// 'priv_promo',
];
// check required parameters
$msg = $this->checkRequiredParameters($req, $required_params);
if ($msg)
return new APIResponse(false, $msg);
// get mobile user
$mobile_user = $this->findMobileUser($em);
if ($mobile_user == null)
return new APIResponse(false, 'No mobile user found.');
// get customer
$cust = $mobile_user->getCustomer();
if ($cust == null)
return new APIResponse(false, 'No customer information found');
// set privacy settings
$priv_promo = $req->request->get('priv_promo', false);
$priv_third_party = $req->request->get('priv_third_party');
$cust->setPrivacyThirdParty($priv_third_party)
->setPrivacyPromo($priv_promo);
// get the policy ids from .env
$dotenv = new Dotenv();
$dotenv->loadEnv(__DIR__.'/../../../.env');
$policy_promo_id = $_ENV['POLICY_PROMO'];
$policy_third_party_id = $_ENV['POLICY_THIRD_PARTY'];
// check if privacy settings are true
// if true, set the private policy for the customer
if ($priv_promo)
{
// find the promo policy
$policy = $em->getRepository(PrivacyPolicy::class)->find($policy_promo_id);
// set policy id
if ($policy != null)
{
$cust->setPrivacyPolicyPromo($policy);
}
}
if ($priv_third_party)
{
// find the third party policy
$policy = $em->getRepository(PrivacyPolicy::class)->find($policy_third_party_id);
// set policy id
if ($policy != null)
{
$cust->setPrivacyPolicyThirdParty($policy);
}
}
$em->flush();
return new APIResponse(true, 'Privacy policy settings set');
}
protected function findMobileUser($em)
{
// get capi user to link to mobile user
$user_id = $this->getUser()->getID();
$mobile_user = $em->getRepository(MobileUser::class)->findOneBy(['capi_user_id' => $user_id]);
return $mobile_user;
}
// TODO: find session customer by phone number
protected function findNumberMobileUser($number, $em)
{
$query = $em->getRepository(MobileUser::class)->createQueryBuilder('s')
->where('s.phone_number = :number')
->andWhere('s.customer is not null')
->andWhere('s.confirm_flag = 1')
->setParameter('number', $number)
->setMaxResults(1)
->getQuery();
// we just need one
$res = $query->getOneOrNullResult();
return $res;
}
protected function findCustomerByNumber($number, $em)
{
$customers = $em->getRepository(Customer::class)->findBy(['phone_mobile' => $number]);
// find the customer with the most number of cars
$car_count = 0;
$cust = null;
foreach($customers as $customer)
{
$vehicles = $customer->getVehicles();
if (count($vehicles) > $car_count)
{
$car_count = count($vehicles);
// "save" customer object
$cust = $customer;
}
}
return $cust;
}
protected function updateCustomerInfo($req, $em, $mobile_user)
{
// create new customer if it's not there
$cust = $mobile_user->getCustomer();
if ($cust == null)
{
$cust = new Customer();
// set customer source
$cust->setCreateSource(CustomerSource::MOBILE);
$em->persist($cust);
$mobile_user->setCustomer($cust);
}
$cust->setFirstName($req->request->get('first_name'))
->setLastName($req->request->get('last_name'))
->setEmail($req->request->get('email', ''))
->setConfirmed($mobile_user->isConfirmed());
// update mobile phone of customer
$cust->setPhoneMobile(substr($mobile_user->getPhoneNumber(), 2));
return $cust;
}
protected function sendConfirmationCode(RisingTideGateway $rt, $phone_number, $code)
{
// send sms to number
$message = "Your Resq confirmation code is $code.";
$rt->sendSMS($phone_number, 'MOTOLITE', $message);
}
// TODO: this might not be needed if we use APIController's checkRequiredParameters
// or we put this into a service?
protected function checkMissingParameters(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')
{
$check = $req->request->get($param);
if (empty($check))
$missing[] = $param;
}
else
return $params;
}
return $missing;
}
// TODO: since we broke the functions into separate files, we need
// to figure out how to make this accessible to all ResqAPI controllers
protected function checkParamsAndKey(Request $req, $em, $params)
{
// TODO: depends on what we decide to return
// returns APIResult object
$res = new APIResult();
// check for api_key in query string
$api_key = $req->query->get('api_key');
if (empty($api_key))
{
$res->setError(true)
->setErrorMessage('Missing API key');
return $res;
}
// check missing parameters
$missing = $this->checkMissingParameters($req, $params);
if (count($missing) > 0)
{
$miss_string = implode(', ', $missing);
$res->setError(true)
->setErrorMessage('Missing parameter(s): ' . $miss_string);
return $res;
}
// check api key
$mobile_user = $this->checkAPIKey($em, $req->query->get('api_key'));
if ($mobile_user == null)
{
$res->setError(true)
->setErrorMessage('Invalid API Key');
return $res;
}
// store session
$this->session = $sess;
return $res;
}
// TODO: type hint entity manager
// TODO: since we broke the functions into separate files, we need
// to figure out how to make this accessible to all ResqAPI controllers
protected function checkAPIKey($em, $api_key)
{
// find the api key (session id)
// TODO: user validation needs to be changed
$m_user = $em->getRepository(MobileUser::class)->find($api_key);
if ($m_user == null)
return null;
return $m_user;
}
}