@crowdin/app-project-module 0.79.2 → 0.81.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.
package/out/index.js CHANGED
@@ -191,7 +191,7 @@ function addCrowdinEndpoints(app, clientConfig) {
191
191
  webhooks.register({ config, app });
192
192
  workflowStepType.register({ config, app });
193
193
  addFormSchema({ config, app });
194
- return Object.assign(Object.assign({}, exports.metadataStore), { establishCrowdinConnection: (authRequest, moduleKey) => {
194
+ return Object.assign(Object.assign({}, exports.metadataStore), { storage: storage.getStorage(), establishCrowdinConnection: (authRequest, moduleKey) => {
195
195
  let jwtToken = '';
196
196
  if (typeof authRequest === 'string') {
197
197
  jwtToken = authRequest;
@@ -8,13 +8,25 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
9
  });
10
10
  };
11
+ var __rest = (this && this.__rest) || function (s, e) {
12
+ var t = {};
13
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
14
+ t[p] = s[p];
15
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
16
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
17
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
18
+ t[p[i]] = s[p[i]];
19
+ }
20
+ return t;
21
+ };
11
22
  Object.defineProperty(exports, "__esModule", { value: true });
12
23
  const util_1 = require("../../../util");
13
24
  const logger_1 = require("../../../util/logger");
14
25
  function handle(aiPromptProvider) {
15
26
  return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
16
27
  try {
17
- const compiledPrompt = yield aiPromptProvider.compile(req.body.options, req.body.payload, req.crowdinApiClient, req.crowdinContext);
28
+ const _a = req.body, { options, payload } = _a, body = __rest(_a, ["options", "payload"]);
29
+ const compiledPrompt = yield aiPromptProvider.compile(options, payload, req.crowdinApiClient, req.crowdinContext, body);
18
30
  res.send({ data: { content: compiledPrompt } });
19
31
  }
20
32
  catch (e) {
@@ -20,7 +20,7 @@ function register({ config, app }) {
20
20
  checkSubscriptionExpiration: true,
21
21
  moduleKey: config.aiPromptProvider.key,
22
22
  }), (0, compile_1.default)(config.aiPromptProvider));
23
- if (config.aiPromptProvider.formSchema) {
23
+ if (config.aiPromptProvider.formSchema || config.aiPromptProvider.uiPath) {
24
24
  app.use('/prompt-provider/settings', (0, ui_module_1.default)({ config, allowUnauthorized: true, moduleType: config.aiPromptProvider.key }), (0, render_ui_module_1.default)(config.aiPromptProvider));
25
25
  }
26
26
  app.get((0, util_1.getLogoUrl)(config.aiPromptProvider, '/ai-prompt-provider'), (req, res) => { var _a; return res.sendFile(((_a = config.aiPromptProvider) === null || _a === void 0 ? void 0 : _a.imagePath) || config.imagePath); });
@@ -4,5 +4,5 @@ export interface AiPromptProviderModule extends UiModule {
4
4
  /**
5
5
  * generates prompt text based on provided options
6
6
  */
7
- compile: (options: any, payload: any, client: Crowdin, context: CrowdinContextInfo) => Promise<string>;
7
+ compile: (options: any, payload: any, client: Crowdin, context: CrowdinContextInfo, requestData: any) => Promise<string>;
8
8
  }
@@ -499,9 +499,9 @@ function addDefaultApiEndpoints(app, config) {
499
499
  checkSubscriptionExpiration: true,
500
500
  moduleKey: config.projectIntegration.key,
501
501
  }), (req, res) => {
502
- var _a;
502
+ var _a, _b;
503
503
  let fields = [];
504
- if ((_a = config.projectIntegration) === null || _a === void 0 ? void 0 : _a.loginForm.fields) {
504
+ if ((_b = (_a = config.projectIntegration) === null || _a === void 0 ? void 0 : _a.loginForm) === null || _b === void 0 ? void 0 : _b.fields) {
505
505
  fields = getFormFields(config === null || config === void 0 ? void 0 : config.projectIntegration.loginForm.fields);
506
506
  }
507
507
  res.send({ fields });
@@ -47,19 +47,25 @@ function handle() {
47
47
  if (!hasManagerAccess) {
48
48
  return res.status(403).send({ error: 'Access denied' });
49
49
  }
50
+ const inviteRestricted = !isAdmin &&
51
+ 'invite_restrict_enabled' in req.crowdinContext.jwtPayload.context &&
52
+ !!req.crowdinContext.jwtPayload.context.invite_restrict_enabled;
50
53
  const usersWhoWillBeInvitedToOrganization = filterOrganizationUsers(usersToInvite);
51
54
  const usersWhoWillBeInvitedToProject = filterProjectUsers({
55
+ inviteRestricted,
52
56
  usersToInvite,
53
57
  projectMembers,
54
58
  organizationMembers,
55
59
  });
56
60
  const usersWhoNotIssetInApplicationInstallation = filterNotIssetApplicationUsers({
61
+ inviteRestricted,
57
62
  usersToInvite,
58
63
  applicationInstallation,
59
64
  projectMembers,
60
65
  organizationMembers,
61
66
  });
62
67
  const usersWhoNotIssetInIntegration = filterNotIssetIntegrationUsers({
68
+ inviteRestricted,
63
69
  usersToInvite,
64
70
  integrationCredentials,
65
71
  projectMembers,
@@ -69,6 +75,7 @@ function handle() {
69
75
  return res.send({
70
76
  data: {
71
77
  isAdmin,
78
+ inviteRestricted,
72
79
  editApplicationAvailable: applicationInstallation !== null,
73
80
  usersWhoWillBeInvitedToOrganization,
74
81
  usersWhoWillBeInvitedToProject,
@@ -81,6 +88,7 @@ function handle() {
81
88
  req,
82
89
  projectId,
83
90
  isAdmin,
91
+ inviteRestricted,
84
92
  usersToInvite,
85
93
  applicationInstallation,
86
94
  usersWhoWillBeInvitedToOrganization,
@@ -94,10 +102,10 @@ exports.default = handle;
94
102
  function filterOrganizationUsers(usersToInvite) {
95
103
  return usersToInvite.filter((identifier) => (0, util_1.validateEmail)(identifier)).map((name) => ({ name: `${name}` }));
96
104
  }
97
- function filterProjectUsers({ usersToInvite, projectMembers, organizationMembers, }) {
105
+ function filterProjectUsers({ inviteRestricted, usersToInvite, projectMembers, organizationMembers, }) {
98
106
  return usersToInvite
99
107
  .map((identifier) => {
100
- if ((0, util_1.validateEmail)(identifier)) {
108
+ if ((0, util_1.validateEmail)(identifier) && !inviteRestricted) {
101
109
  return { name: `${identifier}` };
102
110
  }
103
111
  const user = projectMembers.find((member) => member.id === +identifier);
@@ -113,7 +121,7 @@ function filterProjectUsers({ usersToInvite, projectMembers, organizationMembers
113
121
  })
114
122
  .filter(Boolean);
115
123
  }
116
- function filterNotIssetApplicationUsers({ usersToInvite, applicationInstallation, projectMembers, organizationMembers, }) {
124
+ function filterNotIssetApplicationUsers({ inviteRestricted, usersToInvite, applicationInstallation, projectMembers, organizationMembers, }) {
117
125
  let userIdentifiers = [];
118
126
  if (!applicationInstallation) {
119
127
  return [];
@@ -130,7 +138,7 @@ function filterNotIssetApplicationUsers({ usersToInvite, applicationInstallation
130
138
  }
131
139
  return userIdentifiers
132
140
  .map((identifier) => {
133
- if ((0, util_1.validateEmail)(identifier)) {
141
+ if ((0, util_1.validateEmail)(identifier) && !inviteRestricted) {
134
142
  return { name: `${identifier}` };
135
143
  }
136
144
  let user;
@@ -142,7 +150,7 @@ function filterNotIssetApplicationUsers({ usersToInvite, applicationInstallation
142
150
  })
143
151
  .filter(Boolean);
144
152
  }
145
- function filterNotIssetIntegrationUsers({ usersToInvite, integrationCredentials, projectMembers, organizationMembers, }) {
153
+ function filterNotIssetIntegrationUsers({ inviteRestricted, usersToInvite, integrationCredentials, projectMembers, organizationMembers, }) {
146
154
  let integrationManagers = [];
147
155
  if (integrationCredentials === null || integrationCredentials === void 0 ? void 0 : integrationCredentials.managers) {
148
156
  integrationManagers = JSON.parse(integrationCredentials.managers);
@@ -152,7 +160,7 @@ function filterNotIssetIntegrationUsers({ usersToInvite, integrationCredentials,
152
160
  if (integrationManagers.includes(`${identifier}`)) {
153
161
  return null;
154
162
  }
155
- if ((0, util_1.validateEmail)(identifier)) {
163
+ if ((0, util_1.validateEmail)(identifier) && !inviteRestricted) {
156
164
  return { name: `${identifier}` };
157
165
  }
158
166
  let user;
@@ -164,9 +172,12 @@ function filterNotIssetIntegrationUsers({ usersToInvite, integrationCredentials,
164
172
  })
165
173
  .filter(Boolean);
166
174
  }
167
- function inviteUsers({ req, projectId, isAdmin, usersToInvite, applicationInstallation, usersWhoWillBeInvitedToOrganization, usersWhoWillBeInvitedToProject, usersWhoNotIssetInApplicationInstallation, }) {
175
+ function inviteUsers({ req, projectId, isAdmin, inviteRestricted, usersToInvite, applicationInstallation, usersWhoWillBeInvitedToOrganization, usersWhoWillBeInvitedToProject, usersWhoNotIssetInApplicationInstallation, }) {
168
176
  return __awaiter(this, void 0, void 0, function* () {
169
177
  const client = req.crowdinApiClient;
178
+ if (inviteRestricted) {
179
+ usersWhoWillBeInvitedToOrganization = [];
180
+ }
170
181
  const alreadyAddedUserIds = yield inviteUsersToProject({
171
182
  client,
172
183
  projectId,
@@ -197,8 +197,10 @@ function register({ config, app }) {
197
197
  }
198
198
  // remove user errors
199
199
  cron.schedule('0 0 * * *', () => __awaiter(this, void 0, void 0, function* () {
200
- const date = (0, util_1.getPreviousDate)(integrationLogic.userErrorLifetimeDays);
201
- yield (0, storage_1.getStorage)().deleteAllUsersErrorsOlderThan(`${date.getTime()}`);
200
+ if (integrationLogic.userErrorLifetimeDays) {
201
+ const date = (0, util_1.getPreviousDate)(integrationLogic.userErrorLifetimeDays);
202
+ yield (0, storage_1.getStorage)().deleteAllUsersErrorsOlderThan(`${date.getTime()}`);
203
+ }
202
204
  }));
203
205
  if (integrationLogic.webhooks) {
204
206
  app.post(`${integrationLogic.webhooks.crowdinWebhookUrl
@@ -6,7 +6,7 @@ export interface IntegrationLogic extends ModuleKey {
6
6
  /**
7
7
  * Customize your app login form
8
8
  */
9
- loginForm: LoginForm;
9
+ loginForm?: LoginForm;
10
10
  /**
11
11
  * Define login process via OAuth2 protocol
12
12
  */
@@ -162,7 +162,7 @@ export interface IntegrationLogic extends ModuleKey {
162
162
  /**
163
163
  * The duration for storing user errors, default is 30 days.
164
164
  */
165
- userErrorLifetimeDays: number;
165
+ userErrorLifetimeDays?: number;
166
166
  /**
167
167
  * When true, folder filtering during automatic translation sync is bypassed, and the file tree is returned unchanged.
168
168
  */
@@ -240,7 +240,7 @@ function handle(config) {
240
240
  // prevent possible overrides of the other modules
241
241
  config.aiPromptProvider = Object.assign(Object.assign({}, config.aiPromptProvider), { key: config.identifier + '-ai-prompt-provider' });
242
242
  modules['ai-prompt-provider'] = [
243
- Object.assign({ key: config.aiPromptProvider.key, name: config.aiPromptProvider.name || config.name, logo: (0, util_1.getLogoUrl)(config.aiPromptProvider, '/ai-prompt-provider'), compileUrl: '/prompt-provider/compile' }, (!!config.aiPromptProvider.formSchema
243
+ Object.assign({ key: config.aiPromptProvider.key, name: config.aiPromptProvider.name || config.name, logo: (0, util_1.getLogoUrl)(config.aiPromptProvider, '/ai-prompt-provider'), compileUrl: '/prompt-provider/compile' }, (config.aiPromptProvider.formSchema || config.aiPromptProvider.uiPath
244
244
  ? {
245
245
  configuratorUrl: '/prompt-provider/settings/' + (config.aiPromptProvider.fileName || 'index.html'),
246
246
  }
@@ -68,6 +68,17 @@ export interface Storage {
68
68
  saveUnsyncedFiles(params: UnsyncedFiles): Promise<void>;
69
69
  getUnsyncedFiles(params: GetUnsyncedFiles): Promise<UnsyncedFiles | undefined>;
70
70
  updateUnsyncedFiles(params: UnsyncedFiles): Promise<void>;
71
+ registerCustomTable(tableName: string, schema: Record<string, string>): Promise<void>;
72
+ insertRecord(tableName: string, data: Record<string, any>): Promise<void>;
73
+ selectRecords(tableName: string, options?: {
74
+ columns?: string[];
75
+ whereClause?: string;
76
+ orderBy?: string;
77
+ limit?: number;
78
+ distinct?: boolean;
79
+ }, params?: any[]): Promise<any[]>;
80
+ updateRecord(tableName: string, data: Record<string, any>, whereClause: string, params?: any[]): Promise<void>;
81
+ deleteRecord(tableName: string, whereClause: string, params?: any[]): Promise<void>;
71
82
  }
72
83
  export declare function initialize(config: Config | UnauthorizedConfig): Promise<void>;
73
84
  export declare function getStorage(): Storage;
@@ -84,4 +84,15 @@ export declare class MySQLStorage implements Storage {
84
84
  saveUnsyncedFiles({ integrationId, crowdinId, files }: UnsyncedFiles): Promise<void>;
85
85
  updateUnsyncedFiles({ integrationId, crowdinId, files }: UnsyncedFiles): Promise<void>;
86
86
  getUnsyncedFiles({ integrationId, crowdinId }: GetUnsyncedFiles): Promise<UnsyncedFiles | undefined>;
87
+ registerCustomTable(tableName: string, schema: Record<string, string>): Promise<void>;
88
+ insertRecord(tableName: string, data: Record<string, any>): Promise<void>;
89
+ selectRecords(tableName: string, options?: {
90
+ columns?: string[];
91
+ whereClause?: string;
92
+ orderBy?: string;
93
+ limit?: number;
94
+ distinct?: boolean;
95
+ }, params?: any[]): Promise<any[]>;
96
+ updateRecord(tableName: string, data: Record<string, any>, whereClause: string, params?: any[]): Promise<void>;
97
+ deleteRecord(tableName: string, whereClause: string, params?: any[]): Promise<void>;
87
98
  }
@@ -438,7 +438,12 @@ class MySQLStorage {
438
438
  }
439
439
  yield this.dbPromise;
440
440
  const placeholders = fileIds.map(() => '?').join(',');
441
- yield this.executeQuery((connection) => connection.execute(`DELETE FROM webhooks WHERE file_id IN (${placeholders}) AND integration_id = ? AND crowdin_id = ? AND provider = ?`, [...fileIds, integrationId, crowdinId, provider]));
441
+ yield this.executeQuery((connection) => connection.execute(`DELETE
442
+ FROM webhooks
443
+ WHERE file_id IN (${placeholders})
444
+ AND integration_id = ?
445
+ AND crowdin_id = ?
446
+ AND provider = ?`, [...fileIds, integrationId, crowdinId, provider]));
442
447
  });
443
448
  }
444
449
  getAllUserErrors(crowdinId, integrationId) {
@@ -451,7 +456,9 @@ class MySQLStorage {
451
456
  whereIntegrationCondition = 'integration_id = ?';
452
457
  params.push(integrationId);
453
458
  }
454
- const [rows] = yield connection.execute(`SELECT id, action, message, data, created_at as "createdAt" FROM user_errors WHERE crowdin_id = ? AND ${whereIntegrationCondition}`, params);
459
+ const [rows] = yield connection.execute(`SELECT id, action, message, data, created_at as "createdAt"
460
+ FROM user_errors
461
+ WHERE crowdin_id = ? AND ${whereIntegrationCondition}`, params);
455
462
  return rows || [];
456
463
  }));
457
464
  });
@@ -472,7 +479,11 @@ class MySQLStorage {
472
479
  whereIntegrationCondition = 'integration_id = ?';
473
480
  params.push(integrationId);
474
481
  }
475
- return connection.execute(`DELETE FROM user_errors WHERE created_at < ? AND crowdin_id = ? AND ${whereIntegrationCondition}`, params);
482
+ return connection.execute(`DELETE
483
+ FROM user_errors
484
+ WHERE created_at < ?
485
+ AND crowdin_id = ?
486
+ AND ${whereIntegrationCondition}`, params);
476
487
  });
477
488
  });
478
489
  }
@@ -525,7 +536,7 @@ class MySQLStorage {
525
536
  yield this.executeQuery((connection) => connection.execute(`
526
537
  INSERT INTO job(id, integration_id, crowdin_id, type, payload, title, created_at)
527
538
  VALUES (?, ?, ?, ?, ?, ?, ?)
528
- `, [id, integrationId, crowdinId, type, payload, title, Date.now().toString()]));
539
+ `, [id, integrationId, crowdinId, type, payload, title, Date.now().toString()]));
529
540
  return id;
530
541
  });
531
542
  }
@@ -567,7 +578,7 @@ class MySQLStorage {
567
578
  UPDATE job
568
579
  SET ${updateFields.join(', ')}
569
580
  WHERE id = ?
570
- `, updateParams));
581
+ `, updateParams));
571
582
  });
572
583
  }
573
584
  getJob({ id }) {
@@ -575,11 +586,23 @@ class MySQLStorage {
575
586
  yield this.dbPromise;
576
587
  return this.executeQuery((connection) => __awaiter(this, void 0, void 0, function* () {
577
588
  const [rows] = yield connection.execute(`
578
- SELECT id, integration_id as "integrationId", crowdin_id as "crowdinId", type, payload, progress, status,
579
- title, info, data, attempt, created_at as "createdAt", updated_at as "updatedAt", finished_at as "finishedAt"
580
- FROM job
581
- WHERE id = ?
582
- `, [id]);
589
+ SELECT id,
590
+ integration_id as "integrationId",
591
+ crowdin_id as "crowdinId",
592
+ type,
593
+ payload,
594
+ progress,
595
+ status,
596
+ title,
597
+ info,
598
+ data,
599
+ attempt,
600
+ created_at as "createdAt",
601
+ updated_at as "updatedAt",
602
+ finished_at as "finishedAt"
603
+ FROM job
604
+ WHERE id = ?
605
+ `, [id]);
583
606
  return (rows || [])[0];
584
607
  }));
585
608
  });
@@ -589,11 +612,25 @@ class MySQLStorage {
589
612
  yield this.dbPromise;
590
613
  return this.executeQuery((connection) => __awaiter(this, void 0, void 0, function* () {
591
614
  const [rows] = yield connection.execute(`
592
- SELECT id, integration_id as "integrationId", crowdin_id as "crowdinId", type, payload, progress, status,
593
- title, info, data, attempt, created_at as "createdAt", updated_at as "updatedAt", finished_at as "finishedAt"
594
- FROM job
595
- WHERE integration_id = ? AND crowdin_id = ? AND finished_at is NULL
596
- `, [integrationId, crowdinId]);
615
+ SELECT id,
616
+ integration_id as "integrationId",
617
+ crowdin_id as "crowdinId",
618
+ type,
619
+ payload,
620
+ progress,
621
+ status,
622
+ title,
623
+ info,
624
+ data,
625
+ attempt,
626
+ created_at as "createdAt",
627
+ updated_at as "updatedAt",
628
+ finished_at as "finishedAt"
629
+ FROM job
630
+ WHERE integration_id = ?
631
+ AND crowdin_id = ?
632
+ AND finished_at is NULL
633
+ `, [integrationId, crowdinId]);
597
634
  return rows || [];
598
635
  }));
599
636
  });
@@ -609,11 +646,24 @@ class MySQLStorage {
609
646
  yield this.dbPromise;
610
647
  return this.executeQuery((connection) => __awaiter(this, void 0, void 0, function* () {
611
648
  const [rows] = yield connection.execute(`
612
- SELECT id, integration_id as "integrationId", crowdin_id as "crowdinId", type, payload, progress, status,
613
- title, info, data, attempt, created_at as "createdAt", updated_at as "updatedAt", finished_at as "finishedAt"
614
- FROM job
615
- WHERE status IN (?, ?) AND finished_at is NULL
616
- `, [types_1.JobStatus.IN_PROGRESS, types_1.JobStatus.CREATED]);
649
+ SELECT id,
650
+ integration_id as "integrationId",
651
+ crowdin_id as "crowdinId",
652
+ type,
653
+ payload,
654
+ progress,
655
+ status,
656
+ title,
657
+ info,
658
+ data,
659
+ attempt,
660
+ created_at as "createdAt",
661
+ updated_at as "updatedAt",
662
+ finished_at as "finishedAt"
663
+ FROM job
664
+ WHERE status IN (?, ?)
665
+ AND finished_at is NULL
666
+ `, [types_1.JobStatus.IN_PROGRESS, types_1.JobStatus.CREATED]);
617
667
  return rows || [];
618
668
  }));
619
669
  });
@@ -624,7 +674,7 @@ class MySQLStorage {
624
674
  yield this.executeQuery((connection) => connection.execute(`
625
675
  INSERT INTO translation_file_cache(integration_id, crowdin_id, file_id, language_id, etag)
626
676
  VALUES (?, ?, ?, ?, ?)
627
- `, [integrationId, crowdinId, fileId, languageId, etag]));
677
+ `, [integrationId, crowdinId, fileId, languageId, etag]));
628
678
  });
629
679
  }
630
680
  getFileTranslationCache({ integrationId, crowdinId, fileId, }) {
@@ -632,10 +682,16 @@ class MySQLStorage {
632
682
  yield this.dbPromise;
633
683
  return this.executeQuery((connection) => __awaiter(this, void 0, void 0, function* () {
634
684
  const [rows] = yield connection.execute(`
635
- SELECT integration_id as "integrationId", crowdin_id as "crowdinId", file_id as "fileId", language_id as "languageId", etag
636
- FROM translation_file_cache
637
- WHERE integration_id = ? AND crowdin_id = ? AND file_id = ?
638
- `, [integrationId, crowdinId, fileId]);
685
+ SELECT integration_id as "integrationId",
686
+ crowdin_id as "crowdinId",
687
+ file_id as "fileId",
688
+ language_id as "languageId",
689
+ etag
690
+ FROM translation_file_cache
691
+ WHERE integration_id = ?
692
+ AND crowdin_id = ?
693
+ AND file_id = ?
694
+ `, [integrationId, crowdinId, fileId]);
639
695
  return rows || [];
640
696
  }));
641
697
  });
@@ -645,10 +701,17 @@ class MySQLStorage {
645
701
  yield this.dbPromise;
646
702
  return this.executeQuery((connection) => __awaiter(this, void 0, void 0, function* () {
647
703
  const [rows] = yield connection.execute(`
648
- SELECT integration_id as "integrationId", crowdin_id as "crowdinId", file_id as "fileId", language_id as "languageId", etag
649
- FROM translation_file_cache
650
- WHERE integration_id = ? AND crowdin_id = ? AND file_id = ? AND language_id = ?
651
- `, [integrationId, crowdinId, fileId, languageId]);
704
+ SELECT integration_id as "integrationId",
705
+ crowdin_id as "crowdinId",
706
+ file_id as "fileId",
707
+ language_id as "languageId",
708
+ etag
709
+ FROM translation_file_cache
710
+ WHERE integration_id = ?
711
+ AND crowdin_id = ?
712
+ AND file_id = ?
713
+ AND language_id = ?
714
+ `, [integrationId, crowdinId, fileId, languageId]);
652
715
  return (rows || [])[0];
653
716
  }));
654
717
  });
@@ -659,8 +722,11 @@ class MySQLStorage {
659
722
  yield this.executeQuery((connection) => connection.execute(`
660
723
  UPDATE translation_file_cache
661
724
  SET etag = ?
662
- WHERE integration_id = ? AND crowdin_id = ? AND file_id = ? AND language_id = ?
663
- `, [etag, integrationId, crowdinId, fileId, languageId]));
725
+ WHERE integration_id = ?
726
+ AND crowdin_id = ?
727
+ AND file_id = ?
728
+ AND language_id = ?
729
+ `, [etag, integrationId, crowdinId, fileId, languageId]));
664
730
  });
665
731
  }
666
732
  saveUnsyncedFiles({ integrationId, crowdinId, files }) {
@@ -669,7 +735,7 @@ class MySQLStorage {
669
735
  yield this.executeQuery((connection) => connection.execute(`
670
736
  INSERT INTO unsynced_files(integration_id, crowdin_id, files)
671
737
  VALUES (?, ?, ?,)
672
- `, [integrationId, crowdinId, files]));
738
+ `, [integrationId, crowdinId, files]));
673
739
  });
674
740
  }
675
741
  updateUnsyncedFiles({ integrationId, crowdinId, files }) {
@@ -678,8 +744,9 @@ class MySQLStorage {
678
744
  yield this.executeQuery((connection) => connection.execute(`
679
745
  UPDATE unsynced_files
680
746
  SET files = ?
681
- WHERE integration_id = ? AND crowdin_id = ?
682
- `, [files, integrationId, crowdinId]));
747
+ WHERE integration_id = ?
748
+ AND crowdin_id = ?
749
+ `, [files, integrationId, crowdinId]));
683
750
  });
684
751
  }
685
752
  getUnsyncedFiles({ integrationId, crowdinId }) {
@@ -687,13 +754,83 @@ class MySQLStorage {
687
754
  yield this.dbPromise;
688
755
  return this.executeQuery((connection) => __awaiter(this, void 0, void 0, function* () {
689
756
  const [rows] = yield connection.execute(`
690
- SELECT fileIds
691
- FROM unsynced_files
692
- WHERE integration_id = ? AND crowdin_id = ?
693
- `, [integrationId, crowdinId]);
757
+ SELECT fileIds
758
+ FROM unsynced_files
759
+ WHERE integration_id = ?
760
+ AND crowdin_id = ?
761
+ `, [integrationId, crowdinId]);
694
762
  return (rows || [])[0];
695
763
  }));
696
764
  });
697
765
  }
766
+ registerCustomTable(tableName, schema) {
767
+ return __awaiter(this, void 0, void 0, function* () {
768
+ const columns = Object.entries(schema)
769
+ .map(([col, def]) => `${col} ${def}`)
770
+ .join(', ');
771
+ const query = `CREATE TABLE IF NOT EXISTS ${tableName}
772
+ (
773
+ ${columns}
774
+ );`;
775
+ yield this.executeQuery((connection) => __awaiter(this, void 0, void 0, function* () {
776
+ yield connection.execute(query);
777
+ return;
778
+ }));
779
+ });
780
+ }
781
+ insertRecord(tableName, data) {
782
+ return __awaiter(this, void 0, void 0, function* () {
783
+ const columns = Object.keys(data).join(', ');
784
+ const placeholders = Object.keys(data)
785
+ .map(() => '?')
786
+ .join(', ');
787
+ const values = Object.values(data);
788
+ const query = `INSERT INTO ${tableName} (${columns})
789
+ VALUES (${placeholders});`;
790
+ yield this.executeQuery((connection) => __awaiter(this, void 0, void 0, function* () {
791
+ yield connection.execute(query, values);
792
+ return;
793
+ }));
794
+ });
795
+ }
796
+ selectRecords(tableName, options = {}, params = []) {
797
+ var _a;
798
+ return __awaiter(this, void 0, void 0, function* () {
799
+ const columns = ((_a = options.columns) === null || _a === void 0 ? void 0 : _a.length) ? options.columns.join(', ') : '*';
800
+ const distinctKeyword = options.distinct ? 'DISTINCT ' : '';
801
+ const whereClause = options.whereClause ? ` ${options.whereClause}` : '';
802
+ const orderByClause = options.orderBy ? ` ORDER BY ${options.orderBy}` : '';
803
+ const limitClause = options.limit ? ` LIMIT ${options.limit}` : '';
804
+ const query = `SELECT ${distinctKeyword}${columns} FROM ${tableName}${whereClause}${orderByClause}${limitClause};`;
805
+ return this.executeQuery((connection) => __awaiter(this, void 0, void 0, function* () {
806
+ const [rows] = yield connection.execute(query, params);
807
+ return rows;
808
+ }));
809
+ });
810
+ }
811
+ updateRecord(tableName, data, whereClause, params = []) {
812
+ return __awaiter(this, void 0, void 0, function* () {
813
+ const setClause = Object.keys(data)
814
+ .map((key) => `${key} = ?`)
815
+ .join(', ');
816
+ const values = Object.values(data);
817
+ const query = `UPDATE ${tableName}
818
+ SET ${setClause} ${whereClause};`;
819
+ yield this.executeQuery((connection) => __awaiter(this, void 0, void 0, function* () {
820
+ yield connection.execute(query, [...values, ...params]);
821
+ return;
822
+ }));
823
+ });
824
+ }
825
+ deleteRecord(tableName, whereClause, params = []) {
826
+ return __awaiter(this, void 0, void 0, function* () {
827
+ const query = `DELETE
828
+ FROM ${tableName} ${whereClause};`;
829
+ yield this.executeQuery((connection) => __awaiter(this, void 0, void 0, function* () {
830
+ yield connection.execute(query, params);
831
+ return;
832
+ }));
833
+ });
834
+ }
698
835
  }
699
836
  exports.MySQLStorage = MySQLStorage;
@@ -99,5 +99,16 @@ export declare class PostgreStorage implements Storage {
99
99
  saveUnsyncedFiles({ integrationId, crowdinId, files }: UnsyncedFiles): Promise<void>;
100
100
  updateUnsyncedFiles({ integrationId, crowdinId, files }: UnsyncedFiles): Promise<void>;
101
101
  getUnsyncedFiles({ integrationId, crowdinId }: GetUnsyncedFiles): Promise<UnsyncedFiles | undefined>;
102
+ registerCustomTable(tableName: string, schema: Record<string, string>): Promise<void>;
103
+ insertRecord(tableName: string, data: Record<string, any>): Promise<void>;
104
+ selectRecords(tableName: string, options?: {
105
+ columns?: string[];
106
+ whereClause?: string;
107
+ orderBy?: string;
108
+ limit?: number;
109
+ distinct?: boolean;
110
+ }, params?: any[]): Promise<any[]>;
111
+ updateRecord(tableName: string, data: Record<string, any>, whereClause: string, params?: any[]): Promise<void>;
112
+ deleteRecord(tableName: string, whereClause: string, params?: any[]): Promise<void>;
102
113
  }
103
114
  export {};
@@ -816,5 +816,69 @@ class PostgreStorage {
816
816
  }));
817
817
  });
818
818
  }
819
+ registerCustomTable(tableName, schema) {
820
+ return __awaiter(this, void 0, void 0, function* () {
821
+ const columns = Object.entries(schema)
822
+ .map(([col, def]) => `"${col}" ${def}`)
823
+ .join(', ');
824
+ const query = `CREATE TABLE IF NOT EXISTS "${tableName}" (${columns});`;
825
+ yield this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () {
826
+ yield client.query(query);
827
+ return;
828
+ }));
829
+ });
830
+ }
831
+ insertRecord(tableName, data) {
832
+ return __awaiter(this, void 0, void 0, function* () {
833
+ const columns = Object.keys(data)
834
+ .map((col) => `"${col}"`)
835
+ .join(', ');
836
+ const placeholders = Object.keys(data)
837
+ .map((_, i) => `$${i + 1}`)
838
+ .join(', ');
839
+ const values = Object.values(data);
840
+ const query = `INSERT INTO "${tableName}" (${columns}) VALUES (${placeholders});`;
841
+ yield this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () {
842
+ yield client.query(query, values);
843
+ return;
844
+ }));
845
+ });
846
+ }
847
+ selectRecords(tableName, options = {}, params = []) {
848
+ var _a;
849
+ return __awaiter(this, void 0, void 0, function* () {
850
+ const columns = ((_a = options.columns) === null || _a === void 0 ? void 0 : _a.length) ? options.columns.join(', ') : '*';
851
+ const distinctKeyword = options.distinct ? 'DISTINCT ' : '';
852
+ const whereClause = options.whereClause ? ` ${options.whereClause}` : '';
853
+ const orderByClause = options.orderBy ? ` ORDER BY ${options.orderBy}` : '';
854
+ const limitClause = options.limit ? ` LIMIT ${options.limit}` : '';
855
+ const query = `SELECT ${distinctKeyword}${columns} FROM ${tableName}${whereClause}${orderByClause}${limitClause};`;
856
+ return this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () {
857
+ const res = yield client.query(query, params);
858
+ return res.rows;
859
+ }));
860
+ });
861
+ }
862
+ updateRecord(tableName, data, whereClause, params = []) {
863
+ return __awaiter(this, void 0, void 0, function* () {
864
+ const keys = Object.keys(data);
865
+ const setClause = keys.map((key, index) => `"${key}" = $${index + 1}`).join(', ');
866
+ const values = Object.values(data);
867
+ const query = `UPDATE "${tableName}" SET ${setClause} ${whereClause};`;
868
+ yield this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () {
869
+ yield client.query(query, [...values, ...params]);
870
+ return;
871
+ }));
872
+ });
873
+ }
874
+ deleteRecord(tableName, whereClause, params = []) {
875
+ return __awaiter(this, void 0, void 0, function* () {
876
+ const query = `DELETE FROM ${tableName} ${whereClause};`;
877
+ yield this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () {
878
+ yield client.query(query, params);
879
+ return;
880
+ }));
881
+ });
882
+ }
819
883
  }
820
884
  exports.PostgreStorage = PostgreStorage;
@@ -87,4 +87,15 @@ export declare class SQLiteStorage implements Storage {
87
87
  saveUnsyncedFiles({ integrationId, crowdinId, files }: UnsyncedFiles): Promise<void>;
88
88
  updateUnsyncedFiles({ integrationId, crowdinId, files }: UnsyncedFiles): Promise<void>;
89
89
  getUnsyncedFiles({ integrationId, crowdinId }: GetUnsyncedFiles): Promise<UnsyncedFiles | undefined>;
90
+ registerCustomTable(tableName: string, schema: Record<string, string>): Promise<void>;
91
+ insertRecord(tableName: string, data: Record<string, any>): Promise<void>;
92
+ selectRecords(tableName: string, options?: {
93
+ columns?: string[];
94
+ whereClause?: string;
95
+ orderBy?: string;
96
+ limit?: number;
97
+ distinct?: boolean;
98
+ }, params?: any[]): Promise<any[]>;
99
+ updateRecord(tableName: string, data: Record<string, any>, whereClause: string, params?: any[]): Promise<void>;
100
+ deleteRecord(tableName: string, whereClause: string, params?: any[]): Promise<void>;
90
101
  }
@@ -694,5 +694,53 @@ class SQLiteStorage {
694
694
  }
695
695
  });
696
696
  }
697
+ registerCustomTable(tableName, schema) {
698
+ return __awaiter(this, void 0, void 0, function* () {
699
+ const columns = Object.entries(schema)
700
+ .map(([col, def]) => `${col} ${def}`)
701
+ .join(', ');
702
+ const query = `CREATE TABLE IF NOT EXISTS ${tableName} (${columns});`;
703
+ yield this.run(query, []);
704
+ });
705
+ }
706
+ insertRecord(tableName, data) {
707
+ return __awaiter(this, void 0, void 0, function* () {
708
+ const columns = Object.keys(data).join(', ');
709
+ const placeholders = Object.keys(data)
710
+ .map(() => '?')
711
+ .join(', ');
712
+ const values = Object.values(data);
713
+ const query = `INSERT INTO ${tableName} (${columns}) VALUES (${placeholders});`;
714
+ return this.run(query, values);
715
+ });
716
+ }
717
+ selectRecords(tableName, options = {}, params = []) {
718
+ var _a;
719
+ return __awaiter(this, void 0, void 0, function* () {
720
+ const columns = ((_a = options.columns) === null || _a === void 0 ? void 0 : _a.length) ? options.columns.join(', ') : '*';
721
+ const distinctKeyword = options.distinct ? 'DISTINCT ' : '';
722
+ const whereClause = options.whereClause ? ` ${options.whereClause}` : '';
723
+ const orderByClause = options.orderBy ? ` ORDER BY ${options.orderBy}` : '';
724
+ const limitClause = options.limit ? ` LIMIT ${options.limit}` : '';
725
+ const query = `SELECT ${distinctKeyword}${columns} FROM ${tableName}${whereClause}${orderByClause}${limitClause};`;
726
+ return this.each(query, params);
727
+ });
728
+ }
729
+ updateRecord(tableName, data, whereClause, params = []) {
730
+ return __awaiter(this, void 0, void 0, function* () {
731
+ const setClause = Object.keys(data)
732
+ .map((key) => `${key} = ?`)
733
+ .join(', ');
734
+ const values = Object.values(data);
735
+ const query = `UPDATE ${tableName} SET ${setClause} ${whereClause};`;
736
+ yield this.run(query, [...values, ...params]);
737
+ });
738
+ }
739
+ deleteRecord(tableName, whereClause, params = []) {
740
+ return __awaiter(this, void 0, void 0, function* () {
741
+ const query = `DELETE FROM ${tableName} ${whereClause};`;
742
+ yield this.run(query, params);
743
+ });
744
+ }
697
745
  }
698
746
  exports.SQLiteStorage = SQLiteStorage;
package/out/types.d.ts CHANGED
@@ -7,6 +7,7 @@ import { CustomSpellcheckerModule } from './modules/custom-spell-check/types';
7
7
  import { EditorPanels } from './modules/editor-right-panel/types';
8
8
  import { CustomFileFormatLogic, FilePostExportLogic, FilePostImportLogic, FilePreExportLogic, FilePreImportLogic } from './modules/file-processing/types';
9
9
  import { IntegrationLogic } from './modules/integration/types';
10
+ import { Storage } from './storage';
10
11
  import { MySQLStorageConfig } from './storage/mysql';
11
12
  import { PostgreStorageConfig } from './storage/postgre';
12
13
  import { ApiModule } from './modules/api/types';
@@ -353,6 +354,7 @@ export interface CrowdinAppUtilities extends CrowdinMetadataStore {
353
354
  client: Crowdin;
354
355
  extra: Record<string, any>;
355
356
  }>;
357
+ storage: Storage;
356
358
  }
357
359
  export interface CrowdinMetadataStore {
358
360
  saveMetadata: (id: string, metadata: any, crowdinId?: string) => Promise<void>;
@@ -71,7 +71,7 @@
71
71
  integration-one-level-fetching="true"
72
72
  {{/if}}
73
73
  {{#if integrationSearchListener}}
74
- allow-filter-change-listener="true"
74
+ allow-integration-filter-change-listener="true"
75
75
  {{/if}}
76
76
  {{#if integrationPagination}}
77
77
  integration-load-more-files="true"
@@ -166,6 +166,7 @@
166
166
  <tr class="organization-invite">
167
167
  <td>
168
168
  <crowdin-p>Registration in Organization</crowdin-p>
169
+ <div class="permission-description"></div>
169
170
  </td>
170
171
  <td class="affected-users"></td>
171
172
  <td class="status"></td>
@@ -1009,7 +1010,7 @@
1009
1010
  if (organizationInvite) {
1010
1011
  const organizationWillGrantElement = `<span class="badge badge-will-be-granted">Will Be Registered</span>`;
1011
1012
 
1012
- processUsersWhoWillBeInvited(organizationInvite, usersData.usersWhoWillBeInvitedToOrganization, organizationWillGrantElement, grantedElement);
1013
+ processUsersWhoWillBeInvitedToOrganization(organizationInvite, usersData, organizationWillGrantElement, grantedElement, notAvailableElement);
1013
1014
  }
1014
1015
 
1015
1016
  processUsersWhoWillBeInvited(projectInvite, usersData.usersWhoWillBeInvitedToProject, willGrantedElement, grantedElement);
@@ -1018,6 +1019,38 @@
1018
1019
  processUsersWhoWillBeInvitedApplicationSettings(usersData, willGrantedElement, grantedElement, notAvailableElement);
1019
1020
  }
1020
1021
 
1022
+ function processUsersWhoWillBeInvitedToOrganization(element, usersData, willGrantedElement, alreadyGrantedMessage, notAvailableElement) {
1023
+ const tooltip = element.querySelector('.status');
1024
+ const description = element.querySelector('.permission-description');
1025
+ const userList = element.querySelector('.affected-users');
1026
+
1027
+ description.classList.remove('text-warning');
1028
+ description.innerText = '';
1029
+
1030
+ const users = usersData.usersWhoWillBeInvitedToOrganization;
1031
+
1032
+ if (users.length) {
1033
+ if (usersData.inviteRestricted) {
1034
+ description.classList.add('text-warning');
1035
+ description.innerText = 'New user invitations are restricted to administrators.';
1036
+
1037
+ tooltip.innerHTML = notAvailableElement;
1038
+ } else {
1039
+ tooltip.innerHTML = willGrantedElement;
1040
+ }
1041
+
1042
+ let affectedUsers = '<ul>';
1043
+ for (const user of users) {
1044
+ affectedUsers += `<li><crowdin-p>${sanitizeHTML(user.name)}</crowdin-p></li>`;
1045
+ }
1046
+ affectedUsers += '</ul>';
1047
+ userList.innerHTML = affectedUsers;
1048
+ } else {
1049
+ tooltip.innerHTML = alreadyGrantedMessage;
1050
+ userList.innerHTML = alreadyGrantedMessage;
1051
+ }
1052
+ }
1053
+
1021
1054
  function processUsersWhoWillBeInvited(element, users, grantMessage, alreadyGrantedMessage) {
1022
1055
  const tooltip = element.querySelector('.status');
1023
1056
  const userList = element.querySelector('.affected-users');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crowdin/app-project-module",
3
- "version": "0.79.2",
3
+ "version": "0.81.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",