From f62112983635a281108575bb69bb90df6bed68b7 Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Sun, 26 Jun 2016 15:17:38 -0400 Subject: Added new API calls for project attachements --- ChangeLog | 2 +- app/Api/Procedure/ProjectFileProcedure.php | 68 +++++++ app/Api/Procedure/SubtaskTimeTrackingProcedure.php | 6 +- app/Api/Procedure/TaskFileProcedure.php | 4 +- app/ServiceProvider/ApiProvider.php | 2 + app/ServiceProvider/AuthenticationProvider.php | 1 + doc/api-file-procedures.markdown | 217 -------------------- doc/api-json-rpc.markdown | 3 +- doc/api-project-file-procedures.markdown | 221 +++++++++++++++++++++ doc/api-task-file-procedures.markdown | 217 ++++++++++++++++++++ tests/docker/entrypoint.sh | 2 +- tests/integration/ProjectFileProcedureTest.php | 66 ++++++ tests/integration/TaskFileProcedureTest.php | 2 +- 13 files changed, 585 insertions(+), 226 deletions(-) create mode 100644 app/Api/Procedure/ProjectFileProcedure.php delete mode 100644 doc/api-file-procedures.markdown create mode 100644 doc/api-project-file-procedures.markdown create mode 100644 doc/api-task-file-procedures.markdown create mode 100644 tests/integration/ProjectFileProcedureTest.php diff --git a/ChangeLog b/ChangeLog index 85966ed8..7a56139c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -5,7 +5,7 @@ New features: * Added application and project roles validation for API procedure calls * Added new API call: "getProjectByIdentifier" -* Added new API calls for external task links +* Added new API calls for external task links, project attachments Improvements: diff --git a/app/Api/Procedure/ProjectFileProcedure.php b/app/Api/Procedure/ProjectFileProcedure.php new file mode 100644 index 00000000..48466ce3 --- /dev/null +++ b/app/Api/Procedure/ProjectFileProcedure.php @@ -0,0 +1,68 @@ +container)->check($this->getClassName(), 'getProjectFile', $project_id); + return $this->projectFileModel->getById($file_id); + } + + public function getAllProjectFiles($project_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getAllProjectFiles', $project_id); + return $this->projectFileModel->getAll($project_id); + } + + public function downloadProjectFile($project_id, $file_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'downloadProjectFile', $project_id); + + try { + $file = $this->projectFileModel->getById($file_id); + + if (! empty($file)) { + return base64_encode($this->objectStorage->get($file['path'])); + } + } catch (ObjectStorageException $e) { + $this->logger->error($e->getMessage()); + } + + return ''; + } + + public function createProjectFile($project_id, $filename, $blob) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'createProjectFile', $project_id); + + try { + return $this->projectFileModel->uploadContent($project_id, $filename, $blob); + } catch (ObjectStorageException $e) { + $this->logger->error(__METHOD__.': '.$e->getMessage()); + return false; + } + } + + public function removeProjectFile($project_id, $file_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeProjectFile', $project_id); + return $this->projectFileModel->remove($file_id); + } + + public function removeAllProjectFiles($project_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeAllProjectFiles', $project_id); + return $this->projectFileModel->removeAll($project_id); + } +} diff --git a/app/Api/Procedure/SubtaskTimeTrackingProcedure.php b/app/Api/Procedure/SubtaskTimeTrackingProcedure.php index 5d1988d6..b6d1102a 100644 --- a/app/Api/Procedure/SubtaskTimeTrackingProcedure.php +++ b/app/Api/Procedure/SubtaskTimeTrackingProcedure.php @@ -5,7 +5,7 @@ namespace Kanboard\Api\Procedure; use Kanboard\Api\Authorization\SubtaskAuthorization; /** - * Subtask Time Tracking API controller + * Subtask Time Tracking API controller * * @package Kanboard\Api\Procedure * @author Frederic Guillot @@ -25,13 +25,13 @@ class SubtaskTimeTrackingProcedure extends BaseProcedure return $this->subtaskTimeTrackingModel->logStartTime($subtask_id, $user_id); } - public function logSubtaskEndTime($subtask_id,$user_id) + public function logSubtaskEndTime($subtask_id, $user_id) { SubtaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'logSubtaskEndTime', $subtask_id); return $this->subtaskTimeTrackingModel->logEndTime($subtask_id, $user_id); } - public function getSubtaskTimeSpent($subtask_id,$user_id) + public function getSubtaskTimeSpent($subtask_id, $user_id) { SubtaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getSubtaskTimeSpent', $subtask_id); return $this->subtaskTimeTrackingModel->getTimeSpent($subtask_id, $user_id); diff --git a/app/Api/Procedure/TaskFileProcedure.php b/app/Api/Procedure/TaskFileProcedure.php index 5aa7ea0b..bd006578 100644 --- a/app/Api/Procedure/TaskFileProcedure.php +++ b/app/Api/Procedure/TaskFileProcedure.php @@ -30,7 +30,7 @@ class TaskFileProcedure extends BaseProcedure public function downloadTaskFile($file_id) { TaskFileAuthorization::getInstance($this->container)->check($this->getClassName(), 'downloadTaskFile', $file_id); - + try { $file = $this->taskFileModel->getById($file_id); @@ -51,7 +51,7 @@ class TaskFileProcedure extends BaseProcedure try { return $this->taskFileModel->uploadContent($task_id, $filename, $blob); } catch (ObjectStorageException $e) { - $this->logger->error($e->getMessage()); + $this->logger->error(__METHOD__.': '.$e->getMessage()); return false; } } diff --git a/app/ServiceProvider/ApiProvider.php b/app/ServiceProvider/ApiProvider.php index 194bee5b..5cf6231c 100644 --- a/app/ServiceProvider/ApiProvider.php +++ b/app/ServiceProvider/ApiProvider.php @@ -9,6 +9,7 @@ use Kanboard\Api\Procedure\BoardProcedure; use Kanboard\Api\Procedure\CategoryProcedure; use Kanboard\Api\Procedure\ColumnProcedure; use Kanboard\Api\Procedure\CommentProcedure; +use Kanboard\Api\Procedure\ProjectFileProcedure; use Kanboard\Api\Procedure\TaskExternalLinkProcedure; use Kanboard\Api\Procedure\TaskFileProcedure; use Kanboard\Api\Procedure\GroupProcedure; @@ -58,6 +59,7 @@ class ApiProvider implements ServiceProviderInterface ->withObject(new CategoryProcedure($container)) ->withObject(new CommentProcedure($container)) ->withObject(new TaskFileProcedure($container)) + ->withObject(new ProjectFileProcedure($container)) ->withObject(new LinkProcedure($container)) ->withObject(new ProjectProcedure($container)) ->withObject(new ProjectPermissionProcedure($container)) diff --git a/app/ServiceProvider/AuthenticationProvider.php b/app/ServiceProvider/AuthenticationProvider.php index 34b81b9d..978bc05b 100644 --- a/app/ServiceProvider/AuthenticationProvider.php +++ b/app/ServiceProvider/AuthenticationProvider.php @@ -202,6 +202,7 @@ class AuthenticationProvider implements ServiceProviderInterface $acl->add('SubtaskProcedure', '*', Role::PROJECT_MEMBER); $acl->add('SubtaskTimeTrackingProcedure', '*', Role::PROJECT_MEMBER); $acl->add('SwimlaneProcedure', '*', Role::PROJECT_MANAGER); + $acl->add('ProjectFileProcedure', '*', Role::PROJECT_MEMBER); $acl->add('TaskFileProcedure', '*', Role::PROJECT_MEMBER); $acl->add('TaskLinkProcedure', '*', Role::PROJECT_MEMBER); $acl->add('TaskExternalLinkProcedure', array('createExternalTaskLink', 'updateExternalTaskLink', 'removeExternalTaskLink'), Role::PROJECT_MEMBER); diff --git a/doc/api-file-procedures.markdown b/doc/api-file-procedures.markdown deleted file mode 100644 index 930be733..00000000 --- a/doc/api-file-procedures.markdown +++ /dev/null @@ -1,217 +0,0 @@ -API File Procedures -=================== - -## createTaskFile - -- Purpose: **Create and upload a new task attachment** -- Parameters: - - **project_id** (integer, required) - - **task_id** (integer, required) - - **filename** (integer, required) - - **blob** File content encoded in base64 (string, required) -- Result on success: **file_id** -- Result on failure: **false** -- Note: **The maximum file size depends of your PHP configuration, this method should not be used to upload large files** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "createTaskFile", - "id": 94500810, - "params": [ - 1, - 1, - "My file", - "cGxhaW4gdGV4dCBmaWxl" - ] -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 94500810, - "result": 1 -} -``` - -## getAllTaskFiles - -- Purpose: **Get all files attached to task** -- Parameters: - - **task_id** (integer, required) -- Result on success: **list of files** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "getAllTaskFiles", - "id": 1880662820, - "params": { - "task_id": 1 - } -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 1880662820, - "result": [ - { - "id": "1", - "name": "My file", - "path": "1\/1\/0db4d0a897a4c852f6e12f0239d4805f7b4ab596", - "is_image": "0", - "task_id": "1", - "date": "1432509941", - "user_id": "0", - "size": "15", - "username": null, - "user_name": null - } - ] -} -``` - -## getTaskFile - -- Purpose: **Get file information** -- Parameters: - - **file_id** (integer, required) -- Result on success: **file properties** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "getTaskFile", - "id": 318676852, - "params": [ - "1" - ] -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 318676852, - "result": { - "id": "1", - "name": "My file", - "path": "1\/1\/0db4d0a897a4c852f6e12f0239d4805f7b4ab596", - "is_image": "0", - "task_id": "1", - "date": "1432509941", - "user_id": "0", - "size": "15" - } -} -``` - -## downloadTaskFile - -- Purpose: **Download file contents (encoded in base64)** -- Parameters: - - **file_id** (integer, required) -- Result on success: **base64 encoded string** -- Result on failure: **empty string** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "downloadTaskFile", - "id": 235943344, - "params": [ - "1" - ] -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 235943344, - "result": "cGxhaW4gdGV4dCBmaWxl" -} -``` - -## removeTaskFile - -- Purpose: **Remove file** -- Parameters: - - **file_id** (integer, required) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "removeTaskFile", - "id": 447036524, - "params": [ - "1" - ] -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 447036524, - "result": true -} -``` - -## removeAllTaskFiles - -- Purpose: **Remove all files associated to a task** -- Parameters: - - **task_id** (integer, required) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "removeAllTaskFiles", - "id": 593312993, - "params": { - "task_id": 1 - } -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 593312993, - "result": true -} -``` diff --git a/doc/api-json-rpc.markdown b/doc/api-json-rpc.markdown index 8e783e71..6498b0cc 100644 --- a/doc/api-json-rpc.markdown +++ b/doc/api-json-rpc.markdown @@ -58,7 +58,8 @@ Usage - [Automatic Actions](api-action-procedures.markdown) - [Tasks](api-task-procedures.markdown) - [Subtasks](api-subtask-procedures.markdown) -- [Files](api-file-procedures.markdown) +- [Task Files](api-task-file-procedures.markdown) +- [Project Files](api-project-file-procedures.markdown) - [Links](api-link-procedures.markdown) - [Internal Task Links](api-internal-task-link-procedures.markdown) - [External Task Links](api-external-task-link-procedures.markdown) diff --git a/doc/api-project-file-procedures.markdown b/doc/api-project-file-procedures.markdown new file mode 100644 index 00000000..fdc5da1a --- /dev/null +++ b/doc/api-project-file-procedures.markdown @@ -0,0 +1,221 @@ +Project File API Procedures +=========================== + +## createProjectFile + +- Purpose: **Create and upload a new project attachment** +- Parameters: + - **project_id** (integer, required) + - **filename** (integer, required) + - **blob** File content encoded in base64 (string, required) +- Result on success: **file_id** +- Result on failure: **false** +- Note: **The maximum file size depends of your PHP configuration, this method should not be used to upload large files** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "createProjectFile", + "id": 94500810, + "params": [ + 1, + "My file", + "cGxhaW4gdGV4dCBmaWxl" + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 94500810, + "result": 1 +} +``` + +## getAllProjectFiles + +- Purpose: **Get all files attached to a project** +- Parameters: + - **project_id** (integer, required) +- Result on success: **list of files** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "getAllProjectFiles", + "id": 1880662820, + "params": { + "project_id": 1 + } +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 1880662820, + "result": [ + { + "id": "1", + "name": "My file", + "path": "1\/1\/0db4d0a897a4c852f6e12f0239d4805f7b4ab596", + "is_image": "0", + "project_id": "1", + "date": "1432509941", + "user_id": "0", + "size": "15", + "username": null, + "user_name": null + } + ] +} +``` + +## getProjectFile + +- Purpose: **Get file information** +- Parameters: + - **project_id** (integer, required) + - **file_id** (integer, required) +- Result on success: **file properties** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "getProjectFile", + "id": 318676852, + "params": [ + "42", + "1" + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 318676852, + "result": { + "id": "1", + "name": "My file", + "path": "1\/1\/0db4d0a897a4c852f6e12f0239d4805f7b4ab596", + "is_image": "0", + "project_id": "1", + "date": "1432509941", + "user_id": "0", + "size": "15" + } +} +``` + +## downloadProjectFile + +- Purpose: **Download project file contents (encoded in base64)** +- Parameters: + - **project_id** (integer, required) + - **file_id** (integer, required) +- Result on success: **base64 encoded string** +- Result on failure: **empty string** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "downloadProjectFile", + "id": 235943344, + "params": [ + "1", + "1" + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 235943344, + "result": "cGxhaW4gdGV4dCBmaWxl" +} +``` + +## removeProjectFile + +- Purpose: **Remove a file associated to a project** +- Parameters: + - **project_id** (integer, required) + - **file_id** (integer, required) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "removeProjectFile", + "id": 447036524, + "params": [ + "1", + "1" + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 447036524, + "result": true +} +``` + +## removeAllProjectFiles + +- Purpose: **Remove all files associated to a project** +- Parameters: + - **project_id** (integer, required) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "removeAllProjectFiles", + "id": 593312993, + "params": { + "project_id": 1 + } +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 593312993, + "result": true +} +``` diff --git a/doc/api-task-file-procedures.markdown b/doc/api-task-file-procedures.markdown new file mode 100644 index 00000000..51840bea --- /dev/null +++ b/doc/api-task-file-procedures.markdown @@ -0,0 +1,217 @@ +Task File API Procedures +======================== + +## createTaskFile + +- Purpose: **Create and upload a new task attachment** +- Parameters: + - **project_id** (integer, required) + - **task_id** (integer, required) + - **filename** (integer, required) + - **blob** File content encoded in base64 (string, required) +- Result on success: **file_id** +- Result on failure: **false** +- Note: **The maximum file size depends of your PHP configuration, this method should not be used to upload large files** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "createTaskFile", + "id": 94500810, + "params": [ + 1, + 1, + "My file", + "cGxhaW4gdGV4dCBmaWxl" + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 94500810, + "result": 1 +} +``` + +## getAllTaskFiles + +- Purpose: **Get all files attached to task** +- Parameters: + - **task_id** (integer, required) +- Result on success: **list of files** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "getAllTaskFiles", + "id": 1880662820, + "params": { + "task_id": 1 + } +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 1880662820, + "result": [ + { + "id": "1", + "name": "My file", + "path": "1\/1\/0db4d0a897a4c852f6e12f0239d4805f7b4ab596", + "is_image": "0", + "task_id": "1", + "date": "1432509941", + "user_id": "0", + "size": "15", + "username": null, + "user_name": null + } + ] +} +``` + +## getTaskFile + +- Purpose: **Get file information** +- Parameters: + - **file_id** (integer, required) +- Result on success: **file properties** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "getTaskFile", + "id": 318676852, + "params": [ + "1" + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 318676852, + "result": { + "id": "1", + "name": "My file", + "path": "1\/1\/0db4d0a897a4c852f6e12f0239d4805f7b4ab596", + "is_image": "0", + "task_id": "1", + "date": "1432509941", + "user_id": "0", + "size": "15" + } +} +``` + +## downloadTaskFile + +- Purpose: **Download file contents (encoded in base64)** +- Parameters: + - **file_id** (integer, required) +- Result on success: **base64 encoded string** +- Result on failure: **empty string** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "downloadTaskFile", + "id": 235943344, + "params": [ + "1" + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 235943344, + "result": "cGxhaW4gdGV4dCBmaWxl" +} +``` + +## removeTaskFile + +- Purpose: **Remove file** +- Parameters: + - **file_id** (integer, required) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "removeTaskFile", + "id": 447036524, + "params": [ + "1" + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 447036524, + "result": true +} +``` + +## removeAllTaskFiles + +- Purpose: **Remove all files associated to a task** +- Parameters: + - **task_id** (integer, required) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "removeAllTaskFiles", + "id": 593312993, + "params": { + "task_id": 1 + } +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 593312993, + "result": true +} +``` diff --git a/tests/docker/entrypoint.sh b/tests/docker/entrypoint.sh index a88c7ed8..5a37ae4e 100755 --- a/tests/docker/entrypoint.sh +++ b/tests/docker/entrypoint.sh @@ -23,7 +23,7 @@ case "$1" in /var/www/html/vendor/phpunit/phpunit/phpunit -c /var/www/html/tests/integration.sqlite.xml ;; "integration-test-postgres") - wait_schema_creation 5 + wait_schema_creation 10 /var/www/html/vendor/phpunit/phpunit/phpunit -c /var/www/html/tests/integration.postgres.xml ;; "integration-test-mysql") diff --git a/tests/integration/ProjectFileProcedureTest.php b/tests/integration/ProjectFileProcedureTest.php new file mode 100644 index 00000000..8ac70d87 --- /dev/null +++ b/tests/integration/ProjectFileProcedureTest.php @@ -0,0 +1,66 @@ +assertCreateTeamProject(); + $this->assertCreateProjectFile(); + $this->assertGetProjectFile(); + $this->assertDownloadProjectFile(); + $this->assertGetAllFiles(); + $this->assertRemoveProjectFile(); + $this->assertRemoveAllProjectFiles(); + } + + public function assertCreateProjectFile() + { + $this->fileId = $this->app->createProjectFile($this->projectId, 'My file.txt', base64_encode('plain text file')); + $this->assertNotFalse($this->fileId); + } + + public function assertGetProjectFile() + { + $file = $this->app->getProjectFile($this->projectId, $this->fileId); + $this->assertNotEmpty($file); + $this->assertEquals('My file.txt', $file['name']); + } + + public function assertDownloadProjectFile() + { + $content = $this->app->downloadProjectFile($this->projectId, $this->fileId); + $this->assertNotEmpty($content); + $this->assertEquals('plain text file', base64_decode($content)); + } + + public function assertGetAllFiles() + { + $files = $this->app->getAllProjectFiles($this->projectId); + $this->assertCount(1, $files); + $this->assertEquals('My file.txt', $files[0]['name']); + } + + public function assertRemoveProjectFile() + { + $this->assertTrue($this->app->removeProjectFile($this->projectId, $this->fileId)); + + $files = $this->app->getAllProjectFiles($this->projectId); + $this->assertEmpty($files); + } + + public function assertRemoveAllProjectFiles() + { + $this->assertCreateProjectFile(); + $this->assertCreateProjectFile(); + + $this->assertTrue($this->app->removeAllProjectFiles($this->projectId)); + + $files = $this->app->getAllProjectFiles($this->projectId); + $this->assertEmpty($files); + } +} diff --git a/tests/integration/TaskFileProcedureTest.php b/tests/integration/TaskFileProcedureTest.php index 61155555..60909ecd 100644 --- a/tests/integration/TaskFileProcedureTest.php +++ b/tests/integration/TaskFileProcedureTest.php @@ -21,7 +21,7 @@ class TaskFileProcedureTest extends BaseProcedureTest public function assertCreateTaskFile() { - $this->fileId = $this->app->createTaskFile(1, $this->taskId, 'My file', base64_encode('plain text file')); + $this->fileId = $this->app->createTaskFile($this->projectId, $this->taskId, 'My file', base64_encode('plain text file')); $this->assertNotFalse($this->fileId); } -- cgit v1.2.3