Skip to content
This repository has been archived by the owner on Apr 17, 2023. It is now read-only.

WFM api framework - HTTP RESTfull api for WFM (ex-webapi) #75

Merged
merged 26 commits into from
Aug 14, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ cache:
- cloud/logger/node_modules
- cloud/passportauth/node_modules
- cloud/datasync/node_modules
- cloud/wfm-rest-api/node_modules
- client/logger/node_modules
- client/wfm/node_modules
- client/datasync-client/node_modules
4 changes: 4 additions & 0 deletions cloud/wfm-rest-api/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# generated code
/**/*.map
/**/*.js
coverage_report/
113 changes: 113 additions & 0 deletions cloud/wfm-rest-api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# RainCatcher Api module

Module used to expose express based api for WFM objects.

### WFM specific implementations

Following api is being exposed:

- `/workorders`
- `/workflows`
- `/results`

This api is being added to new express router and it can be applied to any existing express based application
as follows:

```typescript
// Create api
const api = new WfmRestApi();

// Mount api into path
app.use('/api', api.createWFMRouter());
```

Api requires mongodb connection that needs to be setup as separate step

```typescript
api.setDb(db);
```
See demo application integration or [example application](./example) for more details.

### Custom database integrations

Custom database integrations are possible thanks to `CrudRepository` interface.
See Api documentation for more details.

## Rest API

Module provides a way to dynamically create API for different business objects.
Created api will use simplified implementations for typical create, read, delete and update operations. It's not recomended to use `wfm-rest-api` outside the WFM framework. Please use database driver or ORM framework or your choice.

## Rest API definitions

Definitions apply to every object exposed by this API. Placeholder `{object}` can be replaced by `workflow`, `workorder` and `result`.

### Retrieve list

> GET {object}/

##### Pagination
Supports pagination and sorting by providing additional query parameters:

- `page` page number
- `size` number of elements to return
- `sortField` sorting field
- `order` -1 for DESC and 1 for ASC

Example `/workorders?page=0&size=5&sortField=id&order=-1`

> **Note** - sorting parameters are optional. When missing default sorting values are applied (10 results)

##### Filtering

List can be filtered by providing json as `filter` query parameter or `filter` as request body.

`filter` - json object with specific field

For example `filter = { 'reviewer': 'Paolo'}`

> **Note** - Due to nature of the url filter needs to be encoded to be passed as url

### Retrieve specific object by id

> GET {object}/:objectId

Retrieve specific object by id

Example `/workorders/B1r71fOBr`

### Save object

> POST {object}/

### Update object

> PUT {object}/

### Delete object

> DELETE {object}/:objectId

### Error handling

In case of error express `next` callback is being called with `ApiError` instance.
Users should build their own middleware for global error handling in their application.

Api returns non 200 status in case of error.

`400` - For user input error (missing required field etc.)
`500` - For internal server errors

For every error `ApiError` object is being returned.
For example:

```typescript
{
"code": "InvalidID",
"message": "Provided id is invalid",
"statusCode": 400
}
```
Clients can adapt it to their preffered way of presenting errors to user (html,json etc.)

> **Note:** If you apply middleware security, additional `401` and `403` statuses may be returned
15 changes: 15 additions & 0 deletions cloud/wfm-rest-api/example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
## wfm-web-api example

### Requirements

Mongodb running on standard port

### Running example

ts-node example/index.ts

To test example try

curl http://localhost:3000/api/workorders

> **Note**: This example assumes that you have run the demo application that populated your database.
33 changes: 33 additions & 0 deletions cloud/wfm-rest-api/example/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { getLogger } from '@raincatcher/logger';
import * as express from 'express';
import { MongoClient } from 'mongodb';
import * as path from 'path';
import { WfmRestApi } from '../src/index';

const app = express();

// Create api
const api = new WfmRestApi();

// Mount api into path
app.use('/api', api.createWFMRouter());

// Use connect method to connect to the server
const url = 'mongodb://localhost:27017/raincatcher';
MongoClient.connect(url, function(err, db) {
if (db) {
api.setDb(db);
} else {
console.info('MongoDb not connected', err);
process.exit(1);
}
});

app.use(function(err: any, req: express.Request, res: express.Response, next: any) {
getLogger().error(err);
res.status(500).send(err);
});

app.listen(3000, function() {
getLogger().info('Example auth app listening on port 3000');
});
68 changes: 68 additions & 0 deletions cloud/wfm-rest-api/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{
"name": "@raincatcher/wfm-rest-api",
"version": "1.0.0",
"description": "Module used for building a RESTful api on top of the WFM solution",
"types": "src/index.ts",
"author": "feedhenry-raincatcher@redhat.com",
"license": "Apache-2.0",
"main": "src/",
"scripts": {
"clean": "del coverage_report src/*.js src/*.map src/**/*.js src/**/*.map test/**/*.js test/**/*.map",
"build": "tsc",
"start": "ts-node src/index.ts",
"test": "npm run clean && nyc mocha"
},
"nyc": {
"include": [
"src/**/*.ts"
],
"extension": [
".ts"
],
"require": [
"ts-node/register"
],
"reporter": [
"lcov",
"text"
],
"report-dir": "coverage_report",
"check-coverage": true,
"lines": 75,
"functions": 80,
"branches": 70
},
"dependencies": {
"@raincatcher/logger": "1.0.0",
"@raincatcher/wfm": "1.0.0",
"bluebird": "^3.5.0",
"express": "^4.15.3",
"lodash": "^4.17.4",
"mongodb": "^2.2.31",
"shortid": "^2.2.8"
},
"devDependencies": {
"@types/bluebird": "^3.5.5",
"@types/chai": "^4.0.3",
"@types/chai-as-promised": "0.0.31",
"@types/express": "^4.0.35",
"@types/lodash": "^4.14.72",
"@types/mocha": "^2.2.41",
"@types/mongodb": "^2.2.9",
"@types/proxyquire": "^1.3.27",
"@types/shortid": "0.0.29",
"@types/sinon": "^2.3.3",
"@types/sinon-express-mock": "^1.3.2",
"chai": "^4.1.1",
"chai-as-promised": "^7.1.1",
"del-cli": "^1.0.0",
"mocha": "^3.4.2",
"nyc": "^11.0.1",
"proxyquire": "^1.8.0",
"sinon": "^3.2.0",
"sinon-express-mock": "^1.3.1",
"source-map-support": "^0.4.15",
"ts-node": "^3.0.4",
"typescript": "^2.3.4"
}
}
31 changes: 31 additions & 0 deletions cloud/wfm-rest-api/src/ApiConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

/**
* Module configuration interface
* Holds all values that can be changed to modify module behavior
*/
export interface ApiConfig {
/** Root path for the WorkOrder http endpoints */
workorderApiName?: string;
/** Root path for the WorkFlow http endpoints */
workflowApiName?: string;
/** Root path for the Result http endpoints */
resultApiName?: string;
/** Collection name to make database query for workorder */
workorderCollectionName?: string;
/** Collection name to make database query for workflow */
workflowCollectionName?: string;
/** Collection name to make database query for result */
resultCollectionName?: string;
}

/**
* Default module configuration
*/
export const defaultConfiguration: ApiConfig = {
workorderApiName: 'workorders',
workflowApiName: 'workflows',
resultApiName: 'results',
workorderCollectionName: 'workorders',
workflowCollectionName: 'workflows',
resultCollectionName: 'result'
};
53 changes: 53 additions & 0 deletions cloud/wfm-rest-api/src/WfmRestApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@

import { WorkFlow, WorkOrder, WorkOrderResult } from '@raincatcher/wfm';
import * as Promise from 'bluebird';
import * as express from 'express';
import * as _ from 'lodash';
import { Db } from 'mongodb';
import { ApiConfig, defaultConfiguration } from './ApiConfig';
import { ApiController } from './impl/ApiController';
import { MongoDbRepository } from './impl/MongoDbRepository';

/**
* RESTfull api handlers for Workorders, Workflows and Results (WFM objects)
*/
export class WfmRestApi {
private config;
private workorderService: MongoDbRepository<WorkOrder>;
private workflowService: MongoDbRepository<WorkFlow>;
private resultService: MongoDbRepository<WorkOrderResult>;

constructor(userConfig?: ApiConfig) {
this.config = _.defaults(defaultConfiguration, userConfig);
this.createWFMServices();
}

/**
* Create new router for hosting WFM http api.
*/
public createWFMRouter() {
const router: express.Router = express.Router();
const { workorderApiName, workflowApiName, resultApiName } = this.config;
router.use(`/${workorderApiName}`, new ApiController<WorkOrder>(this.workorderService).buildRouter());
router.use(`/${workflowApiName}`, new ApiController<WorkFlow>(this.workflowService).buildRouter());
router.use(`/${resultApiName}`, new ApiController<WorkOrderResult>(this.resultService).buildRouter());
return router;
}

/**
* Inject database connection to services
*
* @param db - mongodb driver
*/
public setDb(db: Db) {
this.workorderService.setDb(db);
this.workflowService.setDb(db);
this.resultService.setDb(db);
}

protected createWFMServices() {
this.workorderService = new MongoDbRepository(this.config.workorderCollectionName);
this.workflowService = new MongoDbRepository(this.config.workflowCollectionName);
this.resultService = new MongoDbRepository(this.config.resultCollectionName);
}
}
9 changes: 9 additions & 0 deletions cloud/wfm-rest-api/src/data-api/ApiError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

/**
* Interface used to construct standardized response for api error handlers.
*/
export class ApiError extends Error {
constructor(public readonly code: string, public readonly message: string, public statusCode: number = 500) {
super(message);
}
}
Loading