@crowdin/app-project-module 0.74.0 → 0.76.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.
@@ -16,13 +16,107 @@ Object.defineProperty(exports, "__esModule", { value: true });
16
16
  exports.SQLiteStorage = void 0;
17
17
  const path_1 = require("path");
18
18
  const uuid_1 = require("uuid");
19
- const types_1 = require("../modules/integration/util/types");
19
+ const types_1 = require("../types");
20
+ const types_2 = require("../modules/integration/util/types");
20
21
  class SQLiteStorage {
21
22
  constructor(config) {
22
23
  this.dbPromise = new Promise((res, rej) => {
23
24
  this._res = res;
24
25
  this._rej = rej;
25
26
  });
27
+ this.tables = {
28
+ crowdin_credentials: `(
29
+ id varchar not null primary key,
30
+ app_secret varchar null,
31
+ domain varchar null,
32
+ user_id varchar null,
33
+ agent_id varchar null,
34
+ organization_id varchar null,
35
+ base_url varchar null,
36
+ access_token varchar not null,
37
+ refresh_token varchar not null,
38
+ expire varchar not null,
39
+ type varchar not null
40
+ )`,
41
+ integration_credentials: `(
42
+ id varchar not null primary key,
43
+ credentials varchar not null,
44
+ crowdin_id varchar not null,
45
+ managers varchar null
46
+ )`,
47
+ sync_settings: `(
48
+ id integer not null primary key autoincrement,
49
+ files varchar null,
50
+ integration_id varchar not null,
51
+ crowdin_id varchar not null,
52
+ type varchar not null,
53
+ provider varchar not null
54
+ )`,
55
+ app_metadata: `(
56
+ id varchar not null primary key,
57
+ data varchar null,
58
+ crowdin_id varchar null
59
+ )`,
60
+ files_snapshot: `(
61
+ id integer not null primary key autoincrement,
62
+ integration_id varchar not null,
63
+ crowdin_id varchar not null,
64
+ files varchar null,
65
+ provider varchar not null
66
+ )`,
67
+ webhooks: `(
68
+ id integer not null primary key autoincrement,
69
+ file_id varchar not null,
70
+ integration_id varchar not null,
71
+ crowdin_id varchar not null,
72
+ provider varchar not null
73
+ )`,
74
+ user_errors: `(
75
+ id integer not null primary key autoincrement,
76
+ action varchar not null,
77
+ message varchar not null,
78
+ data varchar null,
79
+ created_at varchar not null,
80
+ crowdin_id varchar not null,
81
+ integration_id varchar null
82
+ )`,
83
+ integration_settings: `(
84
+ id integer not null primary key autoincrement,
85
+ integration_id varchar not null,
86
+ crowdin_id varchar not null,
87
+ config varchar null
88
+ )`,
89
+ job: `(
90
+ id varchar not null primary key,
91
+ integration_id varchar not null,
92
+ crowdin_id varchar not null,
93
+ type varchar not null,
94
+ title varchar null,
95
+ progress integer DEFAULT 0,
96
+ status varchar DEFAULT '${types_2.JobStatus.CREATED}',
97
+ payload varchar null,
98
+ info varchar null,
99
+ data varchar null,
100
+ attempt varchar DEFAULT 0,
101
+ created_at varchar not null,
102
+ updated_at varchar null,
103
+ finished_at varchar null
104
+ )`,
105
+ translation_file_cache: `(
106
+ id integer not null primary key autoincrement,
107
+ integration_id varchar not null,
108
+ crowdin_id varchar not null,
109
+ file_id integer not null,
110
+ language_id varchar not null,
111
+ etag varchar
112
+ )`,
113
+ unsynced_files: `(
114
+ id integer not null primary key autoincrement,
115
+ integration_id varchar not null,
116
+ crowdin_id varchar not null,
117
+ files varchar null
118
+ )`,
119
+ };
26
120
  this.config = config;
27
121
  }
28
122
  _run(query, params) {
@@ -132,6 +226,7 @@ class SQLiteStorage {
132
226
  yield this.addColumns(['app_secret', 'domain', 'user_id', 'agent_id', 'organization_id', 'base_url'], 'crowdin_credentials');
133
227
  yield this.addColumns(['crowdin_id'], 'app_metadata');
134
228
  yield this.addColumn('job', 'attempt', 'DEFAULT 0');
229
+ yield this.addColumn('managers', 'integration_credentials', 'null');
135
230
  });
136
231
  }
137
232
  moveIntegrationSettings() {
@@ -175,7 +270,7 @@ class SQLiteStorage {
175
270
  });
176
271
  const sqlite = require('sqlite3');
177
272
  //@ts-ignore
178
- this.db = new sqlite.Database((0, path_1.join)(this.config.dbFolder, 'app.sqlite'), (error) => {
273
+ this.db = new sqlite.Database((0, path_1.join)(this.config.dbFolder, types_1.storageFiles.SQLITE), (error) => {
179
274
  if (error) {
180
275
  _connection_rej(error);
181
276
  }
@@ -185,121 +280,9 @@ class SQLiteStorage {
185
280
  });
186
281
  try {
187
282
  yield connectionPromise;
188
- yield this._run(`
189
- create table if not exists crowdin_credentials
190
- (
191
- id varchar not null primary key,
192
- app_secret varchar null,
193
- domain varchar null,
194
- user_id varchar null,
195
- agent_id varchar null,
196
- organization_id varchar null,
197
- base_url varchar null,
198
- access_token varchar not null,
199
- refresh_token varchar not null,
200
- expire varchar not null,
201
- type varchar not null
202
- );
203
- `, []);
204
- yield this._run(`
205
- create table if not exists integration_credentials
206
- (
207
- id varchar not null primary key,
208
- credentials varchar not null,
209
- crowdin_id varchar not null,
210
- managers varchar null
211
- );
212
- `, []);
213
- yield this._run(`
214
- create table if not exists sync_settings
215
- (
216
- id integer not null primary key autoincrement,
217
- files varchar null,
218
- integration_id varchar not null,
219
- crowdin_id varchar not null,
220
- type varchar not null,
221
- provider varchar not null
222
- );
223
- `, []);
224
- yield this._run(`
225
- create table if not exists app_metadata
226
- (
227
- id varchar not null primary key,
228
- data varchar null,
229
- crowdin_id varchar null
230
- );
231
- `, []);
232
- yield this._run(`
233
- create table if not exists files_snapshot
234
- (
235
- id integer not null primary key autoincrement,
236
- integration_id varchar not null,
237
- crowdin_id varchar not null,
238
- files varchar null,
239
- provider varchar not null
240
- );
241
- `, []);
242
- yield this._run(`
243
- create table if not exists webhooks
244
- (
245
- id integer not null primary key autoincrement,
246
- file_id varchar not null,
247
- integration_id varchar not null,
248
- crowdin_id varchar not null,
249
- provider varchar not null
250
- );
251
- `, []);
252
- yield this._run(`
253
- create table if not exists user_errors
254
- (
255
- id integer not null primary key autoincrement,
256
- action varchar not null,
257
- message varchar not null,
258
- data varchar null,
259
- created_at varchar not null,
260
- crowdin_id varchar not null,
261
- integration_id varchar null
262
- );
263
- `, []);
264
- yield this._run(`
265
- create table if not exists integration_settings
266
- (
267
- id integer not null primary key autoincrement,
268
- integration_id varchar not null,
269
- crowdin_id varchar not null,
270
- config varchar null
271
- );
272
- `, []);
273
- yield this._run(`
274
- create table if not exists job
275
- (
276
- id varchar not null primary key,
277
- integration_id varchar not null,
278
- crowdin_id varchar not null,
279
- type varchar not null,
280
- title varchar null,
281
- progress integer DEFAULT 0,
282
- status varchar DEFAULT '${types_1.JobStatus.CREATED}',
283
- payload varchar null,
284
- info varchar null,
285
- data varchar null,
286
- attempt varchar DEFAULT 0,
287
- created_at varchar not null,
288
- updated_at varchar null,
289
- finished_at varchar null
290
- );
291
- `, []);
292
- yield this._run(`
293
- create table if not exists translation_file_cache
294
- (
295
- id integer not null primary key autoincrement,
296
- integration_id varchar not null,
297
- crowdin_id varchar not null,
298
- file_id integer not null,
299
- language_id varchar not null,
300
- etag varchar
301
- );
302
- `, []);
283
+ for (const [tableName, tableSchema] of Object.entries(this.tables)) {
284
+ yield this._run(`create table if not exists ${tableName} ${tableSchema};`, []);
285
+ }
303
286
  this._res && this._res();
304
287
  // TODO: temporary code
305
288
  yield this.updateTables();
@@ -363,6 +346,7 @@ class SQLiteStorage {
363
346
  yield this.run('DELETE FROM integration_settings WHERE crowdin_id = ?', [id]);
364
347
  yield this.run('DELETE FROM job WHERE crowdin_id = ?', [id]);
365
348
  yield this.run('DELETE FROM translation_file_cache WHERE crowdin_id = ?', [id]);
349
+ yield this.run('DELETE FROM unsynced_files WHERE crowdin_id = ?', [id]);
366
350
  });
367
351
  }
368
352
  saveIntegrationCredentials(id, credentials, crowdinId) {
@@ -396,6 +380,7 @@ class SQLiteStorage {
396
380
  yield this.run('DELETE FROM files_snapshot where integration_id = ?', [id]);
397
381
  yield this.run('DELETE FROM webhooks where integration_id = ?', [id]);
398
382
  yield this.run('DELETE FROM job where integration_id = ?', [id]);
383
+ yield this.run('DELETE FROM unsynced_files where integration_id = ?', [id]);
399
384
  });
400
385
  }
401
386
  deleteAllIntegrationCredentials(crowdinId) {
@@ -406,6 +391,7 @@ class SQLiteStorage {
406
391
  yield this.run('DELETE FROM webhooks where crowdin_id = ?', [crowdinId]);
407
392
  yield this.run('DELETE FROM user_errors where crowdin_id = ?', [crowdinId]);
408
393
  yield this.run('DELETE FROM job where crowdin_id = ?', [crowdinId]);
394
+ yield this.run('DELETE FROM unsynced_files where crowdin_id = ?', [crowdinId]);
409
395
  });
410
396
  }
411
397
  saveMetadata(id, metadata, crowdinId) {
@@ -583,7 +569,7 @@ class SQLiteStorage {
583
569
  if (status) {
584
570
  updateFields.push('status = ?');
585
571
  updateParams.push(status);
586
- if (!updateFields.includes('finished_at = ?') && [types_1.JobStatus.FAILED, types_1.JobStatus.CANCELED].includes(status)) {
572
+ if (!updateFields.includes('finished_at = ?') && [types_2.JobStatus.FAILED, types_2.JobStatus.CANCELED].includes(status)) {
587
573
  updateFields.push('finished_at = ?');
588
574
  updateParams.push(Date.now().toString());
589
575
  }
@@ -641,7 +627,7 @@ class SQLiteStorage {
641
627
  SELECT id, integration_id as integrationId, crowdin_id as crowdinId, type, payload, progress, status, title, info, data, attempt, created_at as createdAt, updated_at as updatedAt, finished_at as finishedAt
642
628
  FROM job
643
629
  WHERE status IN (?,?) AND finished_at is NULL
644
- `, [types_1.JobStatus.IN_PROGRESS, types_1.JobStatus.CREATED]);
630
+ `, [types_2.JobStatus.IN_PROGRESS, types_2.JobStatus.CREATED]);
645
631
  });
646
632
  }
647
633
  saveTranslationCache({ integrationId, crowdinId, fileId, languageId, etag }) {
@@ -679,5 +665,31 @@ class SQLiteStorage {
679
665
  WHERE integration_id = ? AND crowdin_id = ? AND file_id = ? AND language_id = ?
680
666
  `, [etag, integrationId, crowdinId, fileId, languageId]);
681
667
  }
668
+ saveUnsyncedFiles({ integrationId, crowdinId, files }) {
669
+ return this.run(`
670
+ INSERT
671
+ INTO unsynced_files(integration_id, crowdin_id, files)
672
+ VALUES (?, ?, ?)
673
+ `, [integrationId, crowdinId, files]);
674
+ }
675
+ updateUnsyncedFiles({ integrationId, crowdinId, files }) {
676
+ return this.run(`
677
+ UPDATE unsynced_files
678
+ SET files = ?
679
+ WHERE integration_id = ? AND crowdin_id = ?
680
+ `, [files, integrationId, crowdinId]);
681
+ }
682
+ getUnsyncedFiles({ integrationId, crowdinId }) {
683
+ return __awaiter(this, void 0, void 0, function* () {
684
+ const row = yield this.get(`
685
+ SELECT files
686
+ FROM unsynced_files
687
+ WHERE integration_id = ? AND crowdin_id = ?
688
+ `, [integrationId, crowdinId]);
689
+ if (row) {
690
+ return row;
691
+ }
692
+ });
693
+ }
682
694
  }
683
695
  exports.SQLiteStorage = SQLiteStorage;
package/out/types.d.ts CHANGED
@@ -82,6 +82,10 @@ export interface ClientConfig extends ImagePath {
82
82
  * folder where module will create sqlite db file to persist credentials (e.g. {@example __dirname})
83
83
  */
84
84
  dbFolder?: string;
85
+ /**
86
+ * migrate from SQLite to PostgreSQL
87
+ */
88
+ migrateToPostgreFromSQLite?: boolean;
85
89
  /**
86
90
  * config to configure PostgreSQL as a storage
87
91
  */
@@ -449,4 +453,9 @@ export interface SignaturePatterns {
449
453
  fileName?: string;
450
454
  fileContent?: string;
451
455
  }
456
+ export declare enum storageFiles {
457
+ SQLITE = "app.sqlite",
458
+ SQLITE_BACKUP = "backup_app.sqlite",
459
+ DUMP = "dump_table_%s.sql"
460
+ }
452
461
  export {};
package/out/types.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ProjectPermissions = exports.UserPermissions = exports.EditorMode = exports.SubscriptionInfoType = exports.AccountType = exports.Scope = exports.AuthenticationType = void 0;
3
+ exports.storageFiles = exports.ProjectPermissions = exports.UserPermissions = exports.EditorMode = exports.SubscriptionInfoType = exports.AccountType = exports.Scope = exports.AuthenticationType = void 0;
4
4
  var AuthenticationType;
5
5
  (function (AuthenticationType) {
6
6
  AuthenticationType["CODE"] = "authorization_code";
@@ -66,3 +66,9 @@ var ProjectPermissions;
66
66
  ProjectPermissions["OWN"] = "own";
67
67
  ProjectPermissions["RESTRICTED"] = "restricted";
68
68
  })(ProjectPermissions = exports.ProjectPermissions || (exports.ProjectPermissions = {}));
69
+ var storageFiles;
70
+ (function (storageFiles) {
71
+ storageFiles["SQLITE"] = "app.sqlite";
72
+ storageFiles["SQLITE_BACKUP"] = "backup_app.sqlite";
73
+ storageFiles["DUMP"] = "dump_table_%s.sql";
74
+ })(storageFiles = exports.storageFiles || (exports.storageFiles = {}));
@@ -14,3 +14,8 @@ export declare function isJson(string: string): boolean;
14
14
  export declare function getPreviousDate(days: number): Date;
15
15
  export declare function prepareFormDataMetadataId(req: CrowdinClientRequest, config: Config): Promise<string>;
16
16
  export declare function validateEmail(email: string | number): boolean;
17
+ export declare function isApiRequest(req: Request, config: Config): boolean;
18
+ export declare function getFormattedDate({ date, userTimezone }: {
19
+ date: Date | number;
20
+ userTimezone: string | undefined;
21
+ }): string;
package/out/util/index.js CHANGED
@@ -32,7 +32,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
32
32
  });
33
33
  };
34
34
  Object.defineProperty(exports, "__esModule", { value: true });
35
- exports.validateEmail = exports.prepareFormDataMetadataId = exports.getPreviousDate = exports.isJson = exports.isAuthorizedConfig = exports.getLogoUrl = exports.executeWithRetry = exports.decryptData = exports.encryptData = exports.runAsyncWrapper = exports.CodeError = void 0;
35
+ exports.getFormattedDate = exports.isApiRequest = exports.validateEmail = exports.prepareFormDataMetadataId = exports.getPreviousDate = exports.isJson = exports.isAuthorizedConfig = exports.getLogoUrl = exports.executeWithRetry = exports.decryptData = exports.encryptData = exports.runAsyncWrapper = exports.CodeError = void 0;
36
36
  const crypto = __importStar(require("crypto-js"));
37
37
  const storage_1 = require("../storage");
38
38
  const types_1 = require("../types");
@@ -162,3 +162,24 @@ function validateEmail(email) {
162
162
  return emailRegExp.test(String(email).toLowerCase());
163
163
  }
164
164
  exports.validateEmail = validateEmail;
165
+ function isApiRequest(req, config) {
166
+ const origin = req.headers['origin'] || req.headers['referer'];
167
+ return !origin || !origin.includes(config.baseUrl);
168
+ }
169
+ exports.isApiRequest = isApiRequest;
170
+ // Format the date as 'MMM DD, YYYY HH:mm'
171
+ function getFormattedDate({ date, userTimezone }) {
172
+ if (!userTimezone) {
173
+ userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
174
+ }
175
+ return new Intl.DateTimeFormat('en-US', {
176
+ year: 'numeric',
177
+ month: 'short',
178
+ day: 'numeric',
179
+ hour: '2-digit',
180
+ minute: '2-digit',
181
+ hour12: false,
182
+ timeZone: userTimezone,
183
+ }).format(date);
184
+ }
185
+ exports.getFormattedDate = getFormattedDate;
@@ -296,20 +296,20 @@
296
296
  {{/ifeq}}
297
297
  {{#ifeq type "text"}}
298
298
  <crowdin-input
299
- id="{{key}}-settings"
300
- label="{{label}}"
301
- key="{{key}}"
302
- with-fixed-height
303
- {{#if helpText}}
304
- help-text="{{helpText}}"
305
- {{/if}}
306
- {{#if helpTextHtml}}
307
- help-text-html="{{helpTextHtml}}"
308
- {{/if}}
309
- {{#if dependencySettings}}
310
- data-dependency="{{dependencySettings}}"
311
- {{/if}}
312
- value="{{#if defaultValue}}{{defaultValue}}{{/if}}"
299
+ id="{{key}}-settings"
300
+ label="{{label}}"
301
+ key="{{key}}"
302
+ with-fixed-height
303
+ {{#if helpText}}
304
+ help-text="{{helpText}}"
305
+ {{/if}}
306
+ {{#if helpTextHtml}}
307
+ help-text-html="{{helpTextHtml}}"
308
+ {{/if}}
309
+ {{#if dependencySettings}}
310
+ data-dependency="{{dependencySettings}}"
311
+ {{/if}}
312
+ value="{{#if defaultValue}}{{defaultValue}}{{/if}}"
313
313
  >
314
314
  </crowdin-input>
315
315
  {{/ifeq}}
@@ -350,21 +350,21 @@
350
350
  {{/ifeq}}
351
351
  {{else}}
352
352
  {{#if labelHtml}}
353
- <crowdin-p
354
- {{#if dependencySettings}}
355
- data-dependency="{{dependencySettings}}"
356
- {{/if}}
357
- >
358
- {{{labelHtml}}}
359
- </crowdin-p>
353
+ <crowdin-p
354
+ {{#if dependencySettings}}
355
+ data-dependency="{{dependencySettings}}"
356
+ {{/if}}
357
+ >
358
+ {{{labelHtml}}}
359
+ </crowdin-p>
360
360
  {{else}}
361
- <crowdin-p
362
- {{#if dependencySettings}}
363
- data-dependency="{{dependencySettings}}"
364
- {{/if}}
365
- >
366
- {{label}}
367
- </crowdin-p>
361
+ <crowdin-p
362
+ {{#if dependencySettings}}
363
+ data-dependency="{{dependencySettings}}"
364
+ {{/if}}
365
+ >
366
+ {{label}}
367
+ </crowdin-p>
368
368
  {{/if}}
369
369
  {{/if}}
370
370
  <div
@@ -391,19 +391,19 @@
391
391
  body-overflow-unset
392
392
  >
393
393
  <crowdin-checkbox
394
- id="selected-files"
395
- name="selected-files"
396
- label="Selected files"
397
- class="hydrated"
398
- onchange="onChangeAutoSynchronizationOptions(this)"
394
+ id="selected-files"
395
+ name="selected-files"
396
+ label="Selected files"
397
+ class="hydrated"
398
+ onchange="onChangeAutoSynchronizationOptions(this)"
399
399
  >
400
400
  </crowdin-checkbox>
401
401
  <crowdin-checkbox
402
- id="new-files"
403
- name="new-files"
404
- label="New files"
405
- class="hydrated"
406
- onchange="onChangeAutoSynchronizationOptions(this)"
402
+ id="new-files"
403
+ name="new-files"
404
+ label="New files"
405
+ class="hydrated"
406
+ onchange="onChangeAutoSynchronizationOptions(this)"
407
407
  >
408
408
  </crowdin-checkbox>
409
409
  <div slot="footer">
@@ -510,6 +510,7 @@
510
510
  if (e.type) {
511
511
  item.type = e.type;
512
512
  item.node_type = fileType;
513
+ item.failed = e.failed;
513
514
  } else {
514
515
  item.node_type = e.nodeType || folderType;
515
516
  }
@@ -573,16 +574,14 @@
573
574
  } else if (tree.length) {
574
575
  appComponent.pushIntegrationFilesData(tree).then(() => {
575
576
  if (parentId) {
576
- appIntegrationFiles.getSelected().then(selection => {
577
- const selectedIds = selection?.filter((node) => node).map(({id}) => id.toString());
578
- if (!selectedIds?.length) {
579
- return;
580
- }
581
- const filteredNodes = tree.filter((node) => selectedIds.includes(node.parent_id.toString()));
582
- filteredNodes.forEach((node) => {
583
- selectedIds.push(node.id);
584
- });
585
- appIntegrationFiles.setSelected(selectedIds);
577
+ appIntegrationFiles.getSelected().then(async selection => {
578
+ const selectedIds = selection?.filter((node) => node).map(({id}) => id.toString());
579
+ const filteredNodes = tree.filter((node) => !selectedIds.includes(node.id.toString()) && selectedIds.includes(node.parent_id.toString()));
580
+
581
+ if (filteredNodes?.length) {
582
+ const filteredNodesId = filteredNodes.map(({id}) => id.toString());
583
+ await appIntegrationFiles.setSelected(filteredNodesId);
584
+ }
586
585
  });
587
586
  }
588
587
  });
@@ -726,7 +725,7 @@
726
725
  }
727
726
 
728
727
  checkOrigin()
729
- .then(restParams => fetch('api/jobs' + restParams + '&job_id=' + jobId, {
728
+ .then(restParams => fetch('api/jobs' + restParams + '&jobId=' + jobId, {
730
729
  method: 'DELETE',
731
730
  headers: { 'Content-Type': 'application/json' },
732
731
  }))
@@ -767,7 +766,7 @@
767
766
  }
768
767
 
769
768
  checkOrigin()
770
- .then(restParams => fetch('api/jobs' + restParams + '&job_id=' + jobId))
769
+ .then(restParams => fetch('api/jobs' + restParams + '&jobId=' + jobId))
771
770
  .then(checkResponse)
772
771
  .then((job) => {
773
772
  const isFailed = [JOB_STATUS.failed, JOB_STATUS.canceled].includes(job.status);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crowdin/app-project-module",
3
- "version": "0.74.0",
3
+ "version": "0.76.0",
4
4
  "description": "Module that generates for you all common endpoints for serving standalone Crowdin App",
5
5
  "main": "out/index.js",
6
6
  "types": "out/index.d.ts",