Skip to content

Commit

Permalink
chore: list + format fix and restructure
Browse files Browse the repository at this point in the history
  • Loading branch information
shihabmridha committed Jun 26, 2023
1 parent 2eedf77 commit b365a1e
Show file tree
Hide file tree
Showing 26 changed files with 351 additions and 219 deletions.
20 changes: 20 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2020
},
"plugins": [
"@typescript-eslint/eslint-plugin"
],
"extends": [
"plugin:@typescript-eslint/recommended",
"prettier/@typescript-eslint",
"plugin:prettier/recommended"
],
"rules": {
"@typescript-eslint/interface-name-prefix": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-explicit-any": "off"
}
}
25 changes: 0 additions & 25 deletions .eslintrc.js

This file was deleted.

9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
"start": "node ./build/src/app.js",
"dev": "nodemon ./build/src/app.js",
"build": "tsc -p .",
"production": "NODE_ENV=production node ./build/src/app.js",
"test": "LOG_LEVEL=debug jest --forceExit --silent ./build/test",
"coverage": "LOG_LEVEL=debug jest --coverage --forceExit ./build/test/api",
"build:watch": "tsc --watch",
"production": "cross-env NODE_ENV=production node ./build/src/app.js",
"test": "cross-env LOG_LEVEL=debug jest --forceExit --silent ./build/test",
"coverage": "cross-env LOG_LEVEL=debug jest --coverage --forceExit ./build/test/api",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix"
},
"author": "Shihab Mridha",
Expand Down Expand Up @@ -40,12 +41,14 @@
"@types/validator": "12.0.1",
"@typescript-eslint/eslint-plugin": "^5.59.9",
"@typescript-eslint/parser": "^5.59.9",
"cross-env": "^7.0.3",
"eslint": "^8.42.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-prettier": "^4.2.1",
"faker": "4.1.0",
"jest": "^29.5.0",
"jest-junit-reporter": "^1.1.0",
"nodemon": "^2.0.22",
"prettier": "^2.8.8",
"source-map-support": "0.5.21",
"supertest": "^6.3.3",
Expand Down
17 changes: 10 additions & 7 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import * as express from 'express';
import * as compress from 'compression';
import app from './server';
import * as cors from 'cors';
import routes from './routes';
import errorHandler from './common/errors/error.handler';
import logger from './logger';
import initDB from './database';
import logger from './common/logger';
import initDB from './common/database';
import container from './inversify';
import ApplicationRouter from './router';

/**
* This is a bootstrap function
Expand All @@ -23,8 +24,8 @@ async function bootstrap() {
});
}

app.disable('x-powered-by'); // Hide information
app.use(compress()); // Compress
app.disable('x-powered-by');
app.use(compress());

// Enable middleware/whatever only in Production
if (process.env.NODE_ENV === 'production') {
Expand All @@ -38,7 +39,7 @@ async function bootstrap() {
app.use(cors());

/**
* Configure mongoose
* Configure database
**/
if (!initDB.isDbConnected()) {
await initDB.connect();
Expand All @@ -58,7 +59,9 @@ async function bootstrap() {
/**
* Configure routes
*/
routes(app);
// Let inversify resolve the dependency
const router = container.get<ApplicationRouter>(ApplicationRouter);
router.register(app);

/**
* Configure error handler
Expand Down
4 changes: 2 additions & 2 deletions src/constants.ts → src/common/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
enum StaticStringKeys {
enum Constants {
INVALID_REQUEST = 'Invalid request',
INVALID_CREDENTIAL = 'Invalid credential',
INVALID_ACCESS_TOKEN = 'Invalid access token',
Expand All @@ -14,4 +14,4 @@ enum StaticStringKeys {
REPOSITORY_ERROR_INVALID_ID = 'Invalid id',
}

export default StaticStringKeys;
export default Constants;
54 changes: 32 additions & 22 deletions src/database.ts → src/common/database.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { MongoClient, Db, Collection } from 'mongodb';
import { MongoClient, Db, Collection, ServerApiVersion } from 'mongodb';
import logger from './logger';
import { ApplicationError } from './errors/app.errors';

/**
* All the methods and properties mentioned in the following class is
* specific to MongoDB. You should make necessary changes to support
* the database you want to use.
* the database/orm you want to use.
*/

class Database {
private password: string;

Expand All @@ -21,10 +21,26 @@ class Database {
private databaseInstance: Db;

constructor() {
this.password = process.env.DB_PWD || '';
this.user = process.env.DB_USER || '';
this.host = process.env.DB_HOST || 'localhost:27017';
this.dbName = process.env.DB_NAME || 'my-db';
this.password = process.env.DB_PWD;
this.user = process.env.DB_USER;
this.host = process.env.DB_HOST;
this.dbName = process.env.DB_NAME;

if (!this.password) {
throw new Error('Database password not found');
}

if (!this.user) {
throw new Error('Database user not found');
}

if (!this.host) {
throw new Error('Database host not found');
}

if (!this.dbName) {
throw new Error('Database name not found');
}
}

public async connect(): Promise<void> {
Expand All @@ -41,11 +57,14 @@ class Database {
logger.debug(`Database connection string: ${connectionString}`);

const client = new MongoClient(connectionString, {
poolSize: 50,
maxPoolSize: 50,
connectTimeoutMS: TWO_MINUTES_IN_MS,
socketTimeoutMS: ONE_DAY_IN_MS,
useNewUrlParser: true,
useUnifiedTopology: true,
serverApi: {
version: ServerApiVersion.v1,
strict: true,
deprecationErrors: true,
}
});

this.dbClient = await client.connect();
Expand All @@ -55,7 +74,7 @@ class Database {
}

public async disconnect() {
if (this.dbClient.isConnected()) {
if (this.dbClient) {
logger.info(`Disconnected from ${this.host}/${this.dbName}`);
await this.dbClient.close();
}
Expand All @@ -80,16 +99,7 @@ class Database {
* Customize as needed for your database.
*/
private getConnectionString() {
const env = process.env.NODE_ENV;
if (env === 'test' && !process.env.DB_NAME) {
this.dbName += '_test';
}

if (env !== 'localhost' && this.user && this.password) {
return `mongodb+srv://${this.user}:${this.password}@${this.host}/${this.dbName}`;
}

return `mongodb+srv://${this.host}/${this.dbName}`;
return `mongodb://${this.user}:${this.password}@${this.host}/${this.dbName}`;
}

public getDbHost() {
Expand All @@ -109,7 +119,7 @@ class Database {
}

public isDbConnected() {
return this.dbClient && this.dbClient.isConnected();
return Boolean(this.dbClient);
}
}

Expand Down
10 changes: 5 additions & 5 deletions src/common/errors/app.errors.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import StaticStringKeys from '../../constants';
import Constants from '../constants';

export class ApplicationError extends Error {
public code = null;
Expand Down Expand Up @@ -48,23 +48,23 @@ export class InternalError extends ApplicationError {

export class InvalidCredentialError extends BadRequestError {
constructor(...args: any) {
super(StaticStringKeys.INVALID_CREDENTIAL, args);
super(Constants.INVALID_CREDENTIAL, args);
}
}

export class InvalidTokenError extends BadRequestError {
constructor(type: string, ...args: any) {
if (type === 'ACCESS') {
super(StaticStringKeys.INVALID_ACCESS_TOKEN, args);
super(Constants.INVALID_ACCESS_TOKEN, args);
} else {
super(StaticStringKeys.INVALID_REFRESH_TOKEN, args);
super(Constants.INVALID_REFRESH_TOKEN, args);
}
}
}

export class InvalidIdError extends BadRequestError {
constructor(...args: any) {
super(StaticStringKeys.REPOSITORY_ERROR_INVALID_ID, args);
super(Constants.REPOSITORY_ERROR_INVALID_ID, args);
}
}

Expand Down
6 changes: 1 addition & 5 deletions src/common/errors/error.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,9 @@ import {
} from 'express';
import { NotFoundError, ApplicationError } from './app.errors';
import { MongoError } from 'mongodb';
import log from '../../logger';
import log from '../logger';

export default function (app: Application) {
/**
* Handle errors
*/

// If you are lost
app.use(() => {
throw new NotFoundError('You are lost');
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion src/repository.ts → src/common/repository.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { injectable, unmanaged } from 'inversify';
import { Collection, Filter, ObjectId } from 'mongodb';
import db from './database';
import { getValidObjectId } from './common/utils/utils';
import { getValidObjectId } from './utils/utils';

/**
* Fields you want to select. For mongodb it is a key-value pair.
Expand Down
14 changes: 6 additions & 8 deletions src/common/utils/asyncWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,14 @@ import {
} from 'express';

// Wraps async functions, catching all errors and sending them forward to express error handler
export default function asyncWrap(controller: CallableFunction) {
return async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
) => {
const asyncWrap = (controller: CallableFunction) => {
return async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
try {
await controller(req, res, next);
} catch (e) {
next(e);
} catch (error) {
next(error);
}
};
}

export default asyncWrap;
6 changes: 0 additions & 6 deletions src/common/utils/utils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
import { ObjectId } from 'mongodb';
import * as fs from 'fs';
import * as utils from 'util';
import { InvalidIdError } from '../errors/app.errors';

// Promisify some utility functions
export const exists = utils.promisify(fs.exists);
export const mkdir = utils.promisify(fs.mkdir);

export function getValidObjectId(id: string | ObjectId) {
if (!ObjectId.isValid(id)) {
throw new InvalidIdError();
Expand Down
16 changes: 10 additions & 6 deletions src/inversify.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { Container } from 'inversify';
import { TYPES } from './types';
import UserRepository, {
IUserRepository,
} from './repositories/user.repository';
import UserService, { IUserService } from './services/user.service';
import UserController from './controllers/user.controller';
import UserRepository, { IUserRepository } from './user/user.repository';
import UserService from './user/user.service';
import UserController from './user/user.controller';
import IUserService from './user/user.service.interface';
import ApplicationRouter from './router';

const container = new Container({ defaultScope: 'Singleton' });
container.bind(UserController).to(UserController);
// Like other dependencies we do not resolve ApplicationRouter via `TYPES`.
// We get the instance of the class only in app.ts file during bootstrap.
container.bind(ApplicationRouter).to(ApplicationRouter);

container.bind<UserController>(TYPES.UserController).to(UserController);
container.bind<IUserRepository>(TYPES.UserRepository).to(UserRepository);
container.bind<IUserService>(TYPES.UserService).to(UserService);

Expand Down
16 changes: 16 additions & 0 deletions src/router.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Application } from 'express';
import { injectable, inject } from 'inversify';
import asyncWrap from './common/utils/asyncWrapper';
import UserController from './user/user.controller';
import { TYPES } from './types';

@injectable()
export default class ApplicationRouter {
@inject(TYPES.UserController) private userController: UserController;

public register(app: Application) {
app.get('/users', asyncWrap(this.userController.find));
app.get('/users/:id', asyncWrap(this.userController.get));
app.post('/users', asyncWrap(this.userController.create));
}
}
25 changes: 0 additions & 25 deletions src/routes.ts

This file was deleted.

Loading

0 comments on commit b365a1e

Please sign in to comment.