Skip to content

Instantly share code, notes, and snippets.

@cameri
Created July 31, 2017 23:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cameri/84d1b7db3aa475572571361a9215ea80 to your computer and use it in GitHub Desktop.
Save cameri/84d1b7db3aa475572571361a9215ea80 to your computer and use it in GitHub Desktop.
<?php
namespace App\Controller\Api;
use Cake\Utility\Hash;
use Cake\Core\Configure;
use Cake\Collection\Collection;
use Cake\Event\Event;
use App\Controller\Api\AppController;
use App\Model\Response\OkHttpResponse;
use App\Model\Response\BadRequestHttpResponse;
use App\Model\Response\ServiceUnavailableHttpResponse;
use App\Model\Response\NotFoundHttpResponse;
use App\Model\Response\GoneHttpResponse;
use App\Model\Response\NotModifiedHttpResponse;
use App\Model\Response\MethodNotAllowedHttpResponse;
/**
* Businesses Controller.
*
* @property \App\Model\Table\BusinessesTable $Businesses
*/
class BusinessesController extends AppController
{
public function beforeFilter(Event $event)
{
parent::beforeFilter($event);
$this->Auth->allow(['index', 'view','featured', 'newBusinesses']);
}
/**
* Index method.
*
* @return \Cake\Network\Response|null
*/
public function index()
{
if (!$this->request->is(['get'])) {
return $this->respond(new MethodNotAllowedHttpResponse(['message' => __('Method not allowed')], ['X-Status-Reason' => __('Request method is not GET.')]));
}
// Valid ordering fields
$valid_ordering_fields = ['id', 'name', 'created', 'modified'];
// Query filters
$per_page = (int) $this->request->query('per_page');
$per_page = min(max($per_page, 1), 100);
$page = (int) $this->request->query('page');
$page = max($page, 1);
$name = $this->request->query('name');
$visible = $this->request->query('visible');
$q = $this->request->query('q');
$vanity_name = $this->request->query('vanity_name');
$sort = $this->request->query('sort');
$category = $this->request->param('category_id');
$strategy = $this->request->query('strategy');
if (empty($strategy)) {
$strategy = 'contains';
}
if (!in_array($strategy, ['contains','startsWith'])) {
return $this->respond(new BadRequestHttpResponse([ 'message' => __('Bad request') ], [ 'X-Status-Reason' => __('Query field `strategy` is not valid.') ]));
}
if ($category) {
$query = $this->{$this->modelClass}->find('byCategory', ['category' => $category]);
} else {
$query = $this->{$this->modelClass}->find('all');
}
// Filter virtually deleted entities
$query = $query->where(["{$this->modelClass}.deleted" => false]);
// Filter by various field when querying
if (mb_strlen($q) > 0) {
// Validate query field `name`
if (mb_strlen($q) > 100) {
return $this->respond(new BadRequestHttpResponse([ 'message' => __('Bad request') ], [ 'X-Status-Reason' => __('Query field `q` is too long.') ]));
}
if (in_array($strategy, ['startsWith'])) {
if (mb_strlen($q) < 1) {
return $this->respond(new BadRequestHttpResponse([ 'message' => __('Bad request') ], [ 'X-Status-Reason' => __('Query field `q` is too short.') ]));
}
if ($q === '0') {
$query = $query->where([
'name REGEXP' => '^[0-9]',
]);
} else if ($q === '*') {
// no filtering
} else {
$query = $query->where([
'OR' => [
'name LIKE' => $q . '%',
],
]);
}
} else {
$query = $query->where([
'OR' => [
'name LIKE' => '%' . $q . '%',
'keywords LIKE' => '%' . $q . '%',
],
]);
}
}
// Filter by name field
if (!empty($name)) {
// Validate query field `name`
if (mb_strlen($name) < 3) {
return $this->respond(new BadRequestHttpResponse(['message' => __('Bad request')], ['X-Status-Reason' => __('Query field `name` is too short.')]));
} elseif (mb_strlen($name) > 100) {
return $this->respond(new BadRequestHttpResponse(['message' => __('Bad request')], ['X-Status-Reason' => __('Query field `name` is too long.')]));
}
$query = $query->where([
'name LIKE' => '%'.$name.'%',
]);
}
// Filter by vanity_name field
if (!empty($vanity_name)) {
// Validate query field `name`
if (mb_strlen($vanity_name) > 100) {
return $this->respond(new BadRequestHttpResponse(['message' => __('Bad request')], ['X-Status-Reason' => __('Query field `vanity_name` is too long.')]));
}
$query = $query->where(['vanity_name' => $vanity_name]);
}
// Filter by visibility
if (!empty($visible)) {
// Validate query field`visible`
$visible = $this->ApiHelper->parseBooleanField($visible);
if (is_null($visible)) {
return $this->respond(new BadRequestHttpResponse(['message' => __('Bad request')], ['X-Status-Reason' => __('Query field `visible` is not valid.')]));
}
$now = new \DateTime();
if ($visible) {
$query = $query->where(function ($exp) use ($now) {
return $exp->lte('visible_since', $now)->gte('visible_until', $now);
});
} else { // 'false' is implied
$query = $query->orWhere(function ($exp) use ($now) {
return $exp->gt('visible_since', $now)
->lt('visible_until', $now);
});
}
}
// Get total entity count before pagination
try {
$count = $query->count();
} catch (Exception $ex) {
return $this->respond(new ServiceUnavailableHttpResponse(['message' => $ex->getMessage(), 'trace' => $ex->getTrace()]));
}
// Order by most-recent
if (!empty($sort)) {
if (mb_strlen($sort) > 100) {
return $this->respond(new BadRequestHttpResponse(['message' => __('Bad request')], ['X-Status-Reason' => __('Query field `sort` is too long.')]));
}
$sort_fields = $this->ApiHelper->parseSortField($sort);
if (is_null($sort_fields)) {
return $this->respond(new BadRequestHttpResponse(['message' => __('Bad request')], ['X-Status-Reason' => __('Query field `sort` is not valid.')]));
}
//$sort_fields = call_user_func_array('array_merge', $sort_fields);
//call_user_func([$query,'sort'], $sort_fields);
$sort_fields = array_map(function ($sort_spec) {
if ($sort_spec == 'post_count') {
return "{$this->modelClass}.$sort_spec";
} else {
return "$sort_spec";
}
}, $sort_fields);
$query = $query->order($sort_fields);
}
// Count total offers
// $entities = $query->select([ 'total_offers' => $query->func()->count('Offers.id') ])
// ->leftJoinWith('Offers')
// ->group([ "{$this->modelClass}.id" ])
// ->autoFields(true);
// Paginate results
$entities = $query->limit($per_page)
->page($page);
// Build response payload
try {
$data = [
'count' => $count,
'pages' => ceil($count / $per_page),
'data' => $entities->toArray(),
];
} catch (\Exception $ex) {
return $this->respond(new ServiceUnavailableHttpResponse(['message' => $ex->getMessage(), 'trace' => $ex->getTrace()]));
}
if (Configure::read('debug')) {
$new_data = [
'query' => $query->__toString(),
];
$data = Hash::merge($data, $new_data);
}
return $this->respond(new OkHttpResponse($data, ['X-Total-Count' => $count]));
}
/**
* View method.
*
* @param string|null $id
*
* @return \Cake\Network\Response|null
*
* @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found
*/
public function view($id = null)
{
// Validate HTTP method
if (!$this->request->is('get')) {
return $this->respond(new MethodNotAllowedHttpResponse(['message' => __('Method not allowed')], ['X-Status-Reason' => __('Request method is not GET.')]));
}
if ($this->isAdmin()) {
$query = $this->{$this->modelClass}->find('private');
} else {
$query = $this->{$this->modelClass}->find('public');
}
$id_is_numeric = is_numeric($id) && $id > 0;
if ($id_is_numeric) {
$query = $query->where(['id' => (int) $id]);
} else {
$query = $query->where(['vanity_name' => $id]);
}
$modelClass = $this->modelClass;
try {
$entity = $query->first();
} catch (Exception $ex) {
return $this->respond(new ServiceUnavailableHttpResponse(['message' => __('Unable to fetch entity')], ['X-Status-Reason' => __('Unable to fetch entity.')]));
}
// Verify that the entity exists
if (!$entity) {
return $this->respond(new NotFoundHttpResponse(['message' => __('Entity not found')], ['X-Status-Reason' => __('Entity does not exist.')]));
}
$this->response->modified($entity->modified->toIso8601String());
if ($this->response->checkNotModified($this->request)) {
return $this->respond(new NotModifiedHttpResponse([]));
}
// Verify that the entity hasn't been marked as deleted
if ($entity->deleted) {
return $this->respond(new GoneHttpResponse(['message' => __('Entity is gone')], ['X-Status-Reason' => __('Entity does not exist anymore.')]));
}
if (!$this->isAdmin()) {
$venues = (new Collection($entity->venues))
->map(function ($venue) {
$hours = (new Collection($venue->hours))
->sortBy('begin_hour', SORT_ASC, SORT_NUMERIC)
->groupBy('days')
->reduce(function ($carry, $item) {
$ranges = (new Collection($item))
->map(function ($value) {
$range = [
'begin_hour' => $value['begin_hour'],
'hour_range' => $value['hour_range'],
'hour_range_pretty' => $value['hour_range_pretty'],
];
return $range;
});
$carry[] = [
'days' => $item[0]['days'],
'days_pretty' => $item[0]['days_pretty'],
'hour_ranges' => $ranges,
];
return $carry;
}, []);
$venue->hours = $hours;
return $venue;
});
$entity->venues = $venues;
}
// Build response payload
$data = [
'data' => $entity,
];
if (Configure::read('debug')) {
$new_data = [
'query' => $query->__toString(),
];
$data = Hash::merge($data, $new_data);
}
return $this->respond(new OkHttpResponse($data));
}
/**
* Add method.
*
* @return \Cake\Network\Response|void Redirects on successful add, renders view otherwise
*/
public function add()
{
// Validate HTTP method
if (!$this->request->is('post')) {
return $this->respond(new MethodNotAllowedHttpResponse(['message' => __('Method not allowed')], ['X-Status-Reason' => __('Request method is not POST.')]));
}
$entity = $this->{$this->modelClass}->newEntity();
$entity = $this->{$this->modelClass}->patchEntity($entity, $this->request->data, [
'associated' => [
'Venues',
'Venues.VenuePaymentMethods',
'Venues.VenueHours',
'Venues.VenuePhones',
'Venues.VenueCategories',
'BusinessUrls',
'BusinessServices',
'BusinessImages'
],
]);
$errors = $entity->errors();
if ($errors) {
return $this->respond(new BadRequestHttpResponse(['message' => __('Bad request'), 'errors' => $errors], ['X-Status-Reason' => __('Entity did not pass validation.')]));
}
$result = $this->{$this->modelClass}->save($entity);
if (!$result) {
return $this->respond(new BadRequestHttpResponse(['message' => __('Bad request')], ['X-Status-Reason' => __('Entity was not saved.')]));
}
//$entity = $this->{$this->modelClass}->find('private')->where(['id'=>$result->id])->first();
$data = [
'data' => $result,
];
return $this->respond(new OkHttpResponse($data));
}
/**
* Edit method.
*
* @param string|null $id Business id
*
* @return \Cake\Network\Response|void Redirects on successful edit, renders view otherwise
*
* @throws \Cake\Network\Exception\NotFoundException When record not found
*/
public function edit($id = null)
{
// Validate HTTP method
if (!$this->request->is('put', 'patch', 'post')) {
return $this->respond(new MethodNotAllowedHttpResponse(['message' => __('Method not allowed')], ['X-Status-Reason' => __('Request method is not PUT.')]));
}
$query = $this->{$this->modelClass}->find('private')->where(['id' => (int) $id]);
try {
$entity = $query->first();
} catch (Exception $ex) {
return $this->respond(new ServiceUnavailableHttpResponse(['message' => __('Unable to fetch entity')], ['X-Status-Reason' => __('Unable to fetch entity.')]));
}
// Verify that the entity exists
if (!$entity) {
return $this->respond(new NotFoundHttpResponse(['message' => __('Entity not found')], ['X-Status-Reason' => __('Entity does not exist.')]));
}
// Verify that the entity hasn't been marked as deleted
if ($entity->deleted) {
return $this->respond(new GoneHttpResponse(['message' => __('Entity is gone')], ['X-Status-Reason' => __('Entity does not exist anymore.')]));
}
$entity = $this->{$this->modelClass}->patchEntity($entity, $this->request->data, [
'associated' => [
'Venues',
'Venues.VenuePaymentMethods',
'Venues.VenueHours',
'Venues.VenuePhones',
'Venues.VenueCategories',
'BusinessUrls',
'BusinessServices',
'BusinessImages'
],
]);
//print_r($entity);
//die();
$errors = $entity->errors();
if ($errors) {
return $this->respond(new BadRequestHttpResponse(['message' => __('Bad request'), 'errors' => $errors], ['X-Status-Reason' => __('Entity did not pass validation.')]));
}
if (!$this->{$this->modelClass}->save($entity)) {
return $this->respond(new BadRequestHttpResponse(['message' => __('Bad request')], ['X-Status-Reason' => __('Entity was not saved.')]));
}
$entity = $this->{$this->modelClass}->find('private')->where(['id'=>(int)$id])->first();
$data = [
'data' => $entity,
];
return $this->respond(new OkHttpResponse($data));
}
/**
* Delete method.
*
* @param string|null $id Business id
*
* @return \Cake\Network\Response|null Redirects to index
*
* @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found
*/
public function delete($id = null)
{
// Validate HTTP method
if (!$this->request->is('delete')) {
return $this->respond(new MethodNotAllowedHttpResponse(['message' => __('Method not allowed')], ['X-Status-Reason' => __('Request method is not DELETE.')]));
}
$query = $this->{$this->modelClass}->find()->where(['id' => (int) $id]);
try {
$entity = $query->first();
} catch (Exception $ex) {
return $this->respond(new ServiceUnavailableHttpResponse(['message' => __('Unable to fetch entity')], ['X-Status-Reason' => __('Unable to fetch entity.')]));
}
// Verify that the entity exists
if (!$entity) {
return $this->respond(new NotFoundHttpResponse(['message' => __('Entity not found')], ['X-Status-Reason' => __('Entity does not exist.')]));
}
// Verify that the entity hasn't been marked as deleted
if ($entity->deleted) {
return $this->respond(new GoneHttpResponse(['message' => __('Entity is gone')], ['X-Status-Reason' => __('Entity does not exist anymore.')]));
}
$entity->softDelete();
if (!$this->{$this->modelClass}->save($entity)) {
return $this->respond(new BadRequestHttpResponse(['message' => __('Bad request')], ['X-Status-Reason' => __('Entity was not saved.')]));
}
return $this->respond(new OkHttpResponse([]));
}
public function recommended()
{
// Query filters
$per_page = (int) $this->request->query('per_page');
$per_page = min(max($per_page, 1), 100);
$page = (int) $this->request->query('page');
$page = max($page, 1);
$name = $this->request->query('name');
$vanity_name = $this->request->query('vanity_name');
try {
$now = new \DateTime();
$query = $this->{$this->modelClass}->find('recommended')
->find('visible')
->order(["{$this->modelClass}.name ASC"]);
} catch (\Exception $ex) {
return $this->respond(new ServiceUnavailableHttpResponse(['message' => $ex->getMessage(), 'trace' => $ex->getTrace()]));
}
// Filter by name field
if (!empty($name)) {
// Validate query field `name`
if (mb_strlen($name) < 3) {
return $this->respond(new BadRequestHttpResponse(['message' => __('Bad request')], ['X-Status-Reason' => __('Query field `name` is too short.')]));
} elseif (mb_strlen($name) > 100) {
return $this->respond(new BadRequestHttpResponse(['message' => __('Bad request')], ['X-Status-Reason' => __('Query field `name` is too long.')]));
}
$query = $query->where([
'name LIKE' => '%'.$name.'%',
]);
}
// Filter by vanity_name field
if (!empty($vanity_name)) {
// Validate query field `name`
if (mb_strlen($vanity_name) > 100) {
return $this->respond(new BadRequestHttpResponse(['message' => __('Bad request')], ['X-Status-Reason' => __('Query field `vanity_name` is too long.')]));
}
$query = $query->where(['vanity_name' => $vanity_name]);
}
// Count total offers
// $results = $query->select([ 'total_offers' => $query->func()->count('Offers.id') ])
// ->leftJoinWith('Offers')
// ->group([ "{$this->modelClass}.id" ])
// ->autoFields(true);
// Paginate results
$results = $query->limit($per_page)
->page($page);
// Build response payload
$data = [
'data' => $results,
];
if (Configure::read('debug')) {
$new_data = [
'query' => $query->__toString(),
];
$data = Hash::merge($data, $new_data);
}
return $this->respond(new OkHttpResponse($data));
}
public function featured()
{
// Query filters
$per_page = (int) $this->request->query('per_page');
$per_page = min(max($per_page, 1), 100);
$page = (int) $this->request->query('page');
$page = max($page, 1);
$name = $this->request->query('name');
$vanity_name = $this->request->query('vanity_name');
try {
$now = new \DateTime();
$query = $this->{$this->modelClass}->find('featured')
->find('visible')
->order(["{$this->modelClass}.name ASC"]);
} catch (\Exception $ex) {
return $this->respond(new ServiceUnavailableHttpResponse(['message' => $ex->getMessage(), 'trace' => $ex->getTrace()]));
}
// Filter by name field
if (!empty($name)) {
// Validate query field `name`
if (mb_strlen($name) < 3) {
return $this->respond(new BadRequestHttpResponse(['message' => __('Bad request')], ['X-Status-Reason' => __('Query field `name` is too short.')]));
} elseif (mb_strlen($name) > 100) {
return $this->respond(new BadRequestHttpResponse(['message' => __('Bad request')], ['X-Status-Reason' => __('Query field `name` is too long.')]));
}
$query = $query->where([
'name LIKE' => '%'.$name.'%',
]);
}
// Filter by vanity_name field
if (!empty($vanity_name)) {
// Validate query field `name`
if (mb_strlen($vanity_name) > 100) {
return $this->respond(new BadRequestHttpResponse(['message' => __('Bad request')], ['X-Status-Reason' => __('Query field `vanity_name` is too long.')]));
}
$query = $query->where(['vanity_name' => $vanity_name]);
}
// Count total offers
// $results = $query->select([ 'total_offers' => $query->func()->count('Offers.id') ])
// ->leftJoinWith('Offers')
// ->group([ "{$this->modelClass}.id" ])
// ->autoFields(true);
// Paginate results
$results = $query->limit($per_page)
->page($page);
// Build response payload
$data = [
'data' => $results,
];
if (Configure::read('debug')) {
$new_data = [
'query' => $query->__toString(),
];
$data = Hash::merge($data, $new_data);
}
return $this->respond(new OkHttpResponse($data));
}
public function newBusinesses()
{
// Query filters
$per_page = (int) $this->request->query('per_page');
$per_page = min(max($per_page, 1), 100);
$page = (int) $this->request->query('page');
$page = max($page, 1);
$name = $this->request->query('name');
$vanity_name = $this->request->query('vanity_name');
try {
$now = new \DateTime();
$query = $this->{$this->modelClass}->find('new')
->find('visible')
->order(["{$this->modelClass}.created DESC", "{$this->modelClass}.name ASC"]);
// Filter by name field
if (!empty($name)) {
// Validate query field `name`
if (mb_strlen($name) < 3) {
return $this->respond(new BadRequestHttpResponse(['message' => __('Bad request')], ['X-Status-Reason' => __('Query field `name` is too short.')]));
} elseif (mb_strlen($name) > 100) {
return $this->respond(new BadRequestHttpResponse(['message' => __('Bad request')], ['X-Status-Reason' => __('Query field `name` is too long.')]));
}
$query = $query->where([
'name LIKE' => '%'.$name.'%',
]);
}
// Filter by vanity_name field
if (!empty($vanity_name)) {
// Validate query field `name`
if (mb_strlen($vanity_name) > 100) {
return $this->respond(new BadRequestHttpResponse(['message' => __('Bad request')], ['X-Status-Reason' => __('Query field `vanity_name` is too long.')]));
}
$query = $query->where(['vanity_name' => $vanity_name]);
}
// Count total offers
// $results = $query->select([ 'total_offers' => $query->func()->count('Offers.id') ])
// ->leftJoinWith('Offers')
// ->group([ "{$this->modelClass}.id" ])
// ->autoFields(true);
// Paginate results
$results = $query->limit($per_page)
->page($page);
$results = $results->map(function ($row) {
$venues = new Collection($row->venues);
$categories = $venues
->extract('categories.{*}.category') // Extract all categories
->take(1)
->map(function ($row) {
$row = [
'id' => $row->id,
'name' => $row->name,
'vanity_name' => $row->vanity_name,
];
return $row;
})
->indexBy('id') // unique elements
->toArray();
$categories = array_values($categories);
$row = [
'id' => $row->id,
'vanity_name' => $row->vanity_name,
'name' => $row->name,
'categories' => $categories,
];
return $row;
});
// Build response payload
$data = [
'data' => $results,
];
if (Configure::read('debug')) {
$new_data = [
'query' => $query->__toString(),
];
$data = Hash::merge($data, $new_data);
}
} catch (\Exception $ex) {
return $this->respond(new ServiceUnavailableHttpResponse(['message' => $ex->getMessage(), 'trace' => $ex->getTrace()]));
}
return $this->respond(new OkHttpResponse($data));
}
/**
* asList method.
*
* @return \Cake\Network\Response|null
*/
public function listing()
{
if (!$this->request->is(['get'])) {
return $this->respond(new MethodNotAllowedHttpResponse(['message' => __('Method not allowed')], ['X-Status-Reason' => __('Request method is not GET.')]));
}
try {
// Query filters
$query = $this->{$this->modelClass}->find();
// Query fields
$query = $query->select(['id','name']);
// Filter virtually deleted entities
$query = $query->where(["{$this->modelClass}.deleted" => false]);
// Order by name ascendingly
$query = $query->order(["{$this->modelClass}.name ASC"]);
// Build response payload
$count = $query->count();
$data = [
'count' => $count,
'data' => $query->toArray(),
];
if (Configure::read('debug')) {
$new_data = [
'query' => $query->__toString(),
];
$data = Hash::merge($data, $new_data);
}
return $this->respond(new OkHttpResponse($data, ['X-Total-Count' => $count]));
} catch (\Exception $ex) {
return $this->respond(new ServiceUnavailableHttpResponse(['message' => $ex->getMessage(), 'trace' => $ex->getTrace()]));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment