Guide
In this tutorial we will show how to create simple integration app which does not require any authentication. We intend to create a public holidays app which will sync data about holidays for selected countries. The holidays service Nager.Date will be used to retrieve holidays.
The source code can be found here:
https://gitlab.com/fibery-community/holidays-integration-app
App Information
Every integration should have the configuration which describes what the app is doing and the authentication methods. The app configuration should be accessible at GET / endpoint and should be publicly available.
All mentioned properties are required.
Since I don't want my app be authenticated I didn't provide any fields for "Public Access" node in authentication. It means that any user will be able to connect their account to the public holidays app. Find an example with token authentication here .
const appConfig = require(`./config.app.json`);
app.get(`/`, (req, res) => res.json(appConfig));
Response (config.app.json):
{
"id": "holidays-app",
"name": "Public Holidays",
"version": "1.0.1",
"description": "Integrate data about public holidays into Fibery",
"authentication": [
{
"id": "public",
"name": "Public Access",
"description": "There is no any authentication required",
"fields": [
]
}
],
"sources": [
],
"responsibleFor": {
"dataSynchronization": true
}
}
Validate Account
This endpoint is responsible for app account validation. It is required to be implemented. Let's just send back the name of account without any authentication since we are creating an app with public access.
app.post(`/validate`, (req, res) => res.json({name: `Public`}));
Sync configuration
The way data is synchronised should be described.
The endpoint is POST /api/v1/synchronizer/config and you can read about it in Custom App: REST Endpoints.
types - responsible for describing types which will be synced. For the holidays app it is just one type with id " holidays" and name "Public Holidays". It means that only one integration Fibery database will be created in the space, with the name "Public Holidays".
filters - contains information on how the type can be filtered. In our case there is a multi dropdown ('countries') which is required and marked as datalist. It means that options for this dropdown should be retrieved from app and special end-point should be implemented for that. We have two numeric filters from and to which are optional and can be used to filter holidays by years.
Find information about filters here: Custom App: Fields.
const syncConfig = require(`./config.sync.json`);
app.post(`/api/v1/synchronizer/config`, (req, res) => res.json(syncConfig));
Response(config.sync.json):
{
"types": [
{
"id": "holiday",
"name": "Public Holiday"
}
],
"filters": [
{
"id": "countries",
"title": "Countries",
"datalist": true,
"optional": false,
"type": "multidropdown"
},
{
"id": "from",
"type": "number",
"title": "Start Year (by default previous year used)",
"optional": true
},
{
"id": "to",
"type": "number",
"title": "End Year (by default current year used)",
"optional": true
}
]
}
Countries datalist
Endpoint POST /api/v1/synchronizer/datalist should be implemented if synchronizer filters has dropdown marked as "datalist": true. Since we have countries multi dropdown filter which should contain countries it is required to implement the mentioned endpoint as well.
app.post(`/api/v1/synchronizer/datalist`, wrap(async (req, res) => {
const countries = await (got(`https://date.nager.at/api/v3/AvailableCountries`).json());
const items = countries.map((row) => ({title: row.name, value: row.countryCode}));
res.json({items});
}));
For example part of countries response will look like this:
{
"items": [
...
{
"title": "Poland",
"value": "PL"
},
{
"title": "Belarus",
"value": "BY"
},
{
"title": "Cyprus",
"value": "CY"
},
{
"title": "Denmark",
"value": "DK"
},
{
"title": "Russia",
"value": "RU"
}
]
}
For this app, only the list of countries is returned since our config has only one data list. In the case where there are several data lists then we will need to retrieve "field" from request body which will contain an id of the requested list. The response should be formed as an array of items where every element contains title and value properties.
Schema
POST /api/v1/synchronizer/schema endpoint should return the data schema of the app. In our case it should contain only one root element "holiday" named after the id of holiday type in sync configuration above.
const schema = require(`./schema.json`);
app.post(`/api/v1/synchronizer/schema`, (req, res) => res.json(schema));
schema.json
{
"holiday": {
"id": {
"name": "Id",
"type": "id"
},
"name": {
"name": "Name",
"type": "text"
},
"date": {
"name": "Date",
"type": "date"
},
"countryCode": {
"name": "Country Code",
"type": "text"
}
}
}
Every schema node should have id and name elements defined.
Data
The data endpoint POST /api/v1/synchronizer/data is responsible for retrieving data. There is no paging needed in case of our app, so the data is returned according to selected countries and years interval. The requestedType and filter can be retrieved from the request body. The response should be returned as array in items element.
const getYearRange = filter => {
let fromYear = parseInt(filter.from);
let toYear = parseInt(filter.to);
if (_.isNaN(fromYear)) {
fromYear = new Date().getFullYear() - 1;
}
if (_.isNaN(toYear)) {
toYear = new Date().getFullYear();
}
const yearRange = [];
while (fromYear <= toYear) {
yearRange.push(fromYear);
fromYear++;
}
return yearRange;
};
app.post(`/api/v1/synchronizer/data`, wrap(async (req, res) => {
const {requestedType, filter} = req.body;
if (requestedType !== `holiday`) {
throw new Error(`Only holidays database can be synchronized`);
}
if (_.isEmpty(filter.countries)) {
throw new Error(`Countries filter should be specified`);
}
const {countries} = filter;
const yearRange = getYearRange(filter);
const items = [];
for (const country of countries) {
for (const year of yearRange) {
const url = `https://date.nager.at/api/v3/PublicHolidays/${year}/${country}`;
console.log(url);
(await (got(url).json())).forEach((item) => {
item.id = uuid(JSON.stringify(item));
items.push(item);
});
}
}
return res.json({items});
}));