@windingtree/wt-write-api
Version:
API to write data to the Winding Tree platform
512 lines (435 loc) • 17.6 kB
Markdown
# WT Write API
[](https://greenkeeper.io/)
API server written in node.js to interact with the Winding Tree
platform. It is capable of:
- Create and register new hotels in a segment directory
- Update existing hotel records
- Delete hotels from a segment directory
It also automatically publishes notifications about changes via
the WT Update API.
## Requirements
- Nodejs >=10
## Development
In order to install and run tests, we must:
```
git clone git.com:windingtree/wt-write-api.git
nvm install
npm install
npm run resolve-swagger-references
npm test
```
### Running in dev mode
With all the dependencies installed, you can start the dev server.
First step is to initialize the SQLite database used to store your settings.
If you want to use a different database, feel free to change the connection
settings in the appropriate configuration file in `src/config/`.
```bash
npm run createdb-dev
```
If you'd like to start afresh later, just delete the `.dev.sqlite` file.
Second step is starting [Ganache](https://github.com/trufflesuite/ganache)
(local Ethereum network node). You can skip this step if you have a different
network already running.
```bash
npm run dev-net
```
For trying out the interaction with the running dev-net and wt-write-api in general,
you can use the Winding Tree demo wallet protected by password `windingtree`.
It is initialized on `dev-net` with enough ether. For sample interaction scripts, check out our
[Developer guides](https://github.com/windingtree/wiki/tree/master/developer-guides).
**!!!NEVER USE THIS WALLET FOR ANYTHING IN PRODUCTION!!!** Anyone has access to it.
```js
{"version":3,"id":"7fe84016-4686-4622-97c9-dc7b47f5f5c6","address":"d037ab9025d43f60a31b32a82e10936f07484246","crypto":{"ciphertext":"ef9dcce915eeb0c4f7aa2bb16b9ae6ce5a4444b4ed8be45d94e6b7fe7f4f9b47","cipherparams":{"iv":"31b12ef1d308ea1edacc4ab00de80d55"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"d06ccd5d9c5d75e1a66a81d2076628f5716a3161ca204d92d04a42c057562541","n":8192,"r":8,"p":1},"mac":"2c30bc373c19c5b41385b85ffde14b9ea9f0f609c7812a10fdcb0a565034d9db"}};
```
Now we can run our dev server.
```bash
npm run dev
```
When using a `dev` config, we internally run a script to deploy WT Index. It is not immediate,
so you might experience some errors in a first few seconds. And that's the reason why
it is not used in the same manner in integration tests.
You can fiddle with the configuration in `src/config/`.
## Running this server
### Docker
You can run the whole API in a docker container, and you can
control which config will be used by passing an appropriate value
to WT_CONFIG variable at runtime. Database will be setup during the
container startup in the current setup. You can skip this with
`SKIP_DB_SETUP` environment variable.
```sh
$ docker build -t windingtree/wt-write-api .
$ docker run -p 8080:8000 -e ETH_NETWORK_PROVIDER=address_to_node -e WT_CONFIG=playground windingtree/wt-write-api
```
- After that you can access the wt-write-api on local port `8080`
- This deployment is using a Ropsten configuration that can be found in `src/config/playground`
- **Warning** - User wallets (although protected by password) will be stored in the image,
be careful where your API is running and who can access it.
- **Warning** - Once your docker container (and its associated volumes, if any) is deleted,
all accounts (wallets and configuration) will disappear.
### NPM
You can install and run this from NPM as well:
```sh
$ npm install -g /wt-write-api
$ -e ETH_NETWORK_PROVIDER=address_to_node WT_CONFIG=playground wt-write-api
```
This will also create a local SQLite instance in the directory
where you run the `wt-write-api` command. To prevent that,
you can suppress DB creation with `SKIP_DB_SETUP` environment
variable.
Probably the easiest way of getting an Ethereum Node API is to register with
[Infura](https://infura.io/).
### Running in production
You can customize the behaviour of the instance by many environment
variables which get applied if you run the API with `WT_CONFIG=envvar`.
These are:
- `WT_CONFIG` - Which config will be used. Defaults to `dev`.
- `ADAPTER_SWARM_GATEWAY` - Address of a Swarm HTTP Gateway, for example `https://swarm.windingtree.com` or `https://swarm-gateways.net`
- `ADAPTER_SWARM_READ_TIMEOUT` - Read timeout in milliseconds for Swarm, defaults to 1000
- `ADAPTER_SWARM_WRITE_TIMEOUT` - Write timeout in milliseconds for Swarm, defaults to 2500
- `WT_ENTRYPOINT_ADDRESS` - On chain address of [Winding Tree Entrypoint](https://github.com/windingtree/wt-contracts/blob/master/contracts/WindingTreeEntrypoint.sol)
- `PORT` - HTTP Port where the API will lsiten, defaults to 8000.
- `LOG_LEVEL` - Set log level for [winston](https://www.npmjs.com/package/winston).
- `BASE_URL` - Base URL of this API instance, for example `https://playground-api.windingtree.com`
- `ETH_NETWORK_NAME` - Name of Ethereum network for informational purposes, for example `ropsten` or `mainnet`
- `ETH_NETWORK_PROVIDER` - Address of Ethereum node, for example `https://ropsten.infura.io/v3/my-project-id`
- `DB_CLIENT` - [Knex](https://knexjs.org/) database client name, for example `sqlite3`.
- `DB_CLIENT_OPTIONS` - [Knex](https://knexjs.org/) database client options as JSON string, for example `{"filename": "./envvar.sqlite"}`.
- `SKIP_DB_SETUP` - Whether to not setup new database upon startup.
For example the playground configuration can be emulated with the following command (the actual values are probably obsolete, check `src/config/playground.js` for current values):
```sh
docker run -p 8080:8000 \
-e WT_CONFIG=envvar \
-e ADAPTER_SWARM_GATEWAY=https://swarm.windingtree.com \
-e WT_ENTRYPOINT_ADDRESS=0xa268937c2573e2AB274BF6d96e88FfE0827F0D4D \
-e ETH_NETOWRK_NAME=ropsten \
-e ETH_NETWORK_PROVIDER=https://ropsten.infura.io/v3/my-project-id \
-e DB_CLIENT_OPTIONS='{"filename": "./envvar.sqlite"}' \
-e DB_CLIENT=sqlite3 \
-e BASE_URL=https://playground-write-api.windingtree.com \
windingtree/wt-write-api \
```
**In production, this API should not be accessible from the open
internet as it internally holds potentially lots of funds in ETH.**
## How tos
### Account setup
In order to use the API, you need to create an account that stores your configuration.
The account consists of Ethereum wallet in JSON format and a configuration of uploaders.
The uploaders are telling the API where to put data about hotels managed by that
Ethereum wallet. *The API does not store Wallet passwords.*
The JSON-format wallet can easily be created locally with [mycrypto](https://download.mycrypto.com/).
In this case, we are setting up swarm as our preferred storage, make sure it is
actually accessible (via the `swarmProvider` gateway url from config) before you
try to create hotel.
```json
{
"wallet": {"version":3,"id":"7fe84016-4686-4622-97c9-dc7b47f5f5c6","address":"d037ab9025d43f60a31b32a82e10936f07484246","crypto":{"ciphertext":"ef9dcce915eeb0c4f7aa2bb16b9ae6ce5a4444b4ed8be45d94e6b7fe7f4f9b47","cipherparams":{"iv":"31b12ef1d308ea1edacc4ab00de80d55"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"d06ccd5d9c5d75e1a66a81d2076628f5716a3161ca204d92d04a42c057562541","n":8192,"r":8,"p":1},"mac":"2c30bc373c19c5b41385b85ffde14b9ea9f0f609c7812a10fdcb0a565034d9db"}},
"uploaders": {
"root": {
"swarm": {}
}
}
}
```
```sh
$ curl -X POST localhost:8000/accounts -H 'Content-Type: application/json' --data -account.json
# These values are generated and will be different
{"accountId":"aa43edaf8266e8f8","accessKey":"usgq6tSBW+wDYA/MBF367HnNp4tGKaCTRPy3JHPEqJmFBuxq1sA7UhFOpuV80ngC"}
```
#### Uploaders
We currently support two types of uploaders, and each uploader can contain a
configuration. *These configuration values are not encrypted in any way.*
- **Swarm** - No configuration needed, we will use the Swarm gateway configured
for the whole wt-write-api instance and reasonable defaults for timeout. Timeouts
are in milliseconds. You can override these settings with:
- `providerUrl` - address of a Swarm gateway. Mandatory.
- `timeout` - General timeout. Will be used for both read and write.
- `timeoutRead` - Read timeout, overwrites the general timeout.
- `timeoutWrite` - Write timeout, overwrites the general timeout.
- **AWS S3** - We recommend to use a separate IAM account for this with a limited
set of permissions. All of the following options are required unless stated
otherwise.
- `accessKeyId` - AWS credentials
- `secretAccessKey` - AWS credentials
- `region` - AWS region
- `bucket` - S3 bucket to upload to. This is the name that is used in URL.
- `keyPrefix` - a prefix ("directory") to upload hotel data to. Serves to
differentiate between different hotels stored in the same s3 bucket. Optional.
You can use any combination of these uploaders for the following list of objects:
- `root` - `Hotel data index` object. This object is required.
- `description` - `Hotel description` object
- `ratePlans` - `RatePlans` object
- `availability` - `Availability` object
...
If you, for example, would like to store your description on Swarm, but everything else in your S3,
you would use the following account object for your initial `POST` request. For any object type not
explicitely stated in the configuration, the `root` configuration will be used.
```json
{
"wallet": {"....": "...."},
"uploaders": {
"root": {
"s3": {
"accessKeyId": "AX...",
"secretAccessKey": "1234...",
"region": "eu-west-1",
"bucket": "hotel-data"
}
},
"description": {
"swarm": {
"providerUrl": "https://swarm-gateways.net",
"timeout": 3000,
"timeoutRead": 5000,
"timeoutWrite": 7000
}
}
}
}
```
In another example, you want everything on swarm except `availability` and ratePlans:
```json
{
"wallet": {"....": "...."},
"uploaders": {
"root": {
"swarm": {}
},
"availability": {
"s3": {
"accessKeyId": "AX...",
"secretAccessKey": "1234...",
"region": "eu-west-1",
"bucket": "hotel-data"
}
},
"ratePlans": {
"s3": {
"accessKeyId": "AX...",
"secretAccessKey": "1234...",
"region": "eu-west-1",
"bucket": "hotel-data"
}
}
}
}
```
However, there is a third option. If you plan to host all of the data by yourself, for
example on your own webserver, you might not need to specify any uploaders:
```json
{
"wallet": {"....": "...."},
"uploaders": {}
}
```
### Create hotel
#### Registering a hotel
In case you plan to manage all of your data on your own, you can only register a hotel
with a single `orgJson` and a keccak256 hash of its contents.
```json
{
"orgJson": "https://example.com/my-hotel-org.json",
"hash": "0x123456..."
}
```
```sh
$ curl -X POST localhost:8000/hotels -H 'Content-Type: application/json' \
-H 'X-Access-Key: usgq6tSBW+wDYA/MBF367HnNp4tGKaCTRPy3JHPEqJmFBuxq1sA7UhFOpuV80ngC' \
-H 'X-Wallet-Password: windingtree' \
--data -description.json
# This value will be different
{"address":"0xA603FF7EA9A1B81FB45EF6AeC92A323a88211f40"}
```
You can verify that the hotel data was saved by calling
```sh
$ curl localhost:8000/hotels/0xa8c4cbB500da540D9fEd05BE7Bef0f0f5df3e2cc
```
In this case, the Write API will try to resolve all of the data from the
provided URI as well.
#### Registering a hotel with data
A full-fledged usage of Write API will take care of uploading the data and generating the ORG.JSON hash
for you. This is just an example data that we know will pass all the validations. For a better
description of the actual data model, have a look at `src/services/validators/`,
where you will find JSON schemas used to validate incoming data.
The *legalEntity* is part of the **ORG.ID** standard. Go [check it out](https://developers.windingtree.com).
```json
{
"legalEntity": {
"name": "Hostel Marilena",
"contact": {
"email": "info@marilena.ro",
"phone": "+40213191564",
"url": "https://geotowns.ro/marilena"
},
"address": {
"road": "Horse Saddle",
"houseNumber": "34",
"postcode": "33200",
"city": "Dragolm",
"countryCode": "RO"
}
},
"hotel": {
"description": {
"name": "Random hotel",
"description": "**Beautiful** hotel located in the center of _Prague, Czech Republic_.",
"location": {
"latitude": 50.075388,
"longitude": 14.414170
},
"contacts": {
"general": {
"email": "chadima.jiri@gmail.com",
"phone": "00420224371111",
"url": "https://jirkachadima.cz",
"ethereum": "windingtree.eth"
}
},
"address": {
"line1": "Rašínovo nábřeží 1981/80",
"line2": "Nové Město",
"postalCode": "12000",
"city": "Prague",
"country": "CZ"
},
"timezone": "Europe/Prague",
"currency": "CZK",
"amenities": [],
"images": [
"https://raw.githubusercontent.com/windingtree/media/master/logo-variants/tree/png/tree--gradient-on-white.png",
"https://raw.githubusercontent.com/windingtree/media/master/logo-variants/full-logo/png/logo--black-on-green.png"
],
"updatedAt": "2018-06-19T15:53:00+0200",
"defaultCancellationAmount": 30,
"roomTypes": {
"1234-abcd": {
"name": "string",
"description": "string",
"totalQuantity": 0,
"occupancy": {
"min": 1,
"max": 3
},
"amenities": [
"TV"
],
"images": [
"https://raw.githubusercontent.com/windingtree/media/web-assets/logo-variants/full-logo/png/logo--white.png"
],
"updatedAt": "2018-06-27T14:59:05.830Z",
"properties": {
"nonSmoking": "some"
}
}
}
}
}
}
```
```sh
$ curl -X POST localhost:8000/hotels -H 'Content-Type: application/json' \
-H 'X-Access-Key: usgq6tSBW+wDYA/MBF367HnNp4tGKaCTRPy3JHPEqJmFBuxq1sA7UhFOpuV80ngC' \
-H 'X-Wallet-Password: windingtree' \
--data -description.json
# This value will be different
{"address":"0xA603FF7EA9A1B81FB45EF6AeC92A323a88211f40"}
```
You can verify that the hotel data was saved by calling
```sh
$ curl localhost:8000/hotels/0xa8c4cbB500da540D9fEd05BE7Bef0f0f5df3e2cc
```
### Update hotel
You can also update previously created hotels. The top-level
properties (e.g. `description`) are always replaced as a whole.
```json
{
"legalEntity": {
"name": "Hostel Marilena",
"contact": {
"email": "info@marilena.ro",
"phone": "+40213191564",
"url": "https://geotowns.ro/marilena"
},
"address": {
"road": "Horse Saddle",
"houseNumber": "34",
"postcode": "33200",
"city": "Dragolm",
"countryCode": "RO"
}
},
"hotel": {
"description": {
"name": "Changed hotel name",
"description": "**Beautiful** hotel located in the center of _Prague, Czech Republic_.",
"location": {
"latitude": 50.075388,
"longitude": 14.414170
},
"contacts": {
"general": {
"email": "chadima.jiri@gmail.com",
"phone": "00420224371111",
"url": "https://jirkachadima.cz",
"ethereum": "windingtree.eth"
}
},
"address": {
"line1": "Rašínovo nábřeží 1981/80",
"line2": "Nové Město",
"postalCode": "12000",
"city": "Prague",
"country": "CZ"
},
"timezone": "Europe/Prague",
"currency": "CZK",
"amenities": [],
"images": [
"https://raw.githubusercontent.com/windingtree/media/master/logo-variants/tree/png/tree--gradient-on-white.png",
"https://raw.githubusercontent.com/windingtree/media/master/logo-variants/full-logo/png/logo--black-on-green.png"
],
"updatedAt": "2018-06-19T15:53:00+0200",
"defaultCancellationAmount": 30,
"roomTypes": {
"1234-abcd": {
"name": "string",
"description": "string",
"totalQuantity": 0,
"occupancy": {
"min": 1,
"max": 3
},
"amenities": [
"TV"
],
"images": [
"https://raw.githubusercontent.com/windingtree/media/web-assets/logo-variants/full-logo/png/logo--white.png"
],
"updatedAt": "2018-06-27T14:59:05.830Z",
"properties": {
"nonSmoking": "some"
}
}
}
}
}
}
```
Alternatively, you can update only the `orgJson` as well:
```json
{
"orgJson": "https://example.com/my-updated-hotel-org.json",
"hash": "0x123456..."
}
```
```sh
$ curl -X PATCH localhost:8000/hotels -H 'Content-Type: application/json' \
-H 'X-Access-Key: usgq6tSBW+wDYA/MBF367HnNp4tGKaCTRPy3JHPEqJmFBuxq1sA7UhFOpuV80ngC' \
-H 'X-Wallet-Password: windingtree' \
--data -description.json
```
The data format version indicator in hotel data index is
always updated to the write api's declared data format version,
no matter how many parts of a hotel you update.
## Publicly available instances
For currently available public instances of wt-write-api, please see [this
page](https://github.com/windingtree/wiki/blob/master/developer-resources.md#publicly-available-wt-deployments).