More improvements on the REST API (references #139)

This commit is contained in:
Bernd Bestel 2019-01-21 22:13:42 +01:00
parent bfa59dd29c
commit 276bc94cc6
No known key found for this signature in database
GPG Key ID: 71BD34C0D4891300
15 changed files with 102 additions and 85 deletions

View File

@ -18,13 +18,13 @@ class FilesApiController extends BaseApiController
{
try
{
if (isset($request->getQueryParams()['file_name']) && !empty($request->getQueryParams()['file_name']) && IsValidFileName($request->getQueryParams()['file_name']))
if (IsValidFileName(base64_decode($args['fileName'])))
{
$fileName = $request->getQueryParams()['file_name'];
$fileName = base64_decode($args['fileName']);
}
else
{
throw new \Exception('file_name query parameter missing or contains an invalid filename');
throw new \Exception('Invalid filename');
}
$data = $request->getBody()->getContents();
@ -42,13 +42,13 @@ class FilesApiController extends BaseApiController
{
try
{
if (isset($request->getQueryParams()['file_name']) && !empty($request->getQueryParams()['file_name']) && IsValidFileName($request->getQueryParams()['file_name']))
if (IsValidFileName(base64_decode($args['fileName'])))
{
$fileName = $request->getQueryParams()['file_name'];
$fileName = base64_decode($args['fileName']);
}
else
{
throw new \Exception('file_name query parameter missing or contains an invalid filename');
throw new \Exception('Invalid filename');
}
$filePath = $this->FilesService->GetFilePath($args['group'], $fileName);
@ -74,13 +74,13 @@ class FilesApiController extends BaseApiController
{
try
{
if (isset($request->getQueryParams()['file_name']) && !empty($request->getQueryParams()['file_name']) && IsValidFileName($request->getQueryParams()['file_name']))
if (IsValidFileName(base64_decode($args['fileName'])))
{
$fileName = $request->getQueryParams()['file_name'];
$fileName = base64_decode($args['fileName']);
}
else
{
throw new \Exception('file_name query parameter missing or contains an invalid filename');
throw new \Exception('Invalid filename');
}
$filePath = $this->FilesService->GetFilePath($args['group'], $fileName);

View File

@ -26,7 +26,7 @@
"paths": {
"/system/db-changed-time": {
"get": {
"description": "Returns the time when the database was last changed",
"summary": "Returns the time when the database was last changed",
"tags": [
"System"
],
@ -46,7 +46,8 @@
},
"/system/log-missing-localization": {
"post": {
"description": "Logs a missing localization string (only when MODE == 'dev', so should only be called then)",
"summary": "Logs a missing localization string",
"description": "Only when MODE == 'dev', so should only be called then",
"tags": [
"System"
],
@ -80,7 +81,7 @@
},
"/objects/{entity}": {
"get": {
"description": "Returns all objects of the given entity",
"summary": "Returns all objects of the given entity",
"tags": [
"Generic entity interactions"
],
@ -144,7 +145,7 @@
}
},
"post": {
"description": "Adds a single object of the given entity",
"summary": "Adds a single object of the given entity",
"tags": [
"Generic entity interactions"
],
@ -211,7 +212,7 @@
},
"/objects/{entity}/{objectId}": {
"get": {
"description": "Returns a single object of the given entity",
"summary": "Returns a single object of the given entity",
"tags": [
"Generic entity interactions"
],
@ -282,7 +283,7 @@
}
},
"put": {
"description": "Edits the given object of the given entity",
"summary": "Edits the given object of the given entity",
"tags": [
"Generic entity interactions"
],
@ -356,7 +357,7 @@
}
},
"delete": {
"description": "Deletes a single object of the given entity",
"summary": "Deletes a single object of the given entity",
"tags": [
"Generic entity interactions"
],
@ -397,9 +398,10 @@
}
}
},
"/file/{group}": {
"/files/{group}/{fileName}": {
"get": {
"description": "Serves the given file (with proper Content-Type header)",
"summary": "Serves the given file",
"description": "With proper Content-Type header",
"tags": [
"Files"
],
@ -414,10 +416,10 @@
}
},
{
"in": "query",
"name": "file_name",
"in": "path",
"name": "fileName",
"required": true,
"description": "The file name (including extension)",
"description": "The file name (including extension)<br>**BASE64 encoded**",
"schema": {
"type": "string"
}
@ -448,7 +450,8 @@
}
},
"put": {
"description": "Uploads a single file to /data/storage/{group}/{file_name} (you need to remember the group and file name to get or delete it again)",
"summary": "Uploads a single file",
"description": "The file will be stored at /data/storage/{group}/{file_name} (you need to remember the group and file name to get or delete it again)",
"tags": [
"Files"
],
@ -463,10 +466,10 @@
}
},
{
"in": "query",
"name": "file_name",
"in": "path",
"name": "fileName",
"required": true,
"description": "The file name (including extension)",
"description": "The file name (including extension)<br>**BASE64 encoded**",
"schema": {
"type": "string"
}
@ -499,7 +502,7 @@
}
},
"delete": {
"description": "Deletes the given file",
"summary": "Deletes the given file",
"tags": [
"Files"
],
@ -514,10 +517,10 @@
}
},
{
"in": "query",
"name": "file_name",
"in": "path",
"name": "fileName",
"required": true,
"description": "The file name (including extension)",
"description": "The file name (including extension)<br>**BASE64 encoded**",
"schema": {
"type": "string"
}
@ -542,7 +545,7 @@
},
"/users": {
"get": {
"description": "Returns all users",
"summary": "Returns all users",
"tags": [
"User management"
],
@ -573,7 +576,7 @@
}
},
"post": {
"description": "Creates a new user",
"summary": "Creates a new user",
"tags": [
"User management"
],
@ -607,7 +610,7 @@
},
"/users/{userId}": {
"put": {
"description": "Edits the given user",
"summary": "Edits the given user",
"tags": [
"User management"
],
@ -650,7 +653,7 @@
}
},
"delete": {
"description": "Deletes the given user",
"summary": "Deletes the given user",
"tags": [
"User management"
],
@ -684,7 +687,7 @@
},
"/user/settings/{settingKey}": {
"get": {
"description": "Gets the given setting of the currently logged in user",
"summary": "Gets the given setting of the currently logged in user",
"tags": [
"User settings"
],
@ -723,7 +726,7 @@
}
},
"put": {
"description": "Sets the given setting of the currently logged in user",
"summary": "Sets the given setting of the currently logged in user",
"tags": [
"User settings"
],
@ -768,7 +771,7 @@
},
"/stock": {
"get": {
"description": "Returns all products which are currently in stock incl. the next expiring date per product",
"summary": "Returns all products which are currently in stock incl. the next expiring date per product",
"tags": [
"Stock"
],
@ -791,7 +794,7 @@
},
"/stock/volatile": {
"get": {
"description": "Returns all products which are expiring soon, are already expired or currently missing",
"summary": "Returns all products which are expiring soon, are already expired or currently missing",
"tags": [
"Stock"
],
@ -826,7 +829,7 @@
},
"/stock/products/{productId}": {
"get": {
"description": "Returns details of the given product",
"summary": "Returns details of the given product",
"tags": [
"Stock"
],
@ -867,7 +870,7 @@
},
"/stock/products/{productId}/entries": {
"get": {
"description": "Returns all stock entries of the given product in order of next use (first expiring first, then first in first out)",
"summary": "Returns all stock entries of the given product in order of next use (first expiring first, then first in first out)",
"tags": [
"Stock"
],
@ -911,7 +914,7 @@
},
"/stock/products/{productId}/price-history": {
"get": {
"description": "Returns the price history of the given product",
"summary": "Returns the price history of the given product",
"tags": [
"Stock"
],
@ -955,7 +958,7 @@
},
"/stock/products/{productId}/add": {
"post": {
"description": "Adds the given amount of the given product to stock",
"summary": "Adds the given amount of the given product to stock",
"tags": [
"Stock"
],
@ -1030,7 +1033,7 @@
},
"/stock/products/{productId}/consume": {
"post": {
"description": "Removes the given amount of the given product from stock",
"summary": "Removes the given amount of the given product from stock",
"tags": [
"Stock"
],
@ -1103,7 +1106,7 @@
},
"/stock/products/{productId}/inventory": {
"post": {
"description": "Inventories the given product (adds/removes based on the given new amount)",
"summary": "Inventories the given product (adds/removes based on the given new amount)",
"tags": [
"Stock"
],
@ -1165,7 +1168,7 @@
},
"/stock/products/{productId}/open": {
"post": {
"description": "Marks the given amount of the given product as opened",
"summary": "Marks the given amount of the given product as opened",
"tags": [
"Stock"
],
@ -1229,7 +1232,7 @@
},
"/stock/shoppinglist/add-missing-products": {
"post": {
"description": "Adds currently missing products (below defined min. stock amount) to the shopping list",
"summary": "Adds currently missing products (below defined min. stock amount) to the shopping list",
"tags": [
"Stock"
],
@ -1242,7 +1245,7 @@
},
"/stock/shoppinglist/clear": {
"post": {
"description": "Removes all items from the shopping list",
"summary": "Removes all items from the shopping list",
"tags": [
"Stock"
],
@ -1255,7 +1258,7 @@
},
"/stock/bookings/{bookingId}/undo": {
"post": {
"description": "Undoes a booking",
"summary": "Undoes a booking",
"tags": [
"Stock"
],
@ -1289,7 +1292,7 @@
},
"/stock/barcodes/external-lookup": {
"get": {
"description": "Executes an external barcode lookoup via the configured plugin with the given barcode",
"summary": "Executes an external barcode lookoup via the configured plugin with the given barcode",
"tags": [
"Stock"
],
@ -1340,7 +1343,7 @@
},
"/recipes/{recipeId}/add-not-fulfilled-products-to-shoppinglist": {
"post": {
"description": "Adds all missing products for the given recipe to the shopping list",
"summary": "Adds all missing products for the given recipe to the shopping list",
"tags": [
"Recipes"
],
@ -1364,7 +1367,7 @@
},
"/recipes/{recipeId}/consume": {
"post": {
"description": "Consumes all products of the given recipe",
"summary": "Consumes all products of the given recipe",
"tags": [
"Recipes"
],
@ -1388,7 +1391,7 @@
},
"/chores": {
"get": {
"description": "Returns all chores incl. the next estimated execution time per chore",
"summary": "Returns all chores incl. the next estimated execution time per chore",
"tags": [
"Chores"
],
@ -1411,7 +1414,7 @@
},
"/chores/{choreId}": {
"get": {
"description": "Returns details of the given chore",
"summary": "Returns details of the given chore",
"tags": [
"Chores"
],
@ -1452,7 +1455,7 @@
},
"/chores/{choreId}/execute": {
"post": {
"description": "Tracks an execution of the given chore",
"summary": "Tracks an execution of the given chore",
"tags": [
"Chores"
],
@ -1517,9 +1520,9 @@
}
}
},
"/chores/{executionId}/undo": {
"/chores/executions/{executionId}/undo": {
"post": {
"description": "Undoes a chore execution",
"summary": "Undoes a chore execution",
"tags": [
"Chores"
],
@ -1553,7 +1556,7 @@
},
"/batteries": {
"get": {
"description": "Returns all batteries incl. the next estimated charge time per battery",
"summary": "Returns all batteries incl. the next estimated charge time per battery",
"tags": [
"Batteries"
],
@ -1576,7 +1579,7 @@
},
"/batteries/{batteryId}": {
"get": {
"description": "Returns details of the given battery",
"summary": "Returns details of the given battery",
"tags": [
"Batteries"
],
@ -1617,7 +1620,7 @@
},
"/batteries/{batteryId}/charge": {
"post": {
"description": "Tracks a charge cycle of the given battery",
"summary": "Tracks a charge cycle of the given battery",
"tags": [
"Batteries"
],
@ -1678,9 +1681,9 @@
}
}
},
"/batteries/{chargeCycleId}/undo": {
"/batteries/charge-cycles/{chargeCycleId}/undo": {
"post": {
"description": "Undoes a battery charge cycle",
"summary": "Undoes a battery charge cycle",
"tags": [
"Batteries"
],
@ -1714,7 +1717,7 @@
},
"/tasks": {
"get": {
"description": "Returns all tasks which are not done yet",
"summary": "Returns all tasks which are not done yet",
"tags": [
"Tasks"
],
@ -1737,7 +1740,7 @@
},
"/tasks/{taskId}/complete": {
"post": {
"description": "Marks the given task as completed",
"summary": "Marks the given task as completed",
"tags": [
"Tasks"
],

View File

@ -270,17 +270,24 @@ Grocy.Api.Delete = function(apiFunction, jsonData, success, error)
Grocy.Api.UploadFile = function(file, group, fileName, success, error)
{
var xhr = new XMLHttpRequest();
var url = U('/api/file/' + group + '?file_name=' + encodeURIComponent(fileName));
var url = U('/api/files/' + group + '/' + btoa(fileName));
xhr.onreadystatechange = function()
{
if (xhr.readyState === XMLHttpRequest.DONE)
{
if (xhr.status === 200)
if (xhr.status === 200 || xhr.status === 204)
{
if (success)
{
success(JSON.parse(xhr.responseText));
if (xhr.status === 200)
{
success(JSON.parse(xhr.responseText));
}
else
{
success({ });
}
}
}
else
@ -301,17 +308,24 @@ Grocy.Api.UploadFile = function(file, group, fileName, success, error)
Grocy.Api.DeleteFile = function(fileName, group, success, error)
{
var xhr = new XMLHttpRequest();
var url = U('/api/file/' + group + '?file_name=' + encodeURIComponent(fileName));
var url = U('/api/files/' + group + '/' + btoa(fileName));
xhr.onreadystatechange = function()
{
if (xhr.readyState === XMLHttpRequest.DONE)
{
if (xhr.status === 200)
if (xhr.status === 200 || xhr.status === 204)
{
if (success)
{
success(JSON.parse(xhr.responseText));
if (xhr.status === 200)
{
success(JSON.parse(xhr.responseText));
}
else
{
success({ });
}
}
}
else

View File

@ -56,7 +56,7 @@ $(document).on('click', '.undo-battery-execution-button', function(e)
var element = $(e.currentTarget);
var chargeCycleId = $(e.currentTarget).attr('data-charge-cycle-id');
Grocy.Api.Post('batteries/' + chargeCycleId.toString() + '/undo', { },
Grocy.Api.Post('batteries/charge-cycles/' + chargeCycleId.toString() + '/undo', { },
function(result)
{
element.closest("tr").addClass("text-muted");

View File

@ -92,7 +92,7 @@ $('#tracked_time').find('input').on('keypress', function (e)
function UndoChargeCycle(chargeCycleId)
{
Grocy.Api.Post('batteries/' + chargeCycleId.toString() + '/undo', { },
Grocy.Api.Post('batteries/charge-cycles/' + chargeCycleId.toString() + '/undo', { },
function(result)
{
toastr.success(L("Charge cycle successfully undone"));

View File

@ -56,7 +56,7 @@ $(document).on('click', '.undo-chore-execution-button', function(e)
var element = $(e.currentTarget);
var executionId = $(e.currentTarget).attr('data-execution-id');
Grocy.Api.Post('chores/' + executionId.toString() + '/undo', { },
Grocy.Api.Post('chores/executions/' + executionId.toString() + '/undo', { },
function(result)
{
element.closest("tr").addClass("text-muted");

View File

@ -89,7 +89,7 @@ Grocy.Components.DateTimePicker.GetInputElement().on('keypress', function(e)
function UndoChoreExecution(executionId)
{
Grocy.Api.Post('chores/' + executionId.toString() + '/undo', { },
Grocy.Api.Post('chores/executions/' + executionId.toString() + '/undo', { },
function(result)
{
toastr.success(L("Chore execution successfully undone"));

View File

@ -41,7 +41,7 @@ Grocy.Components.ProductCard.Refresh = function(productId)
{
$("#productcard-no-product-picture").addClass("d-none");
$("#productcard-product-picture").removeClass("d-none");
$("#productcard-product-picture").attr("src", U('/api/file/productpictures?file_name=' + productDetails.product.picture_file_name));
$("#productcard-product-picture").attr("src", U('/api/files/productpictures/' + btoa(productDetails.product.picture_file_name)));
}
else
{

View File

@ -43,7 +43,7 @@ function DisplayEquipment(id)
if (equipmentItem.instruction_manual_file_name !== null && !equipmentItem.instruction_manual_file_name.isEmpty())
{
var pdfUrl = U('/api/file/equipmentmanuals?file_name=' + equipmentItem.instruction_manual_file_name);
var pdfUrl = U('/api/files/equipmentmanuals/' + btoa(equipmentItem.instruction_manual_file_name));
$("#selected-equipment-instruction-manual").attr("src", pdfUrl);
$("#selected-equipment-instruction-manual").removeClass("d-none");
$("#selected-equipment-has-no-instruction-manual-hint").addClass("d-none");

View File

@ -197,7 +197,7 @@ $('#new_amount').on('keyup', function(e)
function UndoStockBooking(bookingId)
{
Grocy.Api.Get('booking/' + bookingId.toString() + '/undo',
Grocy.Api.Post('stock/bookings/' + bookingId.toString() + '/undo', { },
function(result)
{
toastr.success(L("Booking successfully undone"));

View File

@ -202,7 +202,7 @@ if (GetUriParam("flow") === "shoppinglistitemtostock")
function UndoStockBooking(bookingId)
{
Grocy.Api.Get('sbooking/' + bookingId.toString() + '/undo',
Grocy.Api.Post('stock/bookings/' + bookingId.toString() + '/undo', { },
function(result)
{
toastr.success(L("Booking successfully undone"));

View File

@ -7,7 +7,7 @@
if (Grocy.EditMode === 'create')
{
Grocy.Api.Post('users/create', jsonData,
Grocy.Api.Post('users', jsonData,
function(result)
{
window.location.href = U('/users');
@ -21,7 +21,7 @@
}
else
{
Grocy.Api.Post('users/edit/' + Grocy.EditObjectId, jsonData,
Grocy.Api.Put('users/' + Grocy.EditObjectId, jsonData,
function(result)
{
window.location.href = U('/users');

View File

@ -94,9 +94,9 @@ $app->group('/api', function()
$this->delete('/objects/{entity}/{objectId}', '\Grocy\Controllers\GenericEntityApiController:DeleteObject');
// Files
$this->put('/file/{group}', '\Grocy\Controllers\FilesApiController:UploadFile');
$this->get('/file/{group}', '\Grocy\Controllers\FilesApiController:ServeFile');
$this->delete('/file/{group}', '\Grocy\Controllers\FilesApiController:DeleteFile');
$this->put('/files/{group}/{fileName}', '\Grocy\Controllers\FilesApiController:UploadFile');
$this->get('/files/{group}/{fileName}', '\Grocy\Controllers\FilesApiController:ServeFile');
$this->delete('/files/{group}/{fileName}', '\Grocy\Controllers\FilesApiController:DeleteFile');
// Users
$this->get('/users', '\Grocy\Controllers\UsersApiController:GetUsers');
@ -131,13 +131,13 @@ $app->group('/api', function()
$this->get('/chores', '\Grocy\Controllers\ChoresApiController:Current');
$this->get('/chores/{choreId}', '\Grocy\Controllers\ChoresApiController:ChoreDetails');
$this->post('/chores/{choreId}/execute', '\Grocy\Controllers\ChoresApiController:TrackChoreExecution');
$this->post('/chores/{executionId}/undo', '\Grocy\Controllers\ChoresApiController:UndoChoreExecution');
$this->post('/chores/executions/{executionId}/undo', '\Grocy\Controllers\ChoresApiController:UndoChoreExecution');
// Batteries
$this->get('/batteries', '\Grocy\Controllers\BatteriesApiController:Current');
$this->get('/batteries/{batteryId}', '\Grocy\Controllers\BatteriesApiController:BatteryDetails');
$this->post('/batteries/{batteryId}/charge', '\Grocy\Controllers\BatteriesApiController:TrackChargeCycle');
$this->post('/batteries/{chargeCycleId}/undo', '\Grocy\Controllers\BatteriesApiController:UndoChargeCycle');
$this->post('/batteries/charge-cycles/{chargeCycleId}/undo', '\Grocy\Controllers\BatteriesApiController:UndoChargeCycle');
// Tasks
$this->get('/tasks', '\Grocy\Controllers\TasksApiController:Current');

View File

@ -64,7 +64,7 @@
<label class="mt-2">{{ $L('Current instruction manual') }}</label>
<button id="delete-current-instruction-manual-button" class="btn btn-sm btn-danger @if(empty($equipment->instruction_manual_file_name)) disabled @endif"><i class="fas fa-trash"></i> {{ $L('Delete') }}</button>
@if(!empty($equipment->instruction_manual_file_name))
<embed id="current-equipment-instruction-manual" class="embed-responsive embed-responsive-4by3" src="{{ $U('/api/file/equipmentmanuals?file_name=' . $equipment->instruction_manual_file_name) }}" type="application/pdf">
<embed id="current-equipment-instruction-manual" class="embed-responsive embed-responsive-4by3" src="{{ $U('/api/files/equipmentmanuals/' . base64_encode($equipment->instruction_manual_file_name)) }}" type="application/pdf">
<p id="delete-current-instruction-manual-on-save-hint" class="form-text text-muted font-italic d-none">{{ $L('The current instruction manual will be deleted when you save the equipment') }}</p>
@else
<p id="no-current-instruction-manual-hint" class="form-text text-muted font-italic">{{ $L('No instruction manual available') }}</p>

View File

@ -143,7 +143,7 @@
<label class="mt-2">{{ $L('Current picture') }}</label>
<button id="delete-current-product-picture-button" class="btn btn-sm btn-danger @if(empty($product->picture_file_name)) disabled @endif"><i class="fas fa-trash"></i> {{ $L('Delete') }}</button>
@if(!empty($product->picture_file_name))
<p><img id="current-product-picture" src="{{ $U('/api/file/productpictures?file_name=' . $product->picture_file_name) }}" class="img-fluid img-thumbnail mt-2"></p>
<p><img id="current-product-picture" src="{{ $U('/api/files/productpictures/' . base64_encode($product->picture_file_name)) }}" class="img-fluid img-thumbnail mt-2"></p>
<p id="delete-current-product-picture-on-save-hint" class="form-text text-muted font-italic d-none">{{ $L('The current picture will be deleted when you save the product') }}</p>
@else
<p id="no-current-product-picture-hint" class="form-text text-muted font-italic">{{ $L('No picture available') }}</p>