backend-manager 2.4.5 → 2.4.8

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