backend-manager 3.0.35 → 3.0.37
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 +3 -2
- package/src/manager/functions/core/actions/api/admin/cron.js +37 -0
- package/src/manager/functions/core/cron/daily/reset-usage.js +171 -0
- package/src/manager/functions/core/cron/daily.js +35 -39
- package/src/manager/helpers/settings.js +32 -0
- package/src/manager/helpers/usage.js +242 -0
- package/src/manager/helpers/user.js +11 -0
- package/src/manager/index.js +30 -36
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "backend-manager",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.37",
|
|
4
4
|
"description": "Quick tools for developing Firebase functions",
|
|
5
5
|
"main": "src/manager/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"scripts": {
|
|
11
11
|
"_test": "npm run prepare && ./node_modules/mocha/bin/mocha test/ --recursive --timeout=10000",
|
|
12
12
|
"test": "./node_modules/mocha/bin/mocha test/ --recursive --timeout=10000",
|
|
13
|
+
"test:usage": "./node_modules/mocha/bin/mocha test/usage.js --timeout=10000",
|
|
13
14
|
"start": "node src/index.js"
|
|
14
15
|
},
|
|
15
16
|
"engines": {
|
|
@@ -57,7 +58,7 @@
|
|
|
57
58
|
"npm-api": "^1.0.1",
|
|
58
59
|
"paypal-server-api": "^1.0.3",
|
|
59
60
|
"pushid": "^1.0.0",
|
|
60
|
-
"resolve-account": "^1.0.
|
|
61
|
+
"resolve-account": "^1.0.10",
|
|
61
62
|
"semver": "^7.5.4",
|
|
62
63
|
"shortid": "^2.2.16",
|
|
63
64
|
"sizeitup": "^1.0.7",
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
function Module() {
|
|
2
|
+
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
Module.prototype.main = function () {
|
|
6
|
+
const self = this;
|
|
7
|
+
const Manager = self.Manager;
|
|
8
|
+
const Api = self.Api;
|
|
9
|
+
const assistant = self.assistant;
|
|
10
|
+
const payload = self.payload;
|
|
11
|
+
|
|
12
|
+
return new Promise(async function(resolve, reject) {
|
|
13
|
+
|
|
14
|
+
// Check if the user is an admin
|
|
15
|
+
if (!payload.user.roles.admin && assistant.meta.environment === 'production') {
|
|
16
|
+
return reject(assistant.errorManager(`Admin required.`, {code: 401, sentry: false, send: false, log: false}).error)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Check if the ID is set
|
|
20
|
+
if (!payload.data.payload.id) {
|
|
21
|
+
return reject(assistant.errorManager(`Missing parameter {id}`, {code: 400, sentry: false, send: false, log: false}).error)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Run the cron job
|
|
25
|
+
Manager._process((new (require(`../../../cron/${payload.data.payload.id}.js`))()).init(Manager, { context: {}, }))
|
|
26
|
+
.then((res) => {
|
|
27
|
+
return resolve({data: res});
|
|
28
|
+
})
|
|
29
|
+
.catch(e => {
|
|
30
|
+
return reject(assistant.errorManager(e, {code: 400, sentry: false, send: false, log: false}).error)
|
|
31
|
+
})
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
module.exports = Module;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
const _ = require('lodash');
|
|
2
|
+
const fetch = require('wonderful-fetch');
|
|
3
|
+
|
|
4
|
+
function Module() {
|
|
5
|
+
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
Module.prototype.init = function (Manager, data) {
|
|
9
|
+
const self = this;
|
|
10
|
+
self.Manager = Manager;
|
|
11
|
+
|
|
12
|
+
self.libraries = Manager.libraries;
|
|
13
|
+
self.assistant = Manager.Assistant();
|
|
14
|
+
|
|
15
|
+
self.storage = Manager.storage({name: 'usage', temporary: true, clear: false, log: false});
|
|
16
|
+
|
|
17
|
+
self.context = data.context;
|
|
18
|
+
return self;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
Module.prototype.main = function() {
|
|
22
|
+
const self = this;
|
|
23
|
+
const Manager = self.Manager;
|
|
24
|
+
const libraries = self.libraries;
|
|
25
|
+
const assistant = self.assistant;
|
|
26
|
+
const context = self.context;
|
|
27
|
+
|
|
28
|
+
return new Promise(async function(resolve, reject) {
|
|
29
|
+
assistant.log(`cron/daily/reset-usage(): Starting main...`);
|
|
30
|
+
|
|
31
|
+
// Clear local
|
|
32
|
+
await self.clearLocal();
|
|
33
|
+
|
|
34
|
+
// Clear firestore
|
|
35
|
+
await self.clearFirestore();
|
|
36
|
+
|
|
37
|
+
return resolve();
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
Module.prototype.clearLocal = function() {
|
|
42
|
+
const self = this;
|
|
43
|
+
const Manager = self.Manager;
|
|
44
|
+
const libraries = self.libraries;
|
|
45
|
+
const assistant = self.assistant;
|
|
46
|
+
const context = self.context;
|
|
47
|
+
|
|
48
|
+
return new Promise(async function(resolve, reject) {
|
|
49
|
+
// Log status
|
|
50
|
+
assistant.log(`cron/daily/reset-usage() [local]: Starting...`);
|
|
51
|
+
|
|
52
|
+
// Set variables
|
|
53
|
+
const now = new Date();
|
|
54
|
+
|
|
55
|
+
// Log storage
|
|
56
|
+
assistant.log(`cron/daily/reset-usage() [local]: storage(apps)`, self.storage.get('apps', {}).value());
|
|
57
|
+
assistant.log(`cron/daily/reset-usage() [local]: storage(users)`, self.storage.get('users', {}).value());
|
|
58
|
+
|
|
59
|
+
// Clear storage
|
|
60
|
+
self.storage.setState({}).write();
|
|
61
|
+
|
|
62
|
+
// Log status
|
|
63
|
+
assistant.log(`cron/daily/reset-usage() [local]: Completed!`);
|
|
64
|
+
|
|
65
|
+
return resolve();
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
Module.prototype.clearFirestore = function() {
|
|
70
|
+
const self = this;
|
|
71
|
+
const Manager = self.Manager;
|
|
72
|
+
const libraries = self.libraries;
|
|
73
|
+
const assistant = self.assistant;
|
|
74
|
+
const context = self.context;
|
|
75
|
+
|
|
76
|
+
return new Promise(async function(resolve, reject) {
|
|
77
|
+
// Log status
|
|
78
|
+
assistant.log(`cron/daily/reset-usage() [firestore]: Starting...`);
|
|
79
|
+
|
|
80
|
+
// Clear storage
|
|
81
|
+
const metrics = await fetch(`https://us-central1-itw-creative-works.cloudfunctions.net/getApp`, {
|
|
82
|
+
method: 'post',
|
|
83
|
+
response: 'json',
|
|
84
|
+
body: {
|
|
85
|
+
id: Manager.config.app.id,
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
.then(response => {
|
|
89
|
+
response.products = response.products || {};
|
|
90
|
+
|
|
91
|
+
for (let product of Object.values(response.products)) {
|
|
92
|
+
product = product || {};
|
|
93
|
+
product.planId = product.planId || '';
|
|
94
|
+
|
|
95
|
+
if (product.planId.includes('basic')) {
|
|
96
|
+
return product.limits;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return new Error('No basic product found');
|
|
101
|
+
})
|
|
102
|
+
.catch(e => e);
|
|
103
|
+
|
|
104
|
+
// Log status
|
|
105
|
+
assistant.log(`cron/daily/reset-usage() [firestore]: Resetting metrics`, metrics);
|
|
106
|
+
|
|
107
|
+
if (metrics instanceof Error) {
|
|
108
|
+
return reject(assistant.errorManager(`Failed to check providers: ${metrics}`, {code: 500, sentry: false, send: false, log: false}).error)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Reset all metrics with for loop of metrics
|
|
112
|
+
// TODO: OPTIMIZATION: Put all of the changes into a single batch
|
|
113
|
+
for (const metric of Object.keys(metrics)) {
|
|
114
|
+
assistant.log(`cron/daily/reset-usage() [firestore]: Resetting ${metric} for all users`);
|
|
115
|
+
|
|
116
|
+
await Manager.Utilities().iterateCollection((batch, index) => {
|
|
117
|
+
return new Promise(async (resolve, reject) => {
|
|
118
|
+
for (const doc of batch.docs) {
|
|
119
|
+
const data = doc.data();
|
|
120
|
+
|
|
121
|
+
// Normalize the metric
|
|
122
|
+
data.usage = data.usage || {};
|
|
123
|
+
data.usage[metric] = data.usage[metric] || {};
|
|
124
|
+
data.usage[metric].period = data.usage[metric].period || 0;
|
|
125
|
+
data.usage[metric].total = data.usage[metric].total || 0;
|
|
126
|
+
data.usage[metric].last = data.usage[metric].last || {};
|
|
127
|
+
|
|
128
|
+
// Bail if its 0
|
|
129
|
+
if (data.usage[metric].period <= 0) {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Reset the metric
|
|
134
|
+
const original = data.usage[metric].period;
|
|
135
|
+
data.usage[metric].period = 0;
|
|
136
|
+
|
|
137
|
+
// Update the doc
|
|
138
|
+
await doc.ref.update({usage: data.usage})
|
|
139
|
+
.then(r => {
|
|
140
|
+
assistant.log(`cron/daily/reset-usage() [firestore]: Reset ${metric} for ${doc.id} (${original} -> 0)`);
|
|
141
|
+
})
|
|
142
|
+
.catch(e => {
|
|
143
|
+
assistant.errorManager(`Error resetting ${metric} for ${doc.id}: ${e}`, {sentry: false, send: false, log: true})
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Complete
|
|
148
|
+
return resolve();
|
|
149
|
+
});
|
|
150
|
+
}, {
|
|
151
|
+
collection: 'users',
|
|
152
|
+
where: [
|
|
153
|
+
{field: `usage.${metric}.period`, operator: '>', value: 0},
|
|
154
|
+
],
|
|
155
|
+
batchSize: 5000,
|
|
156
|
+
log: true,
|
|
157
|
+
})
|
|
158
|
+
.then((r) => {
|
|
159
|
+
assistant.log(`cron/daily/reset-usage() [firestore]: Reset ${metric} for all users complete!`);
|
|
160
|
+
})
|
|
161
|
+
.catch(e => {
|
|
162
|
+
assistant.errorManager(`Error resetting ${metric} for all users: ${e}`, {sentry: false, send: false, log: true})
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return resolve();
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
module.exports = Module;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const fetch = require('wonderful-fetch');
|
|
2
|
+
const jetpack = require('fs-jetpack');
|
|
2
3
|
|
|
3
4
|
function Module() {
|
|
4
5
|
|
|
@@ -22,47 +23,42 @@ Module.prototype.main = function() {
|
|
|
22
23
|
const context = self.context;
|
|
23
24
|
|
|
24
25
|
return new Promise(async function(resolve, reject) {
|
|
25
|
-
|
|
26
|
-
Promise.all([
|
|
27
|
-
// Backup the database
|
|
28
|
-
// TODO: Disabled this because of Firebase's new PITR Disaster Recovery feature
|
|
29
|
-
// fetch(`${Manager.project.functionsUrl}/bm_api`, {
|
|
30
|
-
// method: 'post',
|
|
31
|
-
// response: 'json',
|
|
32
|
-
// body: {
|
|
33
|
-
// backendManagerKey: Manager.config.backend_manager.key,
|
|
34
|
-
// command: 'admin:backup',
|
|
35
|
-
// }
|
|
36
|
-
// })
|
|
37
|
-
// .then(response => {
|
|
38
|
-
// assistant.log(`Successfully executed backup:`, response)
|
|
39
|
-
// }),
|
|
26
|
+
assistant.log(`cron/daily(): Starting...`);
|
|
40
27
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
// method: 'post',
|
|
45
|
-
// response: 'json',
|
|
46
|
-
// body: {
|
|
47
|
-
// backendManagerKey: Manager.config.backend_manager.key,
|
|
48
|
-
// command: 'admin:sync-users',
|
|
49
|
-
// }
|
|
50
|
-
// })
|
|
51
|
-
// .then(response => {
|
|
52
|
-
// assistant.log(`Successfully executed sync-users:`, response)
|
|
53
|
-
// }),
|
|
28
|
+
const jobsPath = `${__dirname}/daily`;
|
|
29
|
+
const jobs = jetpack.list(jobsPath);
|
|
30
|
+
let caught;
|
|
54
31
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
32
|
+
// For of loop for jobs, load the job, execute it, and log the result
|
|
33
|
+
for (let i = 0; i < jobs.length; i++) {
|
|
34
|
+
const job = jobs[i];
|
|
35
|
+
const jobName = job.replace('.js', '');
|
|
36
|
+
|
|
37
|
+
// Log
|
|
38
|
+
assistant.log(`cron/daily(): Job ${jobName} starting...`);
|
|
39
|
+
|
|
40
|
+
// Load the job
|
|
41
|
+
const Job = require(`${jobsPath}/${job}`);
|
|
42
|
+
const jobInstance = new Job();
|
|
43
|
+
jobInstance.init(Manager, { context: context, });
|
|
44
|
+
|
|
45
|
+
// Execute the job
|
|
46
|
+
await jobInstance.main()
|
|
47
|
+
.then(res => {
|
|
48
|
+
assistant.log(`cron/daily(): Job ${jobName} completed...`);
|
|
49
|
+
})
|
|
50
|
+
.catch(e => {
|
|
51
|
+
assistant.errorManager(`Error executing ${jobName}: ${e}`, {sentry: true, send: false, log: true});
|
|
52
|
+
caught = e;
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (caught) {
|
|
57
|
+
return reject(caught);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Return
|
|
61
|
+
return resolve();
|
|
66
62
|
});
|
|
67
63
|
}
|
|
68
64
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings
|
|
3
|
+
*
|
|
4
|
+
*/
|
|
5
|
+
function Settings(m) {
|
|
6
|
+
const self = this;
|
|
7
|
+
|
|
8
|
+
self.Manager = m;
|
|
9
|
+
|
|
10
|
+
self.user = null;
|
|
11
|
+
|
|
12
|
+
self.initialized = false;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
Settings.prototype.init = function (assistant, options) {
|
|
16
|
+
const self = this;
|
|
17
|
+
|
|
18
|
+
return new Promise(async function(resolve, reject) {
|
|
19
|
+
const Manager = self.Manager;
|
|
20
|
+
|
|
21
|
+
// Set options
|
|
22
|
+
options = options || {};
|
|
23
|
+
|
|
24
|
+
// Set initialized to true
|
|
25
|
+
self.initialized = true;
|
|
26
|
+
|
|
27
|
+
// Resolve
|
|
28
|
+
return resolve(self);
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
module.exports = Settings;
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Usage
|
|
3
|
+
* Meant to check and update usage for a user
|
|
4
|
+
* Uses the ITWCW apps/{app}/products/{product}/limits/{metric} to check limits
|
|
5
|
+
* Stores usage in the user's firestore document OR in local/temp storage if no user
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fetch = require('wonderful-fetch');
|
|
9
|
+
const moment = require('moment');
|
|
10
|
+
const _ = require('lodash');
|
|
11
|
+
const hcaptcha = require('hcaptcha');
|
|
12
|
+
|
|
13
|
+
function Usage(m) {
|
|
14
|
+
const self = this;
|
|
15
|
+
|
|
16
|
+
self.Manager = m;
|
|
17
|
+
|
|
18
|
+
self.user = null;
|
|
19
|
+
self.app = null;
|
|
20
|
+
self.options = null;
|
|
21
|
+
self.assistant = null;
|
|
22
|
+
self.storage = null;
|
|
23
|
+
|
|
24
|
+
self.paths = {
|
|
25
|
+
user: '',
|
|
26
|
+
app: '',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
self.initialized = false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
Usage.prototype.init = function (assistant, options) {
|
|
33
|
+
const self = this;
|
|
34
|
+
|
|
35
|
+
return new Promise(async function(resolve, reject) {
|
|
36
|
+
const Manager = self.Manager;
|
|
37
|
+
|
|
38
|
+
// Set options
|
|
39
|
+
options = options || {};
|
|
40
|
+
options.app = options.app || Manager.config.app.id;
|
|
41
|
+
options.refetch = typeof options.refetch === 'undefined' ? false : options.refetch;
|
|
42
|
+
options.clear = typeof options.clear === 'undefined' ? false : options.clear;
|
|
43
|
+
options.today = options.today || undefined;
|
|
44
|
+
options.log = typeof options.log === 'undefined' ? false : options.log;
|
|
45
|
+
|
|
46
|
+
// Check for required options
|
|
47
|
+
if (!assistant) {
|
|
48
|
+
return reject(new Error('Missing required {assistant} parameter'));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Set options
|
|
52
|
+
self.options = options;
|
|
53
|
+
|
|
54
|
+
// Set assistant
|
|
55
|
+
self.assistant = assistant;
|
|
56
|
+
|
|
57
|
+
// Setup storage
|
|
58
|
+
self.storage = Manager.storage({name: 'usage', temporary: true, clear: options.clear, log: options.log});
|
|
59
|
+
|
|
60
|
+
self.paths.user = `users.${self.assistant.request.geolocation.ip.replace(/[\.:]/g, '_')}`;
|
|
61
|
+
self.paths.app = `apps.${options.app}`;
|
|
62
|
+
|
|
63
|
+
// Get storage data
|
|
64
|
+
const appLastFetched = moment(self.storage.get(`${self.paths.app}.lastFetched`, 0).value());
|
|
65
|
+
const diff = moment().diff(appLastFetched, 'hours');
|
|
66
|
+
|
|
67
|
+
// Authenticate user (user will be resolved as well)
|
|
68
|
+
self.user = await assistant.authenticate();
|
|
69
|
+
|
|
70
|
+
// Replce with local if no user
|
|
71
|
+
if (!self.user.auth.uid) {
|
|
72
|
+
const existingLocal = self.storage.get(self.paths.user, {}).value();
|
|
73
|
+
self.user.usage = existingLocal && existingLocal.usage ? existingLocal.usage : self.user.usage;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Log
|
|
77
|
+
self.log(`Usage.init(): Checking if usage data needs to be fetched (${diff} hours)...`);
|
|
78
|
+
|
|
79
|
+
// Get app data to get plan limits using cached data if possible
|
|
80
|
+
if (diff > 1 || options.refetch) {
|
|
81
|
+
await fetch('https://us-central1-itw-creative-works.cloudfunctions.net/getApp', {
|
|
82
|
+
method: 'post',
|
|
83
|
+
response: 'json',
|
|
84
|
+
body: {
|
|
85
|
+
id: options.app,
|
|
86
|
+
},
|
|
87
|
+
})
|
|
88
|
+
.then((json) => {
|
|
89
|
+
// Write data and last fetched to storage
|
|
90
|
+
self.storage.set(`${self.paths.app}.data`, json).write();
|
|
91
|
+
self.storage.set(`${self.paths.app}.lastFetched`, new Date().toISOString()).write();
|
|
92
|
+
})
|
|
93
|
+
.catch(e => {
|
|
94
|
+
assistant.errorManager(`Usage.init(): Error fetching app data: ${e}`, {sentry: true, send: false, log: true});
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Get app data
|
|
99
|
+
self.app = self.storage.get(`${self.paths.app}.data`, {}).value();
|
|
100
|
+
|
|
101
|
+
if (!self.app) {
|
|
102
|
+
return reject(new Error('Usage.init(): No app data found'));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
self.log(`Usage.init(): Got app data`, self.app);
|
|
106
|
+
self.log(`Usage.init(): Got user`, self.user);
|
|
107
|
+
|
|
108
|
+
// Resolve with the user to get their current plan limits
|
|
109
|
+
// If there is no user, go forward with their IP and make them a basic plan user
|
|
110
|
+
|
|
111
|
+
// Set initialized to true
|
|
112
|
+
self.initialized = true;
|
|
113
|
+
|
|
114
|
+
// Resolve
|
|
115
|
+
return resolve(self);
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
Usage.prototype.validate = function (path, options) {
|
|
120
|
+
const self = this;
|
|
121
|
+
|
|
122
|
+
return new Promise(async function(resolve, reject) {
|
|
123
|
+
const Manager = self.Manager;
|
|
124
|
+
const assistant = self.assistant;
|
|
125
|
+
|
|
126
|
+
options = options || {};
|
|
127
|
+
options.useCaptchaResponse = typeof options.useCaptchaResponse === 'undefined' ? true : options.useCaptchaResponse;
|
|
128
|
+
|
|
129
|
+
// Check for required options
|
|
130
|
+
const period = _.get(self.user, `usage.${path}.period`, 0);
|
|
131
|
+
const allowed = _.get(self.app, `products.${self.options.app}-${self.user.plan.id}.limits.${path}`, 0);
|
|
132
|
+
|
|
133
|
+
// Log
|
|
134
|
+
self.log(`Usage.validate(): Checking ${period}/${allowed} for ${path}...`);
|
|
135
|
+
|
|
136
|
+
// If they are under the limit, resolve
|
|
137
|
+
if (period < allowed) {
|
|
138
|
+
self.log(`Usage.validate(): Valid for ${path}`);
|
|
139
|
+
|
|
140
|
+
return resolve(true);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// If they are using captcha, attempt to resolve
|
|
144
|
+
const captchaResponse = assistant.request.data['h-captcha-response'];
|
|
145
|
+
if (captchaResponse && options.useCaptchaResponse) {
|
|
146
|
+
self.log(`Usage.validate(): Checking captcha response`, captchaResponse);
|
|
147
|
+
|
|
148
|
+
const captchaResult = await hcaptcha.verify(process.env.HCAPTCHA_SECRET, captchaResponse)
|
|
149
|
+
.then((data) => data)
|
|
150
|
+
.catch((e) => e);
|
|
151
|
+
|
|
152
|
+
// If the captcha is valid, resolve
|
|
153
|
+
if (!captchaResult || captchaResult instanceof Error || !captchaResult.success) {
|
|
154
|
+
return reject(
|
|
155
|
+
assistant.errorManager(`Captcha verification failed.`, {code: 400, sentry: false, send: false, log: false}).error
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Otherwise, they are over the limit, reject
|
|
161
|
+
return reject(
|
|
162
|
+
assistant.errorManager(`You have exceeded your ${path} usage limit of ${period}/${allowed}.`, {code: 429, sentry: false, send: false, log: false}).error
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
Usage.prototype.increment = function (path, value, options) {
|
|
168
|
+
const self = this;
|
|
169
|
+
const Manager = self.Manager;
|
|
170
|
+
const assistant = self.assistant;
|
|
171
|
+
|
|
172
|
+
options = options || {};
|
|
173
|
+
options.id = options.id || null;
|
|
174
|
+
|
|
175
|
+
// Update total and period
|
|
176
|
+
['total', 'period', 'last'].forEach((key) => {
|
|
177
|
+
const resolved = `usage.${path}.${key}`;
|
|
178
|
+
|
|
179
|
+
if (key === 'last') {
|
|
180
|
+
const now = moment(
|
|
181
|
+
typeof self.options.today === 'undefined' ? new Date() : self.options.today
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
_.set(self.user, resolved, {
|
|
185
|
+
id: options.id,
|
|
186
|
+
timestamp: now.toISOString(),
|
|
187
|
+
timestampUNIX: now.unix(),
|
|
188
|
+
});
|
|
189
|
+
} else {
|
|
190
|
+
_.set(self.user, resolved,
|
|
191
|
+
_.get(self.user, resolved, 0) + value
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Log the updated user
|
|
197
|
+
self.log(`Usage.init(): Incremented user`, self.user);
|
|
198
|
+
|
|
199
|
+
return self;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
Usage.prototype.update = function () {
|
|
203
|
+
const self = this;
|
|
204
|
+
|
|
205
|
+
return new Promise(async function(resolve, reject) {
|
|
206
|
+
const Manager = self.Manager;
|
|
207
|
+
const assistant = self.assistant;
|
|
208
|
+
|
|
209
|
+
// Write self.user to firestore or local if no user
|
|
210
|
+
if (self.user.auth.uid) {
|
|
211
|
+
Manager.libraries.admin.firestore(`users/${self.user.auth.uid}`)
|
|
212
|
+
.set({
|
|
213
|
+
usage: self.user.usage,
|
|
214
|
+
}, {merge: true})
|
|
215
|
+
.then(() => {
|
|
216
|
+
self.log(`Usage.update(): Updated user in firestore`);
|
|
217
|
+
|
|
218
|
+
return resolve();
|
|
219
|
+
})
|
|
220
|
+
.catch(e => {
|
|
221
|
+
return reject(assistant.errorManager(e, {sentry: true, send: false, log: false}));
|
|
222
|
+
});
|
|
223
|
+
} else {
|
|
224
|
+
self.storage.set(`${self.paths.user}.usage`, self.user.usage).write();
|
|
225
|
+
|
|
226
|
+
self.log(`Usage.update(): Updated user in local storage`);
|
|
227
|
+
|
|
228
|
+
return resolve();
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
Usage.prototype.log = function () {
|
|
234
|
+
const self = this;
|
|
235
|
+
|
|
236
|
+
// Log
|
|
237
|
+
if (self.options.log) {
|
|
238
|
+
self.assistant.log(...arguments);
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
module.exports = Usage;
|
|
@@ -107,6 +107,17 @@ function User(Manager, settings, options) {
|
|
|
107
107
|
clientId: _.get(settings, 'api.clientId', defaults ? `${uuid4()}` : null),
|
|
108
108
|
privateKey: _.get(settings, 'api.privateKey', defaults ? `${uidgen.generateSync()}` : null),
|
|
109
109
|
},
|
|
110
|
+
usage: {
|
|
111
|
+
requests: {
|
|
112
|
+
period: _.get(settings, 'usage.requests.period', defaults ? 0 : null),
|
|
113
|
+
total: _.get(settings, 'usage.requests.total', defaults ? 0 : null),
|
|
114
|
+
last: {
|
|
115
|
+
id: _.get(settings, 'usage.requests.last.id', defaults ? '' : null),
|
|
116
|
+
timestamp: _.get(settings, 'usage.requests.last.timestamp', defaults ? oldDate : null),
|
|
117
|
+
timestampUNIX: _.get(settings, 'usage.requests.last.timestampUNIX', defaults ? oldDateUNIX : null),
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
},
|
|
110
121
|
personal: {
|
|
111
122
|
birthday: {
|
|
112
123
|
timestamp: _.get(settings, 'personal.birthday.timestamp', defaults ? oldDate : null),
|
package/src/manager/index.js
CHANGED
|
@@ -74,8 +74,8 @@ Manager.prototype.init = function (exporter, options) {
|
|
|
74
74
|
self.options = options;
|
|
75
75
|
self.project = options.firebaseConfig || JSON.parse(process.env.FIREBASE_CONFIG || '{}');
|
|
76
76
|
self.project.resourceZone = options.resourceZone;
|
|
77
|
-
self.project.serviceAccountPath = path.resolve(self.cwd, options.serviceAccountPath)
|
|
78
|
-
self.project.backendManagerConfigPath = path.resolve(self.cwd, options.backendManagerConfigPath)
|
|
77
|
+
self.project.serviceAccountPath = path.resolve(self.cwd, options.serviceAccountPath);
|
|
78
|
+
self.project.backendManagerConfigPath = path.resolve(self.cwd, options.backendManagerConfigPath);
|
|
79
79
|
|
|
80
80
|
self.package = resolveProjectPackage();
|
|
81
81
|
self.config = merge(
|
|
@@ -600,6 +600,18 @@ Manager.prototype.SubscriptionResolver = function () {
|
|
|
600
600
|
return new self.libraries.SubscriptionResolver(self, ...arguments);
|
|
601
601
|
};
|
|
602
602
|
|
|
603
|
+
Manager.prototype.Usage = function () {
|
|
604
|
+
const self = this;
|
|
605
|
+
self.libraries.Usage = self.libraries.Usage || require('./helpers/usage.js');
|
|
606
|
+
return new self.libraries.Usage(self, ...arguments);
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
Manager.prototype.Settings = function () {
|
|
610
|
+
const self = this;
|
|
611
|
+
self.libraries.Settings = self.libraries.Settings || require('./helpers/settings.js');
|
|
612
|
+
return new self.libraries.Settings(self, ...arguments);
|
|
613
|
+
};
|
|
614
|
+
|
|
603
615
|
Manager.prototype.Metadata = function () {
|
|
604
616
|
const self = this;
|
|
605
617
|
self.libraries.Metadata = self.libraries.Metadata || require('./helpers/metadata.js');
|
|
@@ -657,21 +669,26 @@ Manager.prototype.storage = function (options) {
|
|
|
657
669
|
if (!self._internal.storage[options.name]) {
|
|
658
670
|
options.temporary = typeof options.temporary === 'undefined' ? false : options.temporary;
|
|
659
671
|
options.clear = typeof options.clear === 'undefined' ? true : options.clear;
|
|
672
|
+
options.log = typeof options.log === 'undefined' ? false : options.log;
|
|
660
673
|
|
|
661
674
|
const low = require('lowdb');
|
|
662
675
|
const FileSync = require('lowdb/adapters/FileSync');
|
|
663
|
-
const
|
|
676
|
+
const location = options.temporary
|
|
664
677
|
? `${require('os').tmpdir()}/storage/${options.name}.json`
|
|
665
678
|
: `./.data/storage/${options.name}.json`;
|
|
666
|
-
const adapter = new FileSync(
|
|
679
|
+
const adapter = new FileSync(location);
|
|
680
|
+
|
|
681
|
+
if (options.log) {
|
|
682
|
+
self.assistant.log('storage(): Location', location);
|
|
683
|
+
}
|
|
667
684
|
|
|
668
685
|
if (
|
|
669
686
|
options.temporary
|
|
670
687
|
&& self.assistant.meta.environment === 'development'
|
|
671
688
|
&& options.clear
|
|
672
689
|
) {
|
|
673
|
-
self.assistant.log('Removed temporary file @',
|
|
674
|
-
jetpack.remove(
|
|
690
|
+
self.assistant.log('Removed temporary file @', location);
|
|
691
|
+
jetpack.remove(location);
|
|
675
692
|
}
|
|
676
693
|
|
|
677
694
|
options.clearInvalid = typeof options.clearInvalid === 'undefined'
|
|
@@ -679,27 +696,27 @@ Manager.prototype.storage = function (options) {
|
|
|
679
696
|
: options.clearInvalid;
|
|
680
697
|
|
|
681
698
|
function _setup() {
|
|
682
|
-
if (!jetpack.exists(
|
|
683
|
-
jetpack.write(
|
|
699
|
+
if (!jetpack.exists(location)) {
|
|
700
|
+
jetpack.write(location, {});
|
|
684
701
|
}
|
|
685
702
|
self._internal.storage[options.name] = low(adapter);
|
|
686
703
|
|
|
687
|
-
self._internal.storage[options.name].set('_location',
|
|
704
|
+
self._internal.storage[options.name].set('_location', location)
|
|
688
705
|
}
|
|
689
706
|
|
|
690
707
|
try {
|
|
691
708
|
_setup()
|
|
692
709
|
} catch (e) {
|
|
693
|
-
self.assistant.error(`Could not setup storage: ${
|
|
710
|
+
self.assistant.error(`Could not setup storage: ${location}`, e);
|
|
694
711
|
|
|
695
712
|
try {
|
|
696
713
|
if (options.clearInvalid) {
|
|
697
|
-
self.assistant.log(`Clearing invalid storage: ${
|
|
698
|
-
jetpack.write(
|
|
714
|
+
self.assistant.log(`Clearing invalid storage: ${location}`);
|
|
715
|
+
jetpack.write(location, {});
|
|
699
716
|
}
|
|
700
717
|
_setup()
|
|
701
718
|
} catch (e) {
|
|
702
|
-
self.assistant.error(`Failed to clear invalid storage: ${
|
|
719
|
+
self.assistant.error(`Failed to clear invalid storage: ${location}`, e);
|
|
703
720
|
}
|
|
704
721
|
}
|
|
705
722
|
}
|
|
@@ -707,29 +724,6 @@ Manager.prototype.storage = function (options) {
|
|
|
707
724
|
return self._internal.storage[options.name]
|
|
708
725
|
};
|
|
709
726
|
|
|
710
|
-
// Manager.prototype.LocalDatabase = function () {
|
|
711
|
-
// const self = this;
|
|
712
|
-
// if (!self.libraries.LocalDatabase) {
|
|
713
|
-
// const low = require('lowdb');
|
|
714
|
-
// const FileSync = require('lowdb/adapters/FileSync');
|
|
715
|
-
// // const dbPath = path.resolve(process.cwd(), './.data/db.json');
|
|
716
|
-
// const dbPath = './.data/db.json';
|
|
717
|
-
// const adapter = new FileSync(dbPath);
|
|
718
|
-
// const jetpack = require('fs-jetpack');
|
|
719
|
-
//
|
|
720
|
-
// try {
|
|
721
|
-
// if (!jetpack.exists(dbPath)) {
|
|
722
|
-
// jetpack.write(dbPath, {});
|
|
723
|
-
// }
|
|
724
|
-
// self.localDatabase = low(adapter);
|
|
725
|
-
// } catch (e) {
|
|
726
|
-
// console.error('Could not load .data', e);
|
|
727
|
-
// }
|
|
728
|
-
// }
|
|
729
|
-
// self.libraries.LocalDatabase = self.libraries.LocalDatabase || require('./helpers/api-manager.js');
|
|
730
|
-
// return new self.libraries.LocalDatabase(self, ...arguments);
|
|
731
|
-
// };
|
|
732
|
-
|
|
733
727
|
Manager.prototype.require = function (p) {
|
|
734
728
|
return require(p);
|
|
735
729
|
};
|