Skip to content

Commit

Permalink
[Refactor] Users service refactor
Browse files Browse the repository at this point in the history
Cleaned up and reorganized the users service.
References #61.
  • Loading branch information
angel-penchev committed Mar 23, 2021
1 parent bbb7df9 commit e52349e
Show file tree
Hide file tree
Showing 40 changed files with 23,359 additions and 4,312 deletions.
24 changes: 24 additions & 0 deletions server/core/@types/apis.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {
VerificationInstance,
} from 'twilio/lib/rest/verify/v2/service/verification';
import {
VerificationCheckInstance,
} from 'twilio/lib/rest/verify/v2/service/verificationCheck';

/**
* SMS API object structure.
*
* @export
* @interface SMSApi
*/
export interface SMSApi {
sendCode(
phoneNumber: string,
channel: 'sms' | 'call',
): Promise<VerificationInstance>;

verifyCode(
phoneNumber: string,
code: string,
): Promise<VerificationCheckInstance>;
}
29 changes: 29 additions & 0 deletions server/core/@types/controllers.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {ParamsDictionary} from 'express-serve-static-core';

/**
* Controller Request object structure.
*
* @exports
* @interface ControllerRequest
*/
export interface ControllerRequest {
headers: any;
body: any;
params?: ParamsDictionary;
ip?: string;
method?: string;
path?: string;
query?: any;
}

/**
* Controller Response object structure.
*
* @exports
* @interface ControllerResponse
*/
export interface ControllerResponse {
headers: any;
body: any;
statusCode: number;
}
32 changes: 32 additions & 0 deletions server/core/@types/entity-exports.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { LocationModel, PaymentCardModel } from "./models";

/**
* Source export object structure.
*
* @exports
* @interface SourceExport
*/
export interface SourceExport {
getIp(): string;
getBrowser(): string;
getReferrer(): string;
}

/**
* ValidatorExport object structure.
*
* @exports
* @interface ValidatorExport
*/
export interface Validator {
validateIdentifier(identifier: string): void;
validateLocation(location: Location): void;
validateRoute(
homeLocation: LocationModel,
senderLocation: LocationModel,
receiverLocation: LocationModel,
maxDistance: number,
): number;
validatePaymentCard(paymentCard: PaymentCardModel): void;
}

1 change: 1 addition & 0 deletions server/core/@types/ip-regex.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare module 'ip-regex';
73 changes: 73 additions & 0 deletions server/core/@types/models.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* OrderModel object structure.
*
* @interface OrderModel
*/
export interface OrderModel {
id?: string;
sender: PersonModel;
receiver: PersonModel;
paymentCardToken: String;
source: SourceModel;
createdOn?: number;
}

/**
* Sender/Receiver object structure.
*
* @exports
* @interface PersonModel
*/
export interface PersonModel {
id: string;
location: LocationModel;
}

/**
* LocationModel object structure.
*
* @exports
* @interface LocationModel
*/
export interface LocationModel {
latitude: number;
longitude: number;
}

/**
* Request Source object structure.
*
* @exports
* @interface SourceModel
*/
export interface SourceModel {
ip: string;
browser: string;
referrer: string;
}

/**
* PaymentCardModel object structure.
*
* @exports
* @interface PaymentCardModel
*/
export interface PaymentCardModel {
number: string;
date: string;
cvc: string;
}

/**
* UserModel object database schema.
*
* @export
* @interface UserModel
*/
export interface UserModel {
id?: string;
token: string;
phoneNumber: string;
createdAt?: string;
}

46 changes: 46 additions & 0 deletions server/core/@types/shared-queue.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {Connection} from 'amqplib';
import * as Bluebird from 'bluebird';
import {Options} from 'amqplib/properties';

/**
* Queue Library object structure.
*
* @exports
* @interface QueueLibrary
*/
export interface QueueLibrary {
connect(
url: string | Options.Connect,
socketOptions?: any
): Bluebird<Connection>;
}

/**
* Queue message object structure.
*
* @export
* @interface QueueMessage
* @template T
*/
export interface QueueMessage<T extends Object> {
subject: string;
body: T;
}

/**
* Shared Queue object structure.
*
* @exports
* @interface SharedQueue
*/
export interface SharedQueue {
emit <T extends Object>(
queueNames: Array<string>,
message: QueueMessage<T>
): Promise<void>;

listen(
queueName: string,
callback: (message: QueueMessage<any>) => any,
): Promise<void>;
}
44 changes: 44 additions & 0 deletions server/core/controllers/express-callback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import {Request, RequestHandler, Response} from 'express';
import {ControllerResponse} from '../@types/controllers';

/**
* Creates an express callback from a given controller.
*
* @export
* @param {Function} controller - express controller function
* @return {RequestHandler} - express response handler
*/
export default function makeExpressCallback(
controller: Function,
): RequestHandler {
return (req: Request, res: Response) => {
const httpRequest = {
body: req.body,
query: req.query,
params: req.params,
ip: req.ip,
method: req.method,
path: req.path,
headers: {
'Content-Type': req.get('Content-Type'),
'Referer': req.get('referer'),
'User-Agent': req.get('User-Agent'),
},
};
controller(httpRequest)
.then((controllerResponse: ControllerResponse) => {
if (controllerResponse.headers) {
res.set(controllerResponse.headers);
}

res.type('json');

res.status(
controllerResponse.statusCode,
).send(controllerResponse.body);
})
.catch((err: Error) => res.status(500).send({
error: `An unhandled error occurred: ${err}.`,
}));
};
};
17 changes: 17 additions & 0 deletions server/core/controllers/not-found.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {ControllerResponse} from '../@types/controllers';

/**
* Express controller for non-existent endpoints.
*
* @export
* @return {Promise<ControllerResponse>} - 404 not found response
*/
export default async function notFound(): Promise<ControllerResponse> {
return {
headers: {
'Content-Type': 'application/json',
},
body: {error: 'Not found.'},
statusCode: 404,
};
}
10 changes: 10 additions & 0 deletions server/core/shared-queue/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import amqplib from 'amqplib';
import config from 'config';
import buildSharedQueue from './shared-queue';

const sharedQueue = buildSharedQueue({
queueLibrary: amqplib,
queueUrl: config.get('RABBITMQ_URL'),
});

export default sharedQueue;
50 changes: 50 additions & 0 deletions server/core/shared-queue/shared-queue.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import config from 'config';
import amqp from 'mock-amqplib';
import {SharedQueue} from '../@types/queue';
import buildSharedQueue from './shared-queue';
describe('shared-queue', () => {
let sharedQueue: SharedQueue;

beforeEach(async () => {
sharedQueue = buildSharedQueue({
queueLibrary: amqp,
queueUrl: config.get('RABBITMQ_URL'),
});
});

it('must emit event on a single queue', async () => {
const message = {subject: 'TEST_MSG', body: {message: 'i do stuff'}};
const queueName = 'test_queue';

// Asserting whether emitting to a single queue was successful
expect(async () => {
await sharedQueue.emit([queueName], message);
}).not.toThrow();
});

it('must get emitted messages when listening', async () => {
const message = {subject: 'TEST_MSG', body: {message: 'i do stuff'}};
const queueName = 'test_queue';

// Asserting whether emitting to a single queue was successful
expect(async () => {
await sharedQueue.emit([queueName], message);
}).not.toThrow();

// Defining a callback function and a spy bound to it which will be used to
// verify the callback was called exactly once (only 1 message in queue)
let response: object;
const callbackObject = {
callback: (result: object) => response = result,
};
const spy = jest.spyOn(callbackObject, 'callback');

// Starting to listen for messages
await sharedQueue.listen(
queueName,
callbackObject.callback,
);
expect(response).toEqual(message);
expect(spy).toHaveBeenCalledTimes(1);
});
});
Loading

0 comments on commit e52349e

Please sign in to comment.