Skip to content

Commit

Permalink
Merge pull request #60 from deanblackborough/patch-support
Browse files Browse the repository at this point in the history
Initial PATCH support and private resource_types
  • Loading branch information
deanblackborough authored Nov 10, 2018
2 parents 61121aa + b1dced6 commit 366181c
Show file tree
Hide file tree
Showing 22 changed files with 582 additions and 292 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

Full changelog for the Costs to Expect REST API.

## 2018-11-10 - v1.08.0

* Initial support for PATCHing, to start /resource_types/{resource_type_id}/resources/{resource_id}/items/{item_id}.
* Added initial support for private resource types, if a valid bearer exists private resource types are shown.
* Updates to README around expected responses.
* Renamed route validation helper methods, now clearer when shown in context.

## 2018-11-03 - v1.07.2

* Route corrections, some routes using unnecessary middleware.
Expand Down
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Overview

What does it costs to raise a child in the UK?
What does it cost to raise a child in the UK?

Costs to Expect is a long-term project, my wife and I are tracking the expenses to raise our child to
adulthood, 18.
Expand Down Expand Up @@ -60,9 +60,10 @@ POSTing to `http://api.local/v1/auth/login` - you will need a bearer for all the
* Collections will return an array and 200.
* Items will return a single object and a 200.
* Successful POST requests will return a single object and a 201.
* Successful PATCH requests will return 204.
* Successful DELETE requests will return a 204.
* Non 2xx results will return an object with a message field and optionally a fields array, in the
case of a validator error, 422, the fields array will contain the validation errors.
case of a validation error, 422, the fields array will contain the validation errors.

## Pagination

Expand Down Expand Up @@ -160,6 +161,7 @@ These routes require authorisation.
| POST | v1/resource_types/{resource_type_id}/resources |
| DELETE | v1/resource_types/{resource_type_id}/resources/{resource_id} |
| POST | v1/resource_types/{resource_type_id}/resources/{resource_id}/items |
| PATCH | v1/resource_types/{resource_type_id}/resources/{resource_id}/items/{item_id} |
| DELETE | v1/resource_types/{resource_type_id}/resources/{resource_id}/items/{item_id} |
| POST | v1/resource_types/{resource_type_id}/resources/{resource_id}/items/{item_id}/category |
| DELETE | v1/resource_types/{resource_type_id}/resources/{resource_id}/items/{item_id}/category/{item_category_id} |
Expand All @@ -174,5 +176,6 @@ These routes require authorisation.
* Move the user model.
* Dev setting to show generated queries.
* Switch to Money class.
* Create a white box version of API.
* Add limits on POST for single item collections.
* Additional helper classes
* General refactoring
8 changes: 4 additions & 4 deletions app/Http/Controllers/CategoryController.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ function ($category)
*/
public function show(Request $request, $category_id): JsonResponse
{
Validate::category($category_id);
Validate::categoryRoute($category_id);

$this->show_parameters = Get::parameters(['include_sub_categories']);

Expand Down Expand Up @@ -127,7 +127,7 @@ public function optionsIndex(Request $request): JsonResponse
*/
public function optionsShow(Request $request, string $category_id): JsonResponse
{
Validate::category($category_id);
Validate::categoryRoute($category_id);

return $this->generateOptionsForShow(
'api.descriptions.category.GET_show',
Expand Down Expand Up @@ -197,7 +197,7 @@ public function delete(
string $category_id
): JsonResponse
{
Validate::category($category_id);
Validate::categoryRoute($category_id);

try {
(new Category())->find($category_id)->delete();
Expand Down Expand Up @@ -255,7 +255,7 @@ private function setConditionalGetParameters()
]
];

(new ResourceType())->paginatedCollection()->map(
(new ResourceType())->paginatedCollection($this->include_private)->map(
function ($resource_type)
{
$this->get_parameters['resource_type']['allowed_values'][$this->hash->encode('resource_type', $resource_type->id)] = [
Expand Down
94 changes: 94 additions & 0 deletions app/Http/Controllers/Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,28 @@
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Config;

class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;

/**
* @var \App\Utilities\Hash
*/
protected $hash;

/**
* @var bool Include private content
*/
protected $include_private;

public function __construct()
{
$this->hash = new Hash();

$this->include_private = Auth::guard('api')->check();
}

/**
Expand Down Expand Up @@ -148,4 +159,87 @@ protected function generateOptionsForShow(

$this->optionsResponse($routes);
}

/**
* Return a 400 as there is nothing to PATCH
*
* @return JsonResponse
*/
protected function returnNothingToPatchError(): JsonResponse
{
response()->json(
[
'message' => 'There is nothing to PATCH, please include a request body'
],
400
)->send();
exit();
}

/**
* Return a 400 as there are invalid fields in the request body
*
* @param array $invalid_fields An array of invalid fields
*
* @return JsonResponse
*/
protected function returnInvalidFieldsInRequestError(array $invalid_fields): JsonResponse
{
response()->json(
[
'message' => 'Non existent fields in PATCH request body',
'fields' => $invalid_fields
],
400
)->send();
exit();
}

/**
* Check the request to ensure there is data to attempt patch
*
* @return boolean
*/
protected function isThereAnythingToPatchInRequest(): bool
{
if (count(request()->all()) === 0) {
return false;
}

return true;
}

/**
* Return success, no content (204)
*
* @return JsonResponse
*/
protected function returnSuccessNoContent(): JsonResponse
{
response()->json([], 204)->send();
exit();
}

/**
* Check to see if there are any invalid fields in the request
*
* @param array $update_fields An array of fields that can be patched
*
* @return false|array
*/
protected function areThereInvalidFieldsInRequest(array $update_fields)
{
$invalid_fields = [];
foreach (request()->all() as $key => $value) {
if (in_array($key, $update_fields) === false) {
$invalid_fields[] = $key;
}
}

if (count($invalid_fields) !== 0) {
return $invalid_fields;
}

return false;
}
}
10 changes: 5 additions & 5 deletions app/Http/Controllers/ItemCategoryController.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class ItemCategoryController extends Controller
*/
public function index(Request $request, string $resource_type_id, string $resource_id, string $item_id): JsonResponse
{
Validate::item($resource_type_id, $resource_id, $item_id);
Validate::itemRoute($resource_type_id, $resource_id, $item_id);

$item_category = (new ItemCategory())->paginatedCollection(
$resource_type_id,
Expand Down Expand Up @@ -78,7 +78,7 @@ public function show(
string $item_category_id
): JsonResponse
{
Validate::item($resource_type_id, $resource_id, $item_id);
Validate::itemRoute($resource_type_id, $resource_id, $item_id);

if ($item_category_id === 'nill') {
UtilityRequest::notFound();
Expand Down Expand Up @@ -118,7 +118,7 @@ public function show(
*/
public function optionsIndex(Request $request, string $resource_type_id, string $resource_id, string $item_id): JsonResponse
{
Validate::item($resource_type_id, $resource_id, $item_id);
Validate::itemRoute($resource_type_id, $resource_id, $item_id);

$this->setConditionalPostParameters($resource_type_id);

Expand Down Expand Up @@ -157,7 +157,7 @@ public function optionsShow(
string $item_category_id
): JsonResponse
{
Validate::item($resource_type_id, $resource_id, $item_id);
Validate::itemRoute($resource_type_id, $resource_id, $item_id);

if ($item_category_id === 'nill') {
UtilityRequest::notFound();
Expand Down Expand Up @@ -198,7 +198,7 @@ public function create(
string $item_id
): JsonResponse
{
Validate::item($resource_type_id, $resource_id, $item_id);
Validate::itemRoute($resource_type_id, $resource_id, $item_id);

$validator = (new ItemCategoryValidator)->create($request);

Expand Down
84 changes: 77 additions & 7 deletions app/Http/Controllers/ItemController.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
use Illuminate\Database\QueryException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Rs\Json\Patch;
use Rs\Json\Patch\InvalidOperationException;
use Rs\Json\Patch\InvalidPatchDocumentJsonException;
use Rs\Json\Patch\InvalidTargetDocumentJsonException;

/**
* Manage items
Expand All @@ -40,7 +44,7 @@ class ItemController extends Controller
*/
public function index(Request $request, string $resource_type_id, string $resource_id): JsonResponse
{
Validate::resource($resource_type_id, $resource_id);
Validate::resourceRoute($resource_type_id, $resource_id);

$this->collection_parameters = Get::parameters(['year', 'month', 'category', 'sub_category']);

Expand Down Expand Up @@ -98,7 +102,7 @@ public function show(
string $item_id
): JsonResponse
{
Validate::resource($resource_type_id, $resource_id);
Validate::resourceRoute($resource_type_id, $resource_id);

$item = (new Item())->single($resource_type_id, $resource_id, $item_id);

Expand Down Expand Up @@ -126,7 +130,7 @@ public function show(
*/
public function optionsIndex(Request $request, string $resource_type_id, string $resource_id): JsonResponse
{
Validate::resource($resource_type_id, $resource_id);
Validate::resourceRoute($resource_type_id, $resource_id);

$this->collection_parameters = Get::parameters(['year', 'month', 'category', 'sub_category']);

Expand Down Expand Up @@ -165,7 +169,7 @@ public function optionsShow(
string $item_id
): JsonResponse
{
Validate::resource($resource_type_id, $resource_id);
Validate::resourceRoute($resource_type_id, $resource_id);

$item = (new Item())->single($resource_type_id, $resource_id, $item_id);

Expand All @@ -191,7 +195,7 @@ public function optionsShow(
*/
public function create(Request $request, string $resource_type_id, string $resource_id): JsonResponse
{
Validate::resource($resource_type_id, $resource_id);
Validate::resourceRoute($resource_type_id, $resource_id);

$validator = (new ItemValidator)->create($request);

Expand Down Expand Up @@ -224,6 +228,72 @@ public function create(Request $request, string $resource_type_id, string $resou
);
}

/**
* Update the selected item
*
* @param Request $request
* @param string $resource_type_id
* @param string $resource_id
* @param string $item_id
*
* @return JsonResponse
*/
public function update(
Request $request,
string $resource_type_id,
string $resource_id,
string $item_id
): JsonResponse
{
Validate::itemRoute($resource_type_id, $resource_id, $item_id);

$validator = (new ItemValidator)->update($request);

if ($validator->fails() === true) {
return $this->returnValidationErrors($validator);
}

if ($this->isThereAnythingToPatchInRequest() === false) {
return $this->returnNothingToPatchError();
}

$invalid_fields = $this->areThereInvalidFieldsInRequest(
(new ItemValidator)->updateFields()
);

if ($invalid_fields !== false) {
return $this->returnInvalidFieldsInRequestError($invalid_fields);
}

$item = (new Item())->single($resource_type_id, $resource_id, $item_id);

$update_actualised = false;
foreach ($request->all() as $key => $value) {
$item->$key = $value;

if (in_array($key, ['total', 'percentage']) === true) {
$update_actualised = true;
}
}

if ($update_actualised === true) {
$item->setActualisedTotal($item->total, $item->percentage);
}

try {
$item->save();
} catch (Exception $e) {
return response()->json(
[
'message' => 'Error updating record'
],
500
);
}

return $this->returnSuccessNoContent();
}

/**
* Delete the assigned item
*
Expand All @@ -241,7 +311,7 @@ public function delete(
string $item_id
): JsonResponse
{
Validate::resource($resource_type_id, $resource_id);
Validate::resourceRoute($resource_type_id, $resource_id);

$item = (new Item())->single($resource_type_id, $resource_id, $item_id);

Expand All @@ -252,7 +322,7 @@ public function delete(
try {
$item->delete();

return response()->json([], 204);
return $this->returnSuccessNoContent();
} catch (QueryException $e) {
UtilityRequest::foreignKeyConstraintError();
} catch (Exception $e) {
Expand Down
Loading

0 comments on commit 366181c

Please sign in to comment.