Daybed¶
Daybed is an Open Source Web API providing validation and storage as a service.
Why ?¶
Usually, when a Web application requires a backend for storage and validation, one of the following solutions is employed :
- A custom API is developed and deployed (reinvent the wheel)
- A commercial and closed-source service is used (Google Forms)
- A CMS is used as a backoffice for customizable content types (twist)
To avoid those situations and make it easy to get your storage backend ready in seconds, we’ve created Daybed.
Daybed is :
- a minimalist, robust, dynamic and generic API ;
- a validation layer with schemaless storage ;
- a reusable layer of permissions logic ;
- a universal REST endpoint for Web and mobile apps ;
- a key component for rapid application building ;
- a simple service deployed and integrated without coding.
How ?¶
- Create a model by posting its definition (title, fields, ...)
- Define the permissions sets (modify definition, create, update or delete records, ...)
- Use the allocated RESTful endpoint in your application (GET, POST, PUT, ...)
- Store and query records !
Since Daybed talks REST and JSON, you can basically use it as a remote storage with any of your favorite technologies (Python, Android, iOS, AngularJS, Ember.js, Backbone.js, etc.).
Note
Currently, the authentication relies on Basic authentication and Hawk
Use Cases¶
- Mobile apps
- Full JavaScript apps
- Online forms
- Data Wiki
- Collaborative Web mapping
Using Daybed in its first versions, we built:
- daybed-map, a geo-pad where you can create you own maps with custom fields, using backbone-forms
- A password manager
- A generic CRUD application using backbone-daybed ;
- A TODO list using jquery-spore ;
Technologies¶
Daybed uses the following stack by default :
Comparison¶
Daybed has many competitors, yet none of them shares the same approach or features set.
Strategy | Custom dedicated API | Generated code | Competitors | |||
---|---|---|---|---|---|---|
Project | Daybed | Django REST framework, Restify, express, Struts... | Python Eve | Loopback | Hoodie | Google Forms |
Minimalist | ✔ | ✔ | ||||
Validation | ✔ | ✔ | ✔ | ✔ | ✔ | |
Permissions | ✔ | ✔ | ✔ | ✔ | ||
Dynamic schemas | ✔ | ✔ | ✔ | ✔ | ||
Reusable instance | ✔ | ✔ | ✔ | |||
Dynamic API end-points | ✔ | ✔ | ||||
Raw data access | ✔ | ✔ | ✔ | ✔ | ✔ | |
Agnostic (REST) | ✔ | ✔ | ✔ | ✔ | ||
Requires SDK | ✔ | |||||
Open Source | ✔ | ✔ | ✔ | ✔ | ✔ | |
Faceted search | ✔ |
Sources: http://python-eve.org ; http://hood.ie ; http://loopback.io ;
More documentation¶
Installing Daybed¶
Daybed has the following requirements:
- A Python 2.6, 2.7, 3.x or PyPy installation
- A CouchDB or Redis, and an ElasticSearch server instance running
Daybed comes with a Makefile to simplify your life when developing. To install daybed in your current virtualenv and get started, just run:
$ make install
Then, running the test suite is a good way to check that everything is going well, and is correctly installed. You can run them with:
$ make tests
The test suite will run all the available tests for every supported Python environment. You can check the current build status on Travis.
Installation on *nix systems¶
Standard installation¶
First, make sure you have CouchDB installed on your system.
Note
If you’re running OSX, you can use Homebrew to install Daybed required dependencies:
$ brew install python couchdb
Make sure you follow any extra setup instruction provided by Homebrew for these packages.
It’s highly recommended to create and use a Python virtualenv local to the project:
$ virtualenv `pwd`/.venv
$ source .venv/bin/activate
Now, install Daybed’s Python dependencies in this venv:
$ make install
Don’t forget to start your CouchDB and ElasticSearch server instances:
$ sudo service couchdb start
$ sudo service elasticsearch start
Then start the Daybed server:
$ make serve
Development installation¶
If you start hacking on Daybed, a good practice is to ensure the tests keep passing for all supported Python environments:
$ make tests
Note
OSX users can install all supported Python platforms using this Homebrew command:
$ brew install python python3 pypy couchdb
Once you’re all set, keep on reading for using daybed.
Using Docker images¶
Docker allows you to easily run a Daybed instance, locally or in production.
Two steps setup¶
Note
You shall use Docker links, available in version 0.11+.
Run a CouchDB instance:
$ sudo docker run --name couchdb klaemo/couchdb
Run an ElasticSearch instance:
$ sudo docker run --name couchdb dockerfile/elasticsearch
Run a Daybed container linked to the previous ones:
sudo docker run --link=couchdb:couchdb \
--link=elasticsearch:elasticsearch \
--publish=8000:8000 makinacorpus/daybed
Test it!
$ curl http://localhost:8000/v1/
Runtime parameters¶
A number of environment variables can be set at runtime, to control the backend connection for instance:
$ sudo docker run ... --env BACKEND_DB_NAME=mydb ...
See the Dockerfile
file for a complete list of variables, and their default
value.
Custom configuration¶
In order to run the container with a custom configuration file. Just create
a file production.ini
in a custom folder (e.g. /myconf
), and mount it
this way:
$ sudo docker run ... --volume=/myconf:/opt/apps/daybed/conf ...
Build the image from sources¶
From the repository folder:
$ make clean
$ sudo docker build -t daybed .
How to use the Daybed API¶
Daybed is a REST interface you can use to create model definitions, edit them and publish data that complies to these models.
Let’s say you want to have a Daybed-managed todo list. Follow the steps and you will have a grasp of how Daybed works.
To simplify API calls, you can use httpie which performs HTTP requests easily.
All examples in this section are using httpie against a local Daybed server running on port 8000.
Daybed uses Hawk, so to run the following example, you’ll need to install the requests-hawk module.
Listing supported fields¶
Daybed supports several field types to build your model definition. All details are given in the dedicated documentation section.
You can also get a list of these fields and their parameters programmatically, on the /fields endpoint:
http GET http://localhost:8000/v1/fields --verbose --json
Authentication¶
You need to be authenticated to be able to run most of the commands. In order to get authenticated, the first thing to do is to get some credentials from Daybed.
In order to get yours, you need to send a POST
request:
http POST http://localhost:8000/v1/tokens
HTTP/1.1 201 Created
Content-Length: 273
Content-Type: application/json; charset=UTF-8
Date: Thu, 24 Jul 2014 16:25:49 GMT
Server: waitress
{
"credentials": {
"algorithm": "sha256",
"id": "e0394574578356252e2033b829b90291e2ff1f33ccbcbcec777485f3a5a10bca",
"key": "416aa1287121218feeb9b751a9614959a1c95fd09aba5959bab7c484dcd1b198"
},
"token": "ad37fc395b7ba83eb496849f6db022fbb316fa11081491b5f00dfae5b0b1cd22"
}
In your next requests, you can either :
- use the
token
, and rely on a Hawk library to wrap everything (recommended); - use the
credentials
pair of id and key, and build the HawkAuthorization
header yourself (or probably via ``hawk.js``).
If you want to get always the same token for a given user, you can access the endpoint using Basic Auth Authorization scheme:
http POST localhost:8000/v1/tokens --auth admin:password -v
POST /v1/tokens HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, compress
Authorization: Basic YWRtaW46cGFzc3dvcmQ=
Content-Length: 0
Host: localhost:8000
User-Agent: HTTPie/0.8.0
HTTP/1.1 201 Created
Content-Length: 266
Content-Type: application/json; charset=UTF-8
Date: Fri, 07 Nov 2014 15:09:01 GMT
Server: waitress
{
"credentials": {
"algorithm": "sha256",
"id": "371ef18f8a054e5c9fb0961cc5b81006080e9ad22078d30b3727c7c32843579f",
"key": "87e72a8e5dcaf8b4be00b8729462814da3d438ce2fd8b6efee335db722d8369d"
},
"token": "9f19de0237c9bd59f803de1785f7aea4e3499b6929df3428e1b415fed81f797a"
}
In that case you will get a 201 Created on the creation time and then a 200 each time you ask for the same token.
Model management¶
PUT and POST /v1/models
You can create models without being authenticated, since model creation is allowed to everyone by default.
When you are authenticated, all the objects you create will be associated to your credentials id.
First, you put a definition under the name “todo” using a PUT request on /models:
http PUT http://localhost:8000/v1/models/todo
Use the token
as the auth
value, as expected by the requests-hawk
library.
echo '{"definition":
{
"title": "todo",
"description": "A list of my stuff to do",
"fields": [
{
"name": "item",
"type": "string",
"label": "The item"
},
{
"name": "status",
"type": "enum",
"choices": [
"done",
"todo"
],
"label": "is it done or not"
}
]
}
}' > definition
http PUT http://localhost:8000/v1/models/todo @definition \
--verbose \
--auth-type=hawk \
--auth='ad37fc395b7ba83eb496849f6db022fbb316fa11081491b5f00dfae5b0b1cd22:'
And you receive the model id back
HTTP/1.1 200 OK
Content-Length: 14
Content-Type: application/json; charset=UTF-8
Date: Thu, 24 Jul 2014 18:35:10 GMT
Server: waitress
{
"id": "todo"
}
Since the token was used, the new model was associated to your id, and you are the only one to get read and write permissions. Of course, the model permissions can be changed later.
Note
In case you don’t want to define a name yourself for your model, you can do the exact same request, replacing the PUT http method by a POST. A random name will be generated.
The definition properties are:
- title: The model title
- description: The model description
- fields: The model fields’ list. See fields documentation
- extra: An optional property to store custom data to your model.
GET /models
Returns the list of models where you have the permission to read the definition:
http GET http://localhost:8000/v1/models --verbose \
--auth-type=hawk \
--auth='ad37fc395b7ba83eb496849f6db022fbb316fa11081491b5f00dfae5b0b1cd22:'
GET /v1/models HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Authorization: Hawk mac="3NXv...=", hash="B0we...=", id="36...0", ts="1407166852", nonce="tQlJHv"
Host: localhost:8000
User-Agent: HTTPie/0.8.0
HTTP/1.1 200 OK
Content-Length: 202
Content-Type: application/json; charset=UTF-8
Date: Mon, 04 Aug 2014 15:40:52 GMT
Server: waitress
{
"models": [
{
"description": "A list of my stuff to do.",
"id": "todo",
"title": "Todo"
}
]
}
GET /v1/models/{modelname}
You can now get your models back:
http GET http://localhost:8000/v1/models/todo \
--verbose \
--auth-type=hawk \
--auth='ad37fc395b7ba83eb496849f6db022fbb316fa11081491b5f00dfae5b0b1cd22:'
GET /v1/models/todo HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Authorization: Hawk mac="CEhSQuh8tqGY8RbdrnMvGyIRJBDmdxJeu2/HIRB0pbQ=", hash="B0weSUXsMcb5UhL41FZbrUJCAotzSI3HawE1NPLRUz8=", id="e03945
74578356252e2033b829b90291e2ff1f33ccbcbcec777485f3a5a10bca", ts="1406228025", nonce="4sEpMQ"
Host: localhost:8000
User-Agent: HTTPie/0.8.0
HTTP/1.1 200 OK
Content-Length: 1330
Content-Type: application/json; charset=UTF-8
Date: Thu, 24 Jul 2014 18:53:45 GMT
Server: waitress
{
"permissions": {
"e0394574578356252e2033b829b90291e2ff1f33ccbcbcec777485f3a5a10bca": [
'create_record',
'delete_all_records',
'delete_model',
'delete_own_records',
'read_permissions',
'read_all_records',
'read_definition',
'read_own_records',
'update_permissions',
'update_all_records',
'update_definition',
'update_own_records',
]
},
"definition": [
{
"description": "A list of my stuff to do",
"fields": [
{
"label": "The item",
"name": "item",
"type": "string"
},
{
"choices": [
"done",
"todo"
],
"label": "is it done or not",
"name": "status",
"type": "enum"
}
],
"title": "todo"
}
],
"records": []
}
Note
You will get a 401 - Unauthorized
response if you don’t have the
permission to read the model definition.
Pushing records¶
POST /v1/models/{modelname}/records
PUT /v1/models/{modelname}/records/{id}
Now that you’ve defined the schema, you may want to push some real record there:
http POST http://localhost:8000/v1/models/todo/records item="work on daybed" status="done" \
--verbose \
--auth-type=hawk \
--auth='ad37fc395b7ba83eb496849f6db022fbb316fa11081491b5f00dfae5b0b1cd22:'
POST /v1/models/todo/records HTTP/1.1
Accept: application/json
Accept-Encoding: gzip, deflate
Authorization: Hawk mac="4Sly1HVkkKsRk43dHOLw/e/AmWeoDEe9ZbVu9cugzg0=", hash="KE3ivKqZxHPTg1yzUAJHOu/PYiYWvEoh3SZxzYshikw=", id="e03945
74578356252e2033b829b90291e2ff1f33ccbcbcec777485f3a5a10bca", ts="1406228375", nonce="T2NP4V"
Content-Length: 44
Content-Type: application/json; charset=utf-8
Host: localhost:8000
User-Agent: HTTPie/0.8.0
{
"item": "work on daybed",
"status": "done"
}
HTTP/1.1 201 Created
Content-Length: 42
Content-Type: application/json; charset=UTF-8
Date: Thu, 24 Jul 2014 18:59:35 GMT
Location: http://localhost:8000/v1/models/todo/records/ebc9f07c8faa4969a76f46b8c514fac6
Server: waitress
{
"id": "ebc9f07c8faa4969a76f46b8c514fac6"
}
The server sends us back the id of the newly created record.
Note
You can also only validate the data your are sending, by setting the
Validate-Only
header, which will prevent storing it as a record.
GET /v1/models/{modelname}/records
Using the GET method, you can get back all the records you have created:
http GET http://localhost:8000/v1/models/todo/records \
--json \
--verbose \
--auth-type=hawk \
--auth='ad37fc395b7ba83eb496849f6db022fbb316fa11081491b5f00dfae5b0b1cd22:'
GET /v1/models/todo/records HTTP/1.1 [5/4051]
Accept: application/json
Accept-Encoding: gzip, deflate
Authorization: Hawk mac="OQ9PYGfLhE7L0TPHFpYteHI0j3PBnKgEjyYjMQXMsaM=", hash="NVuBm+XMyya3Tq4EhpZ0cQWjVUyIA8sKnySkKDOIM4M=", id="e0394574578356252e2033b829b90291e2ff1f33ccbcbcec777485f3a5a10bca", ts="1406232484", nonce="_m0VvY"
Content-Type: application/json; charset=utf-8
Host: localhost:8000
User-Agent: HTTPie/0.8.0
HTTP/1.1 200 OK
Content-Length: 151
Content-Type: application/json; charset=UTF-8
Date: Thu, 24 Jul 2014 20:08:04 GMT
Server: waitress
{
"records": [
{
"item": "work on daybed",
"status": "done"
},
]
}
Get back a definition¶
GET /v1/models/{modelname}/definition
http GET http://localhost:8000/v1/models/todo/definition \
--verbose \
--auth-type=hawk \
--auth='504fd8148d7cdca10baa3c5208b63dc9e13cad1387222550950810a7bdd72d2c:'
GET /v1/models/todo/definition HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Authorization: Hawk mac="k9edIqpoz7cSUJQTroXgM4vgDoZb2Z2KO2u40QCbtYk=", hash="B0weSUXsMcb5UhL41FZbrUJCAotzSI3HawE1NPLRUz8=", id="220a1c4212d8f005f0f56191c5a91f8fe266282d38b042e6b35cad8034f22871", ts="1406645426", nonce="meNBWv"
Host: localhost:8000
User-Agent: HTTPie/0.8.0
HTTP/1.1 200 OK
Content-Length: 224
Content-Type: application/json; charset=UTF-8
Date: Tue, 29 Jul 2014 14:50:26 GMT
Server: waitress
{
"description": "A list of my stuff to do",
"fields": [
{
"label": "The item",
"name": "item",
"type": "string"
},
{
"choices": [
"done",
"todo"
],
"label": "is it done or not",
"name": "status",
"type": "enum"
}
],
"title": "todo"
}
Get back the model permissions¶
GET /v1/models/{modelname}/permissions
http GET http://localhost:8000/v1/models/todo/permissions \
--verbose \
--auth-type=hawk \
--auth='504fd8148d7cdca10baa3c5208b63dc9e13cad1387222550950810a7bdd72d2c:'
GET /v1/models/todo/permissions HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Authorization: Hawk mac="G8PntYqGA0DiP4EC0qvvr70tmCZrsVBdTTTBq9ZeKYg=", hash="B0weSUXsMcb5UhL41FZbrUJCAotzSI3HawE1NPLRUz8=", id="220a1c4212d8f005f0f56191c5a91f8fe266282d38b042e6b35cad8034f22871", ts="1406645480", nonce="4D0z9n"
Host: localhost:8000
User-Agent: HTTPie/0.8.0
HTTP/1.1 200 OK
Content-Length: 293
Content-Type: application/json; charset=UTF-8
Date: Tue, 29 Jul 2014 14:51:20 GMT
Server: waitress
{
"220a1c4212d8f005f0f56191c5a91f8fe266282d38b042e6b35cad8034f22871": [
"create_record",
"delete_all_records",
"delete_model",
"delete_own_records",
"read_all_records",
"read_definition",
"read_own_records",
"read_permissions",
"update_all_records",
"update_definition",
"update_own_records"
"update_permissions",
]
}
Change model permissions¶
As described in the dedicated section about permissions, you can add or remove permissions from models.
For example, you may want to give the permission to read everyone’s records to anonymous users (i.e. Everyone).
Using a PATCH
request, existing permissions configuration is not overwritten
completely :
PATCH /v1/models/{modelname}/permissions
echo '{"Everyone": ["+read_all_records"]}' | http PATCH http://localhost:8000/v1/models/todo/permissions \
--json \
--verbose \
--auth-type=hawk \
--auth='504fd8148d7cdca10baa3c5208b63dc9e13cad1387222550950810a7bdd72d2c:'
PATCH /v1/models/todo/permissions HTTP/1.1
Accept: application/json
Accept-Encoding: gzip, deflate
Authorization: Hawk mac="CWT9du2YxOoTb2i5d15bBTA4XiSYY/99ybh6g7welLM=", hash="Nt8m2h1nc5lVUItOobOliVj6hul0FYXmwpEmkjyp+WU=", id="220a1c4212d8f005f0f56191c5a91f8fe266282d38b042e6b35cad8034f22871", ts="1406645940", nonce="2il3kl"
Content-Length: 34
Content-Type: application/json; charset=utf-8
Host: localhost:8000
User-Agent: HTTPie/0.8.0
{
"Everyone": [
"+read_all_records"
]
}
HTTP/1.1 200 OK
Content-Length: 333
Content-Type: application/json; charset=UTF-8
Date: Tue, 29 Jul 2014 14:59:00 GMT
Server: waitress
{
"220a1c4212d8f005f0f56191c5a91f8fe266282d38b042e6b35cad8034f22871": [
"create_record",
"delete_all_records",
"delete_model",
"delete_own_records",
"read_all_records",
"read_definition",
"read_own_records",
"read_permissions",
"update_all_records",
"update_definition",
"update_own_records"
"update_permissions",
],
"system.Everyone": [
"read_all_records"
]
}
If you add an unknown permission or modify the permissions of an unknown id, you will get an error.
Reset permissions¶
Using a PUT
request, existing permissions will be completely erased and
replaced by the new ones.
Using the ALL
shortcut, you can grant all available permissions.
PUT /v1/models/{modelname}/permissions
echo '{"Everyone": ["read_definition"], "Authenticated": ["ALL"]}' | http PUT http://localhost:8000/v1/models/todo/permissions \
--json \
--verbose \
--auth-type=hawk \
--auth='504fd8148d7cdca10baa3c5208b63dc9e13cad1387222550950810a7bdd72d2c:'
PATCH /v1/models/todo/permissions HTTP/1.1
Accept: application/json
Accept-Encoding: gzip, deflate
Authorization: Hawk mac="CWT9du2YxOoTb2i5d15bBTA4XiSYY/99ybh6g7welLM=", hash="Nt8m2h1nc5lVUItOobOliVj6hul0FYXmwpEmkjyp+WU=", id="220a1c4212d8f005f0f56191c5a91f8fe266282d38b042e6b35cad8034f22871", ts="1406645940", nonce="2il3kl"
Content-Length: 34
Content-Type: application/json; charset=utf-8
Host: localhost:8000
User-Agent: HTTPie/0.8.0
{
"Everyone": [
"read_definition"
],
"Authenticated": [
"ALL"
]
}
HTTP/1.1 200 OK
Content-Length: 333
Content-Type: application/json; charset=UTF-8
Date: Tue, 29 Jul 2014 14:59:00 GMT
Server: waitress
{
"system.Authenticated": [
"create_record",
"delete_all_records",
"delete_model",
"delete_own_records",
"read_all_records",
"read_definition",
"read_own_records",
"read_permissions",
"update_all_records",
"update_definition",
"update_own_records"
"update_permissions",
],
"system.Everyone": [
"read_definition"
]
}
Note
It can be useful if you need to remove permissions associated to an unknown id for example.
Daybed field types¶
A field is defined with these global properties:
- label: human readable field label
- name: storage attribute name
- type: kind of value to be stored
- required: mandatory attribute (optional, default: true)
- hint: help text (optional)
You then have different types of fields, all detailed in this section.
Basic types¶
Basic types don’t have specific properties.
- int: An integer
- string: A set of characters
- text: A text
- email: A valid email
- url: A valid URL
- decimal: A decimal number
- boolean: A boolean
For example, when combining global properties and one of those basic field, it becomes:
{
"label": "Task item",
"name": "task",
"type": "string"
}
You just have to choose a type among those available.
Advanced types¶
- enum: A choice among values
- Specific parameters:
- choices: An array of strings
{
"label": "Task status",
"name": "status",
"type": "enum",
"choices": [
"done",
"todo"
]
}
- choices: Multiple choices among values
- Specific parameters:
- choices: An array of strings
{
"label": "Hobbies",
"name": "hobbies",
"type": "choices",
"choices": [
"Litterature",
"Cinema",
"Mountain Bike",
"Motor Bike",
"Sailing"
]
}
- range: A number within limits
- Specific parameters:
- min: An integer which is the minimum value of the field
- max: An integer which is the maximum value of the field
It will accept a value that is greater than or equal to min and less than or equal to max.
{
"label": "Mountain bike Wheel Size (in mm)",
"name": "wheel-size",
"type": "range",
"min": 239,
"max": 622
}
- regex: A string matching a pattern
- Specific parameters:
- regexp: The pattern the value should match to be valid.
{
"label": "French Mobile Phone Number",
"name": "phone-number",
"type": "regex",
"regex": "^0[6-7][0-9]{8}$"
}
- date: A date in yyyy-mm-dd format
- Specific parameters:
- autonow: Boolean, add the current date automatically if true. (default: false)
{
"label": "Date of Birth",
"name": "date",
"type": "date",
"autonow": true
}
- datetime: A datetime in yyyy-mm-ddTHH:MM:SS format
- Specific parameters:
- autonow: Boolean, add the current datetime automatically if true. (default: false)
{
"label": "Time of Birth",
"name": "date_of_birth",
"type": "datetime"
}
- group: A group of fields, can define fieldsets or multi-pages forms.
- Specific parameters:
- description: A string to describe the group.
- fields: A list of fields of the group.
{
"label": "Fieldset",
"type": "group",
"fields": [
{
"label": "Gender",
"name": "gender",
"type": "enum",
"choices": [
"Mr",
"Miss",
"Ms"
]
},
{
"label": "Firstname",
"name": "firstname",
"type": "string"
},
{
"label": "Lastname",
"name": "lastname",
"type": "string"
}
]
}
Groups are ignored during validation, and records are posted like this:
{"gender": "Mr", "firstname": "Remy", "lastname": "Hubscher"}
- annotation: A model description field not used for validation
No specific parameters.
{
"label": "Title 1",
"type": "annotation",
}
The annotation type is not really a field because the record has no trace of it. It can be used to add a description between fields.
Like the group
field type, it can help to build the form layout.
For instance:
{"definition":
{
"title": "Club members",
"description": "Name and pictures of all members",
"fields": [
{
"label": "First and last name",
"name": "fullname",
"type": "string"
},
{
"label": "Providing a picture is optional",
"type": "annotation",
"css": "font-weigth: bold"
},
{
"label": "Picture",
"name": "picture",
"type": "url",
"required": false
}
]
}
}
The css property is just an example of how we could handle the styling of the annotation here, but it could be anything else.
- json: A JSON value
No specific parameters.
Beyond formatting, the content is not validated
{
"label": "JSON object",
"name": "movie",
"type": "json"
}
Then you can use it like so:
{
"movie": {
"title": "The Island",
"director": "Michael Bay",
"actors": ["Scarlett Johnsson", "Erwan McGregor"],
"year": 2005
}
}
It will also work with a string :
{
"movie": "{\"title\": \"The Island\"}"
}
Nested types¶
- object: An object inside another model
- Specific parameters, used to validate the content. Only one of them should be specified.
- fields: A list of fields like in a model definition.
- model: The id of an existing model.
Unlike the json
type, the content will be validated, using either the list
of fields or the definition of the specified model
{
"label": "Movie",
"name": "movie",
"type": "object",
"fields": [
{
"label": "Title",
"name": "title",
"type": "string"
},
{
"label": "Director",
"name": "director",
"type": "string"
},
{
"label": "Actors",
"name": "actors",
"type": "list",
"item": {"type": "string"}
}
]
}
For example, this record will be valid for the definition above:
{
"movie": {
"title": "Donnie Darko",
"director": "Richard Kelly",
"actors": ["Jake Gyllenhaal", "Patrick Swayze"],
}
}
But this one will not:
{
"movie": {
"title": "Director and actors missing",
}
}
- list: A list of values inside another model
- Specific parameters:
- item: Defines the type of the list items. Specified like a field in a model definition.
Can be used to define a simple list of basic types (integer, string, ...):
{
"label": "Movie titles",
"name": "movies",
"type": "list",
"item": {
"type": "string"
}
}
Or a list of advanced field types (dates, objects, ...):
{
"label": "Movie list",
"name": "movies",
"type": "list",
"item": {
"type": "object",
"hint": "Description of a movie",
"fields": [
{
"label": "Title",
"name": "title",
"type": "string"
},
{
"label": "Director",
"name": "director",
"type": "string"
}
]
}
}
If item
is not specified, the list items can be anything
(e.g. no validation will be done on them):
{
"label": "Last thoughts",
"name": "toughts",
"type": "list"
}
The following records will be considered valid with the definition above:
{ "toughts": [1, 2, 3] }
{ "toughts": [{"miam": true}, 42, ["OSM", "Mapnik"], "World Company"] }
Relation types¶
- anyof: Any number of choices among records of a given model
- Specific parameters:
- model: The model id from which records can be selected
{
"name": "actors",
"type": "anyof",
"model": "generic:people:moviestars",
"label": "Movie actors"
}
- oneof: One choice among records of a given model
- Specific parameters:
- model: The model id from which the record can be selected
{
"name": "maincharacter",
"type": "oneof",
"model": "generic:people:moviestars",
"label": "Main character"
}
Geometric types¶
- geojson: A GeoJSON geometry (not a FeatureCollection)
No specific parameters.
{
"label": "where is it?",
"name": "place",
"type": "geojson"
}
Then you can use it like so:
http POST http://localhost:8000/v1/models/todo/records \
item="work on daybed" status="done" \
place='{"type": "Point", "coordinates": [0.4, 45.0]}' \
--verbose --auth-type=hawk \
--auth='ad37fc395b7ba83eb496849f6db022fbb316fa11081491b5f00dfae5b0b1cd22:'
{
"item": "work on daybed",
"place": {
"coordinates": [
0.4,
45.0
],
"type": "Point"
},
"status": "done"
}
- point: A point
- Specific parameters:
- gps: A boolean that tells if the point coordinates are GPS
coordinates and it will validate that coordinates are between
-180,-90
and+180,+90
(Default: true)
- gps: A boolean that tells if the point coordinates are GPS
coordinates and it will validate that coordinates are between
{
"label": "where is it?",
"name": "place",
"type": "point"
}
Then you can use it like so:
http POST http://localhost:8000/v1/models/todo/records \
item="work on daybed" status="done" \
place="[0.4, 45.0]" \
--verbose --auth-type=hawk \
--auth='ad37fc395b7ba83eb496849f6db022fbb316fa11081491b5f00dfae5b0b1cd22:'
{
"item": "work on daybed",
"place": [
0.4,
45.0
],
"status": "done"
}
- line: A line made of points
- Specific parameters
- gps: A boolean that tells if the point coordinates are GPS
coordinates and it will validate that coordinates are between
-180,-90
and+180,+90
(Default: true)
- gps: A boolean that tells if the point coordinates are GPS
coordinates and it will validate that coordinates are between
{
"label": "where is it?",
"name": "place",
"type": "line"
}
Then you can use it like so:
http POST http://localhost:8000/v1/models/todo/records \
item="work on daybed" status="done" \
place="[[0.4, 45.0], [0.6, 65.0]]" \
--verbose --auth-type=hawk \
--auth='ad37fc395b7ba83eb496849f6db022fbb316fa11081491b5f00dfae5b0b1cd22:'
{
"item": "work on daybed",
"place": [
[
0.4,
45.0
],
[
0.6,
65.0
]
],
"status": "done"
}
- polygon: A polygon made of a closed line
- Specific parameters
- gps: A boolean that tells if the point coordinates are GPS
coordinates and it will validate that coordinates are between
-180,-90
and+180,+90
(Default: true)
- gps: A boolean that tells if the point coordinates are GPS
coordinates and it will validate that coordinates are between
{
"label": "where is it?",
"name": "place",
"type": "polygon"
}
Then you can use it like so:
http POST http://localhost:8000/v1/models/todo/records \
item="work on daybed" status="done" \
place="[[[0.4, 45.0], [0.6, 65.0], [0.8, 85.0], [0.4, 45.0]]]" \
--verbose --auth-type=hawk \
--auth='ad37fc395b7ba83eb496849f6db022fbb316fa11081491b5f00dfae5b0b1cd22:'
{
"item": "work on daybed",
"place": [
[
[
0.4,
45.0
],
[
0.6,
65.0
],
[
0.8,
85.0
],
[
0.4,
45.0
]
]
],
"status": "done"
}
Permissions¶
In Daybed, permissions will let you define access rules on models, records and even permissions.
They allow to express rules like:
- “Everyone can create new records on this model”
- “Alexis is able to delete records created by others”
- “Authenticated users can modify their own records”
- “Everyone can read the model definition”
This section describes how they work and how to use them.
Models permissions¶
Here’s a list of permissions you can define on a model:
- read_definition: read the model definition
- read_permissions: read the model permissions (who can do what)
- update_definition: update the model definition
- update_permissions: change the permissions for the model
- delete_model: delete the model
- create_record: add an entry to the model
- read_all_records: read all model’s records
- update_all_records: update all model’s records
- delete_all_records: delete any model’s records
- read_own_records: read records on which you are an author
- update_own_records: update and change records on which you are an author
- delete_own_records: delete records on which you are an author
Views permissions¶
At the API level, you will often need more than one permission to get access to an API resource.
For example, if you want to get a complete model (definition and records), you will need the following permissions:
- read_definition and read_permissions
- read_all_records or read_own_records
Global permissions¶
There are three extra permissions that are configured at the server level:
- create_model: List of identifiers allowed to create a model
- create_token: List of identifiers allowed to create tokens
- manage_tokens: List of identifiers allowed to delete tokens
Those are configured via a configuration file, with the following options:
- create_model:
daybed.can_create_model
(default:Everyone
) - create_token:
daybed.can_create_token
(default:Everyone
) - manage_tokens:
daybed.can_manage_token
(default: None)
Example:
[app:main]
daybed.can_create_model = Authenticated
daybed.can_create_token = Everyone
Usage¶
Permissions are set on models, as a dictionnary between identifiers (key id) and lists of permissions.
In order to refer to anyone in the world, use the special id Everyone
and to authenticated users with Authenticated
.
Note
As explained in the API usage section, the key ids look like tokens, but they are different : the id is the public part of your credentials. The session token is private, since it contains the secret key.
When you create a model, you gain the full set of available permissions.
This means that the identifier you used in the request will be associated to all permissions:
http GET http://localhost:8000/v1/models/todo/permissions --verbose \
--auth-type=hawk \
--auth='ad37fc395b7ba83eb496849f6db022fbb316fa11081491b5f00dfae5b0b1cd22:'
{
"e0394574578356252e2033b829b90291e2ff1f33ccbcbcec777485f3a5a10bca": [
"create_record",
"delete_all_records",
"delete_model",
"delete_own_records",
"read_all_records",
"read_definition",
"read_own_records",
"read_permissions",
"update_all_records",
"update_definition",
"update_own_records",
"update_permissions"
]
}
Let’s say you want to allow authenticated users to create records and manage their own records on this model.
Permissions become:
{
"Authenticated": [
"create_record",
"read_own_records",
"update_own_records",
"delete_own_records"
],
"e0394574578356252e2033b829b90291e2ff1f33ccbcbcec777485f3a5a10bca": [
"create_record",
"delete_all_records",
"delete_model",
"delete_own_records",
"read_all_records",
"read_definition",
"read_own_records",
"read_permissions",
"update_all_records",
"update_definition",
"update_own_records",
"update_permissions"
]
}
Modification¶
You can use -
and +
to modify the existing set of permissions for an
identifier.
To grant create_record
to anonymous users, read_permissions
to
authenticated users and remove update_permissions
from id 220a1c..871
you would have to send the following request:
{
"Everyone": ["+create_record"],
"Authenticated": ["+read_permissions"],
"220a1c..871": ["-update_permissions"]
}
In order to add/remove all permissions to/from somebody, use the ALL
shortcut:
{
"Authenticated": ["-ALL"],
"220a1c..871": ["+ALL"]
}
Note
+
is implicit, the permission is added if not specified
(i.e. ALL
is equivalent to +ALL
).
Concrete examples¶
Collaborative editor (pad)¶
Everybody can read, create, modify and delete everyone’s records.
However only the owner (id 220a1c..871
) can modify the model definition and
adjust permissions.
{
"Everyone": [
"read_definition",
"create_record",
"read_all_records",
"update_all_records",
"delete_all_records"
],
"220a1c..871": [
"ALL"
]
}
If the administrator wants to share her privileges with others, she can either:
- share her token
- create a new token, assign permissions to its key id, and share the new token
{
"Everyone": [
"read_definition",
"create_record",
"read_all_records",
"update_all_records",
"delete_all_records"
],
"6780dd..df1": [
"update_definition",
"read_permissions",
"update_permissions"
],
"220a1c..871": [
"ALL"
]
}
Online poll¶
Everybody can answer the poll, but are not allowed to correct their answers, nor to see the poll results.
read_definition
is given to everyone, as it might be used to build the
form on the client-side:
{
"Everyone": [
"read_definition",
"create_record"
],
"220a1c..871": [
"ALL"
]
}
TODO-list application¶
The development team, who created the model, has the full set of permissions.
Everybody can manage their own records, but they are private.
{
"Everyone": [
"read_definition",
"create_record",
"read_own_records",
"update_own_records",
"delete_own_records"
],
"220a1c..871": [
"ALL"
]
}
Note
Using Everyone instead of Authenticated will allow anonymous to manage a set of records that are shared among all anonymous users.
Note
Users can share their todo list if they share their token. But they cannot share it as read-only.
In order to accomplish this, instead of having a unique model with everyone’s records, each user will have to create their own model, on which they will gain the control of permissions.
Terminology¶
Daybed concepts are very similar to those of any other storage or model validation software.
- Credentials
Credentials are a way to authenticate yourself, and are composed of two parts:
- an id – identifier that you can publicly share
- a key – similar to a password (you may prefer to not share it)
- Definition
- A schema defined as a list of fields, each one with a name, a type and potential parameters.
- Field
- A model definition is composed of multiple fields. Each one contains a name and a type.
- Field type
- A type, among those available, whose purpose is to validate values
(e.g.
int
,date
, ...). It may have mandatory or optional parameters, when used in a definition (e.g.choices
,regex
, ...). - Identifier
Identifiers A unique id, part of the credentials, that will be associated to the models and records you created.
Identifiers are used to define permissions.
- Model
Models - A model is made of a definition, a set of records, and a list of permissions.
- Permission
Permissions An operation name used to allow or deny requests on models, records or tokens. Permissions are given to identifiers as an associative array on models.
For example, when trying to delete a record, if the request’s identifier doesn’t have
delete_records
among its permission on this model, the permission will be denied.See permissions section.
- Record
Records - An item to be stored in a model. It should provide a value for each required field of the definition.
- Token
Tokens
Hawk-Session-Token - An unique string from each pair of id and key, and helps you keep, handle or share your credentials as a simple string.