backend-manager 2.4.4 → 2.4.7

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.4",
3
+ "version": "2.4.7",
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
+ }
@@ -75,6 +75,7 @@ Manager.prototype.init = function (exporter, options) {
75
75
  self.options = options;
76
76
  self.project = options.firebaseConfig || JSON.parse(process.env.FIREBASE_CONFIG || '{}');
77
77
  self.project.resourceZone = options.resourceZone;
78
+ self.project.serviceAccountPath = path.resolve(self.cwd, options.serviceAccountPath)
78
79
 
79
80
  self.cwd = process.cwd();
80
81
  self.package = resolveProjectPackage();
@@ -86,13 +87,12 @@ Manager.prototype.init = function (exporter, options) {
86
87
  // Init assistant
87
88
  self.assistant = self.Assistant().init(undefined, options.assistant);
88
89
 
89
- process.env.ENVIRONMENT = !process.env.ENVIRONMENT ? self.assistant.meta.environment : process.env.ENVIRONMENT;
90
-
91
90
  // set more properties (need to wait for assistant to determine if DEV)
92
91
  self.project.functionsUrl = self.assistant.meta.environment === 'development'
93
92
  ? `http://localhost:5001/${self.project.projectId}/${self.project.resourceZone}`
94
93
  : `https://${self.project.resourceZone}-${self.project.projectId}.cloudfunctions.net`;
95
94
 
95
+ process.env.ENVIRONMENT = !process.env.ENVIRONMENT ? self.assistant.meta.environment : process.env.ENVIRONMENT;
96
96
 
97
97
  // Use the working Firebase logger that they disabled for whatever reason
98
98
  if (self.assistant.meta.environment !== 'development' && options.useFirebaseLogger) {
@@ -138,7 +138,7 @@ Manager.prototype.init = function (exporter, options) {
138
138
  } else {
139
139
  self.libraries.initializedAdmin = self.libraries.admin.initializeApp({
140
140
  credential: self.libraries.admin.credential.cert(
141
- require(path.resolve(self.cwd, options.serviceAccountPath))
141
+ require(self.project.serviceAccountPath)
142
142
  ),
143
143
  databaseURL: self.project.databaseURL,
144
144
  }, options.uniqueAppName);
@@ -154,15 +154,9 @@ Manager.prototype.init = function (exporter, options) {
154
154
  exporter.bm_api =
155
155
  self.libraries.functions
156
156
  .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
- });
157
+ .https
158
+ .onRequest(async (req, res) => {
159
+ return self._process((new (require(`${core}/actions/api.js`))()).init(self, { req: req, res: res, }))
166
160
  });
167
161
 
168
162
  if (options.setupFunctionsLegacy) {
@@ -338,45 +332,34 @@ Manager.prototype.init = function (exporter, options) {
338
332
  exporter.bm_authOnCreate =
339
333
  self.libraries.functions
340
334
  .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
- });
335
+ .auth.user()
336
+ .onCreate(async (user) => {
337
+ return self._process((new (require(`${core}/events/auth/on-create.js`))()).init(self, { user: user, }))
350
338
  });
351
339
 
352
340
  exporter.bm_authOnDelete =
353
341
  self.libraries.functions
354
342
  .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
- });
343
+ .auth.user()
344
+ .onDelete(async (user) => {
345
+ return self._process((new (require(`${core}/events/auth/on-delete.js`))()).init(self, { user: user, }))
364
346
  });
365
347
 
366
348
  exporter.bm_subOnWrite =
367
349
  self.libraries.functions
368
350
  .runWith({memory: '256MB', timeoutSeconds: 60})
369
- .firestore
370
- .document('notifications/subscriptions/all/{token}')
351
+ .firestore.document('notifications/subscriptions/all/{token}')
371
352
  .onWrite(async (change, context) => {
372
- const Module = require(`${core}/events/firestore/on-subscription.js`);
373
- Module.init(self, { change: change, context: context, });
353
+ return self._process((new (require(`${core}/events/firestore/on-subscription.js`))()).init(self, { change: change, context: context, }))
354
+ });
374
355
 
375
- return self._preProcess(Module)
376
- .then(r => Module.main())
377
- .catch(e => {
378
- self.assistant.error(e, {environment: 'production'});
379
- });
356
+ // Cron
357
+ exporter.bm_backup =
358
+ self.libraries.functions
359
+ .runWith({ memory: '256MB', timeoutSeconds: 60 })
360
+ .pubsub.schedule(get(self.config, 'backup.schedule', 'every 24 hours'))
361
+ .onRun(async (context) => {
362
+ return self._process((new (require(`${core}/cron/backup.js`))()).init(self, { context: context, }))
380
363
  });
381
364
  }
382
365
 
@@ -396,6 +379,46 @@ Manager.prototype.init = function (exporter, options) {
396
379
  };
397
380
 
398
381
  // HELPERS
382
+ Manager.prototype._process = function (mod) {
383
+ const self = this;
384
+ const name = mod.assistant.meta.name;
385
+ const hook = self.handlers && self.handlers[name];
386
+ const req = mod.req;
387
+ const res = mod.res;
388
+
389
+ return new Promise(async function(resolve, reject) {
390
+ let error;
391
+
392
+ function _reject(e, log) {
393
+ if (log) {
394
+ // self.assistant.error(e, {environment: 'production'});
395
+ mod.assistant.errorManager(e, {code: 500, sentry: true, send: false, log: true});
396
+ }
397
+ // res.status(500).send(e.message);
398
+ return resolve()
399
+ }
400
+
401
+ // Run pre
402
+ if (hook) {
403
+ await hook(mod, 'pre').catch(e => {error = e});
404
+ }
405
+ if (error) { return _reject(error, true) }
406
+
407
+ // Run main
408
+ await mod.main().catch(e => {error = e});
409
+ if (error) { return _reject(error, false) }
410
+
411
+ // Run post
412
+ if (hook) {
413
+ await hook(mod, 'post').catch(e => {error = e});
414
+ }
415
+ if (error) { return _reject(error, true) }
416
+
417
+ // Fin
418
+ return resolve();
419
+ });
420
+ };
421
+
399
422
  Manager.prototype._preProcess = function (mod) {
400
423
  const self = this;
401
424
  const name = mod.assistant.meta.name;