@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 ADDED
@@ -0,0 +1 @@
1
+ test/
package/.eslintrc.yml ADDED
@@ -0,0 +1,10 @@
1
+ env:
2
+ commonjs: true
3
+ es2021: true
4
+ node: true
5
+ extends:
6
+ - standard
7
+ parserOptions:
8
+ ecmaVersion: latest
9
+ rules:
10
+ camelcase: 0
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
  }
@@ -1,7 +1,7 @@
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);
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("GO_FLAGS_ENV", true)
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 }
@@ -1,21 +1,35 @@
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);
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("Unauthorized");
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("NODE_ENV"),
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("dotenv").config();
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 = "", 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;
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')
@@ -2,98 +2,113 @@
2
2
  * User Authentication Middleware
3
3
  */
4
4
 
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");
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
- module.exports = {
94
- authenticateAdmin,
95
- authenticateBearerKey,
96
- authenticateParamKey,
97
- authenticateUser,
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
+ }
@@ -1,52 +1,52 @@
1
1
  /** **/
2
- const { ProcessLogger } = require("./logger");
3
- const HTTPLogger = new ProcessLogger("HTTPSetup");
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: "Resource not found",
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, "handleError");
20
+ HTTPLogger.info(error.error, 'handleError')
21
21
  } else {
22
- HTTPLogger.error(error, "handleError");
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 || "Internal Server 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["access-control-allow-origin"] = "*";
43
- request.headers["access-control-allow-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 === "OPTIONS") {
46
- request.headers["access-control-allow-methods"] = "GET, POST, PUT, PATCH, DELETE";
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("../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);
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("APP_NAME", true);
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, "user-agent": user_agent, "sec-ch-ua-platform": os, referer },
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("finish", () => {
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: "request",
56
- status_code: payload ? payload.status_code : statusCode,
57
- };
52
+ type: 'request',
53
+ status_code: payload ? payload.status_code : statusCode
54
+ }
58
55
 
59
- let error = payload ? payload.error : statusMessage;
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 = "System") {
76
- this.service = service;
73
+ constructor (service = 'System') {
74
+ this.service = service
77
75
  }
78
76
 
79
- error(error, method = "unspecified_method", params = {}) {
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 = "unspecified_method", params = {}) {
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: "$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: "size"
230
+ $count: 'size'
231
231
  })
232
232
  }
233
233
 
@@ -29,8 +29,8 @@ class RedisClient {
29
29
  }
30
30
  }
31
31
  //
32
- const redisClient = new RedisClient()
33
- module.exports ={
32
+ const redisClient = new RedisClient()
33
+ module.exports = {
34
34
  RedisClient: redisClient,
35
35
  Schemas
36
36
  }
@@ -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
+ }
@@ -0,0 +1,9 @@
1
+ const Chart = require('./chart')
2
+ const Date = require('./date')
3
+ const Number = require('./number')
4
+
5
+ module.exports = {
6
+ Chart,
7
+ Date,
8
+ Number
9
+ }
@@ -0,0 +1,7 @@
1
+ module.exports = {
2
+ toReadableNumber: (value) => {
3
+ if (!value) return 0
4
+ if (isNaN(value)) return value
5
+ return Number(Number(value).toFixed(2)).toLocaleString()
6
+ }
7
+ }
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@go-mailer/jarvis",
3
- "version": "5.0.4",
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
  }