Tasko is a simple to-do list app with a server backend. You can host the backend on your own server, and you can access and edit the to-dos from multiple clients.
The only client available so far (but not finished yet!) is an Android app.
There are probably already hundreds of apps that can do this (Remember the Milk for example), but this one is mainly for learning purposes (write a mobile app with a backend service).
Note that this is not a hosted service like Remember the Milk!
It comes with a server backend that you have to host yourself, on your own server!
The name "Tasko" just means "task" in Esperanto.
(in order to find a good name for the project, I played around with words like "task", "todo" etc. in Google Translate)
The Tasko server needs IIS and RavenDB to run.
I'm using Windows Azure Websites and RavenHQ for my personal instance.
By default, Tasko enforces SSL, i.e. it will only accept HTTPS requests.
Note that Tasko will never enforce SSL when it's running on AppHarbor, because AppHarbor needs a custom RequireHttps
attribute for that and I wasn't able to get it to work.
You can still use HTTPS on AppHarbor, it's just that Tasko will accept HTTP as well.
If your server doesn't have SSL at all, you can enable HTTP by setting the RequireSsl
key in the appSettings
(in web.config
) to false
.
Tasko uses session token authentication, provided by Thinktecture.IdentityModel.
IdentityModel needs a SigningKey
to sign the tokens it creates.
You should provide a fixed key in web.config
:
<appSettings>
<add key="SigningKey" value="..."/>
</appSettings>
To create a valid key, just create a random 32-character string and convert it to Base64 (like IdentityModel does it under the hood).
If you don't provide a key, a new one will be generated on each application restart, which means that all existing tokens are invalidated. So if you want the tokens to "survive" an application restart, you should provide your own key.
For now, Tasko doesn't support creating new users via the API - you have to create them in RavenDB Management Studio.
Just create a new document (New -> New Document in the top navigation bar), enter users/yourname
as the document id and the following data:
{
"Id": "yourname",
"Password": "123"
}
Here's how it should look like for the user yourname:
As mentioned before, Tasko uses session token authentication.
You log in once with username and password via HTTP Basic Authentication to request a session token:
GET /api/token
Content-Type: application/json; charset=utf-8
Authorization: Basic eW91cm5hbWU6MTIz
(this is the user from the example above, yourname with password 123)
Response:
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"expires_in": 31536000.0
}
This token can now be used for authentication in subsequent requests. With Tasko's default settings, it will be valid for a year (you can change that in web.config
by setting a different value for the TokenLifetime
key).
Don't forget to set the right content type when you send data to the API.
So a correct request header should contain these two lines:
Content-Type: application/json; charset=utf-8
Authorization: Session eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
Note that there is no GET /api/tasks
.
Please use the search feature instead (POST /api/tasks/search
, see below)
GET /api/tasks/1
Response:
{
"Id": 1,
"Description": "First Task",
"Categories": [
"Category1"
],
"CreatedAt": "2013-06-09T21:02:13.78125Z",
"CreatedBy": "someuser",
"LastEditedAt": "2013-06-09T21:02:13.78125Z",
"LastEditedBy": "someuser",
"FinishedAt": null,
"FinishedBy": null,
"IsFinished": false
}
You search for tasks (i.e. you load a list of all tasks, with or without filtering) by creating a new search:
POST /api/tasks/search
Input (optional):
An object with search parameters:
{
"Category": "cat1",
"Finished": false,
"PageNumber": 1,
"PageSize": 10
}
All the parameters are optional. If you omit them all, nothing will be filtered and all tasks will be returned.
The results will always be paged. If you omit PageNumber
and PageSize
, the values shown above will be used by default.
Response:
A list of task objects, like this:
[
{
"Id": 1,
"Description": "First Task",
"Categories": [
"Category1"
],
"CreatedAt": "2013-06-09T21:02:13.78125Z",
"CreatedBy": "someuser",
"LastEditedAt": "2013-06-09T21:02:13.78125Z",
"LastEditedBy": "someuser",
"FinishedAt": null,
"FinishedBy": null,
"IsFinished": false
},
{
"Id": 2,
"Description": "Second Task",
"Categories": [
"Category2"
],
"CreatedAt": "2013-06-09T21:06:16.15625Z",
"CreatedBy": "someuser",
"LastEditedAt": "2013-06-09T21:06:16.15625Z",
"LastEditedBy": "someuser",
"FinishedAt": null,
"FinishedBy": null,
"IsFinished": false
}
]
POST /api/tasks
Input:
{
"Description": "the description",
"Category": "the category"
}
Response:
{
"Id": 3,
"Description": "the description",
"Categories": [
"the category"
],
"CreatedAt": "2013-06-09T22:27:34.875Z",
"CreatedBy": "yourname",
"LastEditedAt": "2013-06-09T22:27:34.875Z",
"LastEditedBy": "yourname",
"FinishedAt": null,
"FinishedBy": null,
"IsFinished": false
}
POST /api/tasks/1/finish
Response:
{
"Id": 1,
"Description": "First Task",
"Categories": [
"Category1"
],
"CreatedAt": "2013-06-09T21:02:13.78125Z",
"CreatedBy": "someuser",
"LastEditedAt": "2014-12-23T18:49:56.4420043Z",
"LastEditedBy": "yourname",
"FinishedAt": "2014-12-23T18:49:56.4420043Z",
"FinishedBy": "yourname",
"IsFinished": true
}
POST /api/tasks/1/reopen
Response:
{
"Id": 1,
"Description": "First Task",
"Categories": [
"Category1"
],
"CreatedAt": "2013-06-09T21:02:13.78125Z",
"CreatedBy": "someuser",
"LastEditedAt": "2014-12-23T18:52:17.3272503Z",
"LastEditedBy": "yourname",
"FinishedAt": null,
"FinishedBy": null,
"IsFinished": false
}
POST /api/tasks/1/categories
Input:
{
"Category": "Category2"
}
Response:
[
"Category1",
"Category2"
]
(all of the task's categories)
GET /api/tasks/1/categories
Response:
[
"Category1",
"Category2"
]
DELETE /api/tasks/1/categories/Category2
(Category2
is the category to delete)
Response:
[
"Category1"
]
(the remaining categories)
PUT /api/tasks/1/description
Input:
{
"Description": "new description"
}
Response:
{
"Id": 1,
"Description": "new description",
"Categories": [
"Category1"
],
"CreatedAt": "2013-06-09T21:02:13.78125Z",
"CreatedBy": "someuser",
"LastEditedAt": "2014-12-28T02:45:18.6704517Z",
"LastEditedBy": "yourname",
"FinishedAt": null,
"FinishedBy": null,
"IsFinished": false
}
If any of the "writing" API calls fail because of invalid input (for example, if you try to finish an already finished task again, or try to set an empty description), the API will return status code 400 and a Message
property with a clear-text error message:
{
"Message": "task is already finished - can't finish it more than once"
}
build-and-run-tests.bat
in the main folder.release
subfolder with the binaries.build.bat
in the main folder.\app\build\outputs\apk
subfolder.Tasko makes use of the following open source projects:
Tasko and the Tasko Android client are licensed under the MIT License. See License.txt for details.