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

Commit

Permalink
RAINCATCH-1131 - wfm-users module (#86)
Browse files Browse the repository at this point in the history
* Git fixes for broken config

* Documentation update

* Static backbone for wfm-user-module

* wfm user implementation

* Removing grunt

* Logger improvements

* wfm-rest-api docs fixes

* Improved error handling for api

* Move users to demo-data

* Demo application integration

* Unit test coverage game

* Ignore case for demo data

* Improvements for error handling

* Documentation

* Api pattern - wrap response in object

* Example application

* Add types to allow stricter compiler options (#2)

* Adding get handler

* Docs improvements

* Update types to lerna hoisted versions

* Fix compile error
  • Loading branch information
wtrocki committed Aug 21, 2017
1 parent c63c5bf commit bf05c4e
Show file tree
Hide file tree
Showing 35 changed files with 602 additions and 109 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ cache:
- cloud/passportauth/node_modules
- cloud/datasync/node_modules
- cloud/wfm-rest-api/node_modules
- cloud/wfm-user/node_modules
- client/logger/node_modules
- client/wfm/node_modules
- client/datasync-client/node_modules
1 change: 1 addition & 0 deletions client/wfm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"@types/bluebird": "^3.5.8",
"@types/lodash": "^4.14.73",
"@types/mocha": "^2.2.41",
"@types/shortid": "0.0.29",
"@types/valid-url": "^1.0.2",
"@types/node": "^8.0.7",
"@types/chai": "^4.0.3",
Expand Down
2 changes: 1 addition & 1 deletion client/wfm/src/service/ResultService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ import { WorkOrderResult } from '../result/WorkOrderResult';
import { DataService } from './DataService';

export interface ResultService extends DataService<WorkOrderResult> {
readByWorkorder(workorderId): Promise<WorkOrderResult | undefined>;
readByWorkorder(workorderId: string): Promise<WorkOrderResult | undefined>;
}
2 changes: 1 addition & 1 deletion cloud/datasync/src/web/SyncWebExpress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class SyncExpressMiddleware {
*/
public createSyncExpressRouter() {
const apiURI = path.join(this.prefix + '/:datasetId');
logger.debug('Creating sync endpoint', apiURI);
logger.debug('Creating sync endpoint');
const syncRoute = this.router.route(apiURI);

/**
Expand Down
8 changes: 4 additions & 4 deletions cloud/logger/src/Logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,28 @@ export interface Logger {
* @param message - message to log
* @param options - object to include in log
*/
debug(message: string, options?: any);
debug(message: string, options?: any): void;

/**
* Log on info level
* @param message - message to log
* @param options - object to include in log
*/
info(message: string, options?: any);
info(message: string, options?: any): void;

/**
* Log on warn level
* @param message - message to log
* @param options - object to include in log
*/
warn(message: string, options?: any);
warn(message: string, options?: any): void;

/**
* Log on error level
* @param message - message to log
* @param options - object to include in log
*/
error(message: string, options?: any);
error(message: string, options?: any): void;
}

/**
Expand Down
12 changes: 6 additions & 6 deletions cloud/wfm-rest-api/src/ApiConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@
*/
export interface ApiConfig {
/** Root path for the WorkOrder http endpoints */
workorderApiName?: string;
workorderApiName: string;
/** Root path for the WorkFlow http endpoints */
workflowApiName?: string;
workflowApiName: string;
/** Root path for the Result http endpoints */
resultApiName?: string;
resultApiName: string;
/** Collection name to make database query for workorder */
workorderCollectionName?: string;
workorderCollectionName: string;
/** Collection name to make database query for workflow */
workflowCollectionName?: string;
workflowCollectionName: string;
/** Collection name to make database query for result */
resultCollectionName?: string;
resultCollectionName: string;
}

/**
Expand Down
8 changes: 5 additions & 3 deletions cloud/wfm-rest-api/src/WfmRestApi.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@

import { getLogger } from '@raincatcher/logger';
import { WorkFlow, WorkOrder, WorkOrderResult } from '@raincatcher/wfm';
import * as Promise from 'bluebird';
import * as express from 'express';
Expand All @@ -12,13 +13,13 @@ import { MongoDbRepository } from './impl/MongoDbRepository';
* RESTfull api handlers for Workorders, Workflows and Results (WFM objects)
*/
export class WfmRestApi {
private config;
private config: ApiConfig;
private workorderService: MongoDbRepository<WorkOrder>;
private workflowService: MongoDbRepository<WorkFlow>;
private resultService: MongoDbRepository<WorkOrderResult>;

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

Expand All @@ -28,6 +29,7 @@ export class WfmRestApi {
public createWFMRouter() {
const router: express.Router = express.Router();
const { workorderApiName, workflowApiName, resultApiName } = this.config;
getLogger().info('WFM web api initialization');
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());
Expand Down
10 changes: 9 additions & 1 deletion cloud/wfm-rest-api/src/data-api/ApiError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@
* 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) {

/**
* @param code - Error code used to determine error type
* @param message - human redable message
* @param statusCode - status code used to setup
* @param originalError - can contain original error that was returned
*/
constructor(public readonly code: string, public readonly message: string, public statusCode: number = 500,
public originalError?: Error) {
super(message);
}
}
20 changes: 8 additions & 12 deletions cloud/wfm-rest-api/src/impl/ApiController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,23 +107,13 @@ export class ApiController<T> {
return this.repository.update(data);
}

public buildExpressHandler(handlerFn: (this: this, req: Request) => Bluebird<T | T[] | undefined>): RequestHandler {
return (req, res, next) => handlerFn.bind(this)(req)
.then(data => data ? res.json(data) : res.status(204).end())
.catch(next);
}

/**
* Build all CRUD routes for `apiPrefix`
* Build all CRUD routes
*
* @param router - router used to attach api
* @param repository - repository to retrieve data
* @param apiPrefix - prefix to mount api in URI path. For example `/prefix/:id`
* @return router containing all routes
*/
public buildRouter(): Router {
const router = express.Router();
getLogger().info('REST api initialization');

router.route('/')
.get(this.buildExpressHandler(this.listHandler))
.post(this.buildExpressHandler(this.postHandler));
Expand All @@ -134,4 +124,10 @@ export class ApiController<T> {

return router;
}

private buildExpressHandler(handlerFn: (this: this, req: Request) => Bluebird<T | T[] | undefined>): RequestHandler {
return (req, res, next) => handlerFn.bind(this)(req)
.then((data: T | T[] | undefined) => data ? res.json(data) : res.status(204).end())
.catch(next);
}
}
27 changes: 20 additions & 7 deletions cloud/wfm-rest-api/src/impl/MongoDbRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,21 @@ export class MongoDbRepository<T extends {
if (!this.db) {
return Bluebird.reject(dbError);
}
const cursor = this.collection.find(filter);
return Bluebird.resolve(cursor.count(false, filter))
.then(count => defaultPaginationEngine.buildPageResponse(request, cursor, count));
try {
const cursor = this.collection.find(filter);
return Bluebird.resolve(cursor.count(false, filter))
.then(count => defaultPaginationEngine.buildPageResponse(request, cursor, count))
.catch(this.handleError);
} catch (err) {
return this.handleError(err);
}
}

public get(id: string): Bluebird<T> {
if (!this.db) {
return Bluebird.reject(dbError);
}
return Bluebird.resolve(this.collection.findOne({ id }));
return Bluebird.resolve(this.collection.findOne({ id })).catch(this.handleError);
}

public create(object: T): Bluebird<T> {
Expand All @@ -50,7 +55,8 @@ export class MongoDbRepository<T extends {
return Bluebird.reject(dbError);
}
return Bluebird.resolve(this.collection.insertOne(object))
.then(() => this.get(object.id));
.then(() => this.get(object.id))
.catch(this.handleError);
}

public update(object: T): Bluebird<T> {
Expand All @@ -61,15 +67,17 @@ export class MongoDbRepository<T extends {
delete object._id;
const id = object.id;
return Bluebird.resolve(this.collection.updateOne({ id }, object))
.then(() => this.get(object.id));
.then(() => this.get(object.id))
.catch(this.handleError);
}

public delete(id: string): Bluebird<boolean> {
if (!this.db) {
return Bluebird.reject(dbError);
}
return Bluebird.resolve(this.collection.deleteOne({ id }))
.then(response => response.result.ok === 1);
.then(response => response.result.ok === 1)
.catch(this.handleError);
}

/**
Expand All @@ -79,4 +87,9 @@ export class MongoDbRepository<T extends {
this.db = db;
this.collection = this.db.collection<T>(this.collectionName);
}

private handleError(err: Error) {
const apiError = new ApiError(errorCodes.DB_ERROR, 'Cannot perform mongo query', 500, err);
return Bluebird.reject(apiError);
}
}
6 changes: 6 additions & 0 deletions cloud/wfm-user/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# generated code
src/**/*.js
src/**/*.map
test/**/*.js
test/**/*.map
coverage_report/
58 changes: 58 additions & 0 deletions cloud/wfm-user/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# wfm-user

Module responsible for user operations required by all WFM modules.

## RESTfull API

### Filter

> GET /?filter=name&limit=10
Retruns JSON array containing users
```json
{
"users": []
}
```

#### Example

http://localhost:8001/api/users?filter=a&limit=1

> Note: Default limit is 10
#### Errors

Missing filter: `{status: 400 , code: 'InvalidFilter'}`

### Get user by id

> GET /:id
Gets user by id

#### Example

http://localhost:8001/api/users/fhj3nf

#### Errors

Missing id `{status: 400 , code: 'InvalidID'}`

## Implementing UserRepository

In order for module to be able to fetch user data any implementations
need to implement user repository class.

See demo application integration or [example application](./example) for more details.

## Relation to security module

The main purpose of this module is to allow administrators to retrieve and manage mobile users.
This data may come from different datasource than data from security module.
This module should not be used to fech any user related details like user profile etc.





9 changes: 9 additions & 0 deletions cloud/wfm-user/example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
## wfm-user example

### Running example

ts-node example/index.ts

To test example execute in your console

curl http://localhost:3000/api/users?filter=example
39 changes: 39 additions & 0 deletions cloud/wfm-user/example/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { getLogger } from '@raincatcher/logger';
import * as Bluebird from 'bluebird';
import * as express from 'express';
import * as path from 'path';
import { User, UserController, UsersRepository } from '../src/index';

const app = express();

const exampleUser: User = { id: 1, name: 'Example User', username: 'example' };

/**
* Simplified example UsersRepository.
* Implementation can fetch users from databases, LDAP etc.
*/
class ExampleRepository implements UsersRepository {
public getUser(id: string | number): Bluebird<User> {
return Bluebird.resolve(exampleUser);
}
public retrieveUsers(filter: string, limit: number): Bluebird<User[]> {
return Bluebird.resolve([exampleUser]);
}
}

// Create repository
const repository = new ExampleRepository();
// Create api
const api = new UserController(repository);

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

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');
});
Loading

0 comments on commit bf05c4e

Please sign in to comment.