@tryghost/limit-service 0.6.4 → 1.0.2

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 CHANGED
@@ -24,7 +24,7 @@ const LimitService = require('@tryghost/limit-service');
24
24
  const limitService = new LimitService();
25
25
 
26
26
  // setup limit configuration
27
- // currently supported limit keys are: staff, members, customThemes, customIntegrations
27
+ // currently supported limit keys are: staff, members, customThemes, customIntegrations, uploads
28
28
  // all limit configs support custom "error" configuration that is a template string
29
29
  const limits = {
30
30
  // staff and member are "max" type of limits accepting "max" configuration
@@ -59,6 +59,12 @@ const limits = {
59
59
  // maxPeriodic: 42,
60
60
  // error: 'Your plan supports up to {{max}} emails. Please upgrade to reenable sending emails.'
61
61
  // }
62
+ uploads: {
63
+ // max key is in bytes
64
+ max: 5000000,
65
+ // formatting of the {{ max }} vairable is in MB, e.g: 5MB
66
+ error: 'Your plan supports uploads of max size up to {{max}}. Please upgrade to reenable uploading.'
67
+ }
62
68
  };
63
69
 
64
70
  // This information is needed for the limit service to work with "max periodic" limits
@@ -116,6 +122,11 @@ if (limitService.isLimited('members')) {
116
122
  await limitService.errorIfIsOverLimit('members', {max: 10000});
117
123
  }
118
124
 
125
+ if (limitService.isLimited('uploads')) {
126
+ // for the uploads limit we HAVE TO pass in the "currentCount" parameter and use bytes as a base unit
127
+ await limitService.errorIfIsOverLimit('uploads', {currentCount: frame.file.size});
128
+ }
129
+
119
130
  // check if any of the limits are acceding
120
131
  if (limitService.checkIfAnyOverLimit()) {
121
132
  console.log('One of the limits has acceded!');
@@ -130,7 +141,7 @@ At the moment there are four different types of limits that limit service allows
130
141
  4. `allowList` - checks if provided value is defined in configured "allowlist". Example usecase: "disable theme activation if it is not an official theme". To configure this limit define ` allowlist: ['VALUE_1', 'VALUE_2', 'VALUE_N']` property in the "limits" parameter.
131
142
 
132
143
  ### Supported limits
133
- There's a limited amount of limits that are supported by limit service. The are defined by "key" property name in the "config" module. List of currently supported limit names: `members`, `staff`, `customIntegrations`, `emails`, `customThemes`.
144
+ There's a limited amount of limits that are supported by limit service. The are defined by "key" property name in the "config" module. List of currently supported limit names: `members`, `staff`, `customIntegrations`, `emails`, `customThemes`, `uploads`.
134
145
 
135
146
  All limits can act as `flag` or `allowList` types. Only certain (`members`, `staff`, and`customIntegrations`) can have a `max` limit. Only `emails` currently supports the `maxPeriodic` type of limit.
136
147
 
package/lib/config.js CHANGED
@@ -45,5 +45,14 @@ module.exports = {
45
45
  return result.count;
46
46
  }
47
47
  },
48
- customThemes: {}
48
+ customThemes: {},
49
+ uploads: {
50
+ // NOTE: this function should not ever be used as for uploads we compare the size
51
+ // of the uploaded file with the configured limit. Noop is here to keep the
52
+ // MaxLimit constructor happy
53
+ currentCountQuery: () => {},
54
+ // NOTE: the uploads limit is based on file sizes provided in Bytes
55
+ // a custom formatter is here for more user-friendly formatting when forming an error
56
+ formatter: count => `${count / 1000000}MB`
57
+ }
49
58
  };
@@ -21,7 +21,7 @@ class LimitService {
21
21
  * @param {Object} [options.subscription] - hash containing subscription configuration with interval and startDate properties
22
22
  * @param {String} options.helpLink - URL pointing to help resources for when limit is reached
23
23
  * @param {Object} options.db - knex db connection instance or other data source for the limit checks
24
- * @param {Object} options.errors - instance of errors compatible with Ghost-Ignition's errors (https://github.com/TryGhost/Ignition#errors)
24
+ * @param {Object} options.errors - instance of errors compatible with GhostError errors (@tryghost/errors)
25
25
  */
26
26
  loadLimits({limits = {}, subscription, helpLink, db, errors}) {
27
27
  if (!errors) {
@@ -101,6 +101,12 @@ class LimitService {
101
101
  }
102
102
  }
103
103
 
104
+ /**
105
+ *
106
+ * @param {String} limitName - name of the configured limit
107
+ * @param {Object} metadata - limit parameters
108
+ * @returns
109
+ */
104
110
  async errorIfIsOverLimit(limitName, metadata = {}) {
105
111
  if (!this.isLimited(limitName)) {
106
112
  return;
@@ -109,6 +115,12 @@ class LimitService {
109
115
  await this.limits[limitName].errorIfIsOverLimit(metadata);
110
116
  }
111
117
 
118
+ /**
119
+ *
120
+ * @param {String} limitName - name of the configured limit
121
+ * @param {Object} metadata - limit parameters
122
+ * @returns
123
+ */
112
124
  async errorIfWouldGoOverLimit(limitName, metadata = {}) {
113
125
  if (!this.isLimited(limitName)) {
114
126
  return;
@@ -118,9 +130,9 @@ class LimitService {
118
130
  }
119
131
 
120
132
  /**
121
- * Checks if any of the configured limits acced
133
+ * Checks if any of the configured limits acceded
122
134
  *
123
- * @returns {boolean}
135
+ * @returns {Promise<boolean>}
124
136
  */
125
137
  async checkIfAnyOverLimit() {
126
138
  for (const limit in this.limits) {
package/lib/limit.js CHANGED
@@ -5,6 +5,15 @@ const {lastPeriodStart, SUPPORTED_INTERVALS} = require('./date-utils');
5
5
  _.templateSettings.interpolate = /{{([\s\S]+?)}}/g;
6
6
 
7
7
  class Limit {
8
+ /**
9
+ *
10
+ * @param {Object} options
11
+ * @param {String} options.name - name of the limit
12
+ * @param {String} options.error - error message to use when limit is reached
13
+ * @param {String} options.helpLink - URL to the resource explaining how the limit works
14
+ * @param {Object} [options.db] - instance of knex db connection that currentCountQuery can use to run state check through
15
+ * @param {Object} options.errors - instance of errors compatible with GhostError errors (@tryghost/errors)
16
+ */
8
17
  constructor({name, error, helpLink, db, errors}) {
9
18
  this.name = name;
10
19
  this.error = error;
@@ -36,9 +45,11 @@ class MaxLimit extends Limit {
36
45
  * @param {Object} options.config - limit configuration
37
46
  * @param {Number} options.config.max - maximum limit the limit would check against
38
47
  * @param {Function} options.config.currentCountQuery - query checking the state that would be compared against the limit
39
- * @param {String} options.helpLink - URL to the resource explaining how the limit works
40
- * @param {Object} options.db - instance of knex db connection that currentCountQuery can use to run state check through
41
- * @param {Object} options.errors - instance of errors compatible with Ghost-Ignition's errors (https://github.com/TryGhost/Ignition#errors)
48
+ * @param {Function} [options.config.formatter] - function to format the limit counts before they are passed to the error message
49
+ * @param {String} [options.config.error] - error message to use when limit is reached
50
+ * @param {String} [options.helpLink] - URL to the resource explaining how the limit works
51
+ * @param {Object} [options.db] - instance of knex db connection that currentCountQuery can use to run state check through
52
+ * @param {Object} options.errors - instance of errors compatible with GhostError errors (@tryghost/errors)
42
53
  */
43
54
  constructor({name, config, helpLink, db, errors}) {
44
55
  super({name, error: config.error || '', helpLink, db, errors});
@@ -53,20 +64,27 @@ class MaxLimit extends Limit {
53
64
 
54
65
  this.currentCountQueryFn = config.currentCountQuery;
55
66
  this.max = config.max;
67
+ this.formatter = config.formatter;
56
68
  this.fallbackMessage = `This action would exceed the ${_.lowerCase(this.name)} limit on your current plan.`;
57
69
  }
58
70
 
71
+ /**
72
+ *
73
+ * @param {Number} count - current count that acceded the limit
74
+ * @returns {Object} instance of HostLimitError
75
+ */
59
76
  generateError(count) {
60
77
  let errorObj = super.generateError();
61
78
 
62
79
  errorObj.message = this.fallbackMessage;
63
80
 
64
81
  if (this.error) {
82
+ const formatter = this.formatter || Intl.NumberFormat().format;
65
83
  try {
66
84
  errorObj.message = _.template(this.error)(
67
85
  {
68
- max: Intl.NumberFormat().format(this.max),
69
- count: Intl.NumberFormat().format(count),
86
+ max: formatter(this.max),
87
+ count: formatter(count),
70
88
  name: this.name
71
89
  });
72
90
  } catch (e) {
@@ -85,13 +103,14 @@ class MaxLimit extends Limit {
85
103
  }
86
104
 
87
105
  /**
88
- * Throws a HostLimitError if the configured or passed max limit is ecceded by currentCountQuery
106
+ * Throws a HostLimitError if the configured or passed max limit is acceded by currentCountQuery
89
107
  *
90
108
  * @param {Object} options
91
109
  * @param {Number} [options.max] - overrides configured default max value to perform checks against
110
+ * @param {Number} [options.addedCount] - number of items to add to the currentCount during the check
92
111
  */
93
112
  async errorIfWouldGoOverLimit({max, addedCount = 1} = {}) {
94
- let currentCount = await this.currentCountQuery(this.db);
113
+ let currentCount = await this.currentCountQuery();
95
114
 
96
115
  if ((currentCount + addedCount) > (max || this.max)) {
97
116
  throw this.generateError(currentCount);
@@ -99,13 +118,14 @@ class MaxLimit extends Limit {
99
118
  }
100
119
 
101
120
  /**
102
- * Throws a HostLimitError if the configured or passed max limit is ecceded by currentCountQuery
121
+ * Throws a HostLimitError if the configured or passed max limit is acceded by currentCountQuery
103
122
  *
104
123
  * @param {Object} options
105
124
  * @param {Number} [options.max] - overrides configured default max value to perform checks against
125
+ * @param {Number} [options.currentCount] - overrides currentCountQuery to perform checks against
106
126
  */
107
- async errorIfIsOverLimit({max} = {}) {
108
- let currentCount = await this.currentCountQuery(this.db);
127
+ async errorIfIsOverLimit({max, currentCount} = {}) {
128
+ currentCount = currentCount || await this.currentCountQuery();
109
129
 
110
130
  if (currentCount > (max || this.max)) {
111
131
  throw this.generateError(currentCount);
@@ -120,12 +140,13 @@ class MaxPeriodicLimit extends Limit {
120
140
  * @param {String} options.name - name of the limit
121
141
  * @param {Object} options.config - limit configuration
122
142
  * @param {Number} options.config.maxPeriodic - maximum limit the limit would check against
143
+ * @param {String} options.config.error - error message to use when limit is reached
123
144
  * @param {Function} options.config.currentCountQuery - query checking the state that would be compared against the limit
124
145
  * @param {('month')} options.config.interval - an interval to take into account when checking the limit. Currently only supports 'month' value
125
146
  * @param {String} options.config.startDate - start date in ISO 8601 format (https://en.wikipedia.org/wiki/ISO_8601), used to calculate period intervals
126
147
  * @param {String} options.helpLink - URL to the resource explaining how the limit works
127
- * @param {Object} options.db - instance of knex db connection that currentCountQuery can use to run state check through
128
- * @param {Object} options.errors - instance of errors compatible with Ghost-Ignition's errors (https://github.com/TryGhost/Ignition#errors)
148
+ * @param {Object} [options.db] - instance of knex db connection that currentCountQuery can use to run state check through
149
+ * @param {Object} options.errors - instance of errors compatible with GhostError errors (@tryghost/errors)
129
150
  */
130
151
  constructor({name, config, helpLink, db, errors}) {
131
152
  super({name, error: config.error || '', helpLink, db, errors});
@@ -188,13 +209,14 @@ class MaxPeriodicLimit extends Limit {
188
209
  }
189
210
 
190
211
  /**
191
- * Throws a HostLimitError if the configured or passed max limit is ecceded by currentCountQuery
212
+ * Throws a HostLimitError if the configured or passed max limit is acceded by currentCountQuery
192
213
  *
193
214
  * @param {Object} options
194
215
  * @param {Number} [options.max] - overrides configured default maxPeriodic value to perform checks against
216
+ * @param {Number} [options.addedCount] - number of items to add to the currentCount during the check
195
217
  */
196
218
  async errorIfWouldGoOverLimit({max, addedCount = 1} = {}) {
197
- let currentCount = await this.currentCountQuery(this.db);
219
+ let currentCount = await this.currentCountQuery();
198
220
 
199
221
  if ((currentCount + addedCount) > (max || this.maxPeriodic)) {
200
222
  throw this.generateError(currentCount);
@@ -202,13 +224,13 @@ class MaxPeriodicLimit extends Limit {
202
224
  }
203
225
 
204
226
  /**
205
- * Throws a HostLimitError if the configured or passed max limit is ecceded by currentCountQuery
227
+ * Throws a HostLimitError if the configured or passed max limit is acceded by currentCountQuery
206
228
  *
207
229
  * @param {Object} options
208
230
  * @param {Number} [options.max] - overrides configured default maxPeriodic value to perform checks against
209
231
  */
210
232
  async errorIfIsOverLimit({max} = {}) {
211
- let currentCount = await this.currentCountQuery(this.db);
233
+ let currentCount = await this.currentCountQuery();
212
234
 
213
235
  if (currentCount > (max || this.maxPeriodic)) {
214
236
  throw this.generateError(currentCount);
@@ -223,9 +245,10 @@ class FlagLimit extends Limit {
223
245
  * @param {String} options.name - name of the limit
224
246
  * @param {Object} options.config - limit configuration
225
247
  * @param {Number} options.config.disabled - disabled/enabled flag for the limit
248
+ * @param {String} options.config.error - error message to use when limit is reached
226
249
  * @param {String} options.helpLink - URL to the resource explaining how the limit works
227
- * @param {Object} options.db - instance of knex db connection that currentCountQuery can use to run state check through
228
- * @param {Object} options.errors - instance of errors compatible with Ghost-Ignition's errors (https://github.com/TryGhost/Ignition#errors)
250
+ * @param {Object} [options.db] - instance of knex db connection that currentCountQuery can use to run state check through
251
+ * @param {Object} options.errors - instance of errors compatible with GhostError errors (@tryghost/errors)
229
252
  */
230
253
  constructor({name, config, helpLink, db, errors}) {
231
254
  super({name, error: config.error || '', helpLink, db, errors});
@@ -265,6 +288,16 @@ class FlagLimit extends Limit {
265
288
  }
266
289
 
267
290
  class AllowlistLimit extends Limit {
291
+ /**
292
+ *
293
+ * @param {Object} options
294
+ * @param {String} options.name - name of the limit
295
+ * @param {Object} options.config - limit configuration
296
+ * @param {[String]} options.config.allowlist - allowlist values that would be compared against
297
+ * @param {String} options.config.error - error message to use when limit is reached
298
+ * @param {String} options.helpLink - URL to the resource explaining how the limit works
299
+ * @param {Object} options.errors - instance of errors compatible with GhostError errors (@tryghost/errors)
300
+ */
268
301
  constructor({name, config, helpLink, errors}) {
269
302
  super({name, error: config.error || '', helpLink, errors});
270
303
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tryghost/limit-service",
3
- "version": "0.6.4",
3
+ "version": "1.0.2",
4
4
  "repository": "https://github.com/TryGhost/Utils/tree/main/packages/limit-service",
5
5
  "author": "Ghost Foundation",
6
6
  "license": "MIT",
@@ -8,7 +8,7 @@
8
8
  "exports": "./lib/limit-service.js",
9
9
  "scripts": {
10
10
  "dev": "echo \"Implement me!\"",
11
- "test": "NODE_ENV=testing c8 mocha './test/**/*.test.js'",
11
+ "test": "NODE_ENV=testing c8 --reporter text --reporter cobertura mocha './test/**/*.test.js'",
12
12
  "lint": "eslint . --ext .js --cache",
13
13
  "posttest": "yarn lint"
14
14
  },
@@ -20,15 +20,15 @@
20
20
  "access": "public"
21
21
  },
22
22
  "devDependencies": {
23
- "c8": "7.9.0",
24
- "mocha": "9.1.2",
23
+ "c8": "7.10.0",
24
+ "mocha": "9.1.3",
25
25
  "should": "13.2.3",
26
26
  "sinon": "11.1.2"
27
27
  },
28
28
  "dependencies": {
29
- "@tryghost/errors": "^0.2.16",
29
+ "@tryghost/errors": "^1.0.1",
30
30
  "lodash": "^4.17.21",
31
31
  "luxon": "^1.26.0"
32
32
  },
33
- "gitHead": "a88c5e82f900a7df2d03c2e07d14ec9b5c59dde1"
33
+ "gitHead": "cef9c2f09117b0966380653a9c2be7aa409a9f86"
34
34
  }