diff --git a/src/.htaccess b/src/.htaccess index 194f78e..76b8d44 100644 --- a/src/.htaccess +++ b/src/.htaccess @@ -1,12 +1,16 @@ -# For Apache +# For REST API in Apache # # RewriteEngine On # RewriteCond %{REQUEST_FILENAME} !-f # RewriteCond %{REQUEST_FILENAME} !-d # RewriteRule ^api/(.*)$ api.php/$1 [L,QSA] # +# +# Allow from all +# -# For Nginx set something like this: + +# In Nginx set something like this: # location /api/ { # rewrite ^/api/(.*) /api.php/$1 last; # } diff --git a/src/api.php b/src/api.php index 5eae879..cb6a8aa 100644 --- a/src/api.php +++ b/src/api.php @@ -32,6 +32,7 @@ $endpoints = array( 'GET' => [ ListsController::class , 'getId' ], 'PUT' => [ ListsController::class , 'putId' ], 'DELETE' => [ ListsController::class , 'deleteId' ], + 'POST' => [ ListsController::class , 'putId' ], //compatibility ], '/tasks' => [ 'GET' => [ TasksController::class , 'get' ], @@ -41,6 +42,7 @@ $endpoints = array( '/tasks/(-?\d+)' => [ 'PUT' => [ TasksController::class , 'putId' ], 'DELETE' => [ TasksController::class , 'deleteId' ], + 'POST' => [ TasksController::class , 'putId' ], //compatibility ], '/tasks/parseTitle' => [ 'POST' => [ TasksController::class , 'postTitleParse' ], @@ -60,6 +62,7 @@ $endpoints = array( '/ext-settings/(.+)' => [ 'GET' => [ ExtSettingsController::class , 'get' ], 'PUT' => [ ExtSettingsController::class , 'put' ], + 'POST' => [ ExtSettingsController::class , 'put' ], //compatibility ] ); diff --git a/src/content/mytinytodo_api.js b/src/content/mytinytodo_api.js index 57a5fe5..af97a1a 100644 --- a/src/content/mytinytodo_api.js +++ b/src/content/mytinytodo_api.js @@ -1,6 +1,6 @@ /* This file is a part of myTinyTodo. - (C) Copyright 2010,2020,2022 Max Pozdeev + (C) Copyright 2010,2020-2023 Max Pozdeev Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details. */ @@ -9,10 +9,12 @@ "use strict"; var mtt; +var useREST = true; function MytinytodoAjaxApi(amtt) { mtt = amtt; + useREST = false; } window.MytinytodoAjaxApi = MytinytodoAjaxApi; @@ -22,7 +24,9 @@ MytinytodoAjaxApi.prototype = /* required method */ request: function(action, params, callback) { - if (!this[action]) throw "Unknown ApiDriver action: " + action; + if (!this[action] || typeof this[action] !== 'function') { + throw "Unknown ApiDriver action: " + action; + } this[action] (params, function(json){ if (json.denied) mtt.errorDenied(); @@ -50,7 +54,7 @@ MytinytodoAjaxApi.prototype = method: 'POST', contentType : 'application/json', data: JSON.stringify({ - action: 'simple', + action: 'newSimple', list: params.list, title: params.title, tag: params.tag, @@ -68,7 +72,7 @@ MytinytodoAjaxApi.prototype = method: 'POST', contentType : 'application/json', data: JSON.stringify({ - action: 'full', + action: 'newFull', list: params.list, title: params.title, note: params.note, @@ -87,7 +91,7 @@ MytinytodoAjaxApi.prototype = { $.ajax({ url: mtt.apiUrl + 'tasks/' + encodeURIComponent(params.id), - method: 'PUT', + method: useREST ? 'PUT' : 'POST', contentType : 'application/json', data: JSON.stringify({ action: 'edit', @@ -107,7 +111,7 @@ MytinytodoAjaxApi.prototype = { $.ajax({ url: mtt.apiUrl + 'tasks/' + encodeURIComponent(params.id), - method: 'PUT', + method: useREST ? 'PUT' : 'POST', contentType : 'application/json', data: JSON.stringify({ action: 'note', @@ -123,7 +127,7 @@ MytinytodoAjaxApi.prototype = { $.ajax({ url: mtt.apiUrl + 'tasks/' + encodeURIComponent(params.id), - method: 'PUT', + method: useREST ? 'PUT' : 'POST', contentType : 'application/json', data: JSON.stringify({ action: 'complete', @@ -139,7 +143,11 @@ MytinytodoAjaxApi.prototype = { $.ajax({ url: mtt.apiUrl + 'tasks/' + encodeURIComponent(params.id), - method: 'DELETE', + method: useREST ? 'DELETE' : 'POST', + contentType : 'application/json', // contentType and data are required if method is POST + data: JSON.stringify({ + action: 'delete', + }), success: callback, dataType: 'json' }); @@ -150,7 +158,7 @@ MytinytodoAjaxApi.prototype = { $.ajax({ url: mtt.apiUrl + 'tasks/' + encodeURIComponent(params.id), - method: 'PUT', + method: useREST ? 'PUT' : 'POST', contentType : 'application/json', data: JSON.stringify({ action: 'priority', @@ -165,7 +173,7 @@ MytinytodoAjaxApi.prototype = { $.ajax({ url: mtt.apiUrl + 'tasks', - method: 'PUT', + method: useREST ? 'PUT' : 'POST', contentType : 'application/json', data: JSON.stringify({ action: 'order', @@ -190,7 +198,7 @@ MytinytodoAjaxApi.prototype = { $.ajax({ url: mtt.apiUrl + 'tasks/' + encodeURIComponent(params.id), - method: 'PUT', + method: useREST ? 'PUT' : 'POST', contentType : 'application/json', data: JSON.stringify({ action: 'move', @@ -232,6 +240,7 @@ MytinytodoAjaxApi.prototype = method: 'POST', contentType : 'application/json', data: JSON.stringify({ + action: 'new', name: params.name, }), success: callback, @@ -244,7 +253,11 @@ MytinytodoAjaxApi.prototype = { $.ajax({ url: mtt.apiUrl + 'lists/' + encodeURIComponent(params.list), - method: 'DELETE', + method: useREST ? 'DELETE' : 'POST', + contentType : 'application/json', // contentType and data are required if method is POST + data: JSON.stringify({ + action: 'delete', + }), success: callback, dataType: 'json' }); @@ -254,7 +267,7 @@ MytinytodoAjaxApi.prototype = { $.ajax({ url: mtt.apiUrl + 'lists/' + encodeURIComponent(params.list), - method: 'PUT', + method: useREST ? 'PUT' : 'POST', contentType : 'application/json', data: JSON.stringify({ action: 'rename', @@ -269,7 +282,7 @@ MytinytodoAjaxApi.prototype = { $.ajax({ url: mtt.apiUrl + 'lists/' + encodeURIComponent(params.list), - method: 'PUT', + method: useREST ? 'PUT' : 'POST', contentType : 'application/json', data: JSON.stringify({ action: 'sort', @@ -285,7 +298,7 @@ MytinytodoAjaxApi.prototype = { $.ajax({ url: mtt.apiUrl + 'lists/' + encodeURIComponent(params.list), - method: 'PUT', + method: useREST ? 'PUT' : 'POST', contentType : 'application/json', data: JSON.stringify({ action: 'publish', @@ -300,7 +313,7 @@ MytinytodoAjaxApi.prototype = { $.ajax({ url: mtt.apiUrl + 'lists/' + encodeURIComponent(params.list), - method: 'PUT', + method: useREST ? 'PUT' : 'POST', contentType : 'application/json', data: JSON.stringify({ action: 'enableFeedKey', @@ -315,7 +328,7 @@ MytinytodoAjaxApi.prototype = { $.ajax({ url: mtt.apiUrl + 'lists/' + encodeURIComponent(params.list), - method: 'PUT', + method: useREST ? 'PUT' : 'POST', contentType : 'application/json', data: JSON.stringify({ action: 'showNotes', @@ -330,7 +343,7 @@ MytinytodoAjaxApi.prototype = { $.ajax({ url: mtt.apiUrl + 'lists/' + encodeURIComponent(params.list), - method: 'PUT', + method: useREST ? 'PUT' : 'POST', contentType : 'application/json', data: JSON.stringify({ action: 'hide', @@ -345,7 +358,7 @@ MytinytodoAjaxApi.prototype = { $.ajax({ url: mtt.apiUrl + 'lists', - method: 'PUT', + method: useREST ? 'PUT' : 'POST', contentType : 'application/json', data: JSON.stringify({ action: 'order', @@ -360,7 +373,7 @@ MytinytodoAjaxApi.prototype = { $.ajax({ url: mtt.apiUrl + 'lists/' + encodeURIComponent(params.list), - method: 'PUT', + method: useREST ? 'PUT' : 'POST', contentType : 'application/json', data: JSON.stringify({ action: 'clearCompleted', diff --git a/src/includes/api/ListsController.php b/src/includes/api/ListsController.php index 6440a7e..cd2dbc1 100644 --- a/src/includes/api/ListsController.php +++ b/src/includes/api/ListsController.php @@ -40,7 +40,7 @@ class ListsController extends ApiController { /** - * Create new list + * Create new list and Actions with all lists * Code 201 on success * @return void * @throws Exception @@ -48,15 +48,12 @@ class ListsController extends ApiController { function post() { checkWriteAccess(); - $id = DBCore::default()->createListWithName($this->req->jsonBody['name'] ?? ''); - $db = DBConnection::instance(); - $t = array(); - $t['total'] = 1; - $r = $db->sqa("SELECT * FROM {$db->prefix}lists WHERE id=$id"); - $oo = $this->prepareList($r, true); - MTTNotificationCenter::postNotification(MTTNotification::didCreateList, $oo); - $t['list'][] = $oo; - $this->response->data = $t; + $action = $this->req->jsonBody['action'] ?? ''; + switch ($action) { + case 'order': $this->response->data = $this->changeListOrder(); break; //compatibility + case 'new': + default: $this->response->data = $this->createList(); + } } /** @@ -105,32 +102,13 @@ class ListsController extends ApiController { function deleteId($id) { checkWriteAccess(); - $db = DBConnection::instance(); - $t = array(); - $t['total'] = 0; - $id = (int)$id; - $list = null; - if (MTTNotificationCenter::hasObserversForNotification(MTTNotification::didDeleteList)) { - $list = $this->getListRowById($id); - } - $db->ex("BEGIN"); - $db->ex("DELETE FROM {$db->prefix}lists WHERE id=$id"); - $t['total'] = $db->affected(); - if ($t['total']) { - $db->ex("DELETE FROM {$db->prefix}tag2task WHERE list_id=$id"); - $db->ex("DELETE FROM {$db->prefix}todolist WHERE list_id=$id"); - } - $db->ex("COMMIT"); - if ($t['total'] && MTTNotificationCenter::hasObserversForNotification(MTTNotification::didDeleteList)) { - MTTNotificationCenter::postNotification(MTTNotification::didDeleteList, $list); - } - $this->response->data = $t; + $this->response->data = $this->deleteList($id); } /** * Edit some properties of List - * Actions: rename + * Actions: rename, ... * @param mixed $id * @return void * @throws Exception @@ -149,6 +127,7 @@ class ListsController extends ApiController { case 'showNotes': $this->response->data = $this->showNotes($id); break; case 'hide': $this->response->data = $this->hideList($id); break; case 'clearCompleted': $this->response->data = $this->clearCompleted($id); break; + case 'delete': $this->response->data = $this->deleteList($id); break; //compatibility default: $this->response->data = ['total' => 0]; } } @@ -214,6 +193,23 @@ class ListsController extends ApiController { ); } + private function createList(): ?array + { + $t = array(); + $t['total'] = 0; + $id = DBCore::default()->createListWithName($this->req->jsonBody['name'] ?? ''); + if (!$id) { + return $t; + } + $db = DBConnection::instance(); + $t['total'] = 1; + $r = $db->sqa("SELECT * FROM {$db->prefix}lists WHERE id=$id"); + $oo = $this->prepareList($r, true); + MTTNotificationCenter::postNotification(MTTNotification::didCreateList, $oo); + $t['list'][] = $oo; + return $t; + } + private function renameList(int $id): ?array { $db = DBConnection::instance(); @@ -371,4 +367,27 @@ class ListsController extends ApiController { return $t; } + private function deleteList(int $id) + { + $db = DBConnection::instance(); + $t = array(); + $t['total'] = 0; + $id = (int)$id; + $list = null; + if (MTTNotificationCenter::hasObserversForNotification(MTTNotification::didDeleteList)) { + $list = $this->getListRowById($id); + } + $db->ex("BEGIN"); + $db->ex("DELETE FROM {$db->prefix}lists WHERE id=$id"); + $t['total'] = $db->affected(); + if ($t['total']) { + $db->ex("DELETE FROM {$db->prefix}tag2task WHERE list_id=$id"); + $db->ex("DELETE FROM {$db->prefix}todolist WHERE list_id=$id"); + } + $db->ex("COMMIT"); + if ($t['total'] && MTTNotificationCenter::hasObserversForNotification(MTTNotification::didDeleteList)) { + MTTNotificationCenter::postNotification(MTTNotification::didDeleteList, $list); + } + return $t; + } } diff --git a/src/includes/api/TasksController.php b/src/includes/api/TasksController.php index 8593764..446300a 100644 --- a/src/includes/api/TasksController.php +++ b/src/includes/api/TasksController.php @@ -135,22 +135,27 @@ class TasksController extends ApiController { /** * Create new task - * action: simple or full + * action: newSimple or newFull * @return void * @throws Exception */ function post() { - $listId = (int)($this->req->jsonBody['list'] ?? 0); - checkWriteAccess($listId); $action = $this->req->jsonBody['action'] ?? ''; - if ($action == 'full') { - $this->response->data = $this->fullNewTaskInList($listId); + if ($action == 'order') { //compatibility + checkWriteAccess(); + $this->response->data = $this->changeTaskOrder(); } else { - $this->response->data = $this->newTaskInList($listId); + $listId = (int)($this->req->jsonBody['list'] ?? 0); + checkWriteAccess($listId); + if ($action == 'newFull') { + $this->response->data = $this->fullNewTaskInList($listId); + } + else { + $this->response->data = $this->newTaskInList($listId); + } } - } /** @@ -178,25 +183,7 @@ class TasksController extends ApiController { function deleteId($id) { checkWriteAccess(); - $id = (int)$id; - $task = null; - if (MTTNotificationCenter::hasObserversForNotification(MTTNotification::didDeleteTask)) { - $task = $this->getTaskRowById($id); - } - $db = DBConnection::instance(); - $db->ex("BEGIN"); - $db->ex("DELETE FROM {$db->prefix}tag2task WHERE task_id=$id"); - //TODO: delete unused tags? - $db->dq("DELETE FROM {$db->prefix}todolist WHERE id=$id"); - $deleted = $db->affected(); - $db->ex("COMMIT"); - if ($deleted && MTTNotificationCenter::hasObserversForNotification(MTTNotification::didDeleteTask)) { - MTTNotificationCenter::postNotification(MTTNotification::didDeleteTask, $task); - } - $t = array(); - $t['total'] = $deleted; - $t['list'][] = array('id' => $id); - $this->response->data = $t; + $this->response->data = $this->deleteTask((int)$id); } /** @@ -222,6 +209,7 @@ class TasksController extends ApiController { case 'note': $this->response->data = $this->editNote($id); break; case 'move': $this->response->data = $this->moveTask($id); break; case 'priority': $this->response->data = $this->priorityTask($id); break; + case 'delete': $this->response->data = $this->deleteTask($id); break; //compatibility default: $this->response->data = ['total' => 0]; } } @@ -557,6 +545,29 @@ class TasksController extends ApiController { return $t; } + private function deleteTask(int $id) + { + $id = (int)$id; + $task = null; + if (MTTNotificationCenter::hasObserversForNotification(MTTNotification::didDeleteTask)) { + $task = $this->getTaskRowById($id); + } + $db = DBConnection::instance(); + $db->ex("BEGIN"); + $db->ex("DELETE FROM {$db->prefix}tag2task WHERE task_id=$id"); + //TODO: delete unused tags? + $db->dq("DELETE FROM {$db->prefix}todolist WHERE id=$id"); + $deleted = $db->affected(); + $db->ex("COMMIT"); + if ($deleted && MTTNotificationCenter::hasObserversForNotification(MTTNotification::didDeleteTask)) { + MTTNotificationCenter::postNotification(MTTNotification::didDeleteTask, $task); + } + $t = array(); + $t['total'] = $deleted; + $t['list'][] = array('id' => $id); + return $t; + } + private function getUserListsSimple(bool $readOnly = false): array { $db = DBConnection::instance(); diff --git a/src/includes/class.dbcore.php b/src/includes/class.dbcore.php index 313d93e..8179f46 100644 --- a/src/includes/class.dbcore.php +++ b/src/includes/class.dbcore.php @@ -170,10 +170,13 @@ class DBCore return $data; } - function createListWithName(string $name): int + function createListWithName(string $name): ?int { $db = DBConnection::instance(); $name = str_replace( ['"',"'",'<','>','&'], '', trim($name) ); + if ($name == '') { + return null; + } $ow = 1 + (int)$db->sq("SELECT MAX(ow) FROM {$db->prefix}lists"); $time = time(); $db->dq("INSERT INTO {$db->prefix}lists (uuid,name,ow,d_created,d_edited,taskview) VALUES (?,?,?,?,?,?)",