@crowdin/app-project-module 0.37.0 → 0.39.0

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.
@@ -138,6 +138,18 @@ class PostgreStorage {
138
138
  provider varchar not null
139
139
  )
140
140
  `);
141
+ yield client.query(`
142
+ create table if not exists user_errors
143
+ (
144
+ id serial primary key,
145
+ action varchar not null,
146
+ message varchar not null,
147
+ data varchar,
148
+ created_at varchar not null,
149
+ crowdin_id varchar not null,
150
+ integration_id varchar
151
+ )
152
+ `);
141
153
  });
142
154
  }
143
155
  saveCrowdinCredentials(credentials) {
@@ -201,6 +213,7 @@ class PostgreStorage {
201
213
  yield client.query('DELETE FROM files_snapshot WHERE crowdin_id = $1', [id]);
202
214
  yield client.query('DELETE FROM app_metadata WHERE crowdin_id = $1', [id]);
203
215
  yield client.query('DELETE FROM webhooks WHERE crowdin_id = $1', [id]);
216
+ yield client.query('DELETE FROM user_errors WHERE crowdin_id = $1', [id]);
204
217
  }));
205
218
  });
206
219
  }
@@ -263,6 +276,7 @@ class PostgreStorage {
263
276
  yield client.query('DELETE FROM sync_settings where crowdin_id = $1', [crowdinId]);
264
277
  yield client.query('DELETE FROM files_snapshot where crowdin_id = $1', [crowdinId]);
265
278
  yield client.query('DELETE FROM webhooks where crowdin_id = $1', [crowdinId]);
279
+ yield client.query('DELETE FROM user_errors where crowdin_id = $1', [crowdinId]);
266
280
  }));
267
281
  });
268
282
  }
@@ -395,5 +409,40 @@ class PostgreStorage {
395
409
  yield this.executeQuery((client) => client.query(`DELETE FROM webhooks WHERE file_id IN ${placeholders} AND integration_id = $${++index} AND crowdin_id = $${++index} AND provider = $${++index}`, [...fileIds, integrationId, crowdinId, provider]));
396
410
  });
397
411
  }
412
+ getAllUserErrors(crowdinId, integrationId) {
413
+ return __awaiter(this, void 0, void 0, function* () {
414
+ yield this.dbPromise;
415
+ return this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () {
416
+ let whereIntegrationCondition = 'integration_id is NULL';
417
+ const params = [crowdinId];
418
+ if (integrationId) {
419
+ whereIntegrationCondition = 'integration_id = $2';
420
+ params.push(integrationId);
421
+ }
422
+ const res = yield client.query(`SELECT id, action, message, data, created_at as "createdAt", integration_id as "integrationId", crowdin_id as "crowdinId" FROM user_errors WHERE crowdin_id = $1 AND ${whereIntegrationCondition}`, params);
423
+ return (res === null || res === void 0 ? void 0 : res.rows) || [];
424
+ }));
425
+ });
426
+ }
427
+ saveUserError(action, message, data, createdAt, crowdinId, integrationId) {
428
+ return __awaiter(this, void 0, void 0, function* () {
429
+ yield this.dbPromise;
430
+ yield this.executeQuery((client) => client.query('INSERT INTO user_errors(action, message, data, created_at, integration_id, crowdin_id) VALUES ($1, $2, $3, $4, $5, $6)', [action, message, data, createdAt, integrationId, crowdinId]));
431
+ });
432
+ }
433
+ deleteUserErrors(createdAt, crowdinId, integrationId) {
434
+ return __awaiter(this, void 0, void 0, function* () {
435
+ yield this.dbPromise;
436
+ yield this.executeQuery((client) => {
437
+ let whereIntegrationCondition = 'integration_id is NULL';
438
+ const params = [createdAt, crowdinId];
439
+ if (integrationId) {
440
+ whereIntegrationCondition = 'integration_id = $3';
441
+ params.push(integrationId);
442
+ }
443
+ return client.query(`DELETE FROM user_errors WHERE created_at < $1 AND crowdin_id = $2 AND ${whereIntegrationCondition}`, params);
444
+ });
445
+ });
446
+ }
398
447
  }
399
448
  exports.PostgreStorage = PostgreStorage;
@@ -1,5 +1,5 @@
1
1
  import { Storage } from '.';
2
- import { CrowdinCredentials, IntegrationCredentials, IntegrationFilesSnapshot, IntegrationSyncSettings, IntegrationWebhooks } from '../models';
2
+ import { CrowdinCredentials, IntegrationCredentials, IntegrationFilesSnapshot, IntegrationSyncSettings, IntegrationWebhooks, UserErrors } from '../models';
3
3
  export interface SQLiteStorageConfig {
4
4
  dbFolder: string;
5
5
  }
@@ -45,4 +45,7 @@ export declare class SQLiteStorage implements Storage {
45
45
  getWebhooks(fileId: string, integrationId: string, crowdinId: string, provider: string): Promise<IntegrationWebhooks | undefined>;
46
46
  saveWebhooks(fileId: string, integrationId: string, crowdinId: string, provider: string): Promise<void>;
47
47
  deleteWebhooks(fileIds: any[], integrationId: string, crowdinId: string, provider: string): Promise<void>;
48
+ getAllUserErrors(crowdinId: string, integrationId?: string): Promise<UserErrors[]>;
49
+ saveUserError(action: string, message: string, data: any, createdAt: string, crowdinId: string, integrationId?: string): Promise<void>;
50
+ deleteUserErrors(createAt: string, crowdinId: string, integrationId?: string): Promise<void>;
48
51
  }
@@ -195,6 +195,18 @@ class SQLiteStorage {
195
195
  crowdin_id varchar not null,
196
196
  provider varchar not null
197
197
  );
198
+ `, []);
199
+ yield this._run(`
200
+ create table if not exists user_errors
201
+ (
202
+ id integer not null primary key autoincrement,
203
+ action varchar not null,
204
+ message varchar not null,
205
+ data varchar null,
206
+ created_at varchar not null,
207
+ crowdin_id varchar not null,
208
+ integration_id varchar null
209
+ );
198
210
  `, []);
199
211
  this._res && this._res();
200
212
  // TODO: temporary code
@@ -251,6 +263,7 @@ class SQLiteStorage {
251
263
  yield this.run('DELETE FROM app_metadata WHERE crowdin_id = ?', [id]);
252
264
  yield this.run('DELETE FROM files_snapshot WHERE crowdin_id = ?', [id]);
253
265
  yield this.run('DELETE FROM webhooks WHERE crowdin_id = ?', [id]);
266
+ yield this.run('DELETE FROM user_errors WHERE crowdin_id = ?', [id]);
254
267
  });
255
268
  }
256
269
  saveIntegrationCredentials(id, credentials, crowdinId) {
@@ -291,6 +304,7 @@ class SQLiteStorage {
291
304
  yield this.run('DELETE FROM sync_settings where crowdin_id = ?', [crowdinId]);
292
305
  yield this.run('DELETE FROM files_snapshot where crowdin_id = ?', [crowdinId]);
293
306
  yield this.run('DELETE FROM webhooks where crowdin_id = ?', [crowdinId]);
307
+ yield this.run('DELETE FROM user_errors where crowdin_id = ?', [crowdinId]);
294
308
  });
295
309
  }
296
310
  saveMetadata(id, metadata, crowdinId) {
@@ -389,5 +403,28 @@ class SQLiteStorage {
389
403
  return this.run(`DELETE FROM webhooks WHERE file_id IN (${placeholders}) AND integration_id = ? AND crowdin_id = ? AND provider = ?`, [...fileIds, integrationId, crowdinId, provider]);
390
404
  });
391
405
  }
406
+ getAllUserErrors(crowdinId, integrationId) {
407
+ let whereIntegrationCondition = 'integration_id is NULL';
408
+ const params = [crowdinId];
409
+ if (integrationId) {
410
+ whereIntegrationCondition = 'integration_id = ?';
411
+ params.push(integrationId);
412
+ }
413
+ return this.each(`SELECT id, action, message, data, created_at as createdAt, integration_id as integrationId, crowdin_id as crowdinId FROM user_errors WHERE crowdin_id = ? AND ${whereIntegrationCondition}`, params);
414
+ }
415
+ saveUserError(action, message, data, createdAt, crowdinId, integrationId) {
416
+ return this.run('INSERT INTO user_errors(action, message, data, created_at, integration_id, crowdin_id) VALUES (?, ?, ?, ?, ?, ?)', [action, message, data, createdAt, integrationId, crowdinId]);
417
+ }
418
+ deleteUserErrors(createAt, crowdinId, integrationId) {
419
+ return __awaiter(this, void 0, void 0, function* () {
420
+ let whereIntegrationCondition = 'integration_id is NULL';
421
+ const params = [createAt, crowdinId];
422
+ if (integrationId) {
423
+ whereIntegrationCondition = 'integration_id = ?';
424
+ params.push(integrationId);
425
+ }
426
+ return this.run(`DELETE FROM user_errors WHERE created_at < ? AND crowdin_id = ? AND ${whereIntegrationCondition}`, params);
427
+ });
428
+ }
392
429
  }
393
430
  exports.SQLiteStorage = SQLiteStorage;
package/out/util/cron.js CHANGED
@@ -103,6 +103,8 @@ function filesCron(config, integration, period) {
103
103
  // eslint-disable-next-line @typescript-eslint/camelcase
104
104
  organization_id: crowdinCredentials.organizationId,
105
105
  // eslint-disable-next-line @typescript-eslint/camelcase
106
+ organization_domain: crowdinCredentials.domain,
107
+ // eslint-disable-next-line @typescript-eslint/camelcase
106
108
  user_id: crowdinCredentials.userId,
107
109
  },
108
110
  },
@@ -20,6 +20,7 @@ interface CustomAxiosError extends AxiosError {
20
20
  }
21
21
  export declare class AppModuleError extends Error {
22
22
  data: any;
23
+ appData: any;
23
24
  isAxiosError: boolean;
24
25
  constructor(error: CustomAxiosError | Error | string, data?: any);
25
26
  }
@@ -27,4 +28,10 @@ export declare class AppModuleAggregateError extends Error {
27
28
  errors: Error[] | AppModuleError[];
28
29
  constructor(errors: Error[] | AppModuleError[], message: string);
29
30
  }
31
+ export declare function handleUserError({ action, error, crowdinId, clientId, }: {
32
+ action: string;
33
+ error: Error | string;
34
+ crowdinId: string;
35
+ clientId?: string;
36
+ }): Promise<void>;
30
37
  export {};
@@ -22,9 +22,19 @@ var __importStar = (this && this.__importStar) || function (mod) {
22
22
  __setModuleDefault(result, mod);
23
23
  return result;
24
24
  };
25
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
+ return new (P || (P = Promise))(function (resolve, reject) {
28
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
31
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
32
+ });
33
+ };
25
34
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.AppModuleAggregateError = exports.AppModuleError = exports.getErrorMessage = exports.isAxiosError = exports.logError = exports.log = exports.withContextError = exports.withContext = exports.prepareContext = exports.initialize = void 0;
35
+ exports.handleUserError = exports.AppModuleAggregateError = exports.AppModuleError = exports.getErrorMessage = exports.isAxiosError = exports.logError = exports.log = exports.withContextError = exports.withContext = exports.prepareContext = exports.initialize = void 0;
27
36
  const logsFormatter = __importStar(require("@crowdin/logs-formatter"));
37
+ const storage_1 = require("../storage");
28
38
  let logConfig;
29
39
  let onError;
30
40
  function initialize(config) {
@@ -49,6 +59,7 @@ function withContextError(context) {
49
59
  }
50
60
  exports.withContextError = withContextError;
51
61
  function log(message, context) {
62
+ var _a, _b, _c;
52
63
  if (logConfig === null || logConfig === void 0 ? void 0 : logConfig.enabled) {
53
64
  if (logConfig.log) {
54
65
  logConfig.log(message, context);
@@ -59,11 +70,20 @@ function log(message, context) {
59
70
  logsFormatter.setContext({
60
71
  project: {
61
72
  id: context.jwtPayload.context.project_id,
73
+ identifier: (_a = context.jwtPayload.context.project_identifier) !== null && _a !== void 0 ? _a : '',
62
74
  // eslint-disable-next-line @typescript-eslint/camelcase
63
75
  organization_id: context.jwtPayload.context.organization_id,
64
76
  // eslint-disable-next-line @typescript-eslint/camelcase
65
77
  user_id: context.jwtPayload.context.user_id,
66
78
  },
79
+ user: {
80
+ id: context.jwtPayload.context.user_id,
81
+ login: (_b = context.jwtPayload.context.user_login) !== null && _b !== void 0 ? _b : '',
82
+ },
83
+ organization: {
84
+ id: context.jwtPayload.context.organization_id,
85
+ domain: (_c = context.jwtPayload.context.organization_domain) !== null && _c !== void 0 ? _c : '',
86
+ },
67
87
  });
68
88
  prefix += ` Context [${JSON.stringify(prepareContext(context))}]`;
69
89
  }
@@ -74,6 +94,7 @@ function log(message, context) {
74
94
  }
75
95
  exports.log = log;
76
96
  function logError(e, context) {
97
+ var _a, _b, _c;
77
98
  if (onError) {
78
99
  onError(e, context);
79
100
  }
@@ -82,11 +103,20 @@ function logError(e, context) {
82
103
  logsFormatter.setContext({
83
104
  project: {
84
105
  id: context.jwtPayload.context.project_id,
106
+ identifier: (_a = context.jwtPayload.context.project_identifier) !== null && _a !== void 0 ? _a : '',
85
107
  // eslint-disable-next-line @typescript-eslint/camelcase
86
108
  organization_id: context.jwtPayload.context.organization_id,
87
109
  // eslint-disable-next-line @typescript-eslint/camelcase
88
110
  user_id: context.jwtPayload.context.user_id,
89
111
  },
112
+ user: {
113
+ id: context.jwtPayload.context.user_id,
114
+ login: (_b = context.jwtPayload.context.user_login) !== null && _b !== void 0 ? _b : '',
115
+ },
116
+ organization: {
117
+ id: context.jwtPayload.context.organization_id,
118
+ domain: (_c = context.jwtPayload.context.organization_domain) !== null && _c !== void 0 ? _c : '',
119
+ },
90
120
  });
91
121
  }
92
122
  if (e instanceof AppModuleAggregateError) {
@@ -119,10 +149,10 @@ function errorOutputByType(error) {
119
149
  status: ((_j = error.response) === null || _j === void 0 ? void 0 : _j.status) || ((_k = error.data) === null || _k === void 0 ? void 0 : _k.response.status),
120
150
  }
121
151
  : {};
122
- console.error(message, { attributes: { request, response } }, { backtrace: error.stack });
152
+ console.error(message, { attributes: JSON.stringify({ request, response }) }, { backtrace: error.stack });
123
153
  }
124
154
  else {
125
- console.error(message, error.data ? { attributes: error.data } : '', error.stack ? { backtrace: error.stack } : '');
155
+ console.error(message, error.data ? { attributes: typeof error.data === 'object' ? JSON.stringify(error.data) : error.data } : '', error.stack ? { backtrace: error.stack } : '');
126
156
  }
127
157
  }
128
158
  function isAxiosError(e) {
@@ -148,8 +178,11 @@ class AppModuleError extends Error {
148
178
  super(typeof error === 'string' ? error : error.message);
149
179
  this.isAxiosError = false;
150
180
  this.name = 'AppModuleError';
151
- this.data = typeof error === 'string' ? Object.assign({}, data) : Object.assign(Object.assign({}, error), data);
152
181
  this.stack = typeof error === 'string' ? this.stack : error.stack;
182
+ this.appData = data;
183
+ if (typeof error !== 'string') {
184
+ this.data = Object.assign({}, error);
185
+ }
153
186
  if (isAxiosError(error)) {
154
187
  this.isAxiosError = true;
155
188
  }
@@ -164,3 +197,76 @@ class AppModuleAggregateError extends Error {
164
197
  }
165
198
  }
166
199
  exports.AppModuleAggregateError = AppModuleAggregateError;
200
+ function storeUserError({ action, error, crowdinId, clientId, }) {
201
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w;
202
+ return __awaiter(this, void 0, void 0, function* () {
203
+ const data = {};
204
+ if (isAxiosError(error)) {
205
+ data.requestParams = {
206
+ method: ((_a = error === null || error === void 0 ? void 0 : error.request) === null || _a === void 0 ? void 0 : _a.method) || ((_c = (_b = error === null || error === void 0 ? void 0 : error.data) === null || _b === void 0 ? void 0 : _b.request) === null || _c === void 0 ? void 0 : _c.method),
207
+ protocol: ((_d = error === null || error === void 0 ? void 0 : error.request) === null || _d === void 0 ? void 0 : _d.protocol) || ((_f = (_e = error === null || error === void 0 ? void 0 : error.data) === null || _e === void 0 ? void 0 : _e.request) === null || _f === void 0 ? void 0 : _f.protocol),
208
+ host: ((_g = error === null || error === void 0 ? void 0 : error.request) === null || _g === void 0 ? void 0 : _g.host) || ((_j = (_h = error === null || error === void 0 ? void 0 : error.data) === null || _h === void 0 ? void 0 : _h.request) === null || _j === void 0 ? void 0 : _j.host),
209
+ path: ((_k = error === null || error === void 0 ? void 0 : error.request) === null || _k === void 0 ? void 0 : _k.path) || ((_m = (_l = error === null || error === void 0 ? void 0 : error.data) === null || _l === void 0 ? void 0 : _l.request) === null || _m === void 0 ? void 0 : _m.path),
210
+ };
211
+ data.responseData = {
212
+ data: ((_o = error === null || error === void 0 ? void 0 : error.response) === null || _o === void 0 ? void 0 : _o.data) || ((_q = (_p = error === null || error === void 0 ? void 0 : error.data) === null || _p === void 0 ? void 0 : _p.response) === null || _q === void 0 ? void 0 : _q.data),
213
+ status: ((_r = error === null || error === void 0 ? void 0 : error.response) === null || _r === void 0 ? void 0 : _r.status) || ((_t = (_s = error === null || error === void 0 ? void 0 : error.data) === null || _s === void 0 ? void 0 : _s.response) === null || _t === void 0 ? void 0 : _t.status),
214
+ statusText: ((_u = error === null || error === void 0 ? void 0 : error.response) === null || _u === void 0 ? void 0 : _u.statusText) || ((_w = (_v = error === null || error === void 0 ? void 0 : error.data) === null || _v === void 0 ? void 0 : _v.response) === null || _w === void 0 ? void 0 : _w.statusText),
215
+ };
216
+ }
217
+ if (error instanceof AppModuleError && error.appData) {
218
+ data.appData = error.appData;
219
+ }
220
+ yield (0, storage_1.getStorage)().saveUserError(action, error.message, JSON.stringify(data), `${Date.now()}`, crowdinId, clientId);
221
+ });
222
+ }
223
+ function clearOldUserErrors(crowdinId, clientId) {
224
+ const date = new Date();
225
+ date.setMonth(date.getMonth() - 1); // previous month
226
+ (0, storage_1.getStorage)().deleteUserErrors(`${date.getTime()}`, crowdinId, clientId);
227
+ }
228
+ function mergeAppModuleAggregateErrors(errors) {
229
+ const result = [];
230
+ const mergedData = {};
231
+ for (const errorItem of errors) {
232
+ if (errorItem instanceof AppModuleError) {
233
+ if (typeof errorItem.appData === 'object' &&
234
+ errorItem.appData !== null &&
235
+ !Array.isArray(errorItem.appData)) {
236
+ if (!mergedData[errorItem.message]) {
237
+ mergedData[errorItem.message] = {};
238
+ }
239
+ for (const key in errorItem.appData) {
240
+ mergedData[errorItem.message][key] = (mergedData[errorItem.message][key] || []).concat(errorItem.appData[key]);
241
+ }
242
+ }
243
+ else if (errorItem.appData) {
244
+ mergedData[errorItem.message] = (mergedData[errorItem.message] || []).concat(errorItem.appData);
245
+ }
246
+ continue;
247
+ }
248
+ result.push(errorItem);
249
+ }
250
+ for (const key in mergedData) {
251
+ result.push(new AppModuleError(key, mergedData[key]));
252
+ }
253
+ return result;
254
+ }
255
+ function handleUserError({ action, error, crowdinId, clientId, }) {
256
+ return __awaiter(this, void 0, void 0, function* () {
257
+ if (typeof error === 'string') {
258
+ return;
259
+ }
260
+ if (error instanceof AppModuleAggregateError) {
261
+ const mergedErrors = mergeAppModuleAggregateErrors(error.errors);
262
+ for (const key in mergedErrors) {
263
+ yield handleUserError({ action, error: mergedErrors[key], crowdinId, clientId });
264
+ }
265
+ }
266
+ else {
267
+ yield storeUserError({ action, error, crowdinId, clientId });
268
+ clearOldUserErrors(crowdinId, clientId);
269
+ }
270
+ });
271
+ }
272
+ exports.handleUserError = handleUserError;