@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 +13 -2
- package/lib/config.js +10 -1
- package/lib/limit-service.js +15 -3
- package/lib/limit.js +51 -18
- package/package.json +6 -6
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
|
};
|
package/lib/limit-service.js
CHANGED
|
@@ -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
|
|
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
|
|
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 {
|
|
40
|
-
* @param {
|
|
41
|
-
* @param {
|
|
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:
|
|
69
|
-
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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.
|
|
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.
|
|
24
|
-
"mocha": "9.1.
|
|
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.
|
|
29
|
+
"@tryghost/errors": "^1.0.1",
|
|
30
30
|
"lodash": "^4.17.21",
|
|
31
31
|
"luxon": "^1.26.0"
|
|
32
32
|
},
|
|
33
|
-
"gitHead": "
|
|
33
|
+
"gitHead": "cef9c2f09117b0966380653a9c2be7aa409a9f86"
|
|
34
34
|
}
|