@go-mailer/jarvis 1.0.0 → 2.1.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/README.md +255 -1
- package/index.js +7 -0
- package/lib/clients/go-flags.js +27 -0
- package/lib/clients/iam.js +42 -0
- package/lib/env.js +18 -6
- package/lib/flag.js +18 -0
- package/lib/middlewares/auth.js +48 -91
- package/lib/middlewares/errors.js +7 -0
- package/lib/middlewares/http.js +52 -0
- package/lib/middlewares/logger.js +95 -0
- package/lib/query.js +42 -71
- package/package.json +7 -5
- package/lib/logger.js +0 -126
- package/lib/middlewares/request.js +0 -50
- package/test.js +0 -0
package/README.md
CHANGED
|
@@ -1,2 +1,256 @@
|
|
|
1
1
|
# jarvis-node
|
|
2
|
-
A multipurpose helper package for our node apps.
|
|
2
|
+
A multipurpose helper package for our node apps. This package contains helpers for the following:
|
|
3
|
+
|
|
4
|
+
* Authentication
|
|
5
|
+
* Feature flag control
|
|
6
|
+
* Logging & Monitoring
|
|
7
|
+
* Environment variable management
|
|
8
|
+
* Request Query processing
|
|
9
|
+
|
|
10
|
+
## **Installation**
|
|
11
|
+
|
|
12
|
+
yarn install @go-mailer/jarvis
|
|
13
|
+
## **Authentication**
|
|
14
|
+
In Go-Mailer, there are two major types of authentication:
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
1. **JWT authentication**:
|
|
18
|
+
is employed in simple username/password log-ins and is the primary method of user authentication in the system.
|
|
19
|
+
We use the `jsonwetoken` package for [JWT](https://jwt.io/) authentication.
|
|
20
|
+
|
|
21
|
+
const { Authenticator } = require('@go-mailer/jarvis');
|
|
22
|
+
const { JWTAuth } = Authenticator;
|
|
23
|
+
|
|
24
|
+
app.use('/route', JWTAuth, route_handler);
|
|
25
|
+
2. **API authentication**:
|
|
26
|
+
is done via in-house authentication methods. The [IAM](https://github.com/go-mailer-ltd/iam) app is responsible
|
|
27
|
+
for managing user identities, authorizations and API keys. This package makes simple API calls to the IAM app to
|
|
28
|
+
verify the provided API keys. There are two ways API keys can be authenticated:
|
|
29
|
+
|
|
30
|
+
* **As a Query Parameter**: Here the API key is passed as a request query parameter - typically for applications
|
|
31
|
+
which intend to authenticate via `GET` HTTP method.
|
|
32
|
+
* **As an Authentication Header**: This is default way to pass API keys and security tokens in HTTP requests.
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
const { Authenticator } = require('@go-mailer/jarvis');
|
|
36
|
+
const { APIAuth } = Authenticator;
|
|
37
|
+
const { defaultAuth, paramAuth } = APIAuth();
|
|
38
|
+
|
|
39
|
+
app.use('/api-route', defaultAuth, handler); //API auth
|
|
40
|
+
app.use('/api-param-route', paramAuth, handler); //Query param auth
|
|
41
|
+
|
|
42
|
+
## **Feature Flag Control**
|
|
43
|
+
Go-Mailer uses [Go-Flags](https://go-flags.com) to control application feature visibility. This gives us the ability to
|
|
44
|
+
build our application's feature continuosly without worry about the customers seeing or using them before release.
|
|
45
|
+
|
|
46
|
+
`async FeatureFlag.verify(<flage_name>, <criteria>)`: used to verify whether or not a feature is turned ON or OFF.
|
|
47
|
+
Returns `true` if feature is enabled and `false` otherwise.
|
|
48
|
+
|
|
49
|
+
* **flag_name** `string`: **required**
|
|
50
|
+
|
|
51
|
+
This is the name of the feature to be checked as defined on the [Go-Flags app](https://go-flags.com).
|
|
52
|
+
|
|
53
|
+
* **criteria** `Object`: **required**
|
|
54
|
+
This is a key -> value pair of criteria with which the status of the flag should be checked.
|
|
55
|
+
These criteria are matched against those configure on the [Go-Flags app](https://go-flags.com).
|
|
56
|
+
|
|
57
|
+
const { FeatureFlag } = require('@go-mailer/jarvis');
|
|
58
|
+
FeatureFlag.verify(<flag_name>: String, <criteria>: Object ).then((result) => {
|
|
59
|
+
// do anything with result.
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
## **Logging**
|
|
63
|
+
Logging is essential to the success of any application. The ability to collect information about the application's processes is crucial
|
|
64
|
+
to making engineering and business decisions as well as resolving any issues that may arise in the system. Jarvis provides logging capabilities. Currently, the logging application we utilize is [Logtail](https://logtail.com/).
|
|
65
|
+
|
|
66
|
+
There are two kinds of Loggers provided by Jarvis:
|
|
67
|
+
|
|
68
|
+
1. Request Logger
|
|
69
|
+
2. Process Logger
|
|
70
|
+
|
|
71
|
+
### **Request Logger**
|
|
72
|
+
|
|
73
|
+
This middleware generates logs each time a request passes through the system. It should be placed before any routes are defined.
|
|
74
|
+
|
|
75
|
+
const { RequestLogger } = require('@go-mailer/jarvis');
|
|
76
|
+
app.use('/', RequestLogger);
|
|
77
|
+
|
|
78
|
+
### **Process Logger**
|
|
79
|
+
|
|
80
|
+
This logger is used to keep various kinds of logs from different parts of the system. There are two kinds of process logs recorded: `error` & `info`.
|
|
81
|
+
|
|
82
|
+
1. **error** logs are recorded whenever an exception is thrown or some anormally is detected or encountered.
|
|
83
|
+
2. **info** logs are strictly informational. They are typically used to provide visibility through the system for things
|
|
84
|
+
like system health, process paths, etc.
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
const { ProcessLogger } = require('@go-mailer/jarvis');
|
|
88
|
+
const logger = new ProcessLogger(<service_name>: String);
|
|
89
|
+
|
|
90
|
+
// error logging
|
|
91
|
+
logger.error(<error_object>: Error, <method_name>: String);
|
|
92
|
+
|
|
93
|
+
// info logging
|
|
94
|
+
logger.error(<message_to_be_logged>: String, <method_name>: String);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
1. **`<service_name>`**: This is the name of the service or part of the system where the log is generated. For example, if the log was generated in the Mailing controller, this would have the value `MailingController`.
|
|
98
|
+
2. **`<error_object>`**: This is the instance of the Error that was thrown and caught.
|
|
99
|
+
3. **`<message_to_be_logged>`**: For informational logs, this is the information to be logged.
|
|
100
|
+
4. **`<method_name>`**: This is the method/function within which the log was generated.
|
|
101
|
+
|
|
102
|
+
.
|
|
103
|
+
## **Environment variable management**
|
|
104
|
+
Jarvis also provides environment variable management. This enables applications to set and retrieve environment variables more seamlessly.
|
|
105
|
+
1. **`set(config: Object)`**: allows for the configuration of new variables. This does not override environment variables.
|
|
106
|
+
|
|
107
|
+
* `config: Object`: a key-value set that specifies application or instance specific criteria to the environment namespace.
|
|
108
|
+
|
|
109
|
+
.
|
|
110
|
+
2. **`fetch(variable_name: String, is_required: Boolean)`**: retrieves the specified variable.
|
|
111
|
+
|
|
112
|
+
* `variable_name: String`: The name of the variable to retrieve.
|
|
113
|
+
* `is_required: Boolean`: Specifies whether or not the variable to be retrieved is required. If set to `true`, an Exception would be thrown if no value exists. default: `false`
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
const { EnvVar } = require('@go-mailer/jarvis');
|
|
117
|
+
|
|
118
|
+
// set environment variable(s)
|
|
119
|
+
EnvVar.set({
|
|
120
|
+
APP_NAME: 'Mailing'
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// retrieve environment variable
|
|
124
|
+
EnvVar.fetch('APP_NAME', true); // required
|
|
125
|
+
EnvVar.fetch('APP_NAME'); // not required
|
|
126
|
+
```
|
|
127
|
+
## **Request Query processing**
|
|
128
|
+
Jarvis provides a query processor that converts request queries into MongoDB compatible queries. Request Queries are processed based on keywords and the use of special characters. A request query is the part of the request path that comes after the question mark (?). The `QueryBuilder.buildQuery()` is used for request processing.
|
|
129
|
+
|
|
130
|
+
GET http://go-mailer.com?<query_part>
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
QueryBuilder.buildQuery(options: Object) : {
|
|
134
|
+
count: boolean
|
|
135
|
+
fields_to_return: string
|
|
136
|
+
limit: number
|
|
137
|
+
seek_conditions: MongooseQueryObject
|
|
138
|
+
skip: number
|
|
139
|
+
sort_condition: string
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### **Keywords**:
|
|
144
|
+
Keywords in request queries are used to prepare the following queries. Given the sample records in the database:
|
|
145
|
+
```
|
|
146
|
+
[{
|
|
147
|
+
name: "Nathan",
|
|
148
|
+
age: 21,
|
|
149
|
+
sex: 'M'
|
|
150
|
+
}, {
|
|
151
|
+
name: "Anita",
|
|
152
|
+
age: 15,
|
|
153
|
+
sex: 'F'
|
|
154
|
+
}, {
|
|
155
|
+
name: "Manchang",
|
|
156
|
+
age: 25,
|
|
157
|
+
sex: 'M'
|
|
158
|
+
}]
|
|
159
|
+
```
|
|
160
|
+
1. **Field inclusion** queries: when the keyword `return_only` is used the query, ONLY the properties that are contained in the value that is assigned to this keyword WILL be returned. For example:
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
// given the request
|
|
164
|
+
GET https://go-mailer.com?return_only=name,age
|
|
165
|
+
|
|
166
|
+
// sample response will return only the `age` and `name` properties on the records
|
|
167
|
+
[{
|
|
168
|
+
name: "Nathan",
|
|
169
|
+
age: 21,
|
|
170
|
+
}, {
|
|
171
|
+
name: "Anita",
|
|
172
|
+
age: 15,
|
|
173
|
+
}, {
|
|
174
|
+
name: "Manchang",
|
|
175
|
+
age: 25,
|
|
176
|
+
}]
|
|
177
|
+
```
|
|
178
|
+
2. **Field exclusion** queries: when the keyword `exclude_only` is used the query, ONLY the properties that are NOT contained in the value that is assigned to this keyword WILL be returned. For example:
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
// given the request
|
|
182
|
+
GET https://go-mailer.com?exclude_only=name,age
|
|
183
|
+
|
|
184
|
+
// sample response will return only the `sex` property on the records.
|
|
185
|
+
[{
|
|
186
|
+
sex: 'M'
|
|
187
|
+
}, {
|
|
188
|
+
sex: 'F'
|
|
189
|
+
}, {
|
|
190
|
+
sex: 'M'
|
|
191
|
+
}]
|
|
192
|
+
```
|
|
193
|
+
3. **Pagination** queries: when the keywords `page` & `population` are specified in the query, the result set would be paginated. NOTE that the first page number is `0`. For example:
|
|
194
|
+
```
|
|
195
|
+
// given the request
|
|
196
|
+
GET https://go-mailer.com?page=0&population=2
|
|
197
|
+
|
|
198
|
+
// sample response. First two matching objects
|
|
199
|
+
[{
|
|
200
|
+
name: "Nathan",
|
|
201
|
+
age: 21,
|
|
202
|
+
sex: 'M'
|
|
203
|
+
}, {
|
|
204
|
+
name: "Anita",
|
|
205
|
+
age: 15,
|
|
206
|
+
sex: 'F'
|
|
207
|
+
}]
|
|
208
|
+
```
|
|
209
|
+
4. **Sort** queries: we can specify sort conditions by using the `sort_by` keyword in the query. The value would contain the property with which we would like to sort. Default sort mode is `ASC`. If you want `DESC`, specify the value with a minus sign (`-`):
|
|
210
|
+
```
|
|
211
|
+
// ASCending (age)
|
|
212
|
+
GET https://go-mailer.com?sort_by=age
|
|
213
|
+
|
|
214
|
+
// DESCending (-age)
|
|
215
|
+
GET https://go-mailer.com?sort_by=-age
|
|
216
|
+
```
|
|
217
|
+
5. **count**: To return a count of items that match the query, the `count` keyword is used.
|
|
218
|
+
|
|
219
|
+
```
|
|
220
|
+
GET https://go-mailer.com?count=1
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### **Special Characters**
|
|
224
|
+
The use of special characters in query values indicate the need to prepare the following queries:
|
|
225
|
+
1. **OR** queries: Whenever a comma separated list is used as a keyword value. This is hand
|
|
226
|
+
|
|
227
|
+
```
|
|
228
|
+
// return records where 'name' is 'Nathan' OR 'Michael'
|
|
229
|
+
GET https://go-mailer.com?name=Nathan,Michael
|
|
230
|
+
```
|
|
231
|
+
3. **NOR** queries: Whenever the values of a query field are separated by an exclamation mark (!), a NOR query is built.
|
|
232
|
+
|
|
233
|
+
```
|
|
234
|
+
// return records where 'name' is neither 'Nathan' NOR 'Michael'
|
|
235
|
+
GET https://go-mailer.com?name=Nathan!Michael
|
|
236
|
+
```
|
|
237
|
+
4. **Range selection** queries: To support range queries, the tilde character (~) is used in between values. currently, ranges only work with integer values
|
|
238
|
+
```
|
|
239
|
+
// get all records where age is between 12 and 34 (inclusive)
|
|
240
|
+
GET https://go-mailer.com?age=12~34
|
|
241
|
+
|
|
242
|
+
// get all records less than 34
|
|
243
|
+
GET https://go-mailer.com?age=~34
|
|
244
|
+
|
|
245
|
+
// get all records greater than 34
|
|
246
|
+
GET https://go-mailer.com?age=34~
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### **Wildcard**
|
|
250
|
+
The `QueryBuilder` closure also provides a `buildWildcardOptions()` function that prepares a mongoose regular expression query object for text matching.
|
|
251
|
+
```
|
|
252
|
+
QueryBuilder.buildWildcardOptions(key_list: string, value) : MongooseQueryObject
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
* `key_list: string`: is a comma-separated list of properties against which value is sought
|
|
256
|
+
* `value: any`: is the value being sought for.
|
package/index.js
CHANGED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
const EnvVar = require('./lib/env')
|
|
2
|
+
const FeatureFlag = require('./lib/flag')
|
|
3
|
+
const QueryBuilder = require('./lib/query')
|
|
4
|
+
const HTTP = require('./lib/middlewares/http')
|
|
5
|
+
const Authenticator = require('./lib/middlewares/auth')
|
|
6
|
+
const { RequestLogger, ProcessLogger } = require('./lib/middlewares/logger')
|
|
7
|
+
module.exports = { RequestLogger, ProcessLogger, Authenticator, EnvVar, FeatureFlag, HTTP, QueryBuilder }
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const axios = require("axios");
|
|
2
|
+
const Env = require("../env");
|
|
3
|
+
const BASE_URI = Env.fetch("GO_FLAGS_URI", true);
|
|
4
|
+
const API_KEY = Env.fetch("GO_FLAGS_KEY", true);
|
|
5
|
+
|
|
6
|
+
const verifyFeatureFlag = async (flag_name, criteria = {}) => {
|
|
7
|
+
const { error, payload } = (
|
|
8
|
+
await axios.post(
|
|
9
|
+
`${BASE_URI}/api/v1/flags/${flag_name}`,
|
|
10
|
+
{
|
|
11
|
+
payload: criteria,
|
|
12
|
+
environment: Env.fetch("NODE_ENV", true)
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
headers: {
|
|
16
|
+
authorization: `Bearer ${API_KEY}`,
|
|
17
|
+
},
|
|
18
|
+
}
|
|
19
|
+
)
|
|
20
|
+
).data;
|
|
21
|
+
|
|
22
|
+
if (error) throw new Error(error);
|
|
23
|
+
|
|
24
|
+
return payload.is_permitted;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
module.exports = { verifyFeatureFlag };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const axios = require("axios");
|
|
2
|
+
const Env = require("../env");
|
|
3
|
+
const IAM_URI = Env.fetch("IAM_SERVICE_URI", true);
|
|
4
|
+
const DEFAULT_TOKEN = Env.fetch("DEFAULT_TOKEN", true);
|
|
5
|
+
|
|
6
|
+
const verifyAPIKey = async (key) => {
|
|
7
|
+
const { error, payload } = (
|
|
8
|
+
await axios.get(`${IAM_URI}/keys/verify/${key}`, {
|
|
9
|
+
headers: {
|
|
10
|
+
authorization: `Bearer ${DEFAULT_TOKEN}`,
|
|
11
|
+
},
|
|
12
|
+
})
|
|
13
|
+
).data;
|
|
14
|
+
|
|
15
|
+
if (error) throw new Error("Unauthorized");
|
|
16
|
+
|
|
17
|
+
return payload.org_id;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const verifyFeatureFlag = async (flag_name, criteria = {}) => {
|
|
21
|
+
const { error, payload } = (
|
|
22
|
+
await axios.post(
|
|
23
|
+
`${IAM_URI}/flags`,
|
|
24
|
+
{
|
|
25
|
+
criteria,
|
|
26
|
+
environment: Env.fetch("NODE_ENV"),
|
|
27
|
+
name: flag_name,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
headers: {
|
|
31
|
+
authorization: `Bearer ${DEFAULT_TOKEN}`,
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
)
|
|
35
|
+
).data;
|
|
36
|
+
|
|
37
|
+
if (error) throw new Error(error);
|
|
38
|
+
|
|
39
|
+
return payload.is_permitted;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
module.exports = { verifyAPIKey, verifyFeatureFlag };
|
package/lib/env.js
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
|
-
require(
|
|
1
|
+
require("dotenv").config();
|
|
2
2
|
|
|
3
3
|
/** */
|
|
4
4
|
class EnvVar {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
constructor() {
|
|
6
|
+
this.config = {};
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
set(config = {}) {
|
|
10
|
+
this.config = {
|
|
11
|
+
...this.config,
|
|
12
|
+
...config,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
fetch(var_name = "", is_required = false) {
|
|
17
|
+
if (!var_name) throw new Error(`Variable name is required.`);
|
|
18
|
+
const value = process.env[var_name] || this.config[var_name];
|
|
19
|
+
if (is_required && !value) throw new Error(`Required EnvVar ${var_name} not found`);
|
|
20
|
+
return value;
|
|
9
21
|
}
|
|
10
22
|
}
|
|
11
23
|
|
|
12
|
-
module.exports = new EnvVar()
|
|
24
|
+
module.exports = new EnvVar();
|
package/lib/flag.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const { verifyFeatureFlag } = require('./clients/go-flags')
|
|
2
|
+
const { ProcessLogger } = require('./middlewares/logger')
|
|
3
|
+
const flagLogger = new ProcessLogger('FeatureFlag')
|
|
4
|
+
|
|
5
|
+
const verify = async (flag_name = '', criteria = {}) => {
|
|
6
|
+
try {
|
|
7
|
+
if (!flag_name) throw new Error('Unspecified flag name');
|
|
8
|
+
if (!Object.keys(criteria)) throw new Error('Unspecified criteria');
|
|
9
|
+
|
|
10
|
+
const result = await verifyFeatureFlag(flag_name, criteria);
|
|
11
|
+
return result
|
|
12
|
+
} catch (e) {
|
|
13
|
+
flagLogger.error(e, 'verify')
|
|
14
|
+
return false
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = { verify }
|
package/lib/middlewares/auth.js
CHANGED
|
@@ -1,116 +1,73 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* User Authentication Middleware
|
|
3
3
|
*/
|
|
4
|
-
const jwt = require("jsonwebtoken");
|
|
5
|
-
const { verify_api_key } = require("../clients/iam");
|
|
6
|
-
const RootService = require("../services/_root");
|
|
7
|
-
const rootService = new RootService();
|
|
8
|
-
const { JWT_ISSUER, JWT_SECRET, DEFAULT_TOKEN: GM_TOKEN } = require("../../.config");
|
|
9
|
-
|
|
10
|
-
const { app_logger } = require("../utilities/logger");
|
|
11
|
-
const logger = app_logger("Authentication Middleware");
|
|
12
|
-
|
|
13
|
-
class Authentication {
|
|
14
|
-
async authenticate_user(request, response, next) {
|
|
15
|
-
try {
|
|
16
|
-
const { authorization } = request.headers;
|
|
17
|
-
if (!authorization) {
|
|
18
|
-
return next(rootService.process_failed_response("Unauthorized", 403));
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const [, token] = authorization.split(" ");
|
|
22
|
-
if (!token) {
|
|
23
|
-
return next(rootService.process_failed_response("Unauthorized", 403));
|
|
24
|
-
}
|
|
25
4
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
5
|
+
const jwt = require("jsonwebtoken");
|
|
6
|
+
const Env = require("../env");
|
|
7
|
+
const Errors = require("./errors");
|
|
8
|
+
const { ProcessLogger } = require("./logger");
|
|
9
|
+
const { verifyAPIKey } = require("../clients/iam");
|
|
10
|
+
const authLogger = new ProcessLogger("Authenticator");
|
|
30
11
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
12
|
+
// helpers
|
|
13
|
+
const extract_token = (headers) => {
|
|
14
|
+
const { authorization } = headers;
|
|
15
|
+
if (!authorization) throw new Error("Unauthorized");
|
|
34
16
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (is_admin) process_request(request);
|
|
17
|
+
const [, token] = authorization.split(" ");
|
|
18
|
+
if (!token) throw new Error("Unauthorized");
|
|
38
19
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
logger.error(`[Auth Error] ${e.message}`);
|
|
42
|
-
next(rootService.process_failed_response("Unauthorized", 403));
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
20
|
+
return token;
|
|
21
|
+
};
|
|
46
22
|
|
|
47
|
-
|
|
23
|
+
// main
|
|
24
|
+
const JWTAuth = async (request, response, next) => {
|
|
48
25
|
try {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
if (
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
request.tenant_id = request.body.tenant_id;
|
|
26
|
+
// env vars
|
|
27
|
+
const DEFAULT_TOKEN = Env.fetch("DEFAULT_TOKEN", true);
|
|
28
|
+
const ISSUER = Env.fetch("JWT_ISSUER", true);
|
|
29
|
+
const SECRET = Env.fetch("JWT_SECRET", true);
|
|
30
|
+
|
|
31
|
+
const token = extract_token(request.headers);
|
|
32
|
+
if (token === DEFAULT_TOKEN) {
|
|
33
|
+
// inter-service requests
|
|
34
|
+
request.is_service_request = true;
|
|
35
|
+
// typically scope requests by tenant_id
|
|
36
|
+
request.tenant_id = request.body.tenant_id || request.query.tenant_id || { $exists: true };
|
|
61
37
|
return next();
|
|
62
38
|
}
|
|
63
39
|
|
|
64
|
-
const
|
|
65
|
-
request.
|
|
40
|
+
const { tenant_id, is_admin } = await jwt.verify(token, SECRET, { issuer: ISSUER });
|
|
41
|
+
request.is_admin = !!is_admin;
|
|
42
|
+
request.tenant_id = is_admin ? { $exists: true } : tenant_id;
|
|
43
|
+
|
|
66
44
|
next();
|
|
67
45
|
} catch (e) {
|
|
68
|
-
|
|
69
|
-
|
|
46
|
+
authLogger.error(e, "JWTAuth");
|
|
47
|
+
return response.status(403).json(Errors.UNAUTHORIZED);
|
|
70
48
|
}
|
|
71
49
|
};
|
|
72
50
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
51
|
+
const authenticateParamKey = async (request, response, next) => {
|
|
52
|
+
try {
|
|
53
|
+
const { api_key: token } = request.params;
|
|
54
|
+
request.tenant_id = await verifyAPIKey(token);
|
|
55
|
+
next();
|
|
56
|
+
} catch (e) {
|
|
57
|
+
authLogger.error(e, "authenticateParamKey");
|
|
58
|
+
return response.status(403).json(Errors.UNAUTHORIZED);
|
|
59
|
+
}
|
|
82
60
|
};
|
|
83
61
|
|
|
84
|
-
const
|
|
62
|
+
const authenticateBearerKey = async (request, response, next) => {
|
|
85
63
|
try {
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
return next(rootService.process_failed_response("Unauthorized", 403));
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const [, token] = authorization.split(" ");
|
|
92
|
-
if (!token) {
|
|
93
|
-
return next(rootService.process_failed_response("Unauthorized", 403));
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (token === GM_TOKEN) {
|
|
97
|
-
request.tenant_id = { $exists: true };
|
|
98
|
-
return next();
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const verified_data = await jwt.verify(token, JWT_SECRET, {
|
|
102
|
-
issuer: JWT_ISSUER,
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
const { tenant_id, is_admin } = verified_data;
|
|
106
|
-
request.tenant_id = tenant_id;
|
|
107
|
-
if (is_admin) process_request(request);
|
|
108
|
-
|
|
64
|
+
const token = extract_token(request.headers);
|
|
65
|
+
request.tenant_id = await verifyAPIKey(token);
|
|
109
66
|
next();
|
|
110
67
|
} catch (e) {
|
|
111
|
-
|
|
112
|
-
|
|
68
|
+
authLogger.error(e, "authenticateBearerKey");
|
|
69
|
+
return response.status(403).json(Errors.UNAUTHORIZED);
|
|
113
70
|
}
|
|
114
71
|
};
|
|
115
72
|
|
|
116
|
-
module.exports = {
|
|
73
|
+
module.exports = { authenticateBearerKey, authenticateParamKey, JWTAuth };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** **/
|
|
2
|
+
const { ProcessLogger } = require("./logger");
|
|
3
|
+
const HTTPLogger = new ProcessLogger("HTTPSetup");
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
handle404(_, __, next) {
|
|
7
|
+
const return_data = {
|
|
8
|
+
status_code: 404,
|
|
9
|
+
success: false,
|
|
10
|
+
error: "Resource not found",
|
|
11
|
+
payload: null,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
next(return_data);
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
handleError(error, __, response, ____) {
|
|
18
|
+
// Log errors
|
|
19
|
+
if (error.error) {
|
|
20
|
+
HTTPLogger.info(error.error, "handleError");
|
|
21
|
+
} else {
|
|
22
|
+
HTTPLogger.error(error, "handleError");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// return error
|
|
26
|
+
return response.status(error.status_code || 500).json({
|
|
27
|
+
success: false,
|
|
28
|
+
status_code: error.status_code || 500,
|
|
29
|
+
error: error.error || "Internal Server Error",
|
|
30
|
+
payload: null,
|
|
31
|
+
});
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
processResponse(request, response, next) {
|
|
35
|
+
if (!request.payload) return next();
|
|
36
|
+
|
|
37
|
+
const { status_code } = request.payload;
|
|
38
|
+
return response.status(status_code).json(request.payload);
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
setupRequest(request, response, next) {
|
|
42
|
+
request.headers["access-control-allow-origin"] = "*";
|
|
43
|
+
request.headers["access-control-allow-headers"] = "*";
|
|
44
|
+
|
|
45
|
+
if (request.method === "OPTIONS") {
|
|
46
|
+
request.headers["access-control-allow-methods"] = "GET, POST, PUT, PATCH, DELETE";
|
|
47
|
+
response.status(200).json();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
next();
|
|
51
|
+
},
|
|
52
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author Oguntuberu Nathan O. <nateoguns.work@gmail.com>
|
|
3
|
+
**/
|
|
4
|
+
|
|
5
|
+
const Env = require("../env");
|
|
6
|
+
const { randomUUID } = require("crypto");
|
|
7
|
+
const { Logtail } = require("@logtail/node");
|
|
8
|
+
const LOGTAIL_SECRET = Env.fetch("LOGTAIL_SECRET", true);
|
|
9
|
+
const logtail = new Logtail(LOGTAIL_SECRET);
|
|
10
|
+
|
|
11
|
+
function RequestLogger() {
|
|
12
|
+
const app_name = Env.fetch("APP_NAME", true);
|
|
13
|
+
return (request, response, next) => {
|
|
14
|
+
if (!request.request_id) request.request_id = randomUUID();
|
|
15
|
+
|
|
16
|
+
//
|
|
17
|
+
const {
|
|
18
|
+
query,
|
|
19
|
+
params,
|
|
20
|
+
headers: { host, origin, "user-agent": user_agent, "sec-ch-ua-platform": os, referer },
|
|
21
|
+
request_id,
|
|
22
|
+
tenant_id,
|
|
23
|
+
} = request;
|
|
24
|
+
|
|
25
|
+
response.on("finish", () => {
|
|
26
|
+
const {
|
|
27
|
+
_parsedUrl: { pathname },
|
|
28
|
+
httpVersion,
|
|
29
|
+
_startTime,
|
|
30
|
+
_remoteAddress,
|
|
31
|
+
payload,
|
|
32
|
+
} = response.req;
|
|
33
|
+
const { statusCode, statusMessage } = response.req.res;
|
|
34
|
+
const duration = Date.now() - Date.parse(_startTime);
|
|
35
|
+
const log = {
|
|
36
|
+
app_name,
|
|
37
|
+
request_id,
|
|
38
|
+
tenant_id,
|
|
39
|
+
query,
|
|
40
|
+
params,
|
|
41
|
+
host,
|
|
42
|
+
origin,
|
|
43
|
+
user_agent,
|
|
44
|
+
os,
|
|
45
|
+
referer,
|
|
46
|
+
httpVersion,
|
|
47
|
+
_remoteAddress,
|
|
48
|
+
pathname,
|
|
49
|
+
duration,
|
|
50
|
+
type: "request",
|
|
51
|
+
status_code: payload ? payload.status_code : statusCode,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
let error = payload ? payload.error : statusMessage;
|
|
55
|
+
if (error) {
|
|
56
|
+
log.error = error;
|
|
57
|
+
logtail.error(pathname, log);
|
|
58
|
+
} else {
|
|
59
|
+
logtail.info(pathname, log);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
logtail.flush();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
next();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
class ProcessLogger {
|
|
70
|
+
constructor(service = "System") {
|
|
71
|
+
this.service = service;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
error(error, method = "unspecified_method") {
|
|
75
|
+
logtail.error(`${this.service}:${method}:${error.message}`, {
|
|
76
|
+
app_name: Env.fetch("APP_NAME", true),
|
|
77
|
+
type: "process",
|
|
78
|
+
message: error.message,
|
|
79
|
+
trace: error.stack,
|
|
80
|
+
});
|
|
81
|
+
logtail.flush();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
info(info, method = "unspecified_method") {
|
|
85
|
+
logtail.info(`${this.service}:${method}:${info}`, {
|
|
86
|
+
app_name: Env.fetch("APP_NAME", true),
|
|
87
|
+
type: "process",
|
|
88
|
+
message: info,
|
|
89
|
+
});
|
|
90
|
+
logtail.flush();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = { RequestLogger, ProcessLogger };
|
|
95
|
+
|
package/lib/query.js
CHANGED
|
@@ -1,29 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
const check_if_value_is_integer = (value) => {
|
|
3
|
-
return !isNaN(Number(value))
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
exports.build_query = (options) => {
|
|
1
|
+
const buildQuery = (options) => {
|
|
7
2
|
const seek_conditions = {}
|
|
8
|
-
const sort_condition = options.sort_by
|
|
9
|
-
|
|
10
|
-
: ''
|
|
11
|
-
const fields_to_return = options.return_only
|
|
12
|
-
? this.build_return_fields_string(options.return_only)
|
|
13
|
-
: ''
|
|
3
|
+
const sort_condition = options.sort_by ? buildSortOrderString(options.sort_by) : ''
|
|
4
|
+
const fields_to_return = options.return_only ? buildReturnFieldsString(options.return_only) : ''
|
|
14
5
|
const count = options.count || false
|
|
15
6
|
|
|
16
|
-
|
|
17
|
-
let limit = Number.MAX_SAFE_INTEGER
|
|
18
|
-
|
|
19
|
-
if (options.page && options.population) {
|
|
20
|
-
const pagination = this.determine_pagination(
|
|
21
|
-
options.page,
|
|
22
|
-
options.population
|
|
23
|
-
)
|
|
24
|
-
limit = pagination.limit
|
|
25
|
-
skip = pagination.skip
|
|
26
|
-
}
|
|
7
|
+
const { skip, limit } = determinePagination(options.page, options.population)
|
|
27
8
|
|
|
28
9
|
/** Delete sort and return fields */
|
|
29
10
|
delete options.count
|
|
@@ -33,24 +14,17 @@ exports.build_query = (options) => {
|
|
|
33
14
|
delete options.sort_by
|
|
34
15
|
|
|
35
16
|
Object.keys(options).forEach((field) => {
|
|
36
|
-
const field_value =
|
|
37
|
-
typeof options[field] === 'number'
|
|
38
|
-
? options[field].toString()
|
|
39
|
-
: options[field]
|
|
17
|
+
const field_value = options[field].toLowerCase()
|
|
40
18
|
let condition
|
|
41
19
|
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
condition = this.build_range_query(field_value)
|
|
49
|
-
} else {
|
|
50
|
-
condition = this.build_or_query(field_value)
|
|
51
|
-
}
|
|
20
|
+
if (field_value.includes(':')) {
|
|
21
|
+
condition = buildInQuery(field_value)
|
|
22
|
+
} else if (field_value.includes('!')) {
|
|
23
|
+
condition = buildNorQuery(field_value)
|
|
24
|
+
} else if (field_value.includes('~')) {
|
|
25
|
+
condition = buildRangeQuery(field_value)
|
|
52
26
|
} else {
|
|
53
|
-
condition = field_value
|
|
27
|
+
condition = buildOrQuery(field_value)
|
|
54
28
|
}
|
|
55
29
|
|
|
56
30
|
seek_conditions[field] = { ...condition }
|
|
@@ -66,74 +40,71 @@ exports.build_query = (options) => {
|
|
|
66
40
|
}
|
|
67
41
|
}
|
|
68
42
|
|
|
69
|
-
|
|
43
|
+
const buildInQuery = (value) => {
|
|
70
44
|
const values = value.split(':')
|
|
71
45
|
return {
|
|
72
|
-
$in: [
|
|
73
|
-
...values.map((value) =>
|
|
74
|
-
check_if_value_is_integer(value) ? Number(value) : value
|
|
75
|
-
)
|
|
76
|
-
]
|
|
46
|
+
$in: [...values]
|
|
77
47
|
}
|
|
78
48
|
}
|
|
79
49
|
|
|
80
|
-
|
|
50
|
+
const buildNorQuery = (value) => {
|
|
81
51
|
const values = value.split('!')
|
|
82
52
|
return {
|
|
83
|
-
$nin: [
|
|
84
|
-
...values
|
|
85
|
-
.slice(1)
|
|
86
|
-
.map((value) =>
|
|
87
|
-
check_if_value_is_integer(value) ? Number(value) : value
|
|
88
|
-
)
|
|
89
|
-
]
|
|
53
|
+
$nin: [...values.slice(1)]
|
|
90
54
|
}
|
|
91
55
|
}
|
|
92
56
|
|
|
93
|
-
|
|
57
|
+
const buildOrQuery = (value) => {
|
|
94
58
|
const values = value.split(',')
|
|
95
59
|
return {
|
|
96
|
-
$in: [
|
|
97
|
-
...values.map((value) =>
|
|
98
|
-
check_if_value_is_integer(value) ? Number(value) : value
|
|
99
|
-
)
|
|
100
|
-
]
|
|
60
|
+
$in: [...values]
|
|
101
61
|
}
|
|
102
62
|
}
|
|
103
63
|
|
|
104
|
-
|
|
105
|
-
const
|
|
64
|
+
const buildRangeQuery = (value) => {
|
|
65
|
+
const [min, max] = value.split('~')
|
|
106
66
|
return {
|
|
107
|
-
$gte:
|
|
108
|
-
$lte:
|
|
67
|
+
$gte: min ? Number(min) : Number.MIN_SAFE_INTEGER,
|
|
68
|
+
$lte: max ? Number(max) : Number.MAX_SAFE_INTEGER
|
|
109
69
|
}
|
|
110
70
|
}
|
|
111
71
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
return fields.join(' ')
|
|
72
|
+
const buildReturnFieldsString = (value) => {
|
|
73
|
+
return value.replace(/,/gi, ' ')
|
|
115
74
|
}
|
|
116
75
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
return values.join(' ')
|
|
76
|
+
const buildSortOrderString = (value) => {
|
|
77
|
+
return value.replace(/,/gi, ' ')
|
|
120
78
|
}
|
|
121
79
|
|
|
122
|
-
|
|
80
|
+
const buildWildcardOptions = (key_list, value) => {
|
|
123
81
|
const keys = key_list.split(',')
|
|
82
|
+
|
|
124
83
|
return {
|
|
125
84
|
$or: keys.map((key) => ({
|
|
126
85
|
[key]: {
|
|
127
|
-
$regex:
|
|
86
|
+
$regex: value || '',
|
|
128
87
|
$options: 'i'
|
|
129
88
|
}
|
|
130
89
|
}))
|
|
131
90
|
}
|
|
132
91
|
}
|
|
133
92
|
|
|
134
|
-
|
|
93
|
+
const determinePagination = (page = 0, population = Number.MAX_SAFE_INTEGER) => {
|
|
135
94
|
return {
|
|
136
95
|
limit: Number(population),
|
|
137
96
|
skip: page * population
|
|
138
97
|
}
|
|
139
98
|
}
|
|
99
|
+
|
|
100
|
+
module.exports = {
|
|
101
|
+
buildInQuery,
|
|
102
|
+
buildNorQuery,
|
|
103
|
+
buildOrQuery,
|
|
104
|
+
buildQuery,
|
|
105
|
+
buildRangeQuery,
|
|
106
|
+
buildReturnFieldsString,
|
|
107
|
+
buildSortOrderString,
|
|
108
|
+
buildWildcardOptions,
|
|
109
|
+
determinePagination
|
|
110
|
+
}
|
package/package.json
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@go-mailer/jarvis",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"main": "index.js",
|
|
5
|
-
"repository": "git@
|
|
5
|
+
"repository": "git@github.com:go-mailer-ltd/jarvis-node.git",
|
|
6
6
|
"author": "Nathan Oguntuberu <nateoguns.work@gmail.com>",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@logtail/node": "^0.3.3",
|
|
10
10
|
"axios": "^1.3.4",
|
|
11
11
|
"dotenv": "^16.0.3",
|
|
12
|
-
"jsonwebtoken": "^9.0.0"
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
"jsonwebtoken": "^9.0.0"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"chai": "^4.3.7",
|
|
16
|
+
"mocha": "^10.2.0"
|
|
15
17
|
}
|
|
16
18
|
}
|
package/lib/logger.js
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @author Oguntuberu Nathan O. <nateoguns.work@gmail.com>
|
|
3
|
-
**/
|
|
4
|
-
|
|
5
|
-
const envVar = require('./env')
|
|
6
|
-
const { Logtail } = require("@logtail/node");
|
|
7
|
-
const logtail = new Logtail("oTNABUxtJb6erTRpZ2jhJhEs");
|
|
8
|
-
|
|
9
|
-
const request_logger = (request, __, next) => {
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/** MORGAN */
|
|
14
|
-
const { createWriteStream } = require('fs')
|
|
15
|
-
const { resolve } = require('path')
|
|
16
|
-
|
|
17
|
-
/** REQUEST LOG */
|
|
18
|
-
const morgan = require('morgan')
|
|
19
|
-
const request_log_format = '[:date[web] :remote-addr :remote-user ] :method :url HTTP/:http-version :referrer - :user-agent | :status :response-time ms'
|
|
20
|
-
|
|
21
|
-
const request_log_stream = createWriteStream(resolve(__dirname, '../../logs/request.log'), { flags: 'a' })
|
|
22
|
-
const morgan_logger = morgan(request_log_format, { stream: request_log_stream })
|
|
23
|
-
|
|
24
|
-
/** WINSTON */
|
|
25
|
-
const {
|
|
26
|
-
createLogger,
|
|
27
|
-
format,
|
|
28
|
-
transports
|
|
29
|
-
} = require('winston')
|
|
30
|
-
|
|
31
|
-
const {
|
|
32
|
-
colorize,
|
|
33
|
-
combine,
|
|
34
|
-
printf,
|
|
35
|
-
timestamp
|
|
36
|
-
} = format
|
|
37
|
-
|
|
38
|
-
const log_transports = {
|
|
39
|
-
client_log: new transports.File({ level: 'error', filename: 'logs/client.log' }),
|
|
40
|
-
console: new transports.Console({ level: 'warn' }),
|
|
41
|
-
combined_log: new transports.File({ level: 'info', filename: 'logs/combined.log' }),
|
|
42
|
-
error_log: new transports.File({ level: 'error', filename: 'logs/error.log' }),
|
|
43
|
-
exception_log: new transports.File({ filename: 'logs/exception.log' }),
|
|
44
|
-
mailer_log: new transports.File({ level: 'error', filename: 'logs/mailer.log' }),
|
|
45
|
-
stream_log: new transports.File({ level: 'error', filename: 'logs/stream.log' })
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const log_format = printf(({ level, message, timestamp }) => `[${timestamp} : ${level}] - ${message}`)
|
|
49
|
-
|
|
50
|
-
const logger = createLogger({
|
|
51
|
-
transports: [
|
|
52
|
-
log_transports.console,
|
|
53
|
-
log_transports.combined_log,
|
|
54
|
-
log_transports.error_log
|
|
55
|
-
],
|
|
56
|
-
exceptionHandlers: [
|
|
57
|
-
log_transports.exception_log
|
|
58
|
-
],
|
|
59
|
-
exitOnError: false,
|
|
60
|
-
format: combine(
|
|
61
|
-
colorize(),
|
|
62
|
-
timestamp(),
|
|
63
|
-
log_format
|
|
64
|
-
)
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
const client_logger = createLogger({
|
|
68
|
-
transports: [
|
|
69
|
-
log_transports.console,
|
|
70
|
-
log_transports.client_log
|
|
71
|
-
],
|
|
72
|
-
exitOnError: false,
|
|
73
|
-
format: combine(
|
|
74
|
-
colorize(),
|
|
75
|
-
timestamp(),
|
|
76
|
-
log_format
|
|
77
|
-
)
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
const mail_logger = createLogger({
|
|
81
|
-
transports: [
|
|
82
|
-
log_transports.console,
|
|
83
|
-
log_transports.mailer_log
|
|
84
|
-
],
|
|
85
|
-
exitOnError: false,
|
|
86
|
-
format: combine(
|
|
87
|
-
colorize(),
|
|
88
|
-
timestamp(),
|
|
89
|
-
log_format
|
|
90
|
-
)
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
const stream_logger = createLogger({
|
|
94
|
-
transports: [
|
|
95
|
-
log_transports.console,
|
|
96
|
-
log_transports.stream_log
|
|
97
|
-
],
|
|
98
|
-
exitOnError: false,
|
|
99
|
-
format: combine(
|
|
100
|
-
colorize(),
|
|
101
|
-
timestamp(),
|
|
102
|
-
log_format
|
|
103
|
-
)
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
const log = console.log
|
|
107
|
-
const app_logger = (service = 'System') => {
|
|
108
|
-
const console = (message, method = 'unspecified_method') => {
|
|
109
|
-
const formatted_message = `[${service} ${method}()]: ${message}`
|
|
110
|
-
log(formatted_message)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const error = (message, method = 'unspecified_method') => {
|
|
114
|
-
const formatted_message = `[${service} ${method}()]: ${message}`
|
|
115
|
-
logger.error(`${formatted_message}`)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const info = (message, method = 'unspecified_method') => {
|
|
119
|
-
const formatted_message = `[${service} ${method}()]: ${message}`
|
|
120
|
-
logger.info(`${formatted_message} ${message}`)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return { console, error, info }
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
module.exports = { app_logger, client_logger, logger, mail_logger, morgan: morgan_logger, stream_logger }
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @author Oguntuberu Nathan O. <nateoguns.work@gmail.com>
|
|
3
|
-
**/
|
|
4
|
-
const { app_logger } = require('../utilities/logger')
|
|
5
|
-
const logger = app_logger('HTTP Middleware')
|
|
6
|
-
|
|
7
|
-
module.exports = {
|
|
8
|
-
handle_404 (request, response, next) {
|
|
9
|
-
const return_data = {
|
|
10
|
-
status_code: 404,
|
|
11
|
-
success: false,
|
|
12
|
-
error: 'Resource not found',
|
|
13
|
-
payload: null
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
next(return_data)
|
|
17
|
-
},
|
|
18
|
-
|
|
19
|
-
handle_error (error, request, response, next) {
|
|
20
|
-
// Log errors
|
|
21
|
-
logger.error(error.error || error.message, 'handle_error')
|
|
22
|
-
|
|
23
|
-
// return error
|
|
24
|
-
return response.status(error.status_code || 500).json({
|
|
25
|
-
success: false,
|
|
26
|
-
status_code: error.status_code || 500,
|
|
27
|
-
error: error.error || 'Internal Server Error',
|
|
28
|
-
payload: null
|
|
29
|
-
})
|
|
30
|
-
},
|
|
31
|
-
|
|
32
|
-
process_response (request, response, next) {
|
|
33
|
-
if (!request.payload) return next()
|
|
34
|
-
|
|
35
|
-
const { status_code } = request.payload
|
|
36
|
-
return response.status(status_code).json(request.payload)
|
|
37
|
-
},
|
|
38
|
-
|
|
39
|
-
setup_request (request, response, next) {
|
|
40
|
-
request.headers['access-control-allow-origin'] = '*'
|
|
41
|
-
request.headers['access-control-allow-headers'] = '*'
|
|
42
|
-
|
|
43
|
-
if (request.method === 'OPTIONS') {
|
|
44
|
-
request.headers['access-control-allow-methods'] = 'GET, POST, PUT, PATCH, DELETE'
|
|
45
|
-
response.status(200).json()
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
next()
|
|
49
|
-
}
|
|
50
|
-
}
|
package/test.js
DELETED
|
File without changes
|