backend-manager 2.4.3 → 2.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backend-manager",
3
- "version": "2.4.3",
3
+ "version": "2.4.6",
4
4
  "description": "Quick tools for developing Firebase functions",
5
5
  "main": "src/manager/index.js",
6
6
  "bin": {
@@ -31,7 +31,7 @@
31
31
  "@google-cloud/storage": "^5.20.5",
32
32
  "@sendgrid/mail": "^7.7.0",
33
33
  "@sentry/node": "^6.19.7",
34
- "backend-assistant": "^0.0.71",
34
+ "backend-assistant": "^0.0.72",
35
35
  "busboy": "^1.6.0",
36
36
  "chalk": "^4.1.2",
37
37
  "cors": "^2.8.5",
@@ -0,0 +1,203 @@
1
+ const moment = require('moment');
2
+ const powertools = require('node-powertools');
3
+ const {Storage} = require('@google-cloud/storage');
4
+ const storage = new Storage();
5
+
6
+ function Module() {
7
+
8
+ }
9
+
10
+ Module.prototype.main = function () {
11
+ const self = this;
12
+ const Manager = self.Manager;
13
+ const Api = self.Api;
14
+ const assistant = self.assistant;
15
+ const payload = self.payload;
16
+
17
+ return new Promise(async function(resolve, reject) {
18
+
19
+ payload.data.payload.deletionRegex = payload.data.payload.deletionRegex ? powertools.regexify(payload.data.payload.deletionRegex) : payload.data.payload.deletionRegex;
20
+
21
+ if (!payload.user.roles.admin) {
22
+ return reject(assistant.errorManager(`Admin required.`, {code: 401, sentry: false, send: false, log: false}).error)
23
+ } else {
24
+ const client = new self.libraries.admin.firestore.v1.FirestoreAdminClient({});
25
+ const projectId = self.Manager.project.projectId;
26
+ const resourceZone = self.Manager.project.resourceZone;
27
+ const databaseName = client.databasePath(projectId, '(default)');
28
+ const bucketName = `bm-backup-firestore-${projectId}`;
29
+ const bucketAddress = `gs://${bucketName}`;
30
+
31
+ await self.createBucket(bucketName, resourceZone);
32
+ await self.deleteOldFiles(bucketName, resourceZone);
33
+
34
+ client.exportDocuments({
35
+ name: databaseName,
36
+ outputUriPrefix: bucketAddress,
37
+ // Leave collectionIds empty to export all collections
38
+ // or set to a list of collection IDs to export,
39
+ collectionIds: []
40
+ })
41
+ .then(responses => {
42
+ const response = responses[0];
43
+ // assistant.log('Saved backup successfully', response['name'], {environment: 'development'})
44
+ assistant.log('Saved backup successfully:', response.metadata.outputUriPrefix, {environment: 'development'})
45
+ return resolve(response['name']);
46
+ })
47
+ .catch(e => {
48
+ return reject(assistant.errorManager(e, {code: 500, sentry: false, send: false, log: true}).error)
49
+ });
50
+
51
+
52
+ }
53
+ });
54
+
55
+ };
56
+
57
+ Module.prototype.createBucket = function (bucketName, resourceZone) {
58
+ const self = this;
59
+ const Manager = self.Manager;
60
+ const Api = self.Api;
61
+ const assistant = self.assistant;
62
+ const payload = self.payload;
63
+
64
+ return new Promise(function(resolve, reject) {
65
+ storage.bucket(bucketName).getMetadata()
66
+ .then(async (meta) => {
67
+ assistant.log(`${bucketName} metadata`, meta[0], {environment: 'development'})
68
+ return resolve();
69
+ })
70
+ .catch(async (e) => {
71
+ const storageCreation = await storage.createBucket(bucketName, {
72
+ // location: 'ASIA',
73
+ location: resourceZone,
74
+ storageClass: 'COLDLINE',
75
+ })
76
+ .then(r => r)
77
+ .catch(e => e)
78
+
79
+ assistant.log('storageCreation', storageCreation, {environment: 'development'})
80
+
81
+ return resolve();
82
+ })
83
+ });
84
+ };
85
+
86
+ // https://github.com/zsolt-dev/auto-delete-gcp-storage-backups/blob/master/index.js
87
+ Module.prototype.deleteOldFiles = function (bucketName, resourceZone) {
88
+ const self = this;
89
+ const Manager = self.Manager;
90
+ const Api = self.Api;
91
+ const assistant = self.assistant;
92
+ const payload = self.payload;
93
+
94
+ return new Promise(async function(resolve, reject) {
95
+ const now = moment();
96
+ const deletionRegex = payload.data.payload.deletionRegex;
97
+
98
+ // Helpers
99
+ const getFileObjectWithMetaData = async (bucketName, fileName) => {
100
+ const [metaData] = await storage.bucket(bucketName).file(fileName).getMetadata();
101
+ return ({ fileName, created: metaData.timeCreated });
102
+ };
103
+
104
+ const deleteFileFromBucket = async (bucketName, fileName) => {
105
+ assistant.log(`Deleting item: ${fileName}...`, );
106
+ return await storage.bucket(bucketName).file(fileName).delete();
107
+ };
108
+
109
+ // Main
110
+ // get the file names as an array
111
+ let [allFiles] = await storage.bucket(bucketName).getFiles();
112
+ let deletePromises = [];
113
+ allFiles = allFiles.map(file => file.name);
114
+ // console.log(`all files: ${allFiles.join(', ')}`);
115
+
116
+ // transform to array of objects with creation timestamp { fileName: xyz, created: }
117
+ allFiles = allFiles.map(fileName => getFileObjectWithMetaData(bucketName, fileName));
118
+ allFiles = await Promise.all(allFiles);
119
+
120
+ allFiles.forEach((backup, i) => {
121
+ const date = moment(backup.created);
122
+ const day = date.date();
123
+ const month = date.month();
124
+ const age = now.diff(date, 'days', false);
125
+
126
+ assistant.log(`Sorting item ${i}: date=${date.format('MMM Do, YYYY')}, day=${day}, month=${month}, age=${age}`, );
127
+
128
+ if (age >= 31) {
129
+ if (day === 1) { return }
130
+ deletePromises.push(deleteFileFromBucket(bucketName, backup.fileName))
131
+ } else if ((deletionRegex && backup.fileName.match(deletionRegex))) {
132
+ deletePromises.push(deleteFileFromBucket(bucketName, backup.fileName))
133
+ }
134
+ })
135
+
136
+ await Promise.all(deletePromises);
137
+
138
+ return resolve();
139
+ });
140
+ };
141
+
142
+ Module.prototype._deleteOldFiles = function (bucketName, resourceZone) {
143
+ const self = this;
144
+ const Manager = self.Manager;
145
+ const Api = self.Api;
146
+ const assistant = self.assistant;
147
+ const payload = self.payload;
148
+
149
+ return new Promise(async function(resolve, reject) {
150
+ // Helpers
151
+ const getFileObjectWithMetaData = async (bucketName, fileName) => {
152
+ const [metaData] = await storage.bucket(bucketName).file(fileName).getMetadata();
153
+ return ({ fileName, created: metaData.timeCreated });
154
+ };
155
+
156
+ const deleteFileFromBucket = async (bucketName, fileName) => {
157
+ return await storage.bucket(bucketName).file(fileName).delete();
158
+ };
159
+
160
+ // Main
161
+ // get the file names as an array
162
+ let [allFiles] = await storage.bucket(bucketName).getFiles();
163
+ allFiles = allFiles.map(file => file.name);
164
+ console.log(`all files: ${allFiles.join(', ')}`);
165
+
166
+ // transform to array of objects with creation timestamp { fileName: xyz, created: }
167
+ allFiles = allFiles.map(fileName => getFileObjectWithMetaData(bucketName, fileName));
168
+ allFiles = await Promise.all(allFiles);
169
+
170
+
171
+ const filesToKeep = new Set(); // using set insted of array since set does not allow duplicates
172
+
173
+ // recent backups
174
+ allFiles.forEach(backup => {
175
+ const createdDate = new Date(backup.created);
176
+ createdDate.setHours( createdDate.getHours() + numHoursToKeepRecentBackups );
177
+ if(createdDate > new Date()) filesToKeep.add(backup.fileName);
178
+ })
179
+
180
+ // daily backups
181
+ for(var i = 0; i < numDaysToKeepOneDailyBackup; i++) {
182
+ // get day
183
+ const now = new Date();
184
+ now.setDate( now.getDate() - i );
185
+ dateString = now.toISOString().substring(0, 10);
186
+ // keep only one from that day
187
+ const backupsFromThatDay = allFiles.filter(backup => backup.created.startsWith(dateString));
188
+ if(backupsFromThatDay && backupsFromThatDay.length > 0) filesToKeep.add(backupsFromThatDay[0].fileName);
189
+ }
190
+
191
+ // filesToKeep.forEach(item => console.log(item));
192
+
193
+ const filesToDelete = allFiles.filter(backup => !filesToKeep.has(backup.fileName));
194
+ console.log(`Deleting ${filesToDelete.length} files: ${filesToDelete.map(backup => backup.fileName).join(', ')}`);
195
+
196
+ const deletePromises = filesToDelete.map(backup => deleteFileFromBucket(bucketName, backup.fileName));
197
+ await Promise.all(deletePromises);
198
+
199
+ return resolve();
200
+ });
201
+ };
202
+
203
+ module.exports = Module;
@@ -15,7 +15,7 @@ Module.prototype.main = function () {
15
15
  const payload = self.payload;
16
16
 
17
17
  return new Promise(async function(resolve, reject) {
18
- if (!payload.user.roles.admin || !payload.user.roles.blogger) {
18
+ if (!payload.user.roles.admin && !payload.user.roles.blogger) {
19
19
  return reject(assistant.errorManager(`Admin required.`, {code: 401, sentry: false, send: false, log: false}).error)
20
20
  }
21
21
 
@@ -13,13 +13,10 @@ Module.prototype.main = function () {
13
13
 
14
14
  if (payload.user.roles.admin) {
15
15
 
16
- // console.log('---payload.data.payload', payload.data.payload);
17
-
18
16
  payload.data.payload.path = `${payload.data.payload.path || ''}`;
19
17
  payload.data.payload.document = payload.data.payload.document || {};
20
18
  payload.data.payload.options = payload.data.payload.options || { merge: true };
21
19
 
22
-
23
20
  if (!payload.data.payload.path) {
24
21
  return reject(assistant.errorManager(`<path> parameter required`, {code: 400, sentry: false, send: false, log: false}).error)
25
22
  } else {
@@ -129,8 +129,8 @@ Module.prototype.main = function () {
129
129
  response.authentication[provider] = response.authentication[provider] || {};
130
130
 
131
131
  if (typeof response.authentication[provider].enabled !== 'undefined') {
132
- payload.response.data[provider] = false;
133
- assistant.log(`Overwriting ${provider}...`, {environment: 'development'});
132
+ payload.response.data[provider] = response.authentication[provider].enabled;
133
+ assistant.log(`Overwriting ${provider} to ${payload.response.data[provider]}...`, {environment: 'development'});
134
134
  }
135
135
  });
136
136
 
@@ -8,9 +8,13 @@ Module.prototype.main = function () {
8
8
  const Api = self.Api;
9
9
  const assistant = self.assistant;
10
10
  const payload = self.payload;
11
+ const powertools = Manager.require('node-powertools');
11
12
 
12
13
  return new Promise(async function(resolve, reject) {
13
14
 
15
+ if (payload.data.payload.delay > 0) {
16
+ await powertools.wait(payload.data.payload.delay)
17
+ }
14
18
 
15
19
  if (payload.data.payload.status >= 200 && payload.data.payload.status <= 299) {
16
20
  return resolve({data: payload.data.payload.response, status: payload.data.payload.status});
@@ -17,8 +17,10 @@ function OAuth2() {
17
17
 
18
18
  OAuth2.prototype.buildUrl = function (state, url) {
19
19
  const self = this;
20
+ const Manager = self.Manager;
21
+ const assistant = self.assistant;
20
22
 
21
- return new Promise(function(resolve, reject) {
23
+ return new Promise(async function(resolve, reject) {
22
24
  if (state === 'authorize') {
23
25
  // do something with url
24
26
  return resolve()
@@ -31,19 +33,36 @@ OAuth2.prototype.buildUrl = function (state, url) {
31
33
  OAuth2.prototype.verifyIdentity = function (tokenizeResult) {
32
34
  const self = this;
33
35
  const Manager = self.Manager;
36
+ const assistant = self.assistant;
37
+
38
+ return new Promise(async function(resolve, reject) {
34
39
 
35
- return new Promise(function(resolve, reject) {
36
- const decoded = decode(tokenizeResult.id_token);
40
+ const identityResponse = await fetch('https://discord.com/api/users/@me', {
41
+ timeout: 60000,
42
+ response: 'json',
43
+ tries: 1,
44
+ log: true,
45
+ cacheBreaker: false,
46
+ headers: {
47
+ authorization: `${tokenizeResult.token_type} ${tokenizeResult.access_token}`,
48
+ },
49
+ })
50
+ .then(json => json)
51
+ .catch(e => e)
37
52
 
38
- // console.log('---decoded', decoded);
53
+ assistant.log('identityResponse', identityResponse, {environment: 'development'});
54
+
55
+ if (identityResponse instanceof Error) {
56
+ return reject(identityResponse);
57
+ }
39
58
 
40
59
  // Check if exists
41
60
  Manager.libraries.admin.firestore().collection(`users`)
42
- .where(`oauth2.${self.provider}.identity.email`, '==', decoded.email)
61
+ .where(`oauth2.${self.provider}.identity.id`, '==', identityResponse.id)
43
62
  .get()
44
63
  .then(async (snap) => {
45
64
  if (snap.size === 0) {
46
- return resolve(decoded);
65
+ return resolve(identityResponse);
47
66
  } else {
48
67
  return reject(new Error(`This ${self.name} account is already connected to a ${Manager.config.brand.name} account`));
49
68
  }
@@ -58,8 +77,9 @@ OAuth2.prototype.verifyIdentity = function (tokenizeResult) {
58
77
  // OAuth2.prototype.verifyConnection = function (newUrl, token) {
59
78
  // const self = this;
60
79
  // const Manager = self.Manager;
80
+ // const assistant = self.assistant;
61
81
  //
62
- // return new Promise(function(resolve, reject) {
82
+ // return new Promise(async function(resolve, reject) {
63
83
  //
64
84
  // fetch(newUrl, {
65
85
  // method: 'post',
@@ -16,8 +16,10 @@ function OAuth2() {
16
16
 
17
17
  OAuth2.prototype.buildUrl = function (state, url) {
18
18
  const self = this;
19
+ const Manager = self.Manager;
20
+ const assistant = self.assistant;
19
21
 
20
- return new Promise(function(resolve, reject) {
22
+ return new Promise(async function(resolve, reject) {
21
23
  if (state === 'authorize') {
22
24
  // do something with url
23
25
  return resolve()
@@ -30,8 +32,9 @@ OAuth2.prototype.buildUrl = function (state, url) {
30
32
  OAuth2.prototype.verifyIdentity = function (tokenizeResult) {
31
33
  const self = this;
32
34
  const Manager = self.Manager;
35
+ const assistant = self.assistant;
33
36
 
34
- return new Promise(function(resolve, reject) {
37
+ return new Promise(async function(resolve, reject) {
35
38
  const decoded = decode(tokenizeResult.id_token);
36
39
 
37
40
  // console.log('---decoded', decoded);
@@ -57,8 +60,9 @@ OAuth2.prototype.verifyIdentity = function (tokenizeResult) {
57
60
  // OAuth2.prototype.verifyConnection = function (newUrl, token) {
58
61
  // const self = this;
59
62
  // const Manager = self.Manager;
63
+ // const assistant = self.assistant;
60
64
  //
61
- // return new Promise(function(resolve, reject) {
65
+ // return new Promise(async function(resolve, reject) {
62
66
  //
63
67
  // fetch(newUrl, {
64
68
  // method: 'post',
@@ -39,9 +39,13 @@ Module.prototype.main = function () {
39
39
  : payload.data.payload.redirect
40
40
 
41
41
  payload.data.payload.referrer = typeof payload.data.payload.referrer === 'undefined'
42
- ? (assistant.meta.environment === 'development' ? `http://localhost:4000/oauth2` : `${Manager.config.brand.url}/oauth2`)
42
+ ? (assistant.meta.environment === 'development' ? `http://localhost:4000/authentication/account` : `${Manager.config.brand.url}/authentication/account`)
43
43
  : payload.data.payload.referrer
44
44
 
45
+ payload.data.payload.serverUrl = typeof payload.data.payload.serverUrl === 'undefined'
46
+ ? (assistant.meta.environment === 'development' ? `${Manager.project.functionsUrl}/bm_api` : `${Manager.project.functionsUrl}/bm_api`)
47
+ : payload.data.payload.serverUrl
48
+
45
49
  payload.data.payload.provider = payload.data.payload.provider || '';
46
50
  payload.data.payload.state = payload.data.payload.state || 'authorize'; // authorize, tokenize, deauthorize, refresh, get
47
51
  payload.data.payload.redirect_uri = payload.data.payload.redirect_uri
@@ -59,17 +63,22 @@ Module.prototype.main = function () {
59
63
  code: 'success',
60
64
  provider: payload.data.payload.provider,
61
65
  authenticationToken: payload.data.authenticationToken,
62
- serverUrl: `${Manager.project.functionsUrl}/bm_api`,
66
+ serverUrl: payload.data.payload.serverUrl,
63
67
  referrer: payload.data.payload.referrer,
64
68
  redirectUrl: payload.data.payload.redirect_uri,
65
69
  }
66
70
 
67
71
  assistant.log('OAuth2 payload', payload.data.payload);
68
72
 
73
+ if (!payload.data.payload.provider) {
74
+ return reject(new Error(`The provider parameter is required.`));
75
+ }
76
+
69
77
  try {
70
78
  self.oauth2 = new (require(`./oauth2/${payload.data.payload.provider}.js`))();
71
79
  self.oauth2.parent = self;
72
80
  self.oauth2.Manager = self.Manager;
81
+ self.oauth2.assistant = self.assistant;
73
82
 
74
83
  newUrl = self.oauth2.urls[payload.data.payload.state]
75
84
 
@@ -159,6 +168,8 @@ Module.prototype.processState_tokenize = function (newUrl) {
159
168
  return new Promise(async function(resolve, reject) {
160
169
  const finalUrl = newUrl.toString();
161
170
 
171
+ assistant.log('Running processState_tokenize()', {environment: 'development'});
172
+
162
173
  const body = {
163
174
  client_id: _.get(Manager.config, `oauth2.${payload.data.payload.provider}.client_id`),
164
175
  client_secret: _.get(Manager.config, `oauth2.${payload.data.payload.provider}.client_secret`),
@@ -168,13 +179,13 @@ Module.prototype.processState_tokenize = function (newUrl) {
168
179
  // scope: '',
169
180
  };
170
181
 
171
- // console.log('----body', body);
182
+ assistant.log('body', body, {environment: 'development'});
172
183
 
173
184
  const tokenizeResponse = await fetch(finalUrl, {
174
185
  method: 'POST',
175
186
  timeout: 60000,
176
187
  response: 'json',
177
- tries: 2,
188
+ tries: 1,
178
189
  log: true,
179
190
  body: new URLSearchParams(body),
180
191
  cacheBreaker: false,
@@ -185,7 +196,7 @@ Module.prototype.processState_tokenize = function (newUrl) {
185
196
  .then(json => json)
186
197
  .catch(e => e)
187
198
 
188
- // console.log('---tokenizeResponse', tokenizeResponse);
199
+ assistant.log('tokenizeResponse', tokenizeResponse, {environment: 'development'});
189
200
 
190
201
  if (tokenizeResponse instanceof Error) {
191
202
  return reject(tokenizeResponse);
@@ -196,7 +207,7 @@ Module.prototype.processState_tokenize = function (newUrl) {
196
207
  .then(identity => identity)
197
208
  .catch(e => e);
198
209
 
199
- // console.log('---verifiedIdentity', verifiedIdentity);
210
+ assistant.log('verifiedIdentity', verifiedIdentity, {environment: 'development'});
200
211
 
201
212
  if (verifiedIdentity instanceof Error) {
202
213
  return reject(verifiedIdentity);
@@ -222,7 +233,7 @@ Module.prototype.processState_tokenize = function (newUrl) {
222
233
  .then(r => r)
223
234
  .catch(e => e)
224
235
 
225
- // console.log('---storeResponse', storeResponse);
236
+ assistant.log('storeResponse', storeResponse, {environment: 'development'});
226
237
 
227
238
  if (storeResponse instanceof Error) {
228
239
  return reject(storeResponse);
@@ -39,64 +39,70 @@ Module.prototype.main = function() {
39
39
  const req = self.req;
40
40
  const res = self.res;
41
41
 
42
- return libraries.cors(req, res, async () => {
43
- self.payload.data = assistant.request.data;
44
- self.payload.user = await assistant.authenticate();
42
+ return new Promise(async function(resolve, reject) {
43
+ return libraries.cors(req, res, async () => {
44
+ self.payload.data = assistant.request.data;
45
+ self.payload.user = await assistant.authenticate();
45
46
 
46
- const resolved = self.resolveCommand(self.payload.data.command);
47
+ const resolved = self.resolveCommand(self.payload.data.command);
47
48
 
48
- self.assistant.log(`Executing: ${resolved.command}`, self.payload, JSON.stringify(self.payload), {environment: 'production'})
49
- self.assistant.log(`Resolved URL: ${Manager.project.functionsUrl}?command=${encodeURIComponent(resolved.command)}&payload=${encodeURIComponent(JSON.stringify(self.assistant.request.data.payload))}`, {environment: 'development'})
49
+ self.assistant.log(`Executing: ${resolved.command}`, self.payload, JSON.stringify(self.payload), {environment: 'production'})
50
+ self.assistant.log(`Resolved URL: ${Manager.project.functionsUrl}?command=${encodeURIComponent(resolved.command)}&payload=${encodeURIComponent(JSON.stringify(self.assistant.request.data.payload))}`, {environment: 'development'})
50
51
 
51
- if (!resolved.exists) {
52
- self.payload.response.status = 400;
53
- self.payload.response.error = new Error(`${self.payload.data.command} is not a valid command`);
54
- } else {
55
- await self.import(resolved.command)
56
- .then(async lib => {
57
- try {
58
- // Call main function
59
- await lib.main()
60
- .then(result => {
61
- result = result || {};
62
- // console.log('---result', result);
63
- // console.log('---self.payload.response.data', self.payload.response.data);
64
- self.payload.response.status = result.status || self.payload.response.status || 200;
65
- self.payload.response.data = result.data || self.payload.response.data || {};
66
- self.payload.response.redirect = result.redirect || self.payload.response.redirect || null;
67
- })
68
- .catch(e => {
69
- self.payload.response.status = e.code || 500;
70
- self.payload.response.error = e || new Error('Unknown error occured');
71
- })
72
- } catch (e) {
73
- self.payload.response.status = 500;
74
- self.payload.response.error = e || new Error('Unknown error occured');
75
- }
76
- })
77
- .catch(e => {
52
+ if (!resolved.exists) {
78
53
  self.payload.response.status = 400;
79
- self.payload.response.error = new Error(`Failed to import: ${e}`);
80
- })
81
- }
54
+ self.payload.response.error = new Error(`${self.payload.data.command} is not a valid command`);
55
+ } else {
56
+ await self.import(resolved.command)
57
+ .then(async lib => {
58
+ try {
59
+ // Call main function
60
+ await lib.main()
61
+ .then(result => {
62
+ result = result || {};
63
+ // console.log('---result', result);
64
+ // console.log('---self.payload.response.data', self.payload.response.data);
65
+ self.payload.response.status = result.status || self.payload.response.status || 200;
66
+ self.payload.response.data = result.data || self.payload.response.data || {};
67
+ self.payload.response.redirect = result.redirect || self.payload.response.redirect || null;
68
+ })
69
+ .catch(e => {
70
+ // console.log('---e', e);
71
+ self.payload.response.status = e.code || 500;
72
+ self.payload.response.error = e || new Error('Unknown error occured');
73
+ })
74
+ } catch (e) {
75
+ self.payload.response.status = 500;
76
+ self.payload.response.error = e || new Error('Unknown error occured');
77
+ }
78
+ })
79
+ .catch(e => {
80
+ self.payload.response.status = 400;
81
+ self.payload.response.error = new Error(`Failed to import: ${e}`);
82
+ })
83
+ }
82
84
 
83
- self.payload.response.status = _fixStatus(self.payload.response.status);
85
+ self.payload.response.status = _fixStatus(self.payload.response.status);
84
86
 
85
- res.status(self.payload.response.status)
87
+ res.status(self.payload.response.status)
86
88
 
87
- if (self.payload.response.status >= 200 && self.payload.response.status < 300) {
88
- self.assistant.log(`Finished ${resolved.command} (status=${self.payload.response.status})`, self.payload, JSON.stringify(self.payload), {environment: 'production'})
89
+ if (self.payload.response.status >= 200 && self.payload.response.status < 300) {
90
+ self.assistant.log(`Finished ${resolved.command} (status=${self.payload.response.status})`, self.payload, JSON.stringify(self.payload), {environment: 'production'})
89
91
 
90
- if (self.payload.response.redirect) {
91
- return res.redirect(self.payload.response.redirect);
92
+ if (self.payload.response.redirect) {
93
+ res.redirect(self.payload.response.redirect);
94
+ return resolve();
95
+ } else {
96
+ res.json(self.payload.response.data);
97
+ return resolve();
98
+ }
92
99
  } else {
93
- return res.json(self.payload.response.data);
100
+ console.error(`Error executing ${resolved.command} @ ${resolved.path} (status=${self.payload.response.status}):`, self.payload.response.error)
101
+ // return res.send(self.payload.response.error.message);
102
+ res.send(`${self.payload.response.error}`)
103
+ return reject(self.payload.response.error);
94
104
  }
95
- } else {
96
- console.error(`Error executing ${resolved.command} @ ${resolved.path} (status=${self.payload.response.status}):`, self.payload.response.error)
97
- // return res.send(self.payload.response.error.message);
98
- return res.send(`${self.payload.response.error}`);
99
- }
105
+ });
100
106
  });
101
107
  }
102
108
 
@@ -0,0 +1,43 @@
1
+ const fetch = require('wonderful-fetch');
2
+
3
+ function Module() {
4
+
5
+ }
6
+
7
+ Module.prototype.init = function (Manager, data) {
8
+ const self = this;
9
+ self.Manager = Manager;
10
+ self.libraries = Manager.libraries;
11
+ self.assistant = Manager.Assistant()
12
+
13
+ self.context = data.context;
14
+ return self;
15
+ }
16
+
17
+ Module.prototype.main = function() {
18
+ const self = this;
19
+ const Manager = self.Manager;
20
+ const libraries = self.libraries;
21
+ const assistant = self.assistant;
22
+ const context = self.context;
23
+
24
+ return new Promise(async function(resolve, reject) {
25
+ fetch(`${Manager.project.functionsUrl}/bm_api`, {
26
+ method: 'post',
27
+ body: {
28
+ backendManagerKey: Manager.config.backend_manager.key,
29
+ command: 'admin:backup',
30
+ }
31
+ })
32
+ .then(response => {
33
+ assistant.log(`Successfully executed backup: ${response}`, {environment: 'production'})
34
+ return resolve(response);
35
+ })
36
+ .catch(e => {
37
+ assistant.errorManager(`Error executing backup: ${e}`, {sentry: true, send: false, log: true})
38
+ return reject();
39
+ })
40
+ });
41
+ }
42
+
43
+ module.exports = Module;
@@ -0,0 +1,235 @@
1
+ if (options.setupFunctions) {
2
+ // exporter.bm_api =
3
+ // self.libraries.functions
4
+ // .runWith({memory: '256MB', timeoutSeconds: 60})
5
+ // .https.onRequest(async (req, res) => {
6
+ // const Module = (new (require(`${core}/actions/api.js`))()).init(self, { req: req, res: res, });
7
+ //
8
+ // return self._preProcess(Module)
9
+ // .then(r => Module.main())
10
+ // .catch(e => {
11
+ // self.assistant.error(e, {environment: 'production'});
12
+ // return res.status(500).send(e.message);
13
+ // });
14
+ // });
15
+ exporter.bm_api =
16
+ self.libraries.functions
17
+ .runWith({memory: '256MB', timeoutSeconds: 60})
18
+ .https.onRequest(async (req, res) => {
19
+ return self._process((new (require(`${core}/actions/api.js`))()).init(self, { req: req, res: res, }))
20
+ });
21
+
22
+ if (options.setupFunctionsLegacy) {
23
+ exporter.bm_signUpHandler =
24
+ self.libraries.functions
25
+ .runWith({memory: '256MB', timeoutSeconds: 60})
26
+ .https.onRequest(async (req, res) => {
27
+ const Module = require(`${core}/actions/sign-up-handler.js`);
28
+ Module.init(self, { req: req, res: res, });
29
+
30
+ return self._preProcess(Module)
31
+ .then(r => Module.main())
32
+ .catch(e => {
33
+ self.assistant.error(e, {environment: 'production'});
34
+ return res.status(500).send(e.message);
35
+ });
36
+ });
37
+
38
+ // Admin
39
+ exporter.bm_createPost =
40
+ self.libraries.functions
41
+ .runWith({memory: '256MB', timeoutSeconds: 60})
42
+ .https.onRequest(async (req, res) => {
43
+ const Module = require(`${core}/admin/create-post.js`);
44
+ Module.init(self, { req: req, res: res, });
45
+
46
+ return self._preProcess(Module)
47
+ .then(r => Module.main())
48
+ .catch(e => {
49
+ self.assistant.error(e, {environment: 'production'});
50
+ return res.status(500).send(e.message);
51
+ });
52
+ });
53
+
54
+ exporter.bm_firestoreWrite =
55
+ self.libraries.functions
56
+ .runWith({memory: '256MB', timeoutSeconds: 60})
57
+ .https.onRequest(async (req, res) => {
58
+ const Module = require(`${core}/admin/firestore-write.js`);
59
+ Module.init(self, { req: req, res: res, });
60
+
61
+ return self._preProcess(Module)
62
+ .then(r => Module.main())
63
+ .catch(e => {
64
+ self.assistant.error(e, {environment: 'production'});
65
+ return res.status(500).send(e.message);
66
+ });
67
+ });
68
+
69
+ exporter.bm_getStats =
70
+ self.libraries.functions
71
+ .runWith({memory: '256MB', timeoutSeconds: 420})
72
+ .https.onRequest(async (req, res) => {
73
+ const Module = require(`${core}/admin/get-stats.js`);
74
+ Module.init(self, { req: req, res: res, });
75
+
76
+ return self._preProcess(Module)
77
+ .then(r => Module.main())
78
+ .catch(e => {
79
+ self.assistant.error(e, {environment: 'production'});
80
+ return res.status(500).send(e.message);
81
+ });
82
+ });
83
+
84
+ exporter.bm_sendNotification =
85
+ self.libraries.functions
86
+ .runWith({memory: '1GB', timeoutSeconds: 420})
87
+ .https.onRequest(async (req, res) => {
88
+ const Module = require(`${core}/admin/send-notification.js`);
89
+ Module.init(self, { req: req, res: res, });
90
+
91
+ return self._preProcess(Module)
92
+ .then(r => Module.main())
93
+ .catch(e => {
94
+ self.assistant.error(e, {environment: 'production'});
95
+ return res.status(500).send(e.message);
96
+ });
97
+ });
98
+
99
+ exporter.bm_query =
100
+ self.libraries.functions
101
+ .runWith({memory: '256MB', timeoutSeconds: 60})
102
+ .https.onRequest(async (req, res) => {
103
+ const Module = require(`${core}/admin/query.js`);
104
+ Module.init(self, { req: req, res: res, });
105
+
106
+ return self._preProcess(Module)
107
+ .then(r => Module.main())
108
+ .catch(e => {
109
+ self.assistant.error(e, {environment: 'production'});
110
+ return res.status(500).send(e.message);
111
+ });
112
+ });
113
+
114
+ exporter.bm_createPostHandler =
115
+ self.libraries.functions
116
+ .runWith({memory: '256MB', timeoutSeconds: 60})
117
+ .https.onRequest(async (req, res) => {
118
+ const Module = require(`${core}/actions/create-post-handler.js`);
119
+ Module.init(self, { req: req, res: res, });
120
+
121
+ return self._preProcess(Module)
122
+ .then(r => Module.main())
123
+ .catch(e => {
124
+ self.assistant.error(e, {environment: 'production'});
125
+ return res.status(500).send(e.message);
126
+ });
127
+ });
128
+
129
+ exporter.bm_generateUuid =
130
+ self.libraries.functions
131
+ .runWith({memory: '256MB', timeoutSeconds: 60})
132
+ .https.onRequest(async (req, res) => {
133
+ const Module = require(`${core}/actions/generate-uuid.js`);
134
+ Module.init(self, { req: req, res: res, });
135
+
136
+ return self._preProcess(Module)
137
+ .then(r => Module.main())
138
+ .catch(e => {
139
+ self.assistant.error(e, {environment: 'production'});
140
+ return res.status(500).send(e.message);
141
+ });
142
+ });
143
+
144
+ // Test
145
+ exporter.bm_test_authenticate =
146
+ self.libraries.functions
147
+ .runWith({memory: '256MB', timeoutSeconds: 60})
148
+ .https.onRequest(async (req, res) => {
149
+ const Module = require(`${test}/authenticate.js`);
150
+ Module.init(self, { req: req, res: res, });
151
+
152
+ return self._preProcess(Module)
153
+ .then(r => Module.main())
154
+ .catch(e => {
155
+ self.assistant.error(e, {environment: 'production'});
156
+ return res.status(500).send(e.message);
157
+ });
158
+ });
159
+
160
+ exporter.bm_test_createTestAccounts =
161
+ self.libraries.functions
162
+ .runWith({memory: '256MB', timeoutSeconds: 60})
163
+ .https.onRequest(async (req, res) => {
164
+ const Module = require(`${test}/create-test-accounts.js`);
165
+ Module.init(self, { req: req, res: res, });
166
+
167
+ return self._preProcess(Module)
168
+ .then(r => Module.main())
169
+ .catch(e => {
170
+ self.assistant.error(e, {environment: 'production'});
171
+ return res.status(500).send(e.message);
172
+ });
173
+ });
174
+
175
+ exporter.bm_test_webhook =
176
+ self.libraries.functions
177
+ .runWith({memory: '256MB', timeoutSeconds: 60})
178
+ .https.onRequest(async (req, res) => {
179
+ const Module = require(`${test}/webhook.js`);
180
+ Module.init(self, { req: req, res: res, });
181
+
182
+ return self._preProcess(Module)
183
+ .then(r => Module.main())
184
+ .catch(e => {
185
+ self.assistant.error(e, {environment: 'production'});
186
+ return res.status(500).send(e.message);
187
+ });
188
+ });
189
+ }
190
+
191
+ // Events
192
+ exporter.bm_authOnCreate =
193
+ self.libraries.functions
194
+ .runWith({memory: '256MB', timeoutSeconds: 60})
195
+ .auth.user().onCreate(async (user) => {
196
+ const Module = require(`${core}/events/auth/on-create.js`);
197
+ Module.init(self, { user: user });
198
+
199
+ return self._preProcess(Module)
200
+ .then(r => Module.main())
201
+ .catch(e => {
202
+ self.assistant.error(e, {environment: 'production'});
203
+ });
204
+ });
205
+
206
+ exporter.bm_authOnDelete =
207
+ self.libraries.functions
208
+ .runWith({memory: '256MB', timeoutSeconds: 60})
209
+ .auth.user().onDelete(async (user) => {
210
+ const Module = require(`${core}/events/auth/on-delete.js`);
211
+ Module.init(self, { user: user });
212
+
213
+ return self._preProcess(Module)
214
+ .then(r => Module.main())
215
+ .catch(e => {
216
+ self.assistant.error(e, {environment: 'production'});
217
+ });
218
+ });
219
+
220
+ exporter.bm_subOnWrite =
221
+ self.libraries.functions
222
+ .runWith({memory: '256MB', timeoutSeconds: 60})
223
+ .firestore
224
+ .document('notifications/subscriptions/all/{token}')
225
+ .onWrite(async (change, context) => {
226
+ const Module = require(`${core}/events/firestore/on-subscription.js`);
227
+ Module.init(self, { change: change, context: context, });
228
+
229
+ return self._preProcess(Module)
230
+ .then(r => Module.main())
231
+ .catch(e => {
232
+ self.assistant.error(e, {environment: 'production'});
233
+ });
234
+ });
235
+ }
@@ -93,7 +93,6 @@ Manager.prototype.init = function (exporter, options) {
93
93
  ? `http://localhost:5001/${self.project.projectId}/${self.project.resourceZone}`
94
94
  : `https://${self.project.resourceZone}-${self.project.projectId}.cloudfunctions.net`;
95
95
 
96
-
97
96
  // Use the working Firebase logger that they disabled for whatever reason
98
97
  if (self.assistant.meta.environment !== 'development' && options.useFirebaseLogger) {
99
98
  require('firebase-functions/lib/logger/compat');
@@ -154,15 +153,9 @@ Manager.prototype.init = function (exporter, options) {
154
153
  exporter.bm_api =
155
154
  self.libraries.functions
156
155
  .runWith({memory: '256MB', timeoutSeconds: 60})
157
- .https.onRequest(async (req, res) => {
158
- const Module = (new (require(`${core}/actions/api.js`))()).init(self, { req: req, res: res, });
159
-
160
- return self._preProcess(Module)
161
- .then(r => Module.main())
162
- .catch(e => {
163
- self.assistant.error(e, {environment: 'production'});
164
- return res.status(500).send(e.message);
165
- });
156
+ .https
157
+ .onRequest(async (req, res) => {
158
+ return self._process((new (require(`${core}/actions/api.js`))()).init(self, { req: req, res: res, }))
166
159
  });
167
160
 
168
161
  if (options.setupFunctionsLegacy) {
@@ -338,45 +331,34 @@ Manager.prototype.init = function (exporter, options) {
338
331
  exporter.bm_authOnCreate =
339
332
  self.libraries.functions
340
333
  .runWith({memory: '256MB', timeoutSeconds: 60})
341
- .auth.user().onCreate(async (user) => {
342
- const Module = require(`${core}/events/auth/on-create.js`);
343
- Module.init(self, { user: user });
344
-
345
- return self._preProcess(Module)
346
- .then(r => Module.main())
347
- .catch(e => {
348
- self.assistant.error(e, {environment: 'production'});
349
- });
334
+ .auth.user()
335
+ .onCreate(async (user) => {
336
+ return self._process((new (require(`${core}/events/auth/on-create.js`))()).init(self, { user: user, }))
350
337
  });
351
338
 
352
339
  exporter.bm_authOnDelete =
353
340
  self.libraries.functions
354
341
  .runWith({memory: '256MB', timeoutSeconds: 60})
355
- .auth.user().onDelete(async (user) => {
356
- const Module = require(`${core}/events/auth/on-delete.js`);
357
- Module.init(self, { user: user });
358
-
359
- return self._preProcess(Module)
360
- .then(r => Module.main())
361
- .catch(e => {
362
- self.assistant.error(e, {environment: 'production'});
363
- });
342
+ .auth.user()
343
+ .onDelete(async (user) => {
344
+ return self._process((new (require(`${core}/events/auth/on-delete.js`))()).init(self, { user: user, }))
364
345
  });
365
346
 
366
347
  exporter.bm_subOnWrite =
367
348
  self.libraries.functions
368
349
  .runWith({memory: '256MB', timeoutSeconds: 60})
369
- .firestore
370
- .document('notifications/subscriptions/all/{token}')
350
+ .firestore.document('notifications/subscriptions/all/{token}')
371
351
  .onWrite(async (change, context) => {
372
- const Module = require(`${core}/events/firestore/on-subscription.js`);
373
- Module.init(self, { change: change, context: context, });
352
+ return self._process((new (require(`${core}/events/firestore/on-subscription.js`))()).init(self, { change: change, context: context, }))
353
+ });
374
354
 
375
- return self._preProcess(Module)
376
- .then(r => Module.main())
377
- .catch(e => {
378
- self.assistant.error(e, {environment: 'production'});
379
- });
355
+ // Cron
356
+ exporter.bm_backup =
357
+ self.libraries.functions
358
+ .runWith({ memory: '256MB', timeoutSeconds: 60 })
359
+ .pubsub.schedule(get(self.config, 'backup.schedule', 'every 24 hours'))
360
+ .onRun(async (context) => {
361
+ return self._process((new (require(`${core}/cron/backup.js`))()).init(self, { context: context, }))
380
362
  });
381
363
  }
382
364
 
@@ -396,6 +378,46 @@ Manager.prototype.init = function (exporter, options) {
396
378
  };
397
379
 
398
380
  // HELPERS
381
+ Manager.prototype._process = function (mod) {
382
+ const self = this;
383
+ const name = mod.assistant.meta.name;
384
+ const hook = self.handlers && self.handlers[name];
385
+ const req = mod.req;
386
+ const res = mod.res;
387
+
388
+ return new Promise(async function(resolve, reject) {
389
+ let error;
390
+
391
+ function _reject(e, log) {
392
+ if (log) {
393
+ // self.assistant.error(e, {environment: 'production'});
394
+ mod.assistant.errorManager(e, {code: 500, sentry: true, send: false, log: true});
395
+ }
396
+ // res.status(500).send(e.message);
397
+ return resolve()
398
+ }
399
+
400
+ // Run pre
401
+ if (hook) {
402
+ await hook(mod, 'pre').catch(e => {error = e});
403
+ }
404
+ if (error) { return _reject(error, true) }
405
+
406
+ // Run main
407
+ await mod.main().catch(e => {error = e});
408
+ if (error) { return _reject(error, false) }
409
+
410
+ // Run post
411
+ if (hook) {
412
+ await hook(mod, 'post').catch(e => {error = e});
413
+ }
414
+ if (error) { return _reject(error, true) }
415
+
416
+ // Fin
417
+ return resolve();
418
+ });
419
+ };
420
+
399
421
  Manager.prototype._preProcess = function (mod) {
400
422
  const self = this;
401
423
  const name = mod.assistant.meta.name;