npm package: @omneo/cf
Description: Omneo CF helps you develop cloud function plugins for Omneo
Author: Omneo
Latest version: 1.3.1

# Omneo Cloud Function Helpers
This is a small library to help boilerplate cloud functions for Omneo. It includes a bunch of handy helpers to help you get started and internal Express handlers for your routes and controllers.

## Getting started
Install with yarn or npm
```sh
yarn add @omneo/cf
# OR
npm install @omneo/cf
```

Require `@omneo/cf` then call the returned function with an object containing top level route path and controller. To make things simple, OmneoCF will register your controllers with the Express.js `use()` call.

```
const app = require('@omneo/cf');

const profileController = (req, res) => {
    res.status(200).json({
        message: `You made a ${req.method} request to the profile controller`
    })   
}

const transactionController = () => {
    const router = express.Router();
    router.get('/', (req, res)=>{
        res.status(200).json({
            message: "Transactions"
        })  
    })

    router.post('/items', (req, res)=>{
        res.status(200).json({
            message: "Transaction items"
        })    
    })
}

return app({
    profiles: profileController,
    transactions: transactionController()
})
```



## Automatic environment handling
This library looks at `NODE_ENV` and will either export just the routes or will start a local express server. The local server will run on `NODE_PORT || 3000`

This local server will also apply the same `bodyParser` as GCP, to give you access to both `res.body` and `res.rawBody` - Useful when validating webhooks etc. It also uses `dotenv()` to include your `.env` file in `process.env`.

## Batteries-included middleware
### The basics
Both local and production outputs include `cors()` and `compression()` Express libraries and a basic request logger. Logging can be disabled by setting the following environment variable: `LOGGING=disabled`

### Error handling
All routes are wrapped in an error handler to ensure the service returns an error http response if possible. Note: You should still wrap your controllers in a `try/catch` to ensure the best handling of this.

### Tenant validation 
All routes registered with the main function will sit under a `/:tenant` route. This is used by the `middleware/validateOmneoTenant` function to check for tenant configuration in `process.env`. By default, only `[tenant]_omneo_token` and `[tenant]_omneo_secret` should be stored in env - All other configurations should be stored elsewhere, such as Omneo Tenant Fields or a DB.

This middleware also adds the `omneo_token` and `omneo_secret` to `globals.tenant`, along with `omneo_url`, matching the following format:
`[tenant].getomneo.com`

### Omneo Plugin validation 
All routes will be valiadated and make sure plugin has been configured in omneo. It's handled by `middleware/validateOmneoPluginConfig`. Plugin handle needs to match the PluginNamespace and "Enabled"
field is required and needs to be true for successful validation. This requirement is valid for version >= 1.0.0.

### Webhook validation
This library includes middleware for Omneo webhook validation at `middleware/validateOmneoWebhook`
This middleware accepts an array of allowed events and will automatically check against the headers Omneo uses for webhook HMAC signatures and event.

In addition, an abstracted `middleware/validateWebhook` is availble for services that use similar methods for webhook validation:
``` js
validateWebhook(
    events, // Array of allowed event strings eg. ['profile.created']
    headerSignature, // Header key for the HMAC signature eg. 'x-omneo-hmac-sha256'
    headerEvent, // Header key for the webhook event eg. 'x-omneo-event'
    hmacSecret, // Secret key used to decode. For Omneo we use the OMNEO_SECRET
    alg // HMAC algorithm to use when decoding
)
```
## A handful of handy utilities
To help make your development life easier, this library includes a small set of useful utility functions.

### Fetch
Our own wrapper for the popular `node-fetch` library. This includes a number of helpers to construct your requsts, as well as response parsing, so you don't have to separately run `response.json()`.

Note that `config.global` is only available after the `validateOmneoTenant` middleware has run, not at initialization. This means that any function relying on these will need to be initialized after that.

```js
// omneo.js
const {Fetch} = require('@omneo/cf/utilities');

export.default = () => new Fetch(`https://api.${global.config.omneo_url}/api/v3`, {
    headers: {
        Authorization: `Bearer ${global.config.omneo_token}`
    }
})
```
```js
// route.js
const omneoClient = require('./omneo');

const route = async (req, res) => {
    const omneo = omneoClient();
    // Searching profiles. get(url, params)  
    const search = await omneo.get('/profiles', {
        "filter[email]":"richard@piedpiper.com"
    })

    // Creating a profile: post(url, body)
    const create = await omneo.post('/profiles', {
        first_name: "Gavin",
        last_name: "Belson",
        email: "gavin@hooli.com"
    })

    res.send()
}
```

### Pluck
Dives through a js/json object, by key, to find the value. Returns the value if found, or `null` where not available. Uses `memoizee` to memoize the value for subsequent attempts.

```js
const {pluck} = require('@omneo/cf/utilities');
const data = {
    "some":{
        "nested":{
            "value":"Success value"
        }
    }
}

pluck(data, 'some.nested.value') // Returns "Success value"
pluck(data, 'some.other.value') // Returns null"
```

### Pluck By
Iterates through an array of objects, to find matching key:value pair and return that object. Can take a 3rd argument to find the array in an object first, using `pluck`; 

```js
const {pluckBy} = require('@omneo/cf/utilities');
const {
    data: {
        profile: {
            "first_name":"Erlich",
            "last_name":"Bachman",
            "email":"erlich@bachmanity.com",
            "attributes":{...},
            "identities":[
                {
                    "handle":"shopify",
                    "identifier":"SHOP12345"
                },
                {
                    "handle":"zendesk",
                    "identifier":"ZEN67890"
                }
            ]
        }
    }
}

pluckBy(data.profile.identities, 'identifier', 'ZEN67890') // Returns {"handle":"zendesk","identifier":"ZEN67890"}
pluckBy(data.profile.identities, 'identifier', 'ZEN12345') // Returns null
pluckBy(profile, 'handle', 'shopify', 'profile.identities') // Returns {"handle":"shopify","identifier":"SHOP12345"}
```

### ErrorHandler
Error constructor used to append additional details to the `handleErrors` middleware.
Takes `status`, `message` and a json `payload`
```js
const route = require('express').Router();
const {ErrorHandler} = require('@omneo/cf/utilities');

route.post('/', (req, res, next)=>{
    const profile = req.body
    try{
        if(!profile.email){
            throw new ErrorHandler(400, "Profile is incomplete", {
                errors:{
                    email: "This field is required"
                }
            })
        }
        return profile
    }catch(e){
        next(e);
    }
})

module.exports = route
```