@go-mailer/jarvis 5.0.4 → 5.0.6
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/.eslintignore +1 -0
- package/.eslintrc.yml +10 -0
- package/index.js +5 -3
- package/lib/clients/go-flags.js +12 -12
- package/lib/clients/iam.js +33 -19
- package/lib/constants/automation.js +1 -1
- package/lib/env.js +12 -12
- package/lib/flag.js +4 -4
- package/lib/middlewares/auth.js +110 -95
- package/lib/middlewares/http.js +26 -26
- package/lib/middlewares/logger.js +29 -31
- package/lib/query.js +4 -4
- package/lib/redis/index.js +2 -2
- package/lib/utilitiy/chart.js +69 -0
- package/lib/utilitiy/date.js +204 -0
- package/lib/utilitiy/index.js +9 -0
- package/lib/utilitiy/number.js +7 -0
- package/package.json +8 -2
package/.eslintignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
test/
|
package/.eslintrc.yml
ADDED
package/index.js
CHANGED
|
@@ -6,15 +6,17 @@ const HTTP = require('./lib/middlewares/http')
|
|
|
6
6
|
const Authenticator = require('./lib/middlewares/auth')
|
|
7
7
|
const AutomationConstants = require('./lib/constants/automation')
|
|
8
8
|
const { RequestLogger, ProcessLogger } = require('./lib/middlewares/logger')
|
|
9
|
+
const Utility = require('./lib/utilitiy/index')
|
|
9
10
|
|
|
10
|
-
module.exports = {
|
|
11
|
+
module.exports = {
|
|
11
12
|
Authenticator,
|
|
12
13
|
AutomationConstants,
|
|
13
14
|
EnvVar,
|
|
14
15
|
FeatureFlag,
|
|
15
16
|
HTTP,
|
|
16
17
|
ProcessLogger,
|
|
17
|
-
RequestLogger,
|
|
18
18
|
QueryBuilder,
|
|
19
|
-
Redis
|
|
19
|
+
Redis,
|
|
20
|
+
RequestLogger,
|
|
21
|
+
Utility
|
|
20
22
|
}
|
package/lib/clients/go-flags.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
const axios = require(
|
|
2
|
-
const Env = require(
|
|
3
|
-
const BASE_URI = Env.fetch(
|
|
4
|
-
const API_KEY = Env.fetch(
|
|
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
5
|
|
|
6
6
|
const verifyFeatureFlag = async (flag_name, criteria = {}, environment = '') => {
|
|
7
7
|
const { error, payload } = (
|
|
@@ -9,19 +9,19 @@ const verifyFeatureFlag = async (flag_name, criteria = {}, environment = '') =>
|
|
|
9
9
|
`${BASE_URI}/api/v1/flags/${flag_name}`,
|
|
10
10
|
{
|
|
11
11
|
payload: criteria,
|
|
12
|
-
environment: environment || Env.fetch(
|
|
12
|
+
environment: environment || Env.fetch('GO_FLAGS_ENV', true)
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
headers: {
|
|
16
|
-
authorization: `Bearer ${API_KEY}
|
|
17
|
-
}
|
|
16
|
+
authorization: `Bearer ${API_KEY}`
|
|
17
|
+
}
|
|
18
18
|
}
|
|
19
19
|
)
|
|
20
|
-
).data
|
|
20
|
+
).data
|
|
21
21
|
|
|
22
|
-
if (error) throw new Error(error)
|
|
22
|
+
if (error) throw new Error(error)
|
|
23
23
|
|
|
24
|
-
return payload.is_permitted
|
|
25
|
-
}
|
|
24
|
+
return payload.is_permitted
|
|
25
|
+
}
|
|
26
26
|
|
|
27
|
-
module.exports = { verifyFeatureFlag }
|
|
27
|
+
module.exports = { verifyFeatureFlag }
|
package/lib/clients/iam.js
CHANGED
|
@@ -1,21 +1,35 @@
|
|
|
1
|
-
const axios = require(
|
|
2
|
-
const Env = require(
|
|
3
|
-
const IAM_URI = Env.fetch(
|
|
4
|
-
const DEFAULT_TOKEN = Env.fetch(
|
|
1
|
+
const axios = require('axios').default
|
|
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 checkAuthority = async ({ action, resource, user_id, tenant_id }) => {
|
|
7
|
+
const { error, payload } = (
|
|
8
|
+
await axios.post(`${IAM_URI}/authorizer`, {
|
|
9
|
+
action,
|
|
10
|
+
resource,
|
|
11
|
+
tenant_id,
|
|
12
|
+
user_id
|
|
13
|
+
})
|
|
14
|
+
).data
|
|
15
|
+
|
|
16
|
+
if (error || !payload.is_permitted) throw new Error('Unauthorized')
|
|
17
|
+
return payload.is_permitted
|
|
18
|
+
}
|
|
5
19
|
|
|
6
20
|
const verifyAPIKey = async (key) => {
|
|
7
21
|
const { error, payload } = (
|
|
8
22
|
await axios.get(`${IAM_URI}/keys/verify/${key}`, {
|
|
9
23
|
headers: {
|
|
10
|
-
authorization: `Bearer ${DEFAULT_TOKEN}
|
|
11
|
-
}
|
|
24
|
+
authorization: `Bearer ${DEFAULT_TOKEN}`
|
|
25
|
+
}
|
|
12
26
|
})
|
|
13
|
-
).data
|
|
27
|
+
).data
|
|
14
28
|
|
|
15
|
-
if (error) throw new Error(
|
|
29
|
+
if (error) throw new Error('Unauthorized')
|
|
16
30
|
|
|
17
|
-
return payload.org_id
|
|
18
|
-
}
|
|
31
|
+
return payload.org_id
|
|
32
|
+
}
|
|
19
33
|
|
|
20
34
|
const verifyFeatureFlag = async (flag_name, criteria = {}) => {
|
|
21
35
|
const { error, payload } = (
|
|
@@ -23,20 +37,20 @@ const verifyFeatureFlag = async (flag_name, criteria = {}) => {
|
|
|
23
37
|
`${IAM_URI}/flags`,
|
|
24
38
|
{
|
|
25
39
|
criteria,
|
|
26
|
-
environment: Env.fetch(
|
|
27
|
-
name: flag_name
|
|
40
|
+
environment: Env.fetch('NODE_ENV'),
|
|
41
|
+
name: flag_name
|
|
28
42
|
},
|
|
29
43
|
{
|
|
30
44
|
headers: {
|
|
31
|
-
authorization: `Bearer ${DEFAULT_TOKEN}
|
|
32
|
-
}
|
|
45
|
+
authorization: `Bearer ${DEFAULT_TOKEN}`
|
|
46
|
+
}
|
|
33
47
|
}
|
|
34
48
|
)
|
|
35
|
-
).data
|
|
49
|
+
).data
|
|
36
50
|
|
|
37
|
-
if (error) throw new Error(error)
|
|
51
|
+
if (error) throw new Error(error)
|
|
38
52
|
|
|
39
|
-
return payload.is_permitted
|
|
40
|
-
}
|
|
53
|
+
return payload.is_permitted
|
|
54
|
+
}
|
|
41
55
|
|
|
42
|
-
module.exports = { verifyAPIKey, verifyFeatureFlag }
|
|
56
|
+
module.exports = { checkAuthority, verifyAPIKey, verifyFeatureFlag }
|
|
@@ -7,7 +7,7 @@ module.exports = {
|
|
|
7
7
|
EVENT_OPENED_TRANSACTIONAL: 'opened_transactional',
|
|
8
8
|
EVENT_SUBSCRIPTION: 'joined_audience',
|
|
9
9
|
EVENT_UNSUBSCRIPTION: 'left_audience',
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
//
|
|
12
12
|
EFFECT_REMOVE_CONTACT: 'delete_contact',
|
|
13
13
|
EFFECT_SEND_TRANSACTIONAL: 'send_transactional_email',
|
package/lib/env.js
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
require(
|
|
1
|
+
require('dotenv').config()
|
|
2
2
|
|
|
3
3
|
/** */
|
|
4
4
|
class EnvVar {
|
|
5
|
-
constructor() {
|
|
6
|
-
this.config = {}
|
|
5
|
+
constructor () {
|
|
6
|
+
this.config = {}
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
set(config = {}) {
|
|
9
|
+
set (config = {}) {
|
|
10
10
|
this.config = {
|
|
11
11
|
...this.config,
|
|
12
|
-
...config
|
|
13
|
-
}
|
|
12
|
+
...config
|
|
13
|
+
}
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
fetch(var_name =
|
|
17
|
-
if (!var_name) throw new Error(
|
|
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
|
|
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
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
module.exports = new EnvVar()
|
|
24
|
+
module.exports = new EnvVar()
|
package/lib/flag.js
CHANGED
|
@@ -4,10 +4,10 @@ const flagLogger = new ProcessLogger('FeatureFlag')
|
|
|
4
4
|
|
|
5
5
|
const verify = async (flag_name = '', criteria = {}) => {
|
|
6
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)
|
|
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
11
|
return result
|
|
12
12
|
} catch (e) {
|
|
13
13
|
flagLogger.error(e, 'verify')
|
package/lib/middlewares/auth.js
CHANGED
|
@@ -2,98 +2,113 @@
|
|
|
2
2
|
* User Authentication Middleware
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
5
|
+
const jwt = require('jsonwebtoken')
|
|
6
|
+
const Env = require('../env')
|
|
7
|
+
const Errors = require('./errors')
|
|
8
|
+
const { ProcessLogger } = require('./logger')
|
|
9
|
+
const { checkAuthority, verifyAPIKey } = require('../clients/iam')
|
|
10
|
+
const authLogger = new ProcessLogger('Authenticator')
|
|
11
|
+
|
|
12
|
+
// helpers
|
|
13
|
+
const extractToken = (headers) => {
|
|
14
|
+
const { authorization } = headers
|
|
15
|
+
if (!authorization) throw new Error('Unauthorized')
|
|
16
|
+
|
|
17
|
+
const [, token] = authorization.split(' ')
|
|
18
|
+
if (!token) throw new Error('Unauthorized')
|
|
19
|
+
|
|
20
|
+
return token
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const extractId = (request, key) => {
|
|
24
|
+
const { params, query, body } = request
|
|
25
|
+
let id = { $exists: true }
|
|
26
|
+
if (query[key]) id = query[key]
|
|
27
|
+
if (params[key]) id = params[key]
|
|
28
|
+
if (body[key]) id = body[key]
|
|
29
|
+
|
|
30
|
+
return id
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// main
|
|
34
|
+
|
|
35
|
+
const authenticateAdmin = async (request, response, next) => {
|
|
36
|
+
const { is_admin } = request
|
|
37
|
+
if (!is_admin) return response.status(403).json(Errors.UNAUTHORIZED)
|
|
38
|
+
|
|
39
|
+
next()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const authenticateBearerKey = async (request, response, next) => {
|
|
43
|
+
try {
|
|
44
|
+
const token = extractToken(request.headers)
|
|
45
|
+
request.tenant_id = await verifyAPIKey(token)
|
|
46
|
+
next()
|
|
47
|
+
} catch (e) {
|
|
48
|
+
authLogger.error(e, 'authenticateBearerKey')
|
|
49
|
+
return response.status(403).json(Errors.UNAUTHORIZED)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const authenticateParamKey = async (request, response, next) => {
|
|
54
|
+
try {
|
|
55
|
+
const { api_key: token } = request.params
|
|
56
|
+
request.tenant_id = await verifyAPIKey(token)
|
|
57
|
+
next()
|
|
58
|
+
} catch (e) {
|
|
59
|
+
authLogger.error(e, 'authenticateParamKey')
|
|
60
|
+
return response.status(403).json(Errors.UNAUTHORIZED)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const authenticateUser = async (request, response, next) => {
|
|
65
|
+
try {
|
|
66
|
+
// env vars
|
|
67
|
+
const DEFAULT_TOKEN = Env.fetch('DEFAULT_TOKEN', true)
|
|
68
|
+
const ISSUER = Env.fetch('JWT_ISSUER', true)
|
|
69
|
+
const SECRET = Env.fetch('JWT_SECRET', true)
|
|
70
|
+
|
|
71
|
+
const token = extractToken(request.headers)
|
|
72
|
+
if (token === DEFAULT_TOKEN) {
|
|
73
|
+
// inter-service requests
|
|
74
|
+
request.is_service_request = true
|
|
75
|
+
// typically scope requests by tenant_id
|
|
76
|
+
request.tenant_id = request.body.tenant_id || request.query.tenant_id || { $exists: true }
|
|
77
|
+
request.user_id = request.body.user_id || request.query.user_id || { $exists: true }
|
|
78
|
+
return next()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const { id: user_id, is_admin, tenant_id } = await jwt.verify(token, SECRET, { issuer: ISSUER })
|
|
82
|
+
request.is_admin = !!is_admin
|
|
83
|
+
request.user_id = is_admin ? extractId(request, 'user_id') : user_id
|
|
84
|
+
request.tenant_id = is_admin ? extractId(request, 'tenant_id') : tenant_id
|
|
85
|
+
|
|
86
|
+
next()
|
|
87
|
+
} catch (e) {
|
|
88
|
+
authLogger.error(e, 'authenticateUser')
|
|
89
|
+
return response.status(403).json(Errors.UNAUTHORIZED)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const authorizeUser = async ({ action, resource }) => {
|
|
94
|
+
return async (request, response, next) => {
|
|
95
|
+
try {
|
|
96
|
+
const { is_admin, tenant_id, user_id } = request
|
|
97
|
+
if (is_admin) next()
|
|
98
|
+
|
|
99
|
+
await checkAuthority({ action, resource, tenant_id, user_id })
|
|
100
|
+
next()
|
|
101
|
+
} catch (e) {
|
|
102
|
+
authLogger.error(e, 'authorizeUser')
|
|
103
|
+
return response.status(403).json(Errors.UNAUTHORIZED)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = {
|
|
109
|
+
authenticateAdmin,
|
|
110
|
+
authenticateBearerKey,
|
|
111
|
+
authenticateParamKey,
|
|
112
|
+
authenticateUser,
|
|
113
|
+
authorizeUser
|
|
114
|
+
}
|
package/lib/middlewares/http.js
CHANGED
|
@@ -1,52 +1,52 @@
|
|
|
1
1
|
/** **/
|
|
2
|
-
const { ProcessLogger } = require(
|
|
3
|
-
const HTTPLogger = new ProcessLogger(
|
|
2
|
+
const { ProcessLogger } = require('./logger')
|
|
3
|
+
const HTTPLogger = new ProcessLogger('HTTPSetup')
|
|
4
4
|
|
|
5
5
|
module.exports = {
|
|
6
|
-
handle404(_, __, next) {
|
|
6
|
+
handle404 (_, __, next) {
|
|
7
7
|
const return_data = {
|
|
8
8
|
status_code: 404,
|
|
9
9
|
success: false,
|
|
10
|
-
error:
|
|
11
|
-
payload: null
|
|
12
|
-
}
|
|
10
|
+
error: 'Resource not found',
|
|
11
|
+
payload: null
|
|
12
|
+
}
|
|
13
13
|
|
|
14
|
-
next(return_data)
|
|
14
|
+
next(return_data)
|
|
15
15
|
},
|
|
16
16
|
|
|
17
|
-
handleError(error, __, response, ____) {
|
|
17
|
+
handleError (error, __, response, ____) {
|
|
18
18
|
// Log errors
|
|
19
19
|
if (error.error) {
|
|
20
|
-
HTTPLogger.info(error.error,
|
|
20
|
+
HTTPLogger.info(error.error, 'handleError')
|
|
21
21
|
} else {
|
|
22
|
-
HTTPLogger.error(error,
|
|
22
|
+
HTTPLogger.error(error, 'handleError')
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
// return error
|
|
26
26
|
return response.status(error.status_code || 500).json({
|
|
27
27
|
success: false,
|
|
28
28
|
status_code: error.status_code || 500,
|
|
29
|
-
error: error.error ||
|
|
30
|
-
payload: null
|
|
31
|
-
})
|
|
29
|
+
error: error.error || 'Internal Server Error',
|
|
30
|
+
payload: null
|
|
31
|
+
})
|
|
32
32
|
},
|
|
33
33
|
|
|
34
|
-
processResponse(request, response, next) {
|
|
35
|
-
if (!request.payload) return next()
|
|
34
|
+
processResponse (request, response, next) {
|
|
35
|
+
if (!request.payload) return next()
|
|
36
36
|
|
|
37
|
-
const { status_code } = request.payload
|
|
38
|
-
return response.status(status_code).json(request.payload)
|
|
37
|
+
const { status_code } = request.payload
|
|
38
|
+
return response.status(status_code).json(request.payload)
|
|
39
39
|
},
|
|
40
40
|
|
|
41
|
-
setupRequest(request, response, next) {
|
|
42
|
-
request.headers[
|
|
43
|
-
request.headers[
|
|
41
|
+
setupRequest (request, response, next) {
|
|
42
|
+
request.headers['access-control-allow-origin'] = '*'
|
|
43
|
+
request.headers['access-control-allow-headers'] = '*'
|
|
44
44
|
|
|
45
|
-
if (request.method ===
|
|
46
|
-
request.headers[
|
|
47
|
-
response.status(200).json()
|
|
45
|
+
if (request.method === 'OPTIONS') {
|
|
46
|
+
request.headers['access-control-allow-methods'] = 'GET, POST, PUT, PATCH, DELETE'
|
|
47
|
+
response.status(200).json()
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
next()
|
|
51
|
-
}
|
|
52
|
-
}
|
|
50
|
+
next()
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -2,41 +2,38 @@
|
|
|
2
2
|
* @author Oguntuberu Nathan O. <nateoguns.work@gmail.com>
|
|
3
3
|
**/
|
|
4
4
|
|
|
5
|
-
const Env = require(
|
|
6
|
-
const { randomUUID } = require(
|
|
7
|
-
const { Logtail } = require("@logtail/node");
|
|
8
|
-
const LOGTAIL_SECRET = Env.fetch("LOGTAIL_SECRET", true);
|
|
9
|
-
const logtail = new Logtail(LOGTAIL_SECRET);
|
|
5
|
+
const Env = require('../env')
|
|
6
|
+
const { randomUUID } = require('crypto')
|
|
10
7
|
|
|
11
8
|
function hashLogData (log = {}) {
|
|
12
9
|
const hashed = JSON.stringify(log).replace(/\w+@/gi, '************')
|
|
13
10
|
return JSON.parse(hashed)
|
|
14
11
|
}
|
|
15
12
|
|
|
16
|
-
function RequestLogger() {
|
|
17
|
-
const app_name = Env.fetch(
|
|
13
|
+
function RequestLogger () {
|
|
14
|
+
const app_name = Env.fetch('APP_NAME', true)
|
|
18
15
|
return (request, response, next) => {
|
|
19
|
-
if (!request.request_id) request.request_id = randomUUID()
|
|
16
|
+
if (!request.request_id) request.request_id = randomUUID()
|
|
20
17
|
|
|
21
18
|
//
|
|
22
19
|
const {
|
|
23
20
|
query,
|
|
24
21
|
params,
|
|
25
|
-
headers: { host, origin,
|
|
22
|
+
headers: { host, origin, 'user-agent': user_agent, 'sec-ch-ua-platform': os, referer },
|
|
26
23
|
request_id,
|
|
27
|
-
tenant_id
|
|
28
|
-
} = request
|
|
24
|
+
tenant_id
|
|
25
|
+
} = request
|
|
29
26
|
|
|
30
|
-
response.on(
|
|
27
|
+
response.on('finish', () => {
|
|
31
28
|
const {
|
|
32
29
|
_parsedUrl: { pathname },
|
|
33
30
|
httpVersion,
|
|
34
31
|
_startTime,
|
|
35
32
|
_remoteAddress,
|
|
36
|
-
payload
|
|
37
|
-
} = response.req
|
|
38
|
-
const { statusCode, statusMessage } = response.req.res
|
|
39
|
-
const duration = Date.now() - Date.parse(_startTime)
|
|
33
|
+
payload
|
|
34
|
+
} = response.req
|
|
35
|
+
const { statusCode, statusMessage } = response.req.res
|
|
36
|
+
const duration = Date.now() - Date.parse(_startTime)
|
|
40
37
|
const log = {
|
|
41
38
|
app_name,
|
|
42
39
|
request_id,
|
|
@@ -52,11 +49,11 @@ function RequestLogger() {
|
|
|
52
49
|
_remoteAddress,
|
|
53
50
|
pathname,
|
|
54
51
|
duration,
|
|
55
|
-
type:
|
|
56
|
-
status_code: payload ? payload.status_code : statusCode
|
|
57
|
-
}
|
|
52
|
+
type: 'request',
|
|
53
|
+
status_code: payload ? payload.status_code : statusCode
|
|
54
|
+
}
|
|
58
55
|
|
|
59
|
-
|
|
56
|
+
const error = payload ? payload.error : statusMessage
|
|
60
57
|
// if (error) {
|
|
61
58
|
// log.error = error;
|
|
62
59
|
// logtail.error(pathname, hashLogData(log));
|
|
@@ -65,18 +62,19 @@ function RequestLogger() {
|
|
|
65
62
|
// }
|
|
66
63
|
|
|
67
64
|
// logtail.flush();
|
|
68
|
-
|
|
65
|
+
console.log(pathname, hashLogData({ ...log, error }))
|
|
66
|
+
})
|
|
69
67
|
|
|
70
|
-
next()
|
|
71
|
-
}
|
|
68
|
+
next()
|
|
69
|
+
}
|
|
72
70
|
}
|
|
73
71
|
|
|
74
72
|
class ProcessLogger {
|
|
75
|
-
constructor(service =
|
|
76
|
-
this.service = service
|
|
73
|
+
constructor (service = 'System') {
|
|
74
|
+
this.service = service
|
|
77
75
|
}
|
|
78
76
|
|
|
79
|
-
error(error, method =
|
|
77
|
+
error (error, method = 'unspecified_method', params = {}) {
|
|
80
78
|
// logtail.error(`${this.service}:${method}:${error.message}`, hashLogData({
|
|
81
79
|
// app_name: Env.fetch("APP_NAME", true),
|
|
82
80
|
// type: "process",
|
|
@@ -85,12 +83,12 @@ class ProcessLogger {
|
|
|
85
83
|
// params,
|
|
86
84
|
// }));
|
|
87
85
|
// logtail.flush();
|
|
88
|
-
console.log(`${this.service}:${method}:${error.message}`, error.stack)
|
|
86
|
+
console.log(`${this.service}:${method}:${error.message}`, params, error.stack)
|
|
89
87
|
}
|
|
90
88
|
|
|
91
|
-
info(info, method =
|
|
92
|
-
const message_str = `${this.service}:${method}:${info}
|
|
93
|
-
console.log(message_str)
|
|
89
|
+
info (info, method = 'unspecified_method', params = {}) {
|
|
90
|
+
const message_str = `${this.service}:${method}:${info}`
|
|
91
|
+
console.log(message_str, params)
|
|
94
92
|
// logtail.info(message_str, hashLogData({
|
|
95
93
|
// app_name: Env.fetch("APP_NAME", true),
|
|
96
94
|
// type: "process",
|
|
@@ -101,4 +99,4 @@ class ProcessLogger {
|
|
|
101
99
|
}
|
|
102
100
|
}
|
|
103
101
|
|
|
104
|
-
module.exports = { RequestLogger, ProcessLogger }
|
|
102
|
+
module.exports = { RequestLogger, ProcessLogger }
|
package/lib/query.js
CHANGED
|
@@ -2,7 +2,7 @@ const buildQuery = (options) => {
|
|
|
2
2
|
const sort_condition = options.sort_by ? buildSortOrderString(options.sort_by) : {}
|
|
3
3
|
const fields_to_return = options.return_only ? buildReturnFieldsString(options.return_only) : ''
|
|
4
4
|
const count = options.count || false
|
|
5
|
-
|
|
5
|
+
|
|
6
6
|
const group_by = options.group_by
|
|
7
7
|
const sum = options.sum
|
|
8
8
|
const is_pipeline = group_by || sum
|
|
@@ -179,7 +179,7 @@ const generatePipeline = ({ group_by, seek_conditions, sort_condition, sum, fiel
|
|
|
179
179
|
})
|
|
180
180
|
|
|
181
181
|
let sort_options = sort_condition
|
|
182
|
-
if (sort_condition && typeof(sort_condition) === 'string') {
|
|
182
|
+
if (sort_condition && typeof (sort_condition) === 'string') {
|
|
183
183
|
sort_options = sort_condition.split(' ').reduce((sac, condition) => {
|
|
184
184
|
let key = condition
|
|
185
185
|
let value = 1
|
|
@@ -219,7 +219,7 @@ const generatePipeline = ({ group_by, seek_conditions, sort_condition, sum, fiel
|
|
|
219
219
|
const sum_field = sum.split(',')[0]
|
|
220
220
|
pipeline.push({
|
|
221
221
|
$group: {
|
|
222
|
-
_id: { tenant_id:
|
|
222
|
+
_id: { tenant_id: '$tenant_id' },
|
|
223
223
|
[sum_field]: { $sum: `$${sum_field}` }
|
|
224
224
|
}
|
|
225
225
|
})
|
|
@@ -227,7 +227,7 @@ const generatePipeline = ({ group_by, seek_conditions, sort_condition, sum, fiel
|
|
|
227
227
|
|
|
228
228
|
if (count) {
|
|
229
229
|
pipeline.push({
|
|
230
|
-
$count:
|
|
230
|
+
$count: 'size'
|
|
231
231
|
})
|
|
232
232
|
}
|
|
233
233
|
|
package/lib/redis/index.js
CHANGED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const processRangeInDays = (min = 0, seconds_in_day = 86400000) => {
|
|
2
|
+
const ranges = []
|
|
3
|
+
let x = min
|
|
4
|
+
for (let i = 0; i < 31; i++) {
|
|
5
|
+
const [, mmm, dd] = new Date(x).toDateString().split(' ')
|
|
6
|
+
ranges.push({ start: x, end: x + seconds_in_day - 1000, label: `${mmm} ${dd}` })
|
|
7
|
+
x += seconds_in_day
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return ranges
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const processRangeInWeeks = (min = 0, seconds_in_day = 86400000) => {
|
|
14
|
+
const ranges = []
|
|
15
|
+
let x = min
|
|
16
|
+
const [, mmm, dd] = new Date(x).toDateString().split(' ')
|
|
17
|
+
for (let i = 0; i < Math.ceil(100 / 7); i++) {
|
|
18
|
+
ranges[i] = {
|
|
19
|
+
start: x,
|
|
20
|
+
end: x + 6 * seconds_in_day - 1000,
|
|
21
|
+
label: `${mmm} ${dd}`
|
|
22
|
+
}
|
|
23
|
+
x += 6 * seconds_in_day
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return ranges
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const processRangeInMonths = (min, max) => {
|
|
30
|
+
// group in months
|
|
31
|
+
const d = new Date(min)
|
|
32
|
+
const year = d.getFullYear()
|
|
33
|
+
const month = d.getUTCMonth()
|
|
34
|
+
|
|
35
|
+
const ranges = []
|
|
36
|
+
let x = min
|
|
37
|
+
for (let i = 1; x <= max; i++) {
|
|
38
|
+
const [, mmm, , yyyy] = new Date(x).toDateString().split(' ')
|
|
39
|
+
ranges.push({
|
|
40
|
+
start: x,
|
|
41
|
+
end: Date.parse(new Date(year, month + i, 1)) - 1000,
|
|
42
|
+
label: `${mmm} ${yyyy}`
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
x = Date.parse(new Date(year, month + i, 1))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return ranges
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = {
|
|
52
|
+
processDateRange: (start, stop) => {
|
|
53
|
+
const min = Date.parse(start)
|
|
54
|
+
const max = Date.parse(stop)
|
|
55
|
+
const difference = max - min
|
|
56
|
+
const seconds_in_day = 86400000
|
|
57
|
+
|
|
58
|
+
let ranges = []
|
|
59
|
+
if (difference / seconds_in_day <= 31) {
|
|
60
|
+
ranges = processRangeInDays(min, seconds_in_day)
|
|
61
|
+
} else if (difference / seconds_in_day <= 100) {
|
|
62
|
+
ranges = processRangeInWeeks(min, seconds_in_day)
|
|
63
|
+
} else {
|
|
64
|
+
ranges = processRangeInMonths(min, max)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return ranges
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/** */
|
|
2
|
+
const MONTH_MAP = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
|
3
|
+
const NUM_OF_MILLISECONDS_IN_ONE_DAY = 86400000
|
|
4
|
+
|
|
5
|
+
/** */
|
|
6
|
+
const convertDateFromIsoToHTMLFormat = (iso_date) => {
|
|
7
|
+
const date = new Date(iso_date)
|
|
8
|
+
const day = padDateValue(date.getDate())
|
|
9
|
+
const month = padDateValue(date.getMonth() + 1)
|
|
10
|
+
const year = date.getFullYear()
|
|
11
|
+
|
|
12
|
+
const converted_date = `${year}-${month}-${day}`
|
|
13
|
+
return iso_date ? converted_date : ''
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Extracts day string of the given timestamp for graphs. Return ex. 'Jan 10'
|
|
18
|
+
* @param {Number} timestamp
|
|
19
|
+
*/
|
|
20
|
+
const extractDayStringForGraph = (timestamp) => {
|
|
21
|
+
const date = new Date(timestamp)
|
|
22
|
+
const month = MONTH_MAP[date.getMonth()]
|
|
23
|
+
const day = padDateValue(date.getDate())
|
|
24
|
+
|
|
25
|
+
return `${month} ${day}`
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const generateHTMLFormDateTimeDefaults = () => {
|
|
29
|
+
const date = new Date().toISOString()
|
|
30
|
+
const generated_date = `${convertDateFromIsoToHTMLFormat(date)}`
|
|
31
|
+
return generated_date
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Generates a configuration of back-counted days starting from a base time.
|
|
36
|
+
*
|
|
37
|
+
* @param {*} base_timestamp The day to start back count
|
|
38
|
+
* @param {*} spread The numbers of days to count back.
|
|
39
|
+
*/
|
|
40
|
+
const generateDaysConfigurationForGraph = (base_timestamp, spread = 7, key_values) => {
|
|
41
|
+
let curr_timestamp = getDayTimestampForRawTimestamp(base_timestamp)
|
|
42
|
+
const config = {}
|
|
43
|
+
for (let i = 0; i < spread; i++) {
|
|
44
|
+
const date_string = extractDayStringForGraph(curr_timestamp)
|
|
45
|
+
// key_values = [subscribers , unsubscribers]; [opens, bounces]
|
|
46
|
+
config[date_string] = {
|
|
47
|
+
date: date_string,
|
|
48
|
+
[key_values[0]]: 0,
|
|
49
|
+
[key_values[1]]: 0
|
|
50
|
+
}
|
|
51
|
+
curr_timestamp -= NUM_OF_MILLISECONDS_IN_ONE_DAY
|
|
52
|
+
}
|
|
53
|
+
return config
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Generates the number of milliseconds at (12:00AM) for the date specified in the raw_timestamp.
|
|
58
|
+
*
|
|
59
|
+
* @param {Number} raw_timestamp number of milliseconds
|
|
60
|
+
*/
|
|
61
|
+
const getDayTimestampForRawTimestamp = (raw_timestamp) => {
|
|
62
|
+
const date = new Date(raw_timestamp)
|
|
63
|
+
const day = `${padDateValue(date.getMonth() + 1)}/${padDateValue(date.getDate())}/${date.getFullYear()}`
|
|
64
|
+
return Date.parse(day)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get the timestamp of the first and last days of the month
|
|
69
|
+
*/
|
|
70
|
+
|
|
71
|
+
const getMonthTimeRange = () => {
|
|
72
|
+
const year = new Date().getFullYear()
|
|
73
|
+
const current_month = new Date().getMonth()
|
|
74
|
+
const start = Date.parse(new Date(year, current_month, 1))
|
|
75
|
+
const end = Date.parse(new Date(year, current_month + 1, 1)) - 1000
|
|
76
|
+
|
|
77
|
+
return { start, end }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get today's midnight time stamp
|
|
82
|
+
*/
|
|
83
|
+
|
|
84
|
+
const getCurrentDayTimestamp = () => {
|
|
85
|
+
const date = new Date()
|
|
86
|
+
const year = date.getFullYear()
|
|
87
|
+
const month = padDateValue(date.getMonth() + 1)
|
|
88
|
+
const day = padDateValue(date.getDate())
|
|
89
|
+
const date_string = `${year}-${month}-${day}`
|
|
90
|
+
return Date.parse(date_string)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* prefixes 0 on numbers with single digits.
|
|
95
|
+
* @param {Number} value
|
|
96
|
+
*/
|
|
97
|
+
const padDateValue = (value) => {
|
|
98
|
+
return value < 10 ? `0${value}` : value
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const isCampaignSendDateValid = (chosen_date) => {
|
|
102
|
+
const today = new Date().toDateString()
|
|
103
|
+
return new Date(`${chosen_date}`) >= new Date(today)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const toDateString = (date = '') => {
|
|
107
|
+
if (!date) return ''
|
|
108
|
+
return new Date(date).toDateString()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const formatDateWithoutDayOfWeek = (date = '') => {
|
|
112
|
+
if (!date) return ''
|
|
113
|
+
const options = { year: 'numeric', month: 'short', day: 'numeric' }
|
|
114
|
+
return new Date(date).toLocaleDateString(undefined, options)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const formatDateForDisplay = (raw_value) => {
|
|
118
|
+
const date = new Date(raw_value)
|
|
119
|
+
if (!raw_value || !date) return raw_value
|
|
120
|
+
const [, mon, day, year, time] = date.toString().split(' ')
|
|
121
|
+
return [mon, `${day},`, year, time].join(' ')
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const toTimeString = (date = '') => {
|
|
125
|
+
if (!date) return ''
|
|
126
|
+
const options = {
|
|
127
|
+
hour: 'numeric',
|
|
128
|
+
minute: 'numeric',
|
|
129
|
+
hour12: true
|
|
130
|
+
}
|
|
131
|
+
return new Date(date).toLocaleTimeString('en-US', options)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const generateDefaultRange = () => {
|
|
135
|
+
const [today] = new Date().toISOString().split('T')
|
|
136
|
+
const [thirty_days_ago] = new Date(Date.parse(today) - 30 * 86400000).toISOString().split('T')
|
|
137
|
+
return [thirty_days_ago, today]
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
*
|
|
142
|
+
* @param {Number} year
|
|
143
|
+
* @returns {startDate, endDate}
|
|
144
|
+
*/
|
|
145
|
+
const getYearRange = (year) => {
|
|
146
|
+
const startDate = new Date(year, 0, 1).getTime() // January 1st
|
|
147
|
+
const endDate = new Date(year, 11, 31).getTime() + 86400000 - 1 // December 31st (23:59:59)
|
|
148
|
+
return { startDate, endDate }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const getMonthTimestamps = (year) => {
|
|
152
|
+
const timestamps = []
|
|
153
|
+
|
|
154
|
+
for (let month = 0; month < 12; month++) {
|
|
155
|
+
const startDate = new Date(year, month, 1)
|
|
156
|
+
const endDate = new Date(year, month + 1, 0, 23, 59, 59, 999) // Last day of the month
|
|
157
|
+
|
|
158
|
+
timestamps.push({
|
|
159
|
+
start: startDate.getTime(),
|
|
160
|
+
end: endDate.getTime()
|
|
161
|
+
})
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return timestamps
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const getMonthNameFromTimestamp = (timestamp) => {
|
|
168
|
+
const date = new Date(timestamp)
|
|
169
|
+
const months = [
|
|
170
|
+
'January',
|
|
171
|
+
'February',
|
|
172
|
+
'March',
|
|
173
|
+
'April',
|
|
174
|
+
'May',
|
|
175
|
+
'June',
|
|
176
|
+
'July',
|
|
177
|
+
'August',
|
|
178
|
+
'September',
|
|
179
|
+
'October',
|
|
180
|
+
'November',
|
|
181
|
+
'December'
|
|
182
|
+
]
|
|
183
|
+
return months[date.getMonth()]
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
module.exports = {
|
|
187
|
+
convertDateFromIsoToHTMLFormat,
|
|
188
|
+
extractDayStringForGraph,
|
|
189
|
+
formatDateForDisplay,
|
|
190
|
+
formatDateWithoutDayOfWeek,
|
|
191
|
+
generateDaysConfigurationForGraph,
|
|
192
|
+
generateHTMLFormDateTimeDefaults,
|
|
193
|
+
getCurrentDayTimestamp,
|
|
194
|
+
getDayTimestampForRawTimestamp,
|
|
195
|
+
generateDefaultRange,
|
|
196
|
+
getMonthTimeRange,
|
|
197
|
+
isCampaignSendDateValid,
|
|
198
|
+
padDateValue,
|
|
199
|
+
toDateString,
|
|
200
|
+
toTimeString,
|
|
201
|
+
getYearRange,
|
|
202
|
+
getMonthTimestamps,
|
|
203
|
+
getMonthNameFromTimestamp
|
|
204
|
+
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@go-mailer/jarvis",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.6",
|
|
4
4
|
"main": "index.js",
|
|
5
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
|
"scripts": {
|
|
9
|
-
"test": "mocha --recursive -w -c || true"
|
|
9
|
+
"test": "mocha --recursive -w -c || true",
|
|
10
|
+
"lint:fix": "eslint --fix . --ext .js"
|
|
10
11
|
},
|
|
11
12
|
"dependencies": {
|
|
12
13
|
"@logtail/node": "^0.3.3",
|
|
@@ -18,6 +19,11 @@
|
|
|
18
19
|
},
|
|
19
20
|
"devDependencies": {
|
|
20
21
|
"chai": "^4.3.7",
|
|
22
|
+
"eslint": "^7.32.0",
|
|
23
|
+
"eslint-config-standard": "^16.0.3",
|
|
24
|
+
"eslint-plugin-import": "^2.25.4",
|
|
25
|
+
"eslint-plugin-node": "^11.1.0",
|
|
26
|
+
"eslint-plugin-promise": "^5.2.0",
|
|
21
27
|
"mocha": "^10.2.0"
|
|
22
28
|
}
|
|
23
29
|
}
|