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

RAINCATCH-1131 - wfm-users module #86

Merged
merged 21 commits into from
Aug 21, 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 @@ -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