From 4a230d331ec220fc32a48525afb308af0d9787fa Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Sun, 26 Jun 2016 10:25:13 -0400 Subject: Added application and project roles validation for API procedure calls --- doc/api-json-rpc.markdown | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'doc/api-json-rpc.markdown') diff --git a/doc/api-json-rpc.markdown b/doc/api-json-rpc.markdown index bb14b008..0f922a7c 100644 --- a/doc/api-json-rpc.markdown +++ b/doc/api-json-rpc.markdown @@ -8,25 +8,25 @@ There are two types of API access: ### Application API -- Access to the API with the user "jsonrpc" and the token available in settings +- Access to the API with the user "jsonrpc" and the token available on the settings page - Access to all procedures - No permission checked - There is no user session on the server +- No access to procedures that starts with "My..." (example: "getMe" or "getMyProjects") - Example of possible clients: tools to migrate/import data, create tasks from another system, etc... ### User API - Access to the API with the user credentials (username and password) -- Access to a restricted set of procedures -- The project permissions are checked +- Application role and project permissions are checked for each procedure - A user session is created on the server -- Example of possible clients: mobile/desktop application, command line utility, etc... +- Example of possible clients: native mobile/desktop application, command line utility, etc... Security -------- -- Always use HTTPS with a valid certificate -- If you make a mobile application, it's your job to store securely the user credentials on the device +- Always use HTTPS with a valid certificate (avoid clear text communication) +- If you make a mobile application, it's your responsability to store securely the user credentials on the device - After 3 authentication failure on the user api, the end-user have to unlock his account by using the login form - Two factor authentication is not yet available through the API -- cgit v1.2.3 From 3d34681610854474cb9dbdd93886dbcf0e208a99 Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Sun, 26 Jun 2016 14:33:53 -0400 Subject: Added new API calls for external task links --- ChangeLog | 1 + app/Api/Procedure/TaskExternalLinkProcedure.php | 106 ++++++++++ app/Core/ExternalLink/ExternalLinkManager.php | 24 +++ app/ServiceProvider/ApiProvider.php | 2 + app/ServiceProvider/AuthenticationProvider.php | 1 + doc/api-external-task-link-procedures.markdown | 221 +++++++++++++++++++++ doc/api-internal-task-link-procedures.markdown | 187 +++++++++++++++++ doc/api-json-rpc.markdown | 2 + doc/api-link-procedures.markdown | 185 ----------------- .../integration/TaskExternalLinkProcedureTest.php | 98 +++++++++ 10 files changed, 642 insertions(+), 185 deletions(-) create mode 100644 app/Api/Procedure/TaskExternalLinkProcedure.php create mode 100644 doc/api-external-task-link-procedures.markdown create mode 100644 doc/api-internal-task-link-procedures.markdown create mode 100644 tests/integration/TaskExternalLinkProcedureTest.php (limited to 'doc/api-json-rpc.markdown') diff --git a/ChangeLog b/ChangeLog index 883cc6cf..85966ed8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -5,6 +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 Improvements: diff --git a/app/Api/Procedure/TaskExternalLinkProcedure.php b/app/Api/Procedure/TaskExternalLinkProcedure.php new file mode 100644 index 00000000..05ec6906 --- /dev/null +++ b/app/Api/Procedure/TaskExternalLinkProcedure.php @@ -0,0 +1,106 @@ +externalLinkManager->getTypes(); + } + + public function getExternalTaskLinkProviderDependencies($providerName) + { + try { + return $this->externalLinkManager->getProvider($providerName)->getDependencies(); + } catch (ExternalLinkProviderNotFound $e) { + $this->logger->error(__METHOD__.': '.$e->getMessage()); + return false; + } + } + + public function getExternalTaskLinkById($task_id, $link_id) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getExternalTaskLink', $task_id); + return $this->taskExternalLinkModel->getById($link_id); + } + + public function getAllExternalTaskLinks($task_id) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getExternalTaskLinks', $task_id); + return $this->taskExternalLinkModel->getAll($task_id); + } + + public function createExternalTaskLink($task_id, $url, $dependency, $type = ExternalLinkManager::TYPE_AUTO, $title = '') + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'createExternalTaskLink', $task_id); + + try { + $provider = $this->externalLinkManager + ->setUserInputText($url) + ->setUserInputType($type) + ->find(); + + $link = $provider->getLink(); + + $values = array( + 'task_id' => $task_id, + 'title' => $title ?: $link->getTitle(), + 'url' => $link->getUrl(), + 'link_type' => $provider->getType(), + 'dependency' => $dependency, + ); + + list($valid, $errors) = $this->externalLinkValidator->validateCreation($values); + + if (! $valid) { + $this->logger->error(__METHOD__.': '.var_export($errors)); + return false; + } + + return $this->taskExternalLinkModel->create($values); + } catch (ExternalLinkProviderNotFound $e) { + $this->logger->error(__METHOD__.': '.$e->getMessage()); + } + + return false; + } + + public function updateExternalTaskLink($task_id, $link_id, $title = null, $url = null, $dependency = null) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateExternalTaskLink', $task_id); + + $link = $this->taskExternalLinkModel->getById($link_id); + $values = $this->filterValues(array( + 'title' => $title, + 'url' => $url, + 'dependency' => $dependency, + )); + + $values = array_merge($link, $values); + list($valid, $errors) = $this->externalLinkValidator->validateModification($values); + + if (! $valid) { + $this->logger->error(__METHOD__.': '.var_export($errors)); + return false; + } + + return $this->taskExternalLinkModel->update($values); + } + + public function removeExternalTaskLink($task_id, $link_id) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeExternalTaskLink', $task_id); + return $this->taskExternalLinkModel->remove($link_id); + } +} diff --git a/app/Core/ExternalLink/ExternalLinkManager.php b/app/Core/ExternalLink/ExternalLinkManager.php index 804e6b34..5a037999 100644 --- a/app/Core/ExternalLink/ExternalLinkManager.php +++ b/app/Core/ExternalLink/ExternalLinkManager.php @@ -152,6 +152,30 @@ class ExternalLinkManager extends Base return $this; } + /** + * Set provider type + * + * @access public + * @param string $userInputType + * @return ExternalLinkManager + */ + public function setUserInputType($userInputType) + { + $this->userInputType = $userInputType; + return $this; + } + + /** + * Set external link + * @param string $userInputText + * @return ExternalLinkManager + */ + public function setUserInputText($userInputText) + { + $this->userInputText = $userInputText; + return $this; + } + /** * Find a provider that user input * diff --git a/app/ServiceProvider/ApiProvider.php b/app/ServiceProvider/ApiProvider.php index f88d9b4f..194bee5b 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\TaskExternalLinkProcedure; use Kanboard\Api\Procedure\TaskFileProcedure; use Kanboard\Api\Procedure\GroupProcedure; use Kanboard\Api\Procedure\GroupMemberProcedure; @@ -65,6 +66,7 @@ class ApiProvider implements ServiceProviderInterface ->withObject(new SwimlaneProcedure($container)) ->withObject(new TaskProcedure($container)) ->withObject(new TaskLinkProcedure($container)) + ->withObject(new TaskExternalLinkProcedure($container)) ->withObject(new UserProcedure($container)) ->withObject(new GroupProcedure($container)) ->withObject(new GroupMemberProcedure($container)) diff --git a/app/ServiceProvider/AuthenticationProvider.php b/app/ServiceProvider/AuthenticationProvider.php index 751fe514..34b81b9d 100644 --- a/app/ServiceProvider/AuthenticationProvider.php +++ b/app/ServiceProvider/AuthenticationProvider.php @@ -204,6 +204,7 @@ class AuthenticationProvider implements ServiceProviderInterface $acl->add('SwimlaneProcedure', '*', Role::PROJECT_MANAGER); $acl->add('TaskFileProcedure', '*', Role::PROJECT_MEMBER); $acl->add('TaskLinkProcedure', '*', Role::PROJECT_MEMBER); + $acl->add('TaskExternalLinkProcedure', array('createExternalTaskLink', 'updateExternalTaskLink', 'removeExternalTaskLink'), Role::PROJECT_MEMBER); $acl->add('TaskProcedure', '*', Role::PROJECT_MEMBER); return $acl; diff --git a/doc/api-external-task-link-procedures.markdown b/doc/api-external-task-link-procedures.markdown new file mode 100644 index 00000000..2858be86 --- /dev/null +++ b/doc/api-external-task-link-procedures.markdown @@ -0,0 +1,221 @@ +Internal Task Links API Procedures +================================== + +## getExternalTaskLinkTypes + +- Purpose: **Get all registered external link providers** +- Parameters: **none** +- Result on success: **dict** +- Result on failure: **false** + +Request example: + +```json +{"jsonrpc":"2.0","method":"getExternalTaskLinkTypes","id":477370568} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "result": { + "auto": "Auto", + "attachment": "Attachment", + "file": "Local File", + "weblink": "Web Link" + }, + "id": 477370568 +} +``` + +## getExternalTaskLinkProviderDependencies + +- Purpose: **Get available dependencies for a given provider** +- Parameters: + - **providerName** (string, required) +- Result on success: **dict** +- Result on failure: **false** + +Request example: + +```json +{"jsonrpc":"2.0","method":"getExternalTaskLinkProviderDependencies","id":124790226,"params":["weblink"]} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "result": { + "related": "Related" + }, + "id": 124790226 +} +``` + +## createExternalTaskLink + +- Purpose: **Create a new external link** +- Parameters: + - **task_id** (integer, required) + - **url** (string, required) + - **dependency** (string, required) + - **type** (string, optional) + - **title** (string, optional) +- Result on success: **link_id** +- Result on failure: **false** + +Request example: + +```json +{"jsonrpc":"2.0","method":"createExternalTaskLink","id":924217495,"params":[9,"http:\/\/localhost\/document.pdf","related","attachment"]} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "result": 1, + "id": 924217495 +} +``` + +## updateExternalTaskLink + +- Purpose: **Update external task link** +- Parameters: + - **task_id** (integer, required) + - **link_id** (integer, required) + - **title** (string, required) + - **url** (string, required) + - **dependency** (string, required) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc":"2.0", + "method":"updateExternalTaskLink", + "id":1123562620, + "params": { + "task_id":9, + "link_id":1, + "title":"New title" + } +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "result": true, + "id": 1123562620 +} +``` + +## getExternalTaskLinkById + +- Purpose: **Get an external task link** +- Parameters: + - **task_id** (integer, required) + - **link_id** (integer, required) +- Result on success: **dict** +- Result on failure: **false** + +Request example: + +```json +{"jsonrpc":"2.0","method":"getExternalTaskLinkById","id":2107066744,"params":[9,1]} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "result": { + "id": "1", + "link_type": "attachment", + "dependency": "related", + "title": "document.pdf", + "url": "http:\/\/localhost\/document.pdf", + "date_creation": "1466965256", + "date_modification": "1466965256", + "task_id": "9", + "creator_id": "0" + }, + "id": 2107066744 +} +``` + +## getAllExternalTaskLinks + +- Purpose: **Get all external links attached to a task** +- Parameters: + - **task_id** (integer, required) +- Result on success: **list of external links** +- Result on failure: **false** + +Request example: + +```json +{"jsonrpc":"2.0","method":"getAllExternalTaskLinks","id":2069307223,"params":[9]} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "result": [ + { + "id": "1", + "link_type": "attachment", + "dependency": "related", + "title": "New title", + "url": "http:\/\/localhost\/document.pdf", + "date_creation": "1466965256", + "date_modification": "1466965256", + "task_id": "9", + "creator_id": "0", + "creator_name": null, + "creator_username": null, + "dependency_label": "Related", + "type": "Attachment" + } + ], + "id": 2069307223 +} +``` + +## removeExternalTaskLink + +- Purpose: **Remove an external link** +- Parameters: + - **task_id** (integer, required) + - **link_id** (integer, required) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{"jsonrpc":"2.0","method":"removeExternalTaskLink","id":552055660,"params":[9,1]} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "result": true, + "id": 552055660 +} +``` diff --git a/doc/api-internal-task-link-procedures.markdown b/doc/api-internal-task-link-procedures.markdown new file mode 100644 index 00000000..859228de --- /dev/null +++ b/doc/api-internal-task-link-procedures.markdown @@ -0,0 +1,187 @@ +Internal Task Links API Procedures +================================== + +## createTaskLink + +- Purpose: **Create a link between two tasks** +- Parameters: + - **task_id** (integer, required) + - **opposite_task_id** (integer, required) + - **link_id** (integer, required) +- Result on success: **task_link_id** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "createTaskLink", + "id": 509742912, + "params": [ + 2, + 3, + 1 + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 509742912, + "result": 1 +} +``` + +## updateTaskLink + +- Purpose: **Update task link** +- Parameters: + - **task_link_id** (integer, required) + - **task_id** (integer, required) + - **opposite_task_id** (integer, required) + - **link_id** (integer, required) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "updateTaskLink", + "id": 669037109, + "params": [ + 1, + 2, + 4, + 2 + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 669037109, + "result": true +} +``` + +## getTaskLinkById + +- Purpose: **Get a task link** +- Parameters: + - **task_link_id** (integer, required) +- Result on success: **task link properties** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "getTaskLinkById", + "id": 809885202, + "params": [ + 1 + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 809885202, + "result": { + "id": "1", + "link_id": "1", + "task_id": "2", + "opposite_task_id": "3" + } +} +``` + +## getAllTaskLinks + +- Purpose: **Get all links related to a task** +- Parameters: + - **task_id** (integer, required) +- Result on success: **list of task link** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "getAllTaskLinks", + "id": 810848359, + "params": [ + 2 + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 810848359, + "result": [ + { + "id": "1", + "task_id": "3", + "label": "relates to", + "title": "B", + "is_active": "1", + "project_id": "1", + "task_time_spent": "0", + "task_time_estimated": "0", + "task_assignee_id": "0", + "task_assignee_username": null, + "task_assignee_name": null, + "column_title": "Backlog" + } + ] +} +``` + +## removeTaskLink + +- Purpose: **Remove a link between two tasks** +- Parameters: + - **task_link_id** (integer, required) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "removeTaskLink", + "id": 473028226, + "params": [ + 1 + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 473028226, + "result": true +} +``` diff --git a/doc/api-json-rpc.markdown b/doc/api-json-rpc.markdown index 0f922a7c..8e783e71 100644 --- a/doc/api-json-rpc.markdown +++ b/doc/api-json-rpc.markdown @@ -60,6 +60,8 @@ Usage - [Subtasks](api-subtask-procedures.markdown) - [Files](api-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) - [Comments](api-comment-procedures.markdown) - [Users](api-user-procedures.markdown) - [Groups](api-group-procedures.markdown) diff --git a/doc/api-link-procedures.markdown b/doc/api-link-procedures.markdown index 6113316f..44e78a2a 100644 --- a/doc/api-link-procedures.markdown +++ b/doc/api-link-procedures.markdown @@ -283,188 +283,3 @@ Response example: "result": true } ``` - -## createTaskLink - -- Purpose: **Create a link between two tasks** -- Parameters: - - **task_id** (integer, required) - - **opposite_task_id** (integer, required) - - **link_id** (integer, required) -- Result on success: **task_link_id** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "createTaskLink", - "id": 509742912, - "params": [ - 2, - 3, - 1 - ] -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 509742912, - "result": 1 -} -``` - -## updateTaskLink - -- Purpose: **Update task link** -- Parameters: - - **task_link_id** (integer, required) - - **task_id** (integer, required) - - **opposite_task_id** (integer, required) - - **link_id** (integer, required) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "updateTaskLink", - "id": 669037109, - "params": [ - 1, - 2, - 4, - 2 - ] -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 669037109, - "result": true -} -``` - -## getTaskLinkById - -- Purpose: **Get a task link** -- Parameters: - - **task_link_id** (integer, required) -- Result on success: **task link properties** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "getTaskLinkById", - "id": 809885202, - "params": [ - 1 - ] -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 809885202, - "result": { - "id": "1", - "link_id": "1", - "task_id": "2", - "opposite_task_id": "3" - } -} -``` - -## getAllTaskLinks - -- Purpose: **Get all links related to a task** -- Parameters: - - **task_id** (integer, required) -- Result on success: **list of task link** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "getAllTaskLinks", - "id": 810848359, - "params": [ - 2 - ] -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 810848359, - "result": [ - { - "id": "1", - "task_id": "3", - "label": "relates to", - "title": "B", - "is_active": "1", - "project_id": "1", - "task_time_spent": "0", - "task_time_estimated": "0", - "task_assignee_id": "0", - "task_assignee_username": null, - "task_assignee_name": null, - "column_title": "Backlog" - } - ] -} -``` - -## removeTaskLink - -- Purpose: **Remove a link between two tasks** -- Parameters: - - **task_link_id** (integer, required) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "removeTaskLink", - "id": 473028226, - "params": [ - 1 - ] -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 473028226, - "result": true -} -``` diff --git a/tests/integration/TaskExternalLinkProcedureTest.php b/tests/integration/TaskExternalLinkProcedureTest.php new file mode 100644 index 00000000..47ff53ad --- /dev/null +++ b/tests/integration/TaskExternalLinkProcedureTest.php @@ -0,0 +1,98 @@ +assertCreateTeamProject(); + $this->assertCreateTask(); + $this->assertGetExternalTaskLinkTypes(); + $this->assertGetExternalTaskLinkProviderDependencies(); + $this->assertGetExternalTaskLinkProviderDependenciesWithProviderNotFound(); + $this->assertCreateExternalTaskLink(); + $this->assertUpdateExternalTaskLink(); + $this->assertGetAllExternalTaskLinks(); + $this->assertRemoveExternalTaskLink(); + } + + public function assertGetExternalTaskLinkTypes() + { + $expected = array( + 'auto' => 'Auto', + 'attachment' => 'Attachment', + 'file' => 'Local File', + 'weblink' => 'Web Link', + ); + + $types = $this->app->getExternalTaskLinkTypes(); + $this->assertEquals($expected, $types); + } + + public function assertGetExternalTaskLinkProviderDependencies() + { + $expected = array( + 'related' => 'Related', + ); + + $dependencies = $this->app->getExternalTaskLinkProviderDependencies('weblink'); + + $this->assertEquals($expected, $dependencies); + } + + public function assertGetExternalTaskLinkProviderDependenciesWithProviderNotFound() + { + $this->assertFalse($this->app->getExternalTaskLinkProviderDependencies('foobar')); + } + + public function assertCreateExternalTaskLink() + { + $url = 'http://localhost/document.pdf'; + $this->linkId = $this->app->createExternalTaskLink($this->taskId, $url, 'related', 'attachment'); + $this->assertNotFalse($this->linkId); + + $link = $this->app->getExternalTaskLinkById($this->taskId, $this->linkId); + $this->assertEquals($this->linkId, $link['id']); + $this->assertEquals($this->taskId, $link['task_id']); + $this->assertEquals('document.pdf', $link['title']); + $this->assertEquals($url, $link['url']); + $this->assertEquals('related', $link['dependency']); + $this->assertEquals(0, $link['creator_id']); + } + + public function assertUpdateExternalTaskLink() + { + $this->assertTrue($this->app->updateExternalTaskLink(array( + 'task_id' => $this->taskId, + 'link_id' => $this->linkId, + 'title' => 'New title', + ))); + + $link = $this->app->getExternalTaskLinkById($this->taskId, $this->linkId); + $this->assertEquals($this->linkId, $link['id']); + $this->assertEquals($this->taskId, $link['task_id']); + $this->assertEquals('New title', $link['title']); + $this->assertEquals('related', $link['dependency']); + $this->assertEquals(0, $link['creator_id']); + } + + public function assertGetAllExternalTaskLinks() + { + $links = $this->app->getAllExternalTaskLinks($this->taskId); + $this->assertCount(1, $links); + $this->assertEquals($this->linkId, $links[0]['id']); + $this->assertEquals($this->taskId, $links[0]['task_id']); + $this->assertEquals('New title', $links[0]['title']); + $this->assertEquals('related', $links[0]['dependency']); + $this->assertEquals(0, $links[0]['creator_id']); + } + + public function assertRemoveExternalTaskLink() + { + $this->assertTrue($this->app->removeExternalTaskLink($this->taskId, $this->linkId)); + } +} -- cgit v1.2.3 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 (limited to 'doc/api-json-rpc.markdown') 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 From 82623f1a212a3a79507ede69586c561efa675224 Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Sun, 26 Jun 2016 15:47:02 -0400 Subject: Added API calls for subtask time tracking --- ChangeLog | 2 +- app/Api/Procedure/SubtaskTimeTrackingProcedure.php | 8 +- doc/api-json-rpc.markdown | 1 + doc/api-subtask-time-tracking-procedures.markdown | 102 +++++++++++++++++++++ tests/integration/BaseProcedureTest.php | 11 +++ tests/integration/SubtaskProcedureTest.php | 11 --- .../SubtaskTimeTrackingProcedureTest.php | 46 ++++++++++ 7 files changed, 165 insertions(+), 16 deletions(-) create mode 100644 doc/api-subtask-time-tracking-procedures.markdown create mode 100644 tests/integration/SubtaskTimeTrackingProcedureTest.php (limited to 'doc/api-json-rpc.markdown') diff --git a/ChangeLog b/ChangeLog index 7a56139c..eccff6a3 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, project attachments +* Added new API calls for external task links, project attachments and subtask time tracking Improvements: diff --git a/app/Api/Procedure/SubtaskTimeTrackingProcedure.php b/app/Api/Procedure/SubtaskTimeTrackingProcedure.php index b6d1102a..5ceaa08d 100644 --- a/app/Api/Procedure/SubtaskTimeTrackingProcedure.php +++ b/app/Api/Procedure/SubtaskTimeTrackingProcedure.php @@ -19,15 +19,15 @@ class SubtaskTimeTrackingProcedure extends BaseProcedure return $this->subtaskTimeTrackingModel->hasTimer($subtask_id, $user_id); } - public function logSubtaskStartTime($subtask_id, $user_id) + public function setSubtaskStartTime($subtask_id, $user_id) { - SubtaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'logSubtaskStartTime', $subtask_id); + SubtaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'setSubtaskStartTime', $subtask_id); return $this->subtaskTimeTrackingModel->logStartTime($subtask_id, $user_id); } - public function logSubtaskEndTime($subtask_id, $user_id) + public function setSubtaskEndTime($subtask_id, $user_id) { - SubtaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'logSubtaskEndTime', $subtask_id); + SubtaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'setSubtaskEndTime', $subtask_id); return $this->subtaskTimeTrackingModel->logEndTime($subtask_id, $user_id); } diff --git a/doc/api-json-rpc.markdown b/doc/api-json-rpc.markdown index 6498b0cc..ab1056f0 100644 --- a/doc/api-json-rpc.markdown +++ b/doc/api-json-rpc.markdown @@ -58,6 +58,7 @@ Usage - [Automatic Actions](api-action-procedures.markdown) - [Tasks](api-task-procedures.markdown) - [Subtasks](api-subtask-procedures.markdown) +- [Subtask Time Tracking](api-subtask-time-tracking-procedures.markdown) - [Task Files](api-task-file-procedures.markdown) - [Project Files](api-project-file-procedures.markdown) - [Links](api-link-procedures.markdown) diff --git a/doc/api-subtask-time-tracking-procedures.markdown b/doc/api-subtask-time-tracking-procedures.markdown new file mode 100644 index 00000000..67447623 --- /dev/null +++ b/doc/api-subtask-time-tracking-procedures.markdown @@ -0,0 +1,102 @@ +Subtask Time Tracking API procedures +==================================== + +## hasSubtaskTimer + +- Purpose: **Check if a timer is started for the given subtask and user** +- Parameters: + - **subtask_id** (integer, required) + - **user_id** (integer, optional) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{"jsonrpc":"2.0","method":"hasSubtaskTimer","id":1786995697,"params":[2,4]} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "result": true, + "id": 1786995697 +} +``` + +## setSubtaskStartTime + +- Purpose: **Start subtask timer for a user** +- Parameters: + - **subtask_id** (integer, required) + - **user_id** (integer, optional) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{"jsonrpc":"2.0","method":"setSubtaskStartTime","id":1168991769,"params":[2,4]} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "result": true, + "id": 1168991769 +} +``` + +## setSubtaskEndTime + +- Purpose: **Stop subtask timer for a user** +- Parameters: + - **subtask_id** (integer, required) + - **user_id** (integer, optional) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{"jsonrpc":"2.0","method":"setSubtaskEndTime","id":1026607603,"params":[2,4]} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "result": true, + "id": 1026607603 +} +``` + +## getSubtaskTimeSpent + +- Purpose: **Get time spent on a subtask for a user** +- Parameters: + - **subtask_id** (integer, required) + - **user_id** (integer, optional) +- Result on success: **number of hours** +- Result on failure: **false** + +Request example: + +```json +{"jsonrpc":"2.0","method":"getSubtaskTimeSpent","id":738527378,"params":[2,4]} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "result": 1.5, + "id": 738527378 +} +``` diff --git a/tests/integration/BaseProcedureTest.php b/tests/integration/BaseProcedureTest.php index e3382e82..beb13ff7 100644 --- a/tests/integration/BaseProcedureTest.php +++ b/tests/integration/BaseProcedureTest.php @@ -17,6 +17,7 @@ abstract class BaseProcedureTest extends PHPUnit_Framework_TestCase protected $projectId = 0; protected $taskTitle = 'My task'; protected $taskId = 0; + protected $subtaskId = 0; protected $groupName1 = 'My Group A'; protected $groupName2 = 'My Group B'; @@ -119,4 +120,14 @@ abstract class BaseProcedureTest extends PHPUnit_Framework_TestCase $this->taskId = $this->app->createTask(array('title' => $this->taskTitle, 'project_id' => $this->projectId)); $this->assertNotFalse($this->taskId); } + + public function assertCreateSubtask() + { + $this->subtaskId = $this->app->createSubtask(array( + 'task_id' => $this->taskId, + 'title' => 'subtask #1', + )); + + $this->assertNotFalse($this->subtaskId); + } } diff --git a/tests/integration/SubtaskProcedureTest.php b/tests/integration/SubtaskProcedureTest.php index 7ab4ef0b..b9868e6f 100644 --- a/tests/integration/SubtaskProcedureTest.php +++ b/tests/integration/SubtaskProcedureTest.php @@ -5,7 +5,6 @@ require_once __DIR__.'/BaseProcedureTest.php'; class SubtaskProcedureTest extends BaseProcedureTest { protected $projectName = 'My project to test subtasks'; - private $subtaskId = 0; public function testAll() { @@ -18,16 +17,6 @@ class SubtaskProcedureTest extends BaseProcedureTest $this->assertRemoveSubtask(); } - public function assertCreateSubtask() - { - $this->subtaskId = $this->app->createSubtask(array( - 'task_id' => $this->taskId, - 'title' => 'subtask #1', - )); - - $this->assertNotFalse($this->subtaskId); - } - public function assertGetSubtask() { $subtask = $this->app->getSubtask($this->subtaskId); diff --git a/tests/integration/SubtaskTimeTrackingProcedureTest.php b/tests/integration/SubtaskTimeTrackingProcedureTest.php new file mode 100644 index 00000000..6c45c983 --- /dev/null +++ b/tests/integration/SubtaskTimeTrackingProcedureTest.php @@ -0,0 +1,46 @@ +assertCreateTeamProject(); + $this->assertCreateTask(); + $this->assertCreateSubtask(); + $this->assertHasNoTimer(); + $this->assertStartTimer(); + $this->assertHasTimer(); + $this->assertStopTimer(); + $this->assertHasNoTimer(); + $this->assertGetSubtaskTimeSpent(); + } + + public function assertHasNoTimer() + { + $this->assertFalse($this->app->hasSubtaskTimer($this->subtaskId, $this->userUserId)); + } + + public function assertHasTimer() + { + $this->assertTrue($this->app->hasSubtaskTimer($this->subtaskId, $this->userUserId)); + } + + public function assertStartTimer() + { + $this->assertTrue($this->app->setSubtaskStartTime($this->subtaskId, $this->userUserId)); + } + + public function assertStopTimer() + { + $this->assertTrue($this->app->setSubtaskEndTime($this->subtaskId, $this->userUserId)); + } + + public function assertGetSubtaskTimeSpent() + { + $this->assertEquals(0, $this->app->getSubtaskTimeSpent($this->subtaskId, $this->userUserId)); + } +} -- cgit v1.2.3