@crowdin/app-project-module 3.0.0 → 3.1.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.
Files changed (37) hide show
  1. package/out/app-test/integration/index.js +3 -3
  2. package/out/app-test/integration/mocks/mock-axios.js +5 -4
  3. package/out/app-test/integration/mocks/mock-crowdin-api-client.js +6 -6
  4. package/out/app-test/integration/update-crowdin.js +5 -5
  5. package/out/app-test/integration/update-integration.js +5 -5
  6. package/out/modules/integration/handlers/user-error-detail.d.ts +2 -0
  7. package/out/modules/integration/handlers/user-error-detail.js +48 -0
  8. package/out/modules/integration/handlers/user-errors.d.ts +6 -0
  9. package/out/modules/integration/handlers/user-errors.js +39 -17
  10. package/out/modules/integration/index.js +8 -0
  11. package/out/static/ui/error.bundle.js +286 -91
  12. package/out/static/ui/error.bundle.js.map +1 -1
  13. package/out/static/ui/form.bundle.js +398 -289
  14. package/out/static/ui/form.bundle.js.map +1 -1
  15. package/out/static/ui/login.bundle.js +351 -121
  16. package/out/static/ui/login.bundle.js.map +1 -1
  17. package/out/static/ui/main.bundle.js +832 -386
  18. package/out/static/ui/main.bundle.js.map +1 -1
  19. package/out/static/ui/subscription.bundle.js +277 -88
  20. package/out/static/ui/subscription.bundle.js.map +1 -1
  21. package/out/storage/drizzle/d1/index.js +2 -1
  22. package/out/storage/drizzle/d1/migrations/user-errors-indexes.d.ts +7 -0
  23. package/out/storage/drizzle/d1/migrations/user-errors-indexes.js +27 -0
  24. package/out/storage/drizzle/index.d.ts +8 -1
  25. package/out/storage/drizzle/index.js +75 -4
  26. package/out/storage/drizzle/postgresql/config.d.ts +3 -2
  27. package/out/storage/drizzle/postgresql/config.js +2 -1
  28. package/out/storage/drizzle/postgresql/migrations/0003_user_errors_pagination_index.sql +5 -0
  29. package/out/storage/drizzle/postgresql/migrations/meta/0003_snapshot.json +646 -0
  30. package/out/storage/drizzle/postgresql/migrations/meta/_journal.json +7 -0
  31. package/out/storage/drizzle/sqlite/config.d.ts +3 -2
  32. package/out/storage/drizzle/sqlite/config.js +2 -1
  33. package/out/storage/drizzle/sqlite/migrations/0002_user_errors_indexes.sql +4 -0
  34. package/out/storage/drizzle/sqlite/migrations/meta/0002_snapshot.json +687 -0
  35. package/out/storage/drizzle/sqlite/migrations/meta/_journal.json +7 -0
  36. package/out/storage/types.d.ts +30 -0
  37. package/package.json +12 -11
@@ -21,15 +21,15 @@ exports.mockProjectId = 1;
21
21
  exports.mockStoragesById = {};
22
22
  exports.mockFiles = [];
23
23
  exports.mockDirectories = [];
24
- jest.mock('axios', () => mock_axios_1.mockAxios);
25
- jest.mock('@crowdin/crowdin-api-client', () => jest.fn().mockImplementation(() => mock_crowdin_api_client_1.mockCrowdinApiClient));
24
+ globals_1.jest.mock('axios', () => mock_axios_1.mockAxios);
25
+ globals_1.jest.mock('@crowdin/crowdin-api-client', () => globals_1.jest.fn().mockImplementation(() => mock_crowdin_api_client_1.mockCrowdinApiClient));
26
26
  const runProjectIntegrationTests = ({ getAppConfig, integrationTestConfig, }) => {
27
27
  (0, globals_1.describe)('Run integration module tests', () => {
28
28
  var _a, _b, _c;
29
29
  if (!((_a = integrationTestConfig.getIntegrationFiles) === null || _a === void 0 ? void 0 : _a.expected.length)) {
30
30
  throw new Error('Please provide a valid expected files');
31
31
  }
32
- beforeAll(() => __awaiter(void 0, void 0, void 0, function* () {
32
+ (0, globals_1.beforeAll)(() => __awaiter(void 0, void 0, void 0, function* () {
33
33
  if (integrationTestConfig === null || integrationTestConfig === void 0 ? void 0 : integrationTestConfig.beforeAll) {
34
34
  yield integrationTestConfig.beforeAll();
35
35
  }
@@ -1,14 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.mockAxios = void 0;
4
+ const globals_1 = require("@jest/globals");
4
5
  const index_1 = require("../index");
5
- const mockOriginalAxios = jest.requireActual('axios');
6
- const mock = Object.assign(Object.assign({}, mockOriginalAxios), { get: jest.fn((url) => {
7
- expect(url).toMatch(/^file-id:(\d+)$/);
6
+ const mockOriginalAxios = globals_1.jest.requireActual('axios');
7
+ const mock = Object.assign(Object.assign({}, mockOriginalAxios), { get: globals_1.jest.fn((url) => {
8
+ (0, globals_1.expect)(url).toMatch(/^file-id:(\d+)$/);
8
9
  const fileId = Number(url.replace('file-id:', ''));
9
10
  const file = index_1.mockFiles.find(({ id }) => id === fileId);
10
11
  const storageId = file === null || file === void 0 ? void 0 : file.storageId;
11
- expect(storageId).not.toBeUndefined();
12
+ (0, globals_1.expect)(storageId).not.toBeUndefined();
12
13
  return {
13
14
  // @ts-ignore
14
15
  data: index_1.mockStoragesById[storageId].data,
@@ -57,7 +57,7 @@ const sleep = () => __awaiter(void 0, void 0, void 0, function* () {
57
57
  }, Math.floor(Math.random() * (max - min) + min));
58
58
  });
59
59
  });
60
- const mockBuildProjectFileTranslation = jest.fn((projectId, fileId) => __awaiter(void 0, void 0, void 0, function* () {
60
+ const mockBuildProjectFileTranslation = globals_1.jest.fn((projectId, fileId) => __awaiter(void 0, void 0, void 0, function* () {
61
61
  (0, globals_1.expect)(projectId).toBe(index_1.mockProjectId);
62
62
  yield sleep();
63
63
  return {
@@ -98,17 +98,17 @@ const mockProjectsGroupsApi = {
98
98
  return getProjectResponse;
99
99
  },
100
100
  };
101
- const mockCreateDirectory = jest.fn((projectId, params) => __awaiter(void 0, void 0, void 0, function* () {
101
+ const mockCreateDirectory = globals_1.jest.fn((projectId, params) => __awaiter(void 0, void 0, void 0, function* () {
102
102
  (0, globals_1.expect)(projectId).toBe(index_1.mockProjectId);
103
103
  yield sleep();
104
104
  return crowdin_storage_1.crowdinFolders.create(params);
105
105
  }));
106
- const mockCreateFile = jest.fn((projectId, params) => __awaiter(void 0, void 0, void 0, function* () {
106
+ const mockCreateFile = globals_1.jest.fn((projectId, params) => __awaiter(void 0, void 0, void 0, function* () {
107
107
  (0, globals_1.expect)(projectId).toBe(index_1.mockProjectId);
108
108
  yield sleep();
109
109
  return crowdin_storage_1.crowdinFiles.create(params);
110
110
  }));
111
- const mockListProjectDirectories = jest.fn((projectId) => __awaiter(void 0, void 0, void 0, function* () {
111
+ const mockListProjectDirectories = globals_1.jest.fn((projectId) => __awaiter(void 0, void 0, void 0, function* () {
112
112
  (0, globals_1.expect)(projectId).toBe(index_1.mockProjectId);
113
113
  return {
114
114
  // data: options ? [] : mockDirectories.map((d: SourceFilesModel.Directory) => ({ data: d })),
@@ -119,7 +119,7 @@ const mockListProjectDirectories = jest.fn((projectId) => __awaiter(void 0, void
119
119
  },
120
120
  };
121
121
  }));
122
- const mockListProjectFiles = jest.fn((projectId) => __awaiter(void 0, void 0, void 0, function* () {
122
+ const mockListProjectFiles = globals_1.jest.fn((projectId) => __awaiter(void 0, void 0, void 0, function* () {
123
123
  (0, globals_1.expect)(projectId).toBe(index_1.mockProjectId);
124
124
  return {
125
125
  data: index_1.mockFiles.map((d) => ({ data: d })),
@@ -129,7 +129,7 @@ const mockListProjectFiles = jest.fn((projectId) => __awaiter(void 0, void 0, vo
129
129
  },
130
130
  };
131
131
  }));
132
- const mockOriginalClient = jest.requireActual('@crowdin/crowdin-api-client');
132
+ const mockOriginalClient = globals_1.jest.requireActual('@crowdin/crowdin-api-client');
133
133
  exports.mockCrowdinApiClient = Object.assign(Object.assign({}, mockOriginalClient), { sourceFilesApi: {
134
134
  withFetchAll: () => {
135
135
  return {
@@ -72,7 +72,7 @@ const updateCrowdinTest = (_a) => __awaiter(void 0, [_a], void 0, function* ({ a
72
72
  }
73
73
  const client = new crowdin_api_client_1.default({ token: 'fakeToken' });
74
74
  const request = getFilesFromTree(expectedTree);
75
- const updateProgressMock = jest.fn(() => __awaiter(void 0, void 0, void 0, function* () {
75
+ const updateProgressMock = globals_1.jest.fn(() => __awaiter(void 0, void 0, void 0, function* () {
76
76
  return { isCanceled: false };
77
77
  }));
78
78
  const rootFolder = (0, crowdin_storage_1.getRootFolder)(appConfig);
@@ -106,10 +106,10 @@ const updateCrowdinTest = (_a) => __awaiter(void 0, [_a], void 0, function* ({ a
106
106
  },
107
107
  update: updateProgressMock,
108
108
  type: types_1.JobClientType.MANUAL,
109
- fetchTranslation: jest.fn(),
110
- translationUploaded: jest.fn(),
111
- markFilesAsUnsynced: jest.fn(),
112
- unmarkFilesAsUnsynced: jest.fn(),
109
+ fetchTranslation: globals_1.jest.fn(),
110
+ translationUploaded: globals_1.jest.fn(),
111
+ markFilesAsUnsynced: globals_1.jest.fn(),
112
+ unmarkFilesAsUnsynced: globals_1.jest.fn(),
113
113
  },
114
114
  });
115
115
  }, 'Fail to run method updateCrowdin()');
@@ -33,7 +33,7 @@ const updateIntegrationTest = (_a) => __awaiter(void 0, [_a], void 0, function*
33
33
  }
34
34
  const { updateIntegration, settings, integrationCredentials } = integrationTestConfig;
35
35
  const client = new crowdin_api_client_1.default({ token: 'fakeToken' });
36
- const updateProgressMock = jest.fn(() => __awaiter(void 0, void 0, void 0, function* () {
36
+ const updateProgressMock = globals_1.jest.fn(() => __awaiter(void 0, void 0, void 0, function* () {
37
37
  return { isCanceled: false };
38
38
  }));
39
39
  const request = (0, exports.getUpdateIntegrationRequest)();
@@ -63,10 +63,10 @@ const updateIntegrationTest = (_a) => __awaiter(void 0, [_a], void 0, function*
63
63
  },
64
64
  update: updateProgressMock,
65
65
  type: types_1.JobClientType.MANUAL,
66
- fetchTranslation: jest.fn(),
67
- translationUploaded: jest.fn(),
68
- markFilesAsUnsynced: jest.fn(),
69
- unmarkFilesAsUnsynced: jest.fn(),
66
+ fetchTranslation: globals_1.jest.fn(),
67
+ translationUploaded: globals_1.jest.fn(),
68
+ markFilesAsUnsynced: globals_1.jest.fn(),
69
+ unmarkFilesAsUnsynced: globals_1.jest.fn(),
70
70
  },
71
71
  });
72
72
  }, 'Fail to run method updateIntegration()');
@@ -0,0 +1,2 @@
1
+ import { Response } from 'express';
2
+ export default function handle(): (req: import("express").Request | import("../../..").CrowdinClientRequest, res: Response, next: Function) => void;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.default = handle;
13
+ const storage_1 = require("../../../storage");
14
+ const util_1 = require("../../../util");
15
+ const logger_1 = require("../../../util/logger");
16
+ const INVALID_DATA = JSON.stringify({
17
+ appData: { Warning: 'Invalid response data. Please contact the Crowdin support manager.' },
18
+ });
19
+ function handle() {
20
+ return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
21
+ const id = Number.parseInt(`${req.params.id}`, 10);
22
+ if (!Number.isInteger(id)) {
23
+ return res.status(400).json({
24
+ error: {
25
+ message: 'Invalid error log id',
26
+ },
27
+ });
28
+ }
29
+ const userError = yield (0, storage_1.getStorage)().getUserError({
30
+ id,
31
+ crowdinId: req.crowdinContext.crowdinId,
32
+ integrationId: req.crowdinContext.clientId,
33
+ });
34
+ if (!userError) {
35
+ req.logInfo(`Error log ${id} not found`);
36
+ return res.status(404).json({
37
+ error: {
38
+ message: `Error log with ID ${id} not found`,
39
+ },
40
+ });
41
+ }
42
+ if (!userError.data || !(0, util_1.isJson)(userError.data)) {
43
+ (0, logger_1.logError)(userError);
44
+ return res.send({ data: INVALID_DATA });
45
+ }
46
+ res.send({ data: userError.data });
47
+ }));
48
+ }
@@ -1,2 +1,8 @@
1
1
  import { Response } from 'express';
2
+ export declare function parseUserErrorsQuery(query?: any): {
3
+ limit: number;
4
+ offset: number;
5
+ orderDirection: "asc" | "desc";
6
+ search: any;
7
+ };
2
8
  export default function handle(): (req: import("express").Request | import("../../..").CrowdinClientRequest, res: Response, next: Function) => void;
@@ -9,27 +9,49 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.parseUserErrorsQuery = parseUserErrorsQuery;
12
13
  exports.default = handle;
13
14
  const storage_1 = require("../../../storage");
14
15
  const util_1 = require("../../../util");
15
- const logger_1 = require("../../../util/logger");
16
+ const DEFAULT_LIMIT = 10;
17
+ const MAX_LIMIT = 100;
18
+ const MAX_SEARCH_LENGTH = 255;
19
+ function parseInteger(value, fallback) {
20
+ const parsed = Number.parseInt(`${value}`, 10);
21
+ return Number.isFinite(parsed) ? parsed : fallback;
22
+ }
23
+ function parseUserErrorsQuery(query = {}) {
24
+ const search = typeof query.search === 'string' ? query.search.trim().slice(0, MAX_SEARCH_LENGTH) : '';
25
+ return {
26
+ limit: Math.min(Math.max(parseInteger(query.limit, DEFAULT_LIMIT), 1), MAX_LIMIT),
27
+ offset: Math.max(parseInteger(query.offset, 0), 0),
28
+ orderDirection: (query.order === 'asc' ? 'asc' : 'desc'),
29
+ search: search || undefined,
30
+ };
31
+ }
16
32
  function handle() {
17
33
  return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
18
- var _a;
19
- const userTimezone = (yield req.crowdinApiClient.usersApi.getAuthenticatedUser()).data.timezone;
20
- req.logInfo(`Loading user errors for crowdinId ${req.crowdinContext.crowdinId} and integrationId ${req.crowdinContext.clientId}`);
21
- const userErrors = (_a = (yield (0, storage_1.getStorage)().getAllUserErrors(req.crowdinContext.crowdinId, req.crowdinContext.clientId))) === null || _a === void 0 ? void 0 : _a.map((userError) => {
22
- if (!(0, util_1.isJson)(userError.data)) {
23
- (0, logger_1.logError)(userError);
24
- userError.data = JSON.stringify({
25
- appData: { Warning: 'Invalid response data. Please contact the Crowdin support manager.' },
26
- });
27
- }
28
- const date = new Date(+userError.createdAt);
29
- const formattedDate = (0, util_1.getFormattedDate)({ date, userTimezone });
30
- return Object.assign(Object.assign({}, userError), { formattedDate });
31
- });
32
- req.logInfo(`Returning ${userErrors === null || userErrors === void 0 ? void 0 : userErrors.length} user errors`);
33
- res.send(userErrors);
34
+ const { limit, offset, orderDirection, search } = parseUserErrorsQuery(req.query);
35
+ const filter = {
36
+ crowdinId: req.crowdinContext.crowdinId,
37
+ integrationId: req.crowdinContext.clientId,
38
+ search,
39
+ };
40
+ req.logInfo(`Loading user errors for crowdinId ${req.crowdinContext.crowdinId} and integrationId ${req.crowdinContext.clientId} (limit ${limit}, offset ${offset})`);
41
+ const [userTimezone, userErrors, total] = yield Promise.all([
42
+ req.crowdinApiClient.usersApi
43
+ .getAuthenticatedUser()
44
+ .then(({ data }) => data.timezone)
45
+ .catch((e) => {
46
+ const errorMessage = e instanceof Error ? e.message : String(e);
47
+ req.logError(`Can't load user timezone, falling back to server timezone: ${errorMessage}`);
48
+ return undefined;
49
+ }),
50
+ (0, storage_1.getStorage)().getUserErrors(Object.assign(Object.assign({}, filter), { limit, offset, orderDirection })),
51
+ (0, storage_1.getStorage)().countUserErrors(filter),
52
+ ]);
53
+ const data = userErrors.map((userError) => (Object.assign(Object.assign({}, userError), { formattedDate: (0, util_1.getFormattedDate)({ date: new Date(+userError.createdAt), userTimezone }) })));
54
+ req.logInfo(`Returning ${data.length} of ${total} user errors`);
55
+ res.send({ data, total });
34
56
  }));
35
57
  }
@@ -43,6 +43,7 @@ const settings_save_1 = __importDefault(require("./handlers/settings-save"));
43
43
  const subscription_info_1 = __importDefault(require("./handlers/subscription-info"));
44
44
  const sync_settings_1 = __importDefault(require("./handlers/sync-settings"));
45
45
  const sync_settings_save_1 = __importDefault(require("./handlers/sync-settings-save"));
46
+ const user_error_detail_1 = __importDefault(require("./handlers/user-error-detail"));
46
47
  const user_errors_1 = __importDefault(require("./handlers/user-errors"));
47
48
  const users_1 = __importDefault(require("./handlers/users"));
48
49
  const invite_users_1 = __importDefault(require("./handlers/invite-users"));
@@ -240,6 +241,13 @@ function register({ config, app }) {
240
241
  moduleKey: integrationLogic.key,
241
242
  isIntegration: true,
242
243
  }), (0, integration_credentials_1.default)({ config, integration: integrationLogic }), (0, user_errors_1.default)());
244
+ app.get('/api/user-errors/:id', json_response_1.handle, (0, crowdin_client_1.handle)({
245
+ config,
246
+ optional: false,
247
+ checkSubscriptionExpiration: true,
248
+ moduleKey: integrationLogic.key,
249
+ isIntegration: true,
250
+ }), (0, integration_credentials_1.default)({ config, integration: integrationLogic }), (0, user_error_detail_1.default)());
243
251
  app.get('/api/users', json_response_1.handle, (0, crowdin_client_1.handle)({
244
252
  config,
245
253
  optional: false,