@vasrefil/api-toolkit 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.nvmrc +1 -0
- package/README.md +5 -0
- package/package.json +52 -0
- package/src/.baseDir.ts +1 -0
- package/src/api-response/user.response.ts +30 -0
- package/src/app-middlewares/airbrake.ts +84 -0
- package/src/controllers/_root.control.ts +138 -0
- package/src/controllers/sample.control.ts +8 -0
- package/src/env.ts +15 -0
- package/src/helpers/currency-formatter.helper.ts +7 -0
- package/src/helpers/query.helper.ts +122 -0
- package/src/interfaces/interface.ts +28 -0
- package/src/interfaces/root-controller.interface.ts +21 -0
- package/src/interfaces/status.interface.ts +13 -0
- package/src/middlewares/auth.midware.ts +41 -0
- package/src/middlewares/validator.midware.ts +29 -0
- package/src/models/_config.ts +23 -0
- package/src/models/sample.model.ts +21 -0
- package/src/routes/index.route.ts +30 -0
- package/src/routes/sample.route.ts +51 -0
- package/src/server.ts +49 -0
- package/src/services/_root.service.ts +70 -0
- package/src/services/sample.service.ts +105 -0
- package/src/utilities/date.util.ts +68 -0
- package/src/utilities/logger.util.ts +52 -0
- package/src/utilities/token.util.ts +20 -0
- package/src/validations/sample.validator.ts +8 -0
- package/tsconfig.json +15 -0
package/.nvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
20.11.0
|
package/README.md
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vasrefil/api-toolkit",
|
|
3
|
+
"description": "This is Vasrefil API toolkit",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"author": "Sodiq Alabi",
|
|
6
|
+
"main": "server.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"start": "ts-node-dev src/server.ts",
|
|
9
|
+
"build": "tsc",
|
|
10
|
+
"serve": "node dist/server.js",
|
|
11
|
+
"dev": "nodemon --exec ts-node src/server.ts",
|
|
12
|
+
"run-ts-node": "ts-node-dev"
|
|
13
|
+
},
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": "20.11.0",
|
|
16
|
+
"npm": "10.2.4"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@airbrake/node": "^2.1.8",
|
|
20
|
+
"@types/jsonwebtoken": "^8.5.0",
|
|
21
|
+
"bcrypt": "^5.0.1",
|
|
22
|
+
"bcrypt-nodejs": "0.0.3",
|
|
23
|
+
"chalk": "^4.1.0",
|
|
24
|
+
"cors": "^2.8.5",
|
|
25
|
+
"date-fns": "^2.29.3",
|
|
26
|
+
"dotenv": "^8.2.0",
|
|
27
|
+
"errorhandler": "^1.5.1",
|
|
28
|
+
"express": "^4.18.3",
|
|
29
|
+
"helmet": "^4.2.0",
|
|
30
|
+
"ioredis": "^5.3.2",
|
|
31
|
+
"joi": "^17.3.0",
|
|
32
|
+
"json-stringify-safe": "^5.0.1",
|
|
33
|
+
"jsonwebtoken": "^8.5.1",
|
|
34
|
+
"mongoose": "^6.0.14",
|
|
35
|
+
"morgan": "^1.10.0",
|
|
36
|
+
"rate-limiter-flexible": "^2.4.2",
|
|
37
|
+
"swagger-ui-express": "^4.1.4",
|
|
38
|
+
"winston": "^3.3.3"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/body-parser": "1.19.0",
|
|
42
|
+
"@types/cors": "^2.8.17",
|
|
43
|
+
"@types/errorhandler": "1.5.0",
|
|
44
|
+
"@types/morgan": "^1.9.3",
|
|
45
|
+
"@types/node": "^14.14.7",
|
|
46
|
+
"cross-env": "^7.0.2",
|
|
47
|
+
"nodemon": "^3.1.0",
|
|
48
|
+
"ts-node": "^10.9.2",
|
|
49
|
+
"ts-node-dev": "^2.0.0",
|
|
50
|
+
"typescript": "^5.3.3"
|
|
51
|
+
}
|
|
52
|
+
}
|
package/src/.baseDir.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// grunt-ts creates this file to help TypeScript find the compilation root of your project. If you wish to get to stop creating it, specify a `rootDir` setting in the Gruntfile ts `options`. See https://github.com/TypeStrong/grunt-ts#rootdir for details. Note that `rootDir` goes under `options`, and is case-sensitive. This message was revised in grunt-ts v6. Note that `rootDir` requires TypeScript 1.5 or higher.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Status } from '../interfaces/status.interface';
|
|
2
|
+
export const UserApiResp = {
|
|
3
|
+
NO_AUTHORIZATION_HEADER: {
|
|
4
|
+
code: 'NAH0401', status: Status.UN_AUTHORIZED, message: 'Please specify authorization header'
|
|
5
|
+
},
|
|
6
|
+
NOT_AUTHORIZED: {
|
|
7
|
+
code: 'NA00401', status: Status.UN_AUTHORIZED, message: 'You are not authorized'
|
|
8
|
+
},
|
|
9
|
+
USER_NOT_FOUND: {
|
|
10
|
+
code: 'UNF0401', status: Status.UN_AUTHORIZED, message: 'User not found'
|
|
11
|
+
},
|
|
12
|
+
USER_DEACTIVATED: {
|
|
13
|
+
code: 'UD00401', status: Status.UN_AUTHORIZED, message: 'Your account has been deactivated'
|
|
14
|
+
},
|
|
15
|
+
UNVERIFIED_EMAIL: {
|
|
16
|
+
code: 'UE00403', status: Status.FORBIDDEN, message: 'Your email address is not verified'
|
|
17
|
+
},
|
|
18
|
+
UNCOMPELETE_ACCOUNT_SETUP: {
|
|
19
|
+
code: 'UAS0403', status: Status.FORBIDDEN, message: 'Account setup is not completed'
|
|
20
|
+
},
|
|
21
|
+
UNCOMPELETE_PIN_SETUP: {
|
|
22
|
+
code: 'UPS0403', status: Status.FORBIDDEN, message: 'Pin setup is not completed'
|
|
23
|
+
},
|
|
24
|
+
NO_PIN_TOKEN_HEADER: {
|
|
25
|
+
code: 'NPTH401', status: Status.UN_AUTHORIZED, message: 'Please specify pin_token header'
|
|
26
|
+
},
|
|
27
|
+
EXPIRED_PIN_TOKEN: {
|
|
28
|
+
code: 'EPT0401', status: Status.UN_AUTHORIZED, message: 'Your session has expired, Please login with your pin'
|
|
29
|
+
},
|
|
30
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import env from '../env';
|
|
2
|
+
import { ServiceRespI } from '../interfaces/interface';
|
|
3
|
+
const Airbrake = require('@airbrake/node');
|
|
4
|
+
const airbrakeExpress = require('@airbrake/node/dist/instrumentation/express');
|
|
5
|
+
const json_stringify_safe = require('json-stringify-safe');
|
|
6
|
+
|
|
7
|
+
class AirbrakeLogger {
|
|
8
|
+
private airbrake: any;
|
|
9
|
+
|
|
10
|
+
constructor(projectId: string, projectKey: string) {
|
|
11
|
+
this.airbrake = new Airbrake.Notifier({
|
|
12
|
+
projectId: projectId,
|
|
13
|
+
projectKey: projectKey,
|
|
14
|
+
environment: `${env.NODE_ENV}`
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
logRequestError(serviceResponse: ServiceRespI) {
|
|
20
|
+
try {
|
|
21
|
+
const { request, error, data, message, actionType } = serviceResponse;
|
|
22
|
+
const dataErr = data ? json_stringify_safe(data) : null;
|
|
23
|
+
const errorErr = error ? json_stringify_safe(error, null) : null;
|
|
24
|
+
const messageErr = message ? json_stringify_safe(message) : null;
|
|
25
|
+
const error_ = messageErr || errorErr || dataErr;
|
|
26
|
+
const actionType_ = actionType || 'App Error';
|
|
27
|
+
const body = request && request.body ? request.body : 'no body';
|
|
28
|
+
const url = request && request.url ? request.url : 'no url';
|
|
29
|
+
this.airbrake.notify({
|
|
30
|
+
error: error_,
|
|
31
|
+
params: { url, body, actionType: actionType_ },
|
|
32
|
+
route: url
|
|
33
|
+
});
|
|
34
|
+
} catch (error) {
|
|
35
|
+
this.airbrake.notify(error);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
logError(error: any, params: any, route: any) {
|
|
41
|
+
try {
|
|
42
|
+
this.airbrake.notify({
|
|
43
|
+
error: json_stringify_safe(error, null),
|
|
44
|
+
params,
|
|
45
|
+
route
|
|
46
|
+
});
|
|
47
|
+
} catch (error) {
|
|
48
|
+
this.airbrake.notify(error);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
getAirbrakeInstance() {
|
|
54
|
+
return this.airbrake;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
getAirbrakeExpress() {
|
|
58
|
+
return airbrakeExpress;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
// Example usage with environment variables (as before):
|
|
64
|
+
// const airbrakeLogger = new AirbrakeLogger(env.AIRBRAKE.PROJECT_ID, env.AIRBRAKE.PROJECT_KEY);
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
// Or, instantiate directly:
|
|
68
|
+
// const airbrakeLogger = new AirbrakeLogger('yourProjectId', 'yourProjectKey');
|
|
69
|
+
|
|
70
|
+
// Access the underlying Airbrake instance if needed:
|
|
71
|
+
// const airbrake = airbrakeLogger.getAirbrakeInstance();
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
// Then use the methods:
|
|
75
|
+
// airbrakeLogger.logRequestError({ /* ... your serviceResponse object */ });
|
|
76
|
+
// airbrakeLogger.logError(err, {/* ...params */}, route);
|
|
77
|
+
// Access airbrake instance directly
|
|
78
|
+
// airbrake.notify()
|
|
79
|
+
// Access airbrake express middleware directly
|
|
80
|
+
// airbrakeExpress
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
export { AirbrakeLogger };
|
|
84
|
+
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { Model, Document, } from 'mongoose';
|
|
2
|
+
import { QueryHelper } from '../helpers/query.helper';
|
|
3
|
+
import { FetchAllQuery, FetchWithPaginationDataI, ModelPopulateI, NestedRecordQueryOptionsI } from '../interfaces/root-controller.interface';
|
|
4
|
+
|
|
5
|
+
export class RootController {
|
|
6
|
+
private model: Model<any>;
|
|
7
|
+
private modelName: string;
|
|
8
|
+
constructor(model: Model<any>, modelName: string = 'Document') {
|
|
9
|
+
this.model = model;
|
|
10
|
+
this.modelName = modelName;
|
|
11
|
+
}
|
|
12
|
+
unique = async (conditions: {key: string, value: string}) => {
|
|
13
|
+
const {key, value} = conditions;
|
|
14
|
+
const res = await this.model.findOne({[key]: value});
|
|
15
|
+
if(res) {
|
|
16
|
+
return false
|
|
17
|
+
} else {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
getDocumentCount = async (conditon = {}, query?: object) => {
|
|
22
|
+
const { filter } = QueryHelper.build_query(query);
|
|
23
|
+
return await this.model.countDocuments({...filter, ...conditon})
|
|
24
|
+
}
|
|
25
|
+
create = async (payload: any) => {
|
|
26
|
+
try {
|
|
27
|
+
const document = await(this.model.create({...payload}))
|
|
28
|
+
return document.toJSON()
|
|
29
|
+
} catch (error) {
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
fetchAll(condition: object = {}, query?: FetchAllQuery, select: string|object = '', populate?: any) {
|
|
34
|
+
const { filter, skip, limit, sort } = QueryHelper.build_query(query);
|
|
35
|
+
return this.model.find({...filter, ...condition}).select(select).skip(skip).limit(limit).sort(sort).populate(populate);
|
|
36
|
+
}
|
|
37
|
+
fetchAllWithPagination = async (
|
|
38
|
+
conditon: object,
|
|
39
|
+
query?: FetchAllQuery,
|
|
40
|
+
select: string|object = '',
|
|
41
|
+
populate?: ModelPopulateI[]): Promise<FetchWithPaginationDataI> => {
|
|
42
|
+
try {
|
|
43
|
+
const records = await this.fetchAll(conditon, query, select, populate);
|
|
44
|
+
const total_records = await this.getDocumentCount(conditon, query);
|
|
45
|
+
return {records, total_records}
|
|
46
|
+
} catch (error) {
|
|
47
|
+
throw error
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
getOneP = async (condition: object, select?: any) => {
|
|
51
|
+
try {
|
|
52
|
+
select = select ? select : '';
|
|
53
|
+
const document = await this.model.findOne(condition).select(select).lean()
|
|
54
|
+
if(!document) throw {message: `${this.modelName} not found`};
|
|
55
|
+
return document
|
|
56
|
+
} catch (error) {
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
getOne(condition: object, select: string|object = '', populate?: any) {
|
|
61
|
+
return (this.model.findOne(condition).select(select).populate(populate).lean() as any)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
getById = async (id: string, select: string|object = '') => {
|
|
66
|
+
try {
|
|
67
|
+
const document = await this.model.findById(id).select(select).lean()
|
|
68
|
+
if(!document) throw {message: `${this.modelName} not found`};
|
|
69
|
+
return document;
|
|
70
|
+
} catch (error) {
|
|
71
|
+
throw error
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
updateOne(condition: object, updateValues: object) {
|
|
75
|
+
return (this.model.updateOne({...condition}, {...updateValues}, {new: true}) as any)
|
|
76
|
+
}
|
|
77
|
+
updateMany(condition: object, updateValues: object) {
|
|
78
|
+
return this.model.updateMany({...condition}, {...updateValues}).lean()
|
|
79
|
+
}
|
|
80
|
+
updateById(id: string, updateValues: object): any {
|
|
81
|
+
return this.model.findByIdAndUpdate(id, {...updateValues}, {new: true}).lean()
|
|
82
|
+
}
|
|
83
|
+
deleteOne(condition: object) {
|
|
84
|
+
return this.model.deleteOne({...condition})
|
|
85
|
+
}
|
|
86
|
+
deleteMany(condition: object) {
|
|
87
|
+
return this.model.deleteMany({...condition})
|
|
88
|
+
}
|
|
89
|
+
deleteById(id: string) {
|
|
90
|
+
return this.model.findByIdAndDelete(id)
|
|
91
|
+
}
|
|
92
|
+
search = async (payload:{key: any, value: any}, select = '') => {
|
|
93
|
+
try {
|
|
94
|
+
const regrex = new RegExp(`${payload.value}`, 'i');
|
|
95
|
+
const records = await this.model.find({[payload.key]: {$regex: regrex}}).select(select);
|
|
96
|
+
if(!records) throw {message: `${this.modelName} not found`}
|
|
97
|
+
throw records;
|
|
98
|
+
} catch (error) {
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
getNestedRecord = async (
|
|
103
|
+
options: NestedRecordQueryOptionsI,
|
|
104
|
+
errorMessage?: string
|
|
105
|
+
) => {
|
|
106
|
+
const {
|
|
107
|
+
parentField,
|
|
108
|
+
childIdField = '_id',
|
|
109
|
+
parentId,
|
|
110
|
+
childId
|
|
111
|
+
} = options;
|
|
112
|
+
|
|
113
|
+
// Build the query
|
|
114
|
+
const query = {
|
|
115
|
+
_id: parentId,
|
|
116
|
+
[`${parentField}.${childIdField}`]: { $eq: childId }
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// Build the projection
|
|
120
|
+
const projection = {
|
|
121
|
+
[parentField]: {
|
|
122
|
+
$elemMatch: {
|
|
123
|
+
[childIdField]: childId
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// Execute the query
|
|
129
|
+
const result = await this.model.findOne(query, projection);
|
|
130
|
+
|
|
131
|
+
if (!result) {
|
|
132
|
+
throw new Error(errorMessage || `Nested record not found in ${parentField}`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Return the first element of the matched array
|
|
136
|
+
return result[parentField][0];
|
|
137
|
+
}
|
|
138
|
+
}
|
package/src/env.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const dotenv = require('dotenv');
|
|
2
|
+
dotenv.config()
|
|
3
|
+
|
|
4
|
+
const env = {
|
|
5
|
+
MONGODB_URI : process.env.MONGODB_URI,
|
|
6
|
+
ADMIN_JWT_KEY: process.env.ADMIN_JWT_KEY as string,
|
|
7
|
+
NODE_ENV: process.env.NODE_ENV,
|
|
8
|
+
RATE_LIMIT_REDIS_URL: process.env.RATE_LIMIT_REDIS_URL,
|
|
9
|
+
API_KEY: process.env.API_KEY,
|
|
10
|
+
AIRBRAKE: {
|
|
11
|
+
PROJECT_ID: process.env.AIRBRAKE_PROJECT_ID,
|
|
12
|
+
PROJECT_KEY: process.env.AIRBRAKE_PROJECT_KEY
|
|
13
|
+
},
|
|
14
|
+
}
|
|
15
|
+
export default env;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { FiltersQueryI } from '../interfaces/interface';
|
|
2
|
+
import { DateUtil } from '../utilities/date.util';
|
|
3
|
+
|
|
4
|
+
class QueryHelper_ {
|
|
5
|
+
build_query(query: any): {sort: any, limit: number, skip: number, filter: any, page: number} {
|
|
6
|
+
if(!query) query = {};
|
|
7
|
+
const { filter, limit, page, filterRange, date_range, date_type, search } = query;
|
|
8
|
+
let range = filterRange ? this.str_to_obj(filterRange) : null;
|
|
9
|
+
const filterx = filter ? this.str_to_obj(filter) : ({} as any);
|
|
10
|
+
let searchx = search ? this.str_to_obj(search) : ({} as any);
|
|
11
|
+
const date_rangex = date_range ? this.str_to_obj(date_range) : ({} as any);
|
|
12
|
+
|
|
13
|
+
const pagex = page ? Number(page) : 1
|
|
14
|
+
const skipx = page ? ((Number(page) - 1) * limit) : 0;
|
|
15
|
+
const limitx = Number(limit) || 20;
|
|
16
|
+
if(range) {
|
|
17
|
+
const {field, ranges:{from, to} } = range;
|
|
18
|
+
range = {[field]: {$gte: from, $lt: to }}
|
|
19
|
+
}
|
|
20
|
+
searchx = searchx.key && searchx.value ? searchx : null;
|
|
21
|
+
const filterResult = range || filterx || {};
|
|
22
|
+
const date_range_ = this.get_date_range({date_type, date_range: date_rangex});
|
|
23
|
+
const search_ = searchx ? {[searchx.key]: {$regex: new RegExp(`${searchx.value}`, 'i')}} : {};
|
|
24
|
+
|
|
25
|
+
const filters = this.process_filters(query)
|
|
26
|
+
const sort = this.get_sort(query);
|
|
27
|
+
const filter_result = {...filterResult, ...date_range_, ...search_, ...filters};
|
|
28
|
+
return { sort, limit: limitx, filter: filter_result, skip: skipx, page: pagex};
|
|
29
|
+
}
|
|
30
|
+
get_date_range(
|
|
31
|
+
payload: {date_type: string, date_range: {start_date: Date, end_date: Date}}
|
|
32
|
+
) {
|
|
33
|
+
let date_filter_range = null as unknown as {start_date: any, end_date: any};
|
|
34
|
+
let filter = {};
|
|
35
|
+
const { date_type , date_range } = payload;
|
|
36
|
+
if(date_type === 'today') {
|
|
37
|
+
date_filter_range = DateUtil.get_today_date_range();
|
|
38
|
+
} else if(date_type === 'this_week') {
|
|
39
|
+
date_filter_range = DateUtil.this_week_date_range();
|
|
40
|
+
} else if(date_type === 'this_month') {
|
|
41
|
+
date_filter_range = DateUtil.this_month_date_range();
|
|
42
|
+
} else if(date_type === 'this_year') {
|
|
43
|
+
date_filter_range = DateUtil.this_year_date_range();
|
|
44
|
+
} else if(date_type === 'custom' && date_range && (date_range.start_date && date_range.end_date)) {
|
|
45
|
+
date_filter_range = DateUtil.get_custom_date_range(date_range)
|
|
46
|
+
} else if(date_type === 'last_30_days') {
|
|
47
|
+
const d = DateUtil.get_date_range({date: new Date(), number_of_days: 30})
|
|
48
|
+
filter = {createdAt: {$gte: d.start_date}}
|
|
49
|
+
}
|
|
50
|
+
if(date_filter_range) {
|
|
51
|
+
filter = {createdAt: {$gte: date_filter_range.start_date, $lt: date_filter_range.end_date}}
|
|
52
|
+
}
|
|
53
|
+
return filter;
|
|
54
|
+
}
|
|
55
|
+
get_filters(filter: any){
|
|
56
|
+
const filters: any = {};
|
|
57
|
+
if(!filter) return filters;
|
|
58
|
+
for (const [ key, value ] of Object.entries(filter)) {
|
|
59
|
+
const value_ = value as any;
|
|
60
|
+
if(key === 'createdAt'){
|
|
61
|
+
filters.startDate = value_['$gte']
|
|
62
|
+
filters.endDate = value_['$lt']
|
|
63
|
+
} else if(key === '$or' || key === '$and') {
|
|
64
|
+
const values = (value as any[])
|
|
65
|
+
if(values && values.length) {
|
|
66
|
+
values.forEach(item => {
|
|
67
|
+
const arrKey = Object.keys(item)[0]
|
|
68
|
+
const arrValue = Object.values(item)[0] as any;
|
|
69
|
+
filters[arrKey] = arrValue ? arrValue['$eq'] : null
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
filters[key] = value;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return filters;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
process_filters = (query: any) => {
|
|
80
|
+
try {
|
|
81
|
+
const { filters } = query;
|
|
82
|
+
let result: any = [];
|
|
83
|
+
let filter_query = {};
|
|
84
|
+
const filters_: FiltersQueryI = filters ? this.str_to_obj(filters) : ({} as any);
|
|
85
|
+
if(filters_ && filters_.filterSet && filters_.filterSet.length && filters_.conjunction) {
|
|
86
|
+
const logical_operator = filters_.conjunction == 'and' ? '$and' : '$or';
|
|
87
|
+
filters_.filterSet.forEach(fit => {
|
|
88
|
+
if(fit.operator === 'contains') {
|
|
89
|
+
result.push({[fit.field]: {$regex: fit.value, $options: 'i'} })
|
|
90
|
+
} else if(fit.operator === 'does_not_contain') {
|
|
91
|
+
result.push({[fit.field]: {$not: { $regex: fit.value, $options: 'i' }} })
|
|
92
|
+
} else if(fit.operator === 'is') {
|
|
93
|
+
result.push({[fit.field]: { $eq: fit.value} })
|
|
94
|
+
} else if(fit.operator === 'is_not') {
|
|
95
|
+
result.push({[fit.field]: { $ne: fit.value} })
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
filter_query = {[logical_operator]: result}
|
|
99
|
+
}
|
|
100
|
+
return filter_query;
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.log(error);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private get_sort(query: any) {
|
|
107
|
+
try {
|
|
108
|
+
const { key, value } = query.sort ? this.str_to_obj(query.sort) : ({} as any);
|
|
109
|
+
const single_sort = {[key || 'createdAt']: value || '-1'}
|
|
110
|
+
const sorts = query.sorts ? this.str_to_obj(query.sorts) : ({} as any);
|
|
111
|
+
const sort = Object.values(sorts).length ? sorts : single_sort
|
|
112
|
+
return sort;
|
|
113
|
+
} catch (error) {
|
|
114
|
+
throw error
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
private str_to_obj(string: string) {
|
|
118
|
+
return JSON.parse(string);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const QueryHelper = new QueryHelper_;
|
|
122
|
+
export { QueryHelper };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Status } from "./status.interface";
|
|
2
|
+
import { Request, Response } from 'express';
|
|
3
|
+
export interface SampleI {
|
|
4
|
+
_id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
createdAt: number;
|
|
8
|
+
}
|
|
9
|
+
export interface ServiceRespI {
|
|
10
|
+
req: Request;
|
|
11
|
+
res: Response;
|
|
12
|
+
status: Status;
|
|
13
|
+
request?: Request;
|
|
14
|
+
actionType: string;
|
|
15
|
+
data?: any;
|
|
16
|
+
message?: string;
|
|
17
|
+
error?: any;
|
|
18
|
+
admin_message?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface FiltersQueryI {
|
|
22
|
+
filterSet: {
|
|
23
|
+
field: string;
|
|
24
|
+
operator: 'contains' | 'does_not_contain' | 'is' | 'is_not';
|
|
25
|
+
value: string;
|
|
26
|
+
}[];
|
|
27
|
+
conjunction: 'or' | 'and'
|
|
28
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface FetchAllQuery {
|
|
2
|
+
filter?: any;
|
|
3
|
+
limit?: number
|
|
4
|
+
skip?: number
|
|
5
|
+
sort?: any;
|
|
6
|
+
}
|
|
7
|
+
export interface ModelPopulateI {
|
|
8
|
+
path: any,
|
|
9
|
+
select?: any,
|
|
10
|
+
model?: string
|
|
11
|
+
}
|
|
12
|
+
export interface FetchWithPaginationDataI {
|
|
13
|
+
records?: any[]
|
|
14
|
+
total_records?: number
|
|
15
|
+
}
|
|
16
|
+
export interface NestedRecordQueryOptionsI {
|
|
17
|
+
parentField: string;
|
|
18
|
+
parentId: string;
|
|
19
|
+
childIdField?: string;
|
|
20
|
+
childId: string;
|
|
21
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export enum Status {
|
|
2
|
+
SUCCESS = 'SUCCESS',
|
|
3
|
+
CREATED = 'CREATED',
|
|
4
|
+
FAILED_VALIDATION = 'FAILED_VALIDATION',
|
|
5
|
+
UN_AUTHORIZED = 'UN_AUTHORIZED',
|
|
6
|
+
ERROR = 'ERROR',
|
|
7
|
+
PROCESSING = 'PROCESSING',
|
|
8
|
+
NOT_FOUND = 'NOT_FOUND',
|
|
9
|
+
PRECONDITION_FAILED = 'PRECONDITION_FAILED',
|
|
10
|
+
SUCCESS_NO_CONTENT = 'SUCCESS_NO_CONTENT',
|
|
11
|
+
FORBIDDEN = 'FORBIDDEN',
|
|
12
|
+
UNPROCESSABLE_ENTRY = 'UNPROCESSABLE_ENTRY'
|
|
13
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import TokenUtil from '../utilities/token.util';
|
|
2
|
+
import { RootService } from '../services/_root.service';
|
|
3
|
+
import { Status } from '../interfaces/status.interface';
|
|
4
|
+
import { UserApiResp } from '../api-response/user.response';
|
|
5
|
+
import { Request, Response, NextFunction } from 'express';
|
|
6
|
+
import env from '../env';
|
|
7
|
+
class AuthMidWare extends RootService {
|
|
8
|
+
|
|
9
|
+
private _is_authenticated = async (req: Request) => {
|
|
10
|
+
try {
|
|
11
|
+
const apiKey = req.headers['api-key'];
|
|
12
|
+
if(apiKey) {
|
|
13
|
+
if(apiKey !== env.API_KEY) throw UserApiResp.NOT_AUTHORIZED;
|
|
14
|
+
} else {
|
|
15
|
+
const authHeader = req.headers.authorization;
|
|
16
|
+
if(!authHeader) throw UserApiResp.NO_AUTHORIZATION_HEADER;
|
|
17
|
+
const token = authHeader.split(' ')[1];
|
|
18
|
+
const tokenData = await TokenUtil.verify_admin_user(token);
|
|
19
|
+
if(!tokenData) throw UserApiResp.NOT_AUTHORIZED;
|
|
20
|
+
(req as any).user = tokenData;
|
|
21
|
+
}
|
|
22
|
+
return req;
|
|
23
|
+
} catch (error: any) {
|
|
24
|
+
const status = error.status || Status.UN_AUTHORIZED;
|
|
25
|
+
const message = error.message || 'You are not authorized';
|
|
26
|
+
throw {status, message, error, req}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
auth = async (req: Request, res: Response, next: NextFunction) => {
|
|
30
|
+
const actionType = 'USER_AUTH_MIDWARE';
|
|
31
|
+
try {
|
|
32
|
+
await this._is_authenticated(req)
|
|
33
|
+
next();
|
|
34
|
+
|
|
35
|
+
} catch (error) {
|
|
36
|
+
const { status, message, data } = this.get_error(error);
|
|
37
|
+
return this.sendResponse({req, res, status, actionType, message, data, error})
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export default new AuthMidWare;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as joi from 'joi'
|
|
2
|
+
import { RootService } from '../services/_root.service';
|
|
3
|
+
import { Status } from "../interfaces/status.interface";
|
|
4
|
+
const { FAILED_VALIDATION } = Status;
|
|
5
|
+
import { Request, Response, NextFunction } from 'express';
|
|
6
|
+
/**
|
|
7
|
+
* Validation middleware that uses joi to validate the request body.
|
|
8
|
+
* @param schema Joi schema to use to validate the request body
|
|
9
|
+
*/
|
|
10
|
+
export class Joi extends RootService {
|
|
11
|
+
vdtor(schema: joi.Schema, field: 'body' | 'query' = 'body') {
|
|
12
|
+
return async (req: Request, res: Response, next: NextFunction) => {
|
|
13
|
+
const actionType = 'SCHEMA_VALIDATION';
|
|
14
|
+
try {
|
|
15
|
+
await schema.validateAsync(req[field], {abortEarly: false})
|
|
16
|
+
next();
|
|
17
|
+
} catch (err: any) {
|
|
18
|
+
const errorDetails = err.details.map((e: any) => e.message);
|
|
19
|
+
const response = {
|
|
20
|
+
message: 'Some validation errors occured',
|
|
21
|
+
errors: errorDetails,
|
|
22
|
+
}
|
|
23
|
+
return this.sendResponse({req, res, status: FAILED_VALIDATION, actionType, data: response})
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const newJoi = new Joi()
|
|
29
|
+
export default newJoi;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import mongoose = require("mongoose");
|
|
2
|
+
import env from '../env';
|
|
3
|
+
import chalk = require('chalk');
|
|
4
|
+
|
|
5
|
+
mongoose.set('strictQuery', false)
|
|
6
|
+
|
|
7
|
+
export const dbConfig = () => {
|
|
8
|
+
// Connect to MongoDB
|
|
9
|
+
if(env.MONGODB_URI) {
|
|
10
|
+
mongoose.connect(env.MONGODB_URI)
|
|
11
|
+
.then(() => {
|
|
12
|
+
console.log('✌🏾 Successfully connected to MongoDB');
|
|
13
|
+
})
|
|
14
|
+
.catch(err => {
|
|
15
|
+
console.log(err);
|
|
16
|
+
console.log(chalk.red.bgBlack.bold('An error occured while conencting to MongoDB'));
|
|
17
|
+
});
|
|
18
|
+
} else {
|
|
19
|
+
console.log('MONGODB_URI environment varaiable is required.');
|
|
20
|
+
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Document, Schema, model, Model } from "mongoose";
|
|
2
|
+
|
|
3
|
+
export interface ISample extends Document {
|
|
4
|
+
name: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const schema: Schema<ISample> = new Schema({
|
|
9
|
+
name: {
|
|
10
|
+
type: String,
|
|
11
|
+
},
|
|
12
|
+
description: {
|
|
13
|
+
type: String
|
|
14
|
+
},
|
|
15
|
+
}, {timestamps: true});
|
|
16
|
+
schema.index({})
|
|
17
|
+
|
|
18
|
+
export interface ISampleModel extends Model<ISample> {}
|
|
19
|
+
|
|
20
|
+
const SampleModel:ISampleModel = model<ISample, ISampleModel>('sample', schema)
|
|
21
|
+
export default SampleModel
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as express from "express";
|
|
2
|
+
import chalk = require('chalk');
|
|
3
|
+
import env from '../env';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
import SampleRoute from './sample.route';
|
|
7
|
+
/**
|
|
8
|
+
* Create and return Router.
|
|
9
|
+
*
|
|
10
|
+
* @class Server
|
|
11
|
+
* @method config
|
|
12
|
+
* @return void
|
|
13
|
+
*/
|
|
14
|
+
export const routes = (app: express.Application) => {
|
|
15
|
+
let router: express.Router;
|
|
16
|
+
router = express.Router();
|
|
17
|
+
|
|
18
|
+
console.log(chalk.yellow.bgBlack.bold("Loading sample routes"));
|
|
19
|
+
SampleRoute.loadRoutes('/samples', router);
|
|
20
|
+
|
|
21
|
+
router.get('/', (req, res) => res.send(`Welcome to Node-Template-TS-2 - ${env.NODE_ENV}`))
|
|
22
|
+
|
|
23
|
+
//use router middleware
|
|
24
|
+
app.use(router);
|
|
25
|
+
|
|
26
|
+
app.all('*', (req, res)=> {
|
|
27
|
+
console.log(req.url);
|
|
28
|
+
return res.status(404).json({ status: 404, error: 'not found' });
|
|
29
|
+
});
|
|
30
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import SampleService from '../services/sample.service';
|
|
3
|
+
import Joi from '../middlewares/validator.midware';
|
|
4
|
+
import SampleValidator from '../validations/sample.validator';
|
|
5
|
+
|
|
6
|
+
class SampleRoute {
|
|
7
|
+
public loadRoutes(prefix: string, router: Router) {
|
|
8
|
+
this.create(prefix, router);
|
|
9
|
+
this.getAll(prefix, router);
|
|
10
|
+
this.getOne(prefix, router);
|
|
11
|
+
this.getById(prefix, router);
|
|
12
|
+
this.updateOne(prefix, router);
|
|
13
|
+
this.updateMany(prefix, router);
|
|
14
|
+
this.updateById(prefix, router);
|
|
15
|
+
this.deleteOne(prefix, router);
|
|
16
|
+
this.deleteMany(prefix, router);
|
|
17
|
+
this.deleteById(prefix, router);
|
|
18
|
+
}
|
|
19
|
+
private create(prefix: string, router: Router) {
|
|
20
|
+
router.post(`${prefix}`, Joi.vdtor(SampleValidator.create), SampleService.create)
|
|
21
|
+
}
|
|
22
|
+
private getAll(prefix: string, router: Router) {
|
|
23
|
+
router.get(`${prefix}`, SampleService.getAll)
|
|
24
|
+
}
|
|
25
|
+
private getOne(prefix: string, router: Router) {
|
|
26
|
+
router.get(`${prefix}/one`, SampleService.getOne)
|
|
27
|
+
}
|
|
28
|
+
private getById(prefix: string, router: Router) {
|
|
29
|
+
router.get(`${prefix}/:id`, SampleService.getById)
|
|
30
|
+
}
|
|
31
|
+
private updateOne(prefix: string, router: Router) {
|
|
32
|
+
router.put(`${prefix}/one`, SampleService.updateOne)
|
|
33
|
+
}
|
|
34
|
+
private updateMany(prefix: string, router: Router) {
|
|
35
|
+
router.put(`${prefix}/many`, SampleService.updateMany)
|
|
36
|
+
}
|
|
37
|
+
private updateById(prefix: string, router: Router) {
|
|
38
|
+
router.put(`${prefix}/:id`, SampleService.updateById)
|
|
39
|
+
}
|
|
40
|
+
private deleteOne(prefix: string, router: Router) {
|
|
41
|
+
router.delete(`${prefix}/one`, SampleService.deleteOne)
|
|
42
|
+
}
|
|
43
|
+
private deleteMany(prefix: string, router: Router) {
|
|
44
|
+
router.delete(`${prefix}/many`, SampleService.deleteMany)
|
|
45
|
+
}
|
|
46
|
+
private deleteById(prefix: string, router: Router) {
|
|
47
|
+
router.delete(`${prefix}/:id`, SampleService.deleteById)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
}
|
|
51
|
+
export default new SampleRoute;
|
package/src/server.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import cors from "cors";
|
|
4
|
+
import helmet from 'helmet'
|
|
5
|
+
import errorHandler = require("errorhandler");
|
|
6
|
+
import { dbConfig } from './models/_config';
|
|
7
|
+
import { routes } from './routes/index.route';
|
|
8
|
+
import { morgan } from './utilities/logger.util';
|
|
9
|
+
// import { airbrake, airbrakeExpress } from './app-middlewares/airbrake';
|
|
10
|
+
const port = process.env.PORT || 8082;
|
|
11
|
+
|
|
12
|
+
const app = express();
|
|
13
|
+
|
|
14
|
+
//configure application
|
|
15
|
+
app.use(express.static(path.join(__dirname, "public")));
|
|
16
|
+
|
|
17
|
+
//mount json form parser
|
|
18
|
+
app.use(express.json());
|
|
19
|
+
|
|
20
|
+
//mount query string parser
|
|
21
|
+
app.use(express.urlencoded({extended: true }));
|
|
22
|
+
|
|
23
|
+
app.use(helmet())
|
|
24
|
+
app.use(morgan)
|
|
25
|
+
|
|
26
|
+
//cors error allow
|
|
27
|
+
app.options("*", cors());
|
|
28
|
+
app.use(cors());
|
|
29
|
+
|
|
30
|
+
// catch 404 and forward to error handler
|
|
31
|
+
app.use(function(err: any, req: express.Request, res: express.Response, next: express.NextFunction) {
|
|
32
|
+
err.status = 404;
|
|
33
|
+
next(err);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
//error handling
|
|
37
|
+
app.use(errorHandler());
|
|
38
|
+
|
|
39
|
+
dbConfig();
|
|
40
|
+
|
|
41
|
+
// app.use(airbrakeExpress.makeMiddleware(airbrake));
|
|
42
|
+
|
|
43
|
+
routes(app);
|
|
44
|
+
|
|
45
|
+
// app.use(airbrakeExpress.makeErrorHandler(airbrake));
|
|
46
|
+
|
|
47
|
+
app.listen(port, () => {
|
|
48
|
+
console.log(`Server is running on port ${port}`);
|
|
49
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { winston } from '../utilities/logger.util'
|
|
2
|
+
import { Status } from '../interfaces/status.interface';
|
|
3
|
+
import { ServiceRespI } from "../interfaces/interface";
|
|
4
|
+
|
|
5
|
+
export class RootService {
|
|
6
|
+
sendResponse = (serviceResponse: ServiceRespI): any => {
|
|
7
|
+
let { res, status, data, message, actionType, error } = serviceResponse;
|
|
8
|
+
try {
|
|
9
|
+
status = status || Status.ERROR;
|
|
10
|
+
const code = error && error.code ? error.code : null;
|
|
11
|
+
const response: any = { status, data, message, code }
|
|
12
|
+
if(error) {
|
|
13
|
+
response.error = this.get_error_(error)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const status_code = this.getHttpStatus(status);
|
|
17
|
+
res.status(status_code).json(response);
|
|
18
|
+
if(status_code >= 400) {
|
|
19
|
+
const dataErr = data ? JSON.stringify(data) : data;
|
|
20
|
+
const error = `[${actionType||'App Error'}] ${response.message} ${dataErr}`
|
|
21
|
+
winston.error(error)
|
|
22
|
+
}
|
|
23
|
+
} catch (error: any) {
|
|
24
|
+
res.status(500).json({status: 'ERROR', data: error, message: error.message});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
get_error(error: any): {status: Status, message: string, data: any} {
|
|
28
|
+
let response = {status: Status.ERROR, message: 'Request failed', data: null};
|
|
29
|
+
const { status, message, data } = error;
|
|
30
|
+
response.status = status ? status : response.status;
|
|
31
|
+
response.message = message ? message : response.message;
|
|
32
|
+
response.data = data ? data : response.data;
|
|
33
|
+
return response;
|
|
34
|
+
}
|
|
35
|
+
private getHttpStatus(status: any): number {
|
|
36
|
+
switch (status) {
|
|
37
|
+
case 'SUCCESS': case 'PROCESSING':
|
|
38
|
+
return 200;
|
|
39
|
+
case 'CREATED':
|
|
40
|
+
return 201;
|
|
41
|
+
case 'SUCCESS_NO_CONTENT':
|
|
42
|
+
return 204;
|
|
43
|
+
case 'FAILED_VALIDATION':
|
|
44
|
+
return 400;
|
|
45
|
+
case 'UN_AUTHORIZED':
|
|
46
|
+
return 401;
|
|
47
|
+
case 'FORBIDDEN':
|
|
48
|
+
return 403;
|
|
49
|
+
case 'NOT_FOUND':
|
|
50
|
+
return 404;
|
|
51
|
+
case 'CONFLICT':
|
|
52
|
+
return 409;
|
|
53
|
+
case 'UNPROCESSABLE_ENTRY':
|
|
54
|
+
return 422;
|
|
55
|
+
case 'UNATHORIZED':
|
|
56
|
+
return 401;
|
|
57
|
+
case 'PRECONDITION_FAILED':
|
|
58
|
+
return 412;
|
|
59
|
+
case 'ERROR':
|
|
60
|
+
return 500
|
|
61
|
+
default:
|
|
62
|
+
return 400;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
private get_error_ = (err: any) => {
|
|
66
|
+
const error = err && err.error ? err.error : err;
|
|
67
|
+
const { req, message, status, code, ...err_rest } = error;
|
|
68
|
+
return err_rest;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { Response, Request } from 'express';
|
|
2
|
+
import SampleController from '../controllers/sample.control';
|
|
3
|
+
import { RootService } from './_root.service';
|
|
4
|
+
import { Status } from '../interfaces/status.interface';
|
|
5
|
+
const { SUCCESS, ERROR, UNPROCESSABLE_ENTRY, PRECONDITION_FAILED, SUCCESS_NO_CONTENT } = Status;
|
|
6
|
+
class SampleService extends RootService {
|
|
7
|
+
create = async (req: Request, res: Response) => {
|
|
8
|
+
const actionType = 'CREATE_SAMPLE';
|
|
9
|
+
try {
|
|
10
|
+
const sample = await SampleController.create(req.body);
|
|
11
|
+
this.sendResponse({req, res, status: SUCCESS, data: sample, actionType});
|
|
12
|
+
} catch (error) {
|
|
13
|
+
this.sendResponse({req, res, status: ERROR, actionType, data: error})
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
getAll = async (req: Request, res: Response) => {
|
|
17
|
+
const actionType = 'GET_ALL_SAMPLES';
|
|
18
|
+
try {
|
|
19
|
+
const samples = await SampleController.fetchAll({},req.query)
|
|
20
|
+
this.sendResponse({req, res, status: SUCCESS, actionType, data: samples});
|
|
21
|
+
} catch (error) {
|
|
22
|
+
this.sendResponse({req, res, status: ERROR, actionType, data: error})
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
getOne = async (req: Request, res: Response) => {
|
|
26
|
+
const actionType = 'GET_ONE_SAMPLE';
|
|
27
|
+
try {
|
|
28
|
+
const sample = await SampleController.getOne(req.query)
|
|
29
|
+
this.sendResponse({req, res, status: SUCCESS, actionType, data: sample});
|
|
30
|
+
} catch (error) {
|
|
31
|
+
this.sendResponse({req, res, status: ERROR, actionType, data: error})
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
getById = async (req: Request, res: Response) => {
|
|
35
|
+
const actionType = 'GET_SAMPLE_BY_ID'
|
|
36
|
+
try {
|
|
37
|
+
const sample = await SampleController.getById(req.params.id);
|
|
38
|
+
this.sendResponse({req, res, status: SUCCESS, actionType, data: sample});
|
|
39
|
+
} catch (error) {
|
|
40
|
+
this.sendResponse({req, res, status: ERROR, actionType, data: error})
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
updateOne = async (req: Request, res: Response) => {
|
|
44
|
+
const actionType = 'UPDATE_ONE_SAMPLE';
|
|
45
|
+
try {
|
|
46
|
+
const sample = await SampleController.updateOne(req.query, req.body)
|
|
47
|
+
if(sample.acknowledged) return this.sendResponse({req, res, status: SUCCESS, actionType, data: sample});
|
|
48
|
+
this.sendResponse({req, res, status: UNPROCESSABLE_ENTRY, actionType, data: 'data not updated'})
|
|
49
|
+
} catch (error) {
|
|
50
|
+
this.sendResponse({req, res, status: ERROR, actionType, data: error})
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
updateMany = async (req: Request, res: Response) => {
|
|
54
|
+
const actionType = 'UPDATE_MANY_SAMPLE';
|
|
55
|
+
try {
|
|
56
|
+
const sample: any = await SampleController.updateMany(req.query, req.body)
|
|
57
|
+
if(sample.acknowledged) return this.sendResponse({req, res, status: SUCCESS, actionType, data: sample});
|
|
58
|
+
this.sendResponse({req, res, status: UNPROCESSABLE_ENTRY, actionType, data: 'data not updated'})
|
|
59
|
+
} catch (error) {
|
|
60
|
+
this.sendResponse({req, res, status: ERROR, actionType, data: error})
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
updateById = async (req: Request, res: Response) => {
|
|
64
|
+
const actionType = 'UPDATE_SAMPLE_BY_ID'
|
|
65
|
+
try {
|
|
66
|
+
const sample: any = await SampleController.updateById(req.params.id, req.body);
|
|
67
|
+
if(sample) return this.sendResponse({req, res, status: SUCCESS, actionType, data: sample});
|
|
68
|
+
this.sendResponse({req, res, status: PRECONDITION_FAILED, actionType, data: 'data not updated'})
|
|
69
|
+
} catch (error) {
|
|
70
|
+
this.sendResponse({req, res, status: ERROR, actionType, data: error})
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
deleteOne = async (req: Request, res: Response) => {
|
|
74
|
+
const actionType = 'DELETE_ONE_SAMPLE'
|
|
75
|
+
try {
|
|
76
|
+
const sample: any = await SampleController.deleteOne(req.query)
|
|
77
|
+
if(sample.acknowledged) return this.sendResponse({req, res, status: SUCCESS_NO_CONTENT, actionType, data: sample});
|
|
78
|
+
this.sendResponse({req, res, status: UNPROCESSABLE_ENTRY, actionType, data: 'data not deleted'})
|
|
79
|
+
} catch (error) {
|
|
80
|
+
this.sendResponse({req, res, status: ERROR, actionType, data: error})
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
deleteMany = async (req: Request, res: Response) => {
|
|
84
|
+
const actionType = 'DELETE_MANY_SAMPLES';
|
|
85
|
+
try {
|
|
86
|
+
const sample: any = await SampleController.deleteMany(req.query)
|
|
87
|
+
if(sample.acknowledged) return this.sendResponse({req, res, status: SUCCESS_NO_CONTENT, actionType, data: sample});
|
|
88
|
+
this.sendResponse({req, res, status: UNPROCESSABLE_ENTRY, actionType, data: 'data not deleted'})
|
|
89
|
+
} catch (error) {
|
|
90
|
+
this.sendResponse({req, res, status: ERROR, actionType, data: error})
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
deleteById = async (req: Request, res: Response) => {
|
|
94
|
+
const actionType = 'DELETE_SAMPLE_BY_ID';
|
|
95
|
+
try {
|
|
96
|
+
const sample = await SampleController.deleteById(req.params.id);
|
|
97
|
+
if(sample) return this.sendResponse({req, res, status: SUCCESS_NO_CONTENT, actionType, data: sample});
|
|
98
|
+
this.sendResponse({req, res, status: PRECONDITION_FAILED, actionType, data: 'data not deleted'})
|
|
99
|
+
} catch (error) {
|
|
100
|
+
this.sendResponse({req, res, status: ERROR, actionType, data: error})
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
}
|
|
105
|
+
export default new SampleService;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import * as dateFns from 'date-fns';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class DateUtil_ {
|
|
5
|
+
|
|
6
|
+
get_local_date = (date = new Date()) => {
|
|
7
|
+
const currentDate = new Date(date);
|
|
8
|
+
// const localOffset = currentDate.getTimezoneOffset() * 60000;
|
|
9
|
+
// const localDate = new Date(currentDate.getTime() - localOffset);
|
|
10
|
+
return currentDate
|
|
11
|
+
}
|
|
12
|
+
get_start_date(date: any) {
|
|
13
|
+
const d = this.get_local_date(date);
|
|
14
|
+
d.setHours(0, 0, 0, 0);
|
|
15
|
+
return d.getTime();
|
|
16
|
+
}
|
|
17
|
+
get_end_date(date: any) {
|
|
18
|
+
const d = this.get_local_date(date);
|
|
19
|
+
d.setHours(23, 0, 0, 0);
|
|
20
|
+
return d.getTime();
|
|
21
|
+
}
|
|
22
|
+
get_date_format(date: Date, format: string) {
|
|
23
|
+
return this.get_local_date(date);
|
|
24
|
+
}
|
|
25
|
+
get_today_date_range(payload?: {format?: string, date: Date}): {start_date: any, end_date: any} {
|
|
26
|
+
const main_date = payload && payload.date ? this.get_local_date(payload.date) : this.get_local_date();
|
|
27
|
+
return {
|
|
28
|
+
start_date: dateFns.startOfDay(main_date),
|
|
29
|
+
end_date: dateFns.endOfDay(main_date)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
get_date_range(payload?: {format?: string, date: Date, number_of_days?: number}): {start_date: any, end_date: any} {
|
|
33
|
+
const main_date = payload && payload.date ? this.get_local_date(payload.date) : this.get_local_date();
|
|
34
|
+
const number_of_days = payload?.number_of_days ? payload.number_of_days : 0;
|
|
35
|
+
const start_date = dateFns.subDays(main_date, number_of_days)
|
|
36
|
+
const end_date = dateFns.addDays(main_date, number_of_days)
|
|
37
|
+
return {
|
|
38
|
+
start_date: dateFns.startOfDay(start_date),
|
|
39
|
+
end_date: dateFns.endOfDay(end_date)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
get_custom_date_range({start_date, end_date}: {start_date: Date; end_date: Date}) {
|
|
43
|
+
return {
|
|
44
|
+
start_date: dateFns.startOfDay(this.get_local_date(start_date)),
|
|
45
|
+
end_date: dateFns.endOfDay(this.get_local_date(end_date))
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
this_week_date_range(): {start_date: Date, end_date: Date} {
|
|
49
|
+
return {
|
|
50
|
+
start_date: dateFns.startOfWeek(this.get_local_date()),
|
|
51
|
+
end_date: dateFns.endOfWeek(this.get_local_date())
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
this_month_date_range(): {start_date: Date, end_date: Date} {
|
|
55
|
+
return {
|
|
56
|
+
start_date: dateFns.startOfMonth(this.get_local_date()),
|
|
57
|
+
end_date: dateFns.endOfMonth(this.get_local_date())
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
this_year_date_range(): {start_date: Date, end_date: Date} {
|
|
61
|
+
return {
|
|
62
|
+
start_date: dateFns.startOfYear(this.get_local_date()),
|
|
63
|
+
end_date: dateFns.endOfYear(this.get_local_date())
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const DateUtil = new DateUtil_;
|
|
68
|
+
export { DateUtil }
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import env from '../env';
|
|
2
|
+
import { createWriteStream } from 'fs';
|
|
3
|
+
import { resolve } from 'path';
|
|
4
|
+
import morgan_ from 'morgan';
|
|
5
|
+
import { createLogger, format, transports } from 'winston';
|
|
6
|
+
|
|
7
|
+
class LoggerUtil {
|
|
8
|
+
morgan() {
|
|
9
|
+
let dev_format = '[:date[web] :remote-addr :remote-user ] :method :url HTTP/:http-version | :status :response-time ms'
|
|
10
|
+
let prod_format = '[:date[web] :remote-addr :remote-user ] :method :url HTTP/:http-version :referrer - :user-agent | :status :response-time ms'
|
|
11
|
+
let morgan_format = env.NODE_ENV === 'prod' ? prod_format : dev_format;
|
|
12
|
+
let request_log_stream = createWriteStream(resolve(__dirname, `../../logs/request.log`), { flags: 'a' });
|
|
13
|
+
return morgan_(morgan_format, { stream: request_log_stream });
|
|
14
|
+
}
|
|
15
|
+
winston() {
|
|
16
|
+
let { colorize, combine, printf, timestamp } = format
|
|
17
|
+
let log_transports = {
|
|
18
|
+
console: new transports.Console({ level: 'warn' }),
|
|
19
|
+
combined_log: new transports.File({ level: 'info', filename: `logs/combined.log` }),
|
|
20
|
+
error_log: new transports.File({ level: 'error', filename: `logs/error.log` }),
|
|
21
|
+
exception_log: new transports.File({ filename: 'logs/exception.log' }),
|
|
22
|
+
};
|
|
23
|
+
let log_format = printf(({ level, message, timestamp }) => `[${timestamp} : ${level}] - ${message}`);
|
|
24
|
+
let logger = createLogger({
|
|
25
|
+
transports: [
|
|
26
|
+
log_transports.console,
|
|
27
|
+
log_transports.combined_log,
|
|
28
|
+
log_transports.error_log,
|
|
29
|
+
],
|
|
30
|
+
exceptionHandlers: [
|
|
31
|
+
log_transports.exception_log,
|
|
32
|
+
],
|
|
33
|
+
exitOnError: false,
|
|
34
|
+
format: combine(
|
|
35
|
+
colorize(),
|
|
36
|
+
timestamp(),
|
|
37
|
+
log_format
|
|
38
|
+
)
|
|
39
|
+
});
|
|
40
|
+
return logger;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
const loggerUtil = new LoggerUtil()
|
|
46
|
+
const morgan = loggerUtil.morgan()
|
|
47
|
+
const winston = loggerUtil.winston()
|
|
48
|
+
|
|
49
|
+
export {
|
|
50
|
+
morgan,
|
|
51
|
+
winston
|
|
52
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as jwt from 'jsonwebtoken';
|
|
2
|
+
import env from '../env';
|
|
3
|
+
import { UserApiResp } from '../api-response/user.response';
|
|
4
|
+
|
|
5
|
+
class TokenUtil {
|
|
6
|
+
sign_admin_user(payload: any, expiresIn: string | number) {
|
|
7
|
+
return jwt.sign(payload, env?.ADMIN_JWT_KEY, {expiresIn: expiresIn ? expiresIn : '1d'});
|
|
8
|
+
}
|
|
9
|
+
verify_admin_user(token: string): any {
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
try {
|
|
12
|
+
const resp = jwt.verify(token, env.ADMIN_JWT_KEY);
|
|
13
|
+
resolve(resp)
|
|
14
|
+
} catch (error) {
|
|
15
|
+
reject({error, ...UserApiResp.NOT_AUTHORIZED})
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export default new TokenUtil;
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "CommonJS",
|
|
5
|
+
"outDir": "./dist",
|
|
6
|
+
"rootDir": "./src",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true,
|
|
11
|
+
"declaration": true // Generates TypeScript declaration files
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*"],
|
|
14
|
+
"exclude": ["node_modules", "dist"]
|
|
15
|
+
}
|