adminmate-express-mongoose 1.3.0 → 1.3.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adminmate-express-mongoose",
3
- "version": "1.3.0",
3
+ "version": "1.3.3",
4
4
  "description": "Adminmate Express/Mongoose connector",
5
5
  "author": "Marc Delalonde",
6
6
  "homepage": "http://adminmate.io",
@@ -23,7 +23,8 @@
23
23
  "url": "https://github.com/Adminmate/adminmate-express-mongoose.git"
24
24
  },
25
25
  "dependencies": {
26
- "adminmate-express-core": "^1.2.0",
26
+ "joi": "^17.6.0",
27
+ "adminmate-express-core": "~1.2.0",
27
28
  "lodash": "^4.17.21",
28
29
  "moment": "^2.29.1",
29
30
  "mongoose": "~5.9.7",
@@ -1,32 +1,64 @@
1
+ const Joi = require('joi');
2
+
1
3
  module.exports = async (currentModel, data) => {
4
+ const paramsSchema = Joi.object({
5
+ type: Joi.string().required(),
6
+ model: Joi.string().required(),
7
+ operation: Joi.string().required(),
8
+ group_by: Joi.string().required(),
9
+ field: Joi.alternatives().conditional('operation', {
10
+ not: 'count',
11
+ then: Joi.string().required(),
12
+ otherwise: Joi.string().optional()
13
+ }),
14
+ limit: Joi.number().optional()
15
+ });
16
+
17
+ // Validate params
18
+ const { error } = paramsSchema.validate(data);
19
+ if (error) {
20
+ return {
21
+ success: false,
22
+ message: error.details[0].message
23
+ };
24
+ }
25
+
2
26
  let _value = 1;
3
27
  if (data.field && ['sum', 'avg'].includes(data.operation)) {
4
28
  _value = `$${data.field}`;
5
29
  }
6
30
 
7
- const repartitionData = await currentModel
8
- .aggregate([
9
- {
10
- $group: {
11
- _id: `$${data.group_by}`,
12
- count: data.operation === 'avg' ? { $avg: _value } : { $sum: _value },
13
- }
14
- },
15
- {
16
- $project: {
17
- key: '$_id',
18
- value: '$count',
19
- _id: false
31
+ try {
32
+ const repartitionData = await currentModel
33
+ .aggregate([
34
+ {
35
+ $group: {
36
+ _id: `$${data.group_by}`,
37
+ count: data.operation === 'avg' ? { $avg: _value } : { $sum: _value },
38
+ }
39
+ },
40
+ {
41
+ $project: {
42
+ key: '$_id',
43
+ value: '$count',
44
+ _id: false
45
+ }
20
46
  }
21
- }
22
- ])
23
- .sort({ key: 1 });
47
+ ])
48
+ .sort({ key: 1 });
24
49
 
25
- return {
26
- success: true,
27
- data: {
28
- config: null,
29
- data: repartitionData
30
- }
31
- };
50
+ return {
51
+ success: true,
52
+ data: {
53
+ config: null,
54
+ data: repartitionData
55
+ }
56
+ };
57
+ }
58
+ catch(e) {
59
+ return {
60
+ success: false,
61
+ message: e.message
62
+ };
63
+ }
32
64
  };
@@ -1,6 +1,31 @@
1
+ const Joi = require('joi');
1
2
  const fnHelper = require('../helpers/functions');
2
3
 
3
4
  module.exports = async (currentModel, data) => {
5
+ const paramsSchema = Joi.object({
6
+ type: Joi.string().required(),
7
+ model: Joi.string().required(),
8
+ field: Joi.string().required(),
9
+ relationship_model: Joi.string().required(),
10
+ relationship_model_ref_field: Joi.string().required(),
11
+ relationship_operation: Joi.string().required(),
12
+ relationship_field: Joi.alternatives().conditional('relationship_operation', {
13
+ not: 'count',
14
+ then: Joi.string().required(),
15
+ otherwise: Joi.string()
16
+ }),
17
+ limit: Joi.number().optional(),
18
+ });
19
+
20
+ // Validate params
21
+ const { error } = paramsSchema.validate(data);
22
+ if (error) {
23
+ return {
24
+ success: false,
25
+ message: error.details[0].message
26
+ };
27
+ }
28
+
4
29
  // Get relationship model
5
30
  const relationshipModel = fnHelper.getModelObject(data.relationship_model);
6
31
  if (!relationshipModel) {
@@ -15,42 +40,50 @@ module.exports = async (currentModel, data) => {
15
40
  _value = `$${data.relationship_field}`;
16
41
  }
17
42
 
18
- const repartitionData = await relationshipModel
19
- .aggregate([
20
- {
21
- $group: {
22
- _id: `$${data.relationship_model_ref_field}`,
23
- count: data.operation === 'avg' ? { $avg: _value } : { $sum: _value },
24
- }
25
- },
26
- {
27
- $project: {
28
- key: '$_id',
29
- value: '$count',
30
- _id: false
43
+ try {
44
+ const repartitionData = await relationshipModel
45
+ .aggregate([
46
+ {
47
+ $group: {
48
+ _id: `$${data.relationship_model_ref_field}`,
49
+ count: data.operation === 'avg' ? { $avg: _value } : { $sum: _value },
50
+ }
51
+ },
52
+ {
53
+ $project: {
54
+ key: '$_id',
55
+ value: '$count',
56
+ _id: false
57
+ }
31
58
  }
59
+ ])
60
+ .limit(limit)
61
+ .sort({ value: -1 });
62
+
63
+ const parentIds = repartitionData.map(d => d.key);
64
+ const parentData = await currentModel.find({ _id: parentIds }).select(data.field).lean();
65
+
66
+ repartitionData.forEach(d => {
67
+ d.item_model = data.model;
68
+ d.item_id = d.key;
69
+ const parent = parentData.find(p => p._id.toString() === d.key.toString());
70
+ if (parent) {
71
+ d.key = parent[data.field];
32
72
  }
33
- ])
34
- .limit(limit)
35
- .sort({ value: -1 });
36
-
37
- const parentIds = repartitionData.map(d => d.key);
38
- const parentData = await currentModel.find({ _id: parentIds }).select(data.field).lean();
39
-
40
- repartitionData.forEach(d => {
41
- d.item_model = data.model;
42
- d.item_id = d.key;
43
- const parent = parentData.find(p => p._id.toString() === d.key.toString());
44
- if (parent) {
45
- d.key = parent[data.field];
46
- }
47
- });
73
+ });
48
74
 
49
- return {
50
- success: true,
51
- data: {
52
- config: null,
53
- data: repartitionData
54
- }
55
- };
75
+ return {
76
+ success: true,
77
+ data: {
78
+ config: null,
79
+ data: repartitionData
80
+ }
81
+ };
82
+ }
83
+ catch(e) {
84
+ return {
85
+ success: false,
86
+ message: e.message
87
+ };
88
+ }
56
89
  };
@@ -1,142 +1,129 @@
1
1
 
2
+ const Joi = require('joi');
2
3
  const _ = require('lodash');
3
4
  const moment = require('moment');
5
+ const fnHelper = require('../helpers/functions');
4
6
 
5
7
  module.exports = async (currentModel, data) => {
6
- if (!['day', 'week', 'month', 'year'].includes(data.timeframe)) {
8
+ const paramsSchema = Joi.object({
9
+ type: Joi.string().required(),
10
+ model: Joi.string().required(),
11
+ operation: Joi.string().required(),
12
+ group_by: Joi.string().required(),
13
+ timeframe: Joi.string().required().valid('day', 'week', 'month', 'year'),
14
+ field: Joi.alternatives().conditional('operation', {
15
+ not: 'count',
16
+ then: Joi.string().required(),
17
+ otherwise: Joi.string()
18
+ }),
19
+ limit: Joi.number().optional(),
20
+ date_from: Joi.date().optional(),
21
+ date_to: Joi.date().optional()
22
+ });
23
+
24
+ // Validate params
25
+ const { error } = paramsSchema.validate(data);
26
+ if (error) {
7
27
  return {
8
28
  success: false,
9
- message: 'Invalid timeframe'
29
+ message: error.details[0].message
10
30
  };
11
31
  }
12
32
 
13
33
  const toSum = data.field && data.operation === 'sum' ? `$${data.field}` : 1;
14
34
 
15
- // To set the max date
16
- const toDate = data.to ? moment(data.to) : moment();
17
-
18
35
  let matchReq = {};
19
36
  let groupFormat = '';
20
37
 
38
+ if (data.date_from) {
39
+ matchReq['$gte'] = new Date(moment(data.date_from));
40
+ }
41
+
42
+ if (data.date_to) {
43
+ matchReq['$lt'] = new Date(moment(data.date_to));
44
+ }
45
+
21
46
  // Day timeframe
22
47
  if (data.timeframe === 'day') {
23
- const startOfCurrentDay = toDate.startOf('day');
24
- matchReq = {
25
- '$gte': new Date(startOfCurrentDay.clone().subtract(30, 'day').startOf('day').format()),
26
- '$lt': new Date(startOfCurrentDay.format())
27
- };
28
- groupFormat = '%Y-%m-%d';
48
+ groupFormat = '%Y-%m-%d 00:00:00';
29
49
  }
30
50
  // Week timeframe
31
51
  else if (data.timeframe === 'week') {
32
- const startOfCurrentWeek = toDate.startOf('week');
33
- matchReq = {
34
- '$gte': new Date(startOfCurrentWeek.clone().subtract(26, 'week').startOf('week').format()),
35
- '$lt': new Date(startOfCurrentWeek.format())
36
- };
37
- groupFormat = '%V';
52
+ groupFormat = '%Y-%V';
38
53
  }
39
54
  // Month timeframe
40
55
  else if (data.timeframe === 'month') {
41
- const startOfCurrentMonth = toDate.startOf('month');
42
- matchReq = {
43
- '$gte': new Date(startOfCurrentMonth.clone().subtract(12, 'month').startOf('month').format()),
44
- '$lt': new Date(startOfCurrentMonth.format())
45
- };
46
- groupFormat = '%m';
56
+ groupFormat = '%Y-%m-01 00:00:00';
47
57
  }
48
58
  // Year timeframe
49
59
  else if (data.timeframe === 'year') {
50
- const startOfCurrentYear = toDate.startOf('year');
51
- matchReq = {
52
- '$gte': new Date(startOfCurrentYear.clone().subtract(8, 'year').startOf('year').format()),
53
- '$lt': new Date(startOfCurrentYear.format())
54
- };
55
- groupFormat = '%Y';
60
+ groupFormat = '%Y-01-01 00:00:00';
56
61
  }
57
62
 
58
63
  if (!groupFormat) {
59
- return res.status(403).json({ message: 'Invalid request' });
64
+ return {
65
+ success: false,
66
+ message: 'Invalid request'
67
+ };
60
68
  }
61
69
 
62
- const repartitionData = await currentModel
63
- .aggregate([
64
- {
65
- $match: {
66
- [data.group_by]: matchReq
67
- }
68
- },
69
- {
70
- $group: {
71
- _id: { $dateToString: { format: groupFormat, date: `$${data.group_by}` } },
72
- count: { $sum: toSum }
70
+ let repartitionData;
71
+ try {
72
+ repartitionData = await currentModel
73
+ .aggregate([
74
+ {
75
+ $match: Object.keys(matchReq).length > 0 ? {
76
+ [data.group_by]: matchReq
77
+ } : {}
78
+ },
79
+ {
80
+ $group: {
81
+ _id: { $dateToString: { format: groupFormat, date: `$${data.group_by}` } },
82
+ count: { $sum: toSum }
83
+ }
84
+ },
85
+ {
86
+ $project: {
87
+ key: '$_id',
88
+ value: '$count',
89
+ _id: false
90
+ }
73
91
  }
74
- },
75
- {
76
- $project: {
77
- key: '$_id',
78
- value: '$count',
79
- _id: false
80
- }
81
- }
82
- ]);
92
+ ]);
93
+ }
94
+ catch(e) {
95
+ return {
96
+ success: false,
97
+ message: e.message
98
+ };
99
+ }
83
100
 
84
101
  const formattedData = [];
102
+ if (repartitionData && repartitionData.length > 0) {
103
+ // Get min & max date in the results
104
+ const momentFormat = data.timeframe === 'week' ? 'YYYY-WW' : 'YYYY-MM-DD HH:mm:ss';
105
+ const unixRange = repartitionData.map(data => moment(data.key, momentFormat));
106
+ const min = _.min(unixRange).clone();
107
+ const max = _.max(unixRange).clone();
85
108
 
86
- // Day timeframe
87
- if (data.timeframe === 'day') {
88
- for (let i = 1; i <= 30; i++) {
89
- const currentDate = toDate.clone().subtract(i, 'day').startOf('day');
90
- const countForTheTimeframe = _.find(repartitionData, { key: currentDate.format('YYYY-MM-DD') });
91
- formattedData.push({
92
- key: currentDate.format('DD/MM'),
93
- value: countForTheTimeframe ? countForTheTimeframe.value : 0
94
- });
95
- }
96
- }
97
- // Week timeframe
98
- else if (data.timeframe === 'week') {
99
- for (let i = 1; i <= 26; i++) {
100
- const currentWeek = toDate.clone().subtract(i, 'week').startOf('week');
101
- const countForTheTimeframe = _.find(repartitionData, { key: currentWeek.format('WW') });
102
- formattedData.push({
103
- key: currentWeek.startOf('week').format('DD/MM'),
104
- value: countForTheTimeframe ? countForTheTimeframe.value : 0
105
- });
106
- }
107
- }
108
- // Month timeframe
109
- else if (data.timeframe === 'month') {
110
- for (let i = 1; i <= 12; i++) {
111
- const currentMonth = toDate.clone().subtract(i, 'month').startOf('month');
112
- const countForTheTimeframe = _.find(repartitionData, { key: currentMonth.format('MM') });
113
- formattedData.push({
114
- key: currentMonth.startOf('month').format('MMM'),
115
- value: countForTheTimeframe ? countForTheTimeframe.value : 0
116
- });
117
- }
118
- }
119
- // Year timeframe
120
- else if (data.timeframe === 'year') {
121
- for (let i = 1; i <= 8; i++) {
122
- const currentYear = toDate.clone().subtract(i, 'year').startOf('year');
123
- const countForTheTimeframe = _.find(repartitionData, { key: currentYear.format('YYYY') });
109
+ let currentDate = min;
110
+ while (currentDate.isSameOrBefore(max)) {
111
+ const countForTheTimeframe = repartitionData.find(d => moment(d.key, momentFormat).isSame(currentDate, data.timeframe));
112
+ const value = countForTheTimeframe ? fnHelper.toFixedIfNecessary(countForTheTimeframe.value, 2) : 0;
124
113
  formattedData.push({
125
- key: currentYear.startOf('year').format('YYYY'),
126
- value: countForTheTimeframe ? countForTheTimeframe.value : 0
114
+ key: currentDate.format('YYYY-MM-DD'),
115
+ value
127
116
  });
117
+ currentDate.add(1, data.timeframe).startOf('day');
128
118
  }
129
119
  }
130
120
 
131
- const formattedDataOrdered = formattedData.reverse();
132
-
133
121
  const chartConfig = {
134
122
  xaxis: [
135
123
  { dataKey: 'key' }
136
124
  ],
137
125
  yaxis: [
138
- { dataKey: 'value' },
139
- // { dataKey: 'test', orientation: 'right' }
126
+ { dataKey: 'value' }
140
127
  ]
141
128
  };
142
129
 
@@ -144,7 +131,7 @@ module.exports = async (currentModel, data) => {
144
131
  success: true,
145
132
  data: {
146
133
  config: chartConfig,
147
- data: formattedDataOrdered
134
+ data: formattedData
148
135
  }
149
136
  };
150
- };
137
+ };
@@ -1,47 +1,75 @@
1
- module.exports = async (currentModel, data) => {
2
- if (data.operation === 'sum') {
3
- const sumData = await currentModel
4
- .aggregate([{
5
- $group: {
6
- _id: `$${data.group_by}`,
7
- count: { $sum: `$${data.field}` },
8
- }
9
- }]);
1
+ const Joi = require('joi');
10
2
 
11
- if (!sumData || !sumData[0] || typeof sumData[0].count !== 'number') {
12
- return res.status(403).json();
13
- }
3
+ module.exports = async (currentModel, data) => {
4
+ const paramsSchema = Joi.object({
5
+ type: Joi.string().required(),
6
+ model: Joi.string().required(),
7
+ operation: Joi.string().required(),
8
+ field: Joi.alternatives().conditional('operation', {
9
+ not: 'count',
10
+ then: Joi.string().required(),
11
+ otherwise: Joi.string()
12
+ })
13
+ });
14
14
 
15
+ // Validate params
16
+ const { error } = paramsSchema.validate(data);
17
+ if (error) {
15
18
  return {
16
- success: true,
17
- data: {
18
- config: null,
19
- data: sumData[0].count
20
- }
19
+ success: false,
20
+ message: error.details[0].message
21
21
  };
22
22
  }
23
- else if (data.operation === 'avg') {
24
- const avgData = await currentModel
25
- .aggregate([{
26
- $group: {
27
- _id: `$${data.group_by}`,
28
- avg: { $avg: `$${data.field}` },
29
- }
30
- }]);
31
23
 
32
- if (!avgData || !avgData[0] || typeof avgData[0].avg !== 'number') {
33
- return res.status(403).json();
24
+ try {
25
+ if (data.operation === 'sum') {
26
+ const sumData = await currentModel
27
+ .aggregate([{
28
+ $group: {
29
+ _id: `$${data.group_by}`,
30
+ count: {
31
+ $sum: `$${data.field}`
32
+ }
33
+ }
34
+ }]);
35
+
36
+ if (!sumData || !sumData[0] || typeof sumData[0].count !== 'number') {
37
+ return res.status(403).json();
38
+ }
39
+
40
+ return {
41
+ success: true,
42
+ data: {
43
+ config: null,
44
+ data: sumData[0].count
45
+ }
46
+ };
34
47
  }
48
+ else if (data.operation === 'avg') {
49
+ const avgData = await currentModel
50
+ .aggregate([{
51
+ $group: {
52
+ _id: `$${data.group_by}`,
53
+ avg: {
54
+ $avg: `$${data.field}`
55
+ }
56
+ }
57
+ }]);
35
58
 
36
- return {
37
- success: true,
38
- data: {
39
- config: null,
40
- data: avgData[0].avg
59
+ if (!avgData || !avgData[0] || typeof avgData[0].avg !== 'number') {
60
+ return res.status(403).json();
41
61
  }
42
- };
43
- }
44
- else {
62
+
63
+ return {
64
+ success: true,
65
+ data: {
66
+ config: null,
67
+ data: avgData[0].avg
68
+ }
69
+ };
70
+ }
71
+
72
+ // Simple count
45
73
  const dataCount = await currentModel.countDocuments({});
46
74
 
47
75
  return {
@@ -52,4 +80,10 @@ module.exports = async (currentModel, data) => {
52
80
  }
53
81
  };
54
82
  }
83
+ catch(e) {
84
+ return {
85
+ success: false,
86
+ message: e.message
87
+ };
88
+ }
55
89
  };
@@ -119,6 +119,9 @@ const cleanString = string => {
119
119
  module.exports.cleanString = cleanString;
120
120
 
121
121
  const queryRule = rule => {
122
+ if (rule.type === 'group') {
123
+ return queryRuleSet(rule);
124
+ }
122
125
  let q = {};
123
126
  if (rule.operator === 'is') {
124
127
  q[rule.field] = { $eq: rule.value };
@@ -217,6 +220,10 @@ const queryRuleSet = ruleSet => {
217
220
  }
218
221
  };
219
222
 
223
+ module.exports.toFixedIfNecessary = (value, dp) => {
224
+ return +parseFloat(value).toFixed(dp);
225
+ };
226
+
220
227
  module.exports.constructQuery = jsonQuery => {
221
228
  if (jsonQuery.operator && jsonQuery.list && jsonQuery.list.length) {
222
229
  return queryRuleSet(jsonQuery);