Custom reporting app with authentication and predefined schema

In the tutorial we will implement more complex app with authentication and predefined schema. We will use NodeJS and Express framework but you can use any other programming language.

App requirements

Let's suppose that we have a company that works on products sailing. We want to give an ability to visualize products and employees. For example, manager may want to see salary distribution across employees. But we want to show salaries of employees only if the user is manager. Otherwise we want to hide the info.

To create the app you need to implement 4 endpoints:

Getting app information

App information should have the structure. Let's implement it.

const getSources = () => ([{id: 'employee', name: 'Employee'}, {id: 'products', name: 'Products'}]);

const getAuthentications = () => ([
    {
        name: 'Login & token',
        id: 'login-token',
        description: 'Provide login and token to our system',
        fields: [
            {
                'optional': false,
                'id': 'login',
                'name': 'Login',
                'type': 'text',
                'description': 'Login',
                'datalist': false
            },
            {
                'optional': false,
                'id': 'token',
                'name': 'Token',
                'type': 'text',
                'description': 'Token',
                'datalist': false
            },
        ]
    }
]);

app.get('/', (req, res) => {
    const app = {
        'name': 'Custom app with schema',
        'version': version,
        'type': 'crunch',
        'description': 'Custom app with predefined data schema',
        'authentication': getAuthentications(),
        'sources': getSources()
    };

    res.json(app);
});

In case you know schema of your data in advance, you can specify type = "crunch" in app schema and control the schema depending on account. From the schema we can see that the app requires authentication and have 2 sources: employees and products. Authentication schema shows that user should input login and token. Sources don't have any filters. You can find more information about the endpoint here.

Validate account

Login and token should be validate using our internal rules (login should be not empty, token should have at least 6 chars). The account name should be return if the provided account is valid (in our case login is returned as account name).

app.post('/validate', (req, res) => {
    const {login, token} = req.body.fields;

    // login should be not empty
    if (login && login.trim().length === 0) {
        return res.status(401).json({message: `Invalid login or token`});
    }

    // token should have at least 6 chars
    if (token && token.trim().length < 6) {
        return res.status(401).json({message: `Invalid login or token`});
    }

    const userInDb = Boolean(users.find((user) => user.login === login && user.token === token));

    if (!userInDb) {
        return res.status(401).json({message: `Invalid login or token`});
    }

    res.status(200).json({name: login});
});

You can find more information about the endpoint here.

Getting schema

Different schemas are returned depending on source and account. Full schema is returned for products source in all cases. Full schema is returned for employee source in case when account belongs to manager, otherwise returned schema is without salary field.

app.post('/schema', (req, res) => {
    const source = req.body.source;
    const account = req.body.account;
    const accountInDb = users.find(({login}) => login === account.login);

    if (source === 'employee') {
        if (accountInDb.type === `manager`) {
            return res.json({
                name: {
                    id: "name",
                    ignore: false,
                    name: "Name",
                    readonly: false,
                    type: "text"
                },
                age: {
                    id: "age",
                    ignore: false,
                    name: "Age",
                    readonly: false,
                    type: "number"
                },
                salary: {
                    id: "salary",
                    ignore: false,
                    name: "Salary",
                    readonly: false,
                    type: "number"
                }
            });
        }

        return res.json({
            name: {
                id: "name",
                ignore: false,
                name: "Name",
                readonly: false,
                type: "text"
            },
            age: {
                id: "age",
                ignore: false,
                name: "Age",
                readonly: false,
                type: "number"
            },
        });
    } else {
        return res.json({
            name: {
                id: "name",
                ignore: false,
                name: "Name",
                readonly: false,
                type: "text"
            },
            price: {
                id: "price",
                ignore: false,
                name: "Price",
                readonly: false,
                type: "number"
            },
            quantity: {
                id: "quantity",
                ignore: false,
                name: "Quantity",
                readonly: false,
                type: "number"
            }
        });
    }
});

You can find more information about the endpoint here.

Fetching data

Source and filter should be passed to endpoint and the actual data should be returned in response.

const employees = require('./data/employees.json');
const products = require('./data/products.json');

const getSourceDb = (source) => {
    if (source === 'employee') {
        return employees;
    } else {
        return products;
    }
};

app.post('/', (req, res) => {
    const source = req.body.source;
    const db = getSourceDb(source);
    const account = req.body.account;
    const accountInDb = users.find(({login}) => login === account.login);

    if (source === `employee` && accountInDb.type !== `manager`) {
        return res.json(db.map(({name, age}) => ({name, age})));
    }

    res.json(db);
});

Let's suppose that employees and products are stored in simple json files. req.body.source contains selected source. If source is employee and account is not manager, we do not return salary. In other cases we return all info as is. You can find more information about the endpoint here.

That was the final step! See full example here.