@crowdin/app-project-module 0.71.2 → 0.73.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.
@@ -53,7 +53,7 @@ function handle(config, integration, optional = false) {
53
53
  if (projectIntegrationCredentials.length) {
54
54
  for (const credentials of projectIntegrationCredentials) {
55
55
  ownerIds.push(crowdinAppFunctions.parseCrowdinId(credentials.id).userId);
56
- if (yield checkUserAccessToIntegration(credentials, `${userId}`)) {
56
+ if (checkUserAccessToIntegration(credentials, `${userId}`)) {
57
57
  integrationCredentials = credentials;
58
58
  clientId = credentials.id;
59
59
  req.crowdinContext.clientId = clientId;
@@ -99,14 +99,11 @@ function handle(config, integration, optional = false) {
99
99
  }
100
100
  exports.default = handle;
101
101
  function checkUserAccessToIntegration(integrationCredentials, userId) {
102
- return __awaiter(this, void 0, void 0, function* () {
103
- const projectIntegrationConfig = yield (0, storage_1.getStorage)().getIntegrationConfig(integrationCredentials.id);
104
- if (projectIntegrationConfig === null || projectIntegrationConfig === void 0 ? void 0 : projectIntegrationConfig.config) {
105
- const appSettings = JSON.parse(projectIntegrationConfig.config);
106
- return ((appSettings === null || appSettings === void 0 ? void 0 : appSettings.managers) || []).includes(userId);
107
- }
108
- return false;
109
- });
102
+ if (integrationCredentials === null || integrationCredentials === void 0 ? void 0 : integrationCredentials.managers) {
103
+ const managers = JSON.parse(integrationCredentials.managers);
104
+ return (managers || []).includes(userId);
105
+ }
106
+ return false;
110
107
  }
111
108
  function getIntegrationManagedBy(ownerIds, req) {
112
109
  return __awaiter(this, void 0, void 0, function* () {
@@ -0,0 +1,3 @@
1
+ /// <reference types="qs" />
2
+ import { Response } from 'express';
3
+ export default function handle(): (req: import("../../../types").CrowdinClientRequest | import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>, next: Function) => void;
@@ -0,0 +1,268 @@
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
+ const util_1 = require("../../../util");
13
+ const types_1 = require("../../../types");
14
+ const storage_1 = require("../../../storage");
15
+ const users_1 = require("./users");
16
+ const CONSTANTS = {
17
+ MAX_EMAIL_LENGTH: 76,
18
+ MANAGER_ROLES: ['owner', 'manager'],
19
+ PROJECT_INTEGRATIONS_MODULE_TYPE: 'project-integrations',
20
+ };
21
+ function handle() {
22
+ return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
23
+ const onlyCheck = req.body.onlyCheck;
24
+ const usersToInvite = req.body.users;
25
+ if (!Array.isArray(usersToInvite)) {
26
+ return res.status(400).send('Invalid request');
27
+ }
28
+ const client = req.crowdinApiClient;
29
+ const projectId = req.crowdinContext.jwtPayload.context.project_id;
30
+ const userId = req.crowdinContext.jwtPayload.context.user_id;
31
+ const { projectMembers, organizationMembers } = yield (0, users_1.getUsers)({ client, projectId });
32
+ const integrationCredentials = yield (0, storage_1.getStorage)().getIntegrationCredentials(req.crowdinContext.clientId);
33
+ let applicationInstallation = null;
34
+ try {
35
+ applicationInstallation = yield client.applicationsApi.getApplicationInstallation(req.crowdinContext.appIdentifier);
36
+ }
37
+ catch (error) {
38
+ if (error.code !== 403) {
39
+ console.error('Failed to get application installation', error);
40
+ }
41
+ }
42
+ const isAdmin = organizationMembers.some((member) => member.id === +userId && member.isAdmin) ||
43
+ projectMembers.some((member) => member.id === +userId && 'role' in member && member.role === 'owner');
44
+ const hasManagerAccess = isAdmin ||
45
+ projectMembers.some((member) => member.id === +userId &&
46
+ ('role' in member ? CONSTANTS.MANAGER_ROLES.includes(member.role) : member.isManager));
47
+ if (!hasManagerAccess) {
48
+ return res.status(403).send({ error: 'Access denied' });
49
+ }
50
+ const usersWhoWillBeInvitedToOrganization = filterOrganizationUsers(usersToInvite);
51
+ const usersWhoWillBeInvitedToProject = filterProjectUsers({
52
+ usersToInvite,
53
+ projectMembers,
54
+ organizationMembers,
55
+ });
56
+ const usersWhoNotIssetInApplicationInstallation = filterNotIssetApplicationUsers({
57
+ usersToInvite,
58
+ applicationInstallation,
59
+ projectMembers,
60
+ organizationMembers,
61
+ });
62
+ const usersWhoNotIssetInIntegration = filterNotIssetIntegrationUsers({
63
+ usersToInvite,
64
+ integrationCredentials,
65
+ projectMembers,
66
+ organizationMembers,
67
+ });
68
+ if (onlyCheck) {
69
+ return res.send({
70
+ data: {
71
+ isAdmin,
72
+ editApplicationAvailable: applicationInstallation !== null,
73
+ usersWhoWillBeInvitedToOrganization,
74
+ usersWhoWillBeInvitedToProject,
75
+ usersWhoNotIssetInApplicationInstallation,
76
+ usersWhoNotIssetInIntegration,
77
+ },
78
+ });
79
+ }
80
+ const response = yield inviteUsers({
81
+ req,
82
+ projectId,
83
+ isAdmin,
84
+ usersToInvite,
85
+ applicationInstallation,
86
+ usersWhoWillBeInvitedToOrganization,
87
+ usersWhoWillBeInvitedToProject,
88
+ usersWhoNotIssetInApplicationInstallation,
89
+ });
90
+ return res.send(response);
91
+ }));
92
+ }
93
+ exports.default = handle;
94
+ function filterOrganizationUsers(usersToInvite) {
95
+ return usersToInvite.filter((identifier) => (0, util_1.validateEmail)(identifier)).map((name) => ({ name: `${name}` }));
96
+ }
97
+ function filterProjectUsers({ usersToInvite, projectMembers, organizationMembers, }) {
98
+ return usersToInvite
99
+ .map((identifier) => {
100
+ if ((0, util_1.validateEmail)(identifier)) {
101
+ return { name: `${identifier}` };
102
+ }
103
+ const user = projectMembers.find((member) => member.id === +identifier);
104
+ if (!user) {
105
+ const organizationUser = organizationMembers.find((member) => member.id === +identifier);
106
+ return organizationUser && !organizationUser.isAdmin
107
+ ? { id: organizationUser.id, name: (0, users_1.getUserFullName)(organizationUser) }
108
+ : null;
109
+ }
110
+ return ('role' in user ? !CONSTANTS.MANAGER_ROLES.includes(user.role) : !user.isManager)
111
+ ? { id: user.id, name: (0, users_1.getUserFullName)(user) }
112
+ : null;
113
+ })
114
+ .filter(Boolean);
115
+ }
116
+ function filterNotIssetApplicationUsers({ usersToInvite, applicationInstallation, projectMembers, organizationMembers, }) {
117
+ let userIdentifiers = [];
118
+ if (!applicationInstallation) {
119
+ return [];
120
+ }
121
+ const projectIntegrationModules = applicationInstallation.data.modules.filter((module) => module.type === CONSTANTS.PROJECT_INTEGRATIONS_MODULE_TYPE);
122
+ if (!projectIntegrationModules.length) {
123
+ return [];
124
+ }
125
+ if (projectIntegrationModules.some((module) => module.permissions.user.value === types_1.UserPermissions.ALL_MEMBERS)) {
126
+ return [];
127
+ }
128
+ else {
129
+ userIdentifiers = usersToInvite.filter((userId) => projectIntegrationModules.every((module) => !module.permissions.user.ids.includes(+userId)));
130
+ }
131
+ return userIdentifiers
132
+ .map((identifier) => {
133
+ if ((0, util_1.validateEmail)(identifier)) {
134
+ return { name: `${identifier}` };
135
+ }
136
+ let user;
137
+ user = projectMembers.find((member) => member.id === +identifier);
138
+ if (!user) {
139
+ user = organizationMembers.find((member) => member.id === +identifier);
140
+ }
141
+ return user ? { id: user.id, name: (0, users_1.getUserFullName)(user) } : null;
142
+ })
143
+ .filter(Boolean);
144
+ }
145
+ function filterNotIssetIntegrationUsers({ usersToInvite, integrationCredentials, projectMembers, organizationMembers, }) {
146
+ let integrationManagers = [];
147
+ if (integrationCredentials === null || integrationCredentials === void 0 ? void 0 : integrationCredentials.managers) {
148
+ integrationManagers = JSON.parse(integrationCredentials.managers);
149
+ }
150
+ return usersToInvite
151
+ .map((identifier) => {
152
+ if (integrationManagers.includes(`${identifier}`)) {
153
+ return null;
154
+ }
155
+ if ((0, util_1.validateEmail)(identifier)) {
156
+ return { name: `${identifier}` };
157
+ }
158
+ let user;
159
+ user = projectMembers.find((member) => member.id === +identifier);
160
+ if (!user) {
161
+ user = organizationMembers.find((member) => member.id === +identifier);
162
+ }
163
+ return user ? { id: user.id, name: (0, users_1.getUserFullName)(user) } : null;
164
+ })
165
+ .filter(Boolean);
166
+ }
167
+ function inviteUsers({ req, projectId, isAdmin, usersToInvite, applicationInstallation, usersWhoWillBeInvitedToOrganization, usersWhoWillBeInvitedToProject, usersWhoNotIssetInApplicationInstallation, }) {
168
+ return __awaiter(this, void 0, void 0, function* () {
169
+ const client = req.crowdinApiClient;
170
+ const alreadyAddedUserIds = yield inviteUsersToProject({
171
+ client,
172
+ projectId,
173
+ usersWhoWillBeInvitedToOrganization,
174
+ usersWhoWillBeInvitedToProject,
175
+ });
176
+ if (isAdmin) {
177
+ yield addUsersToApplicationInstallation({
178
+ client,
179
+ ownerId: req.integrationCredentials.ownerId,
180
+ applicationInstallation,
181
+ alreadyAddedUserIds,
182
+ usersWhoNotIssetInApplicationInstallation,
183
+ });
184
+ }
185
+ yield addUsersToIntegration({
186
+ clientId: req.crowdinContext.clientId,
187
+ alreadyAddedUserIds,
188
+ usersToInvite,
189
+ });
190
+ return { success: true };
191
+ });
192
+ }
193
+ function inviteUsersToProject({ client, projectId, usersWhoWillBeInvitedToOrganization, usersWhoWillBeInvitedToProject, }) {
194
+ return __awaiter(this, void 0, void 0, function* () {
195
+ let addedIds = [];
196
+ const emailInvites = usersWhoWillBeInvitedToOrganization.map((user) => user.name);
197
+ const userIdInvites = usersWhoWillBeInvitedToProject.map((user) => user.id).filter(Boolean);
198
+ try {
199
+ if (emailInvites.length) {
200
+ const inviteResponse = (yield client.usersApi.addProjectMember(projectId, {
201
+ managerAccess: true,
202
+ emails: emailInvites,
203
+ })); // TODO: fix typings in the @crowdin/crowdin-api-client
204
+ addedIds = inviteResponse.data.added.map((member) => member.id);
205
+ }
206
+ }
207
+ catch (error) {
208
+ console.error('Failed to invite users', error);
209
+ }
210
+ try {
211
+ if (userIdInvites.length) {
212
+ yield client.usersApi.addProjectMember(projectId, {
213
+ managerAccess: true,
214
+ userIds: userIdInvites,
215
+ }); // TODO: fix typings in the @crowdin/crowdin-api-client
216
+ }
217
+ }
218
+ catch (error) {
219
+ console.error('Failed to grant project manager access', error);
220
+ }
221
+ return addedIds;
222
+ });
223
+ }
224
+ function addUsersToApplicationInstallation({ client, ownerId, applicationInstallation, alreadyAddedUserIds, usersWhoNotIssetInApplicationInstallation, }) {
225
+ return __awaiter(this, void 0, void 0, function* () {
226
+ if (!applicationInstallation || !usersWhoNotIssetInApplicationInstallation.length) {
227
+ return;
228
+ }
229
+ const applicationInvites = [
230
+ ownerId,
231
+ ...alreadyAddedUserIds,
232
+ ...usersWhoNotIssetInApplicationInstallation.map((user) => user.id).filter(Boolean),
233
+ ];
234
+ const permissions = applicationInstallation.data.modules.filter((module) => module.type === CONSTANTS.PROJECT_INTEGRATIONS_MODULE_TYPE);
235
+ if (!permissions.length) {
236
+ return;
237
+ }
238
+ try {
239
+ for (const module of permissions) {
240
+ if (module.permissions.user.value === types_1.UserPermissions.ALL_MEMBERS) {
241
+ continue;
242
+ }
243
+ const userIds = [...new Set([...module.permissions.user.ids, ...applicationInvites])];
244
+ client.applicationsApi.editApplicationInstallation(applicationInstallation.data.identifier, [
245
+ {
246
+ op: 'replace',
247
+ path: `/modules/${module.key}/permissions`,
248
+ value: {
249
+ user: {
250
+ value: types_1.UserPermissions.RESTRICTED,
251
+ ids: userIds,
252
+ },
253
+ },
254
+ },
255
+ ]);
256
+ }
257
+ }
258
+ catch (error) {
259
+ console.error('Failed to update application permissions', error);
260
+ }
261
+ });
262
+ }
263
+ function addUsersToIntegration({ clientId, alreadyAddedUserIds, usersToInvite, }) {
264
+ return __awaiter(this, void 0, void 0, function* () {
265
+ const integrationInvites = [...alreadyAddedUserIds, ...usersToInvite.filter((identifier) => +identifier)];
266
+ yield (0, storage_1.getStorage)().updateIntegrationManagers(clientId, JSON.stringify(integrationInvites.map((id) => `${id}`)));
267
+ });
268
+ }
@@ -39,7 +39,7 @@ const defaults_1 = require("../util/defaults");
39
39
  const crowdinAppFunctions = __importStar(require("@crowdin/crowdin-apps-functions"));
40
40
  function handle(config, integration) {
41
41
  return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
42
- var _a, _b;
42
+ var _a, _b, _c;
43
43
  const logger = req.logInfo || logger_1.log;
44
44
  const installed = !!req.crowdinApiClient;
45
45
  const loggedIn = !!req.integrationCredentials;
@@ -66,12 +66,18 @@ function handle(config, integration) {
66
66
  }
67
67
  else if (integration.getConfiguration) {
68
68
  const { userId } = crowdinAppFunctions.parseCrowdinId(req.crowdinContext.clientId);
69
- options.isOwner = +req.crowdinContext.jwtPayload.sub === userId;
70
- const configurationFields = yield integration.getConfiguration(req.crowdinContext.jwtPayload.context.project_id, req.crowdinApiClient, req.integrationCredentials);
71
- options.configurationFields = configurationFields;
69
+ options.hasOrganization = !!req.crowdinContext.jwtPayload.domain;
70
+ options.isOwner = req.integrationCredentials.ownerId === userId;
72
71
  options.config = JSON.stringify(req.integrationSettings || {});
73
72
  options.reloadOnConfigSave = !!integration.reloadOnConfigSave;
74
73
  options.integrationPagination = integration.integrationPagination;
74
+ if ((_b = req.query) === null || _b === void 0 ? void 0 : _b.parentUrl) {
75
+ const parentUrl = new URL(req.query.parentUrl);
76
+ parentUrl.searchParams.set('zen-mode', 'true');
77
+ options.zenModeUrl = parentUrl.toString();
78
+ }
79
+ const configurationFields = yield integration.getConfiguration(req.crowdinContext.jwtPayload.context.project_id, req.crowdinApiClient, req.integrationCredentials);
80
+ options.configurationFields = configurationFields;
75
81
  logger(`Adding configuration fields ${JSON.stringify(configurationFields, null, 2)}`);
76
82
  }
77
83
  options.infoModal = integration.infoModal;
@@ -96,7 +102,7 @@ function handle(config, integration) {
96
102
  : null;
97
103
  options.notice = integration.notice;
98
104
  options.asyncProgress = {
99
- checkInterval: ((_b = integration.asyncProgress) === null || _b === void 0 ? void 0 : _b.checkInterval) || 1000,
105
+ checkInterval: ((_c = integration.asyncProgress) === null || _c === void 0 ? void 0 : _c.checkInterval) || 1000,
100
106
  };
101
107
  logger(`Routing user to ${view} view`);
102
108
  return res.render(view, options);
@@ -0,0 +1,13 @@
1
+ /// <reference types="qs" />
2
+ import Crowdin, { UsersModel } from '@crowdin/crowdin-api-client';
3
+ import { Response } from 'express';
4
+ export type ProjectMember = UsersModel.ProjectMember | UsersModel.EnterpriseProjectMember;
5
+ export default function handle(): (req: import("../../../types").CrowdinClientRequest | import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>, next: Function) => void;
6
+ export declare function getUsers({ client, projectId }: {
7
+ client: Crowdin;
8
+ projectId: number;
9
+ }): Promise<{
10
+ projectMembers: ProjectMember[];
11
+ organizationMembers: UsersModel.User[];
12
+ }>;
13
+ export declare function getUserFullName(user: UsersModel.User | ProjectMember): string;
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
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
+ };
34
+ Object.defineProperty(exports, "__esModule", { value: true });
35
+ exports.getUserFullName = exports.getUsers = void 0;
36
+ const util_1 = require("../../../util");
37
+ const storage_1 = require("../../../storage");
38
+ const crowdinAppFunctions = __importStar(require("@crowdin/crowdin-apps-functions"));
39
+ function handle() {
40
+ return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
41
+ const client = req.crowdinApiClient;
42
+ const ownerId = req.integrationCredentials.ownerId;
43
+ const { projectId } = crowdinAppFunctions.parseCrowdinId(req.crowdinContext.clientId);
44
+ const { projectMembers, organizationMembers } = yield getUsers({ client, projectId });
45
+ const uniqueUsers = new Map();
46
+ [...projectMembers, ...organizationMembers].forEach((manager) => {
47
+ if (manager.id !== ownerId && !uniqueUsers.has(manager.id)) {
48
+ uniqueUsers.set(manager.id, manager);
49
+ }
50
+ });
51
+ const sortedUsers = Array.from(uniqueUsers.values())
52
+ .sort((a, b) => {
53
+ const aValue = a.firstName || a.username || '';
54
+ const bValue = b.firstName || b.username || '';
55
+ return aValue.localeCompare(bValue);
56
+ })
57
+ .map((user) => ({
58
+ id: user.id.toString(),
59
+ name: getUserFullName(user),
60
+ }));
61
+ let integrationManagers = [];
62
+ const integrationCredentials = yield (0, storage_1.getStorage)().getIntegrationCredentials(req.crowdinContext.clientId);
63
+ if (integrationCredentials === null || integrationCredentials === void 0 ? void 0 : integrationCredentials.managers) {
64
+ integrationManagers = JSON.parse(integrationCredentials.managers);
65
+ }
66
+ res.json({
67
+ data: {
68
+ users: sortedUsers,
69
+ managers: integrationManagers,
70
+ },
71
+ });
72
+ }));
73
+ }
74
+ exports.default = handle;
75
+ function getUsers({ client, projectId }) {
76
+ return __awaiter(this, void 0, void 0, function* () {
77
+ const users = {
78
+ projectMembers: [],
79
+ organizationMembers: [],
80
+ };
81
+ const projectMembers = (yield client.usersApi.withFetchAll().listProjectMembers(projectId)).data;
82
+ users.projectMembers = projectMembers.map((members) => members.data);
83
+ if (client.organization) {
84
+ try {
85
+ const admins = (yield client.usersApi.withFetchAll().listUsers()).data;
86
+ users.organizationMembers = admins.map((admin) => admin.data);
87
+ }
88
+ catch (error) {
89
+ console.error('Failed to get organization users', error);
90
+ }
91
+ }
92
+ return users;
93
+ });
94
+ }
95
+ exports.getUsers = getUsers;
96
+ function getUserFullName(user) {
97
+ const ownerFullName = 'fullName' in user ? user.fullName : ((user.firstName || '') + ' ' + (user.lastName || '')).trim();
98
+ return !!ownerFullName && (user === null || user === void 0 ? void 0 : user.username) !== ownerFullName
99
+ ? `${ownerFullName} (${user === null || user === void 0 ? void 0 : user.username})`
100
+ : user === null || user === void 0 ? void 0 : user.username;
101
+ }
102
+ exports.getUserFullName = getUserFullName;
@@ -64,6 +64,8 @@ const subscription_info_1 = __importDefault(require("./handlers/subscription-inf
64
64
  const sync_settings_1 = __importDefault(require("./handlers/sync-settings"));
65
65
  const sync_settings_save_1 = __importDefault(require("./handlers/sync-settings-save"));
66
66
  const user_errors_1 = __importDefault(require("./handlers/user-errors"));
67
+ const users_1 = __importDefault(require("./handlers/users"));
68
+ const invite_users_1 = __importDefault(require("./handlers/invite-users"));
67
69
  const cron_1 = require("./util/cron");
68
70
  const storage_1 = require("../../storage");
69
71
  function register({ config, app }) {
@@ -222,6 +224,18 @@ function register({ config, app }) {
222
224
  checkSubscriptionExpiration: true,
223
225
  moduleKey: integrationLogic.key,
224
226
  }), (0, integration_credentials_1.default)(config, integrationLogic), (0, user_errors_1.default)());
227
+ app.get('/api/users', json_response_1.default, (0, crowdin_client_1.default)({
228
+ config,
229
+ optional: false,
230
+ checkSubscriptionExpiration: true,
231
+ moduleKey: integrationLogic.key,
232
+ }), (0, integration_credentials_1.default)(config, integrationLogic), (0, users_1.default)());
233
+ app.post('/api/invite-users', json_response_1.default, (0, crowdin_client_1.default)({
234
+ config,
235
+ optional: false,
236
+ checkSubscriptionExpiration: true,
237
+ moduleKey: integrationLogic.key,
238
+ }), (0, integration_credentials_1.default)(config, integrationLogic), (0, invite_users_1.default)());
225
239
  cron.schedule('0 0 1 * *', () => (0, cron_1.removeFinishedJobs)());
226
240
  }
227
241
  exports.register = register;
@@ -355,6 +355,7 @@ export interface IntegrationCredentials {
355
355
  id: string;
356
356
  credentials: any;
357
357
  crowdinId: string;
358
+ managers?: any;
358
359
  }
359
360
  export interface IntegrationConfig {
360
361
  id: number;
@@ -147,27 +147,6 @@ function applyIntegrationModuleDefaults(config, integration) {
147
147
  fields = yield getUserSettings(projectId, crowdinClient, integrationCredentials);
148
148
  }
149
149
  const defaultSettings = [];
150
- const mangers = yield getManagers(crowdinClient, projectId, integrationCredentials.ownerId);
151
- if (mangers.length) {
152
- defaultSettings.push({
153
- key: 'managers',
154
- label: 'Managers',
155
- type: 'select',
156
- isMulti: true,
157
- isSearchable: true,
158
- options: mangers.map((manager) => {
159
- const ownerFullName = 'fullName' in manager
160
- ? manager.fullName
161
- : ((manager.firstName || '') + ' ' + (manager.lastName || '')).trim();
162
- return {
163
- value: manager.id.toString(),
164
- label: !!ownerFullName && (manager === null || manager === void 0 ? void 0 : manager.username) !== ownerFullName
165
- ? `${ownerFullName} (${manager === null || manager === void 0 ? void 0 : manager.username})`
166
- : manager === null || manager === void 0 ? void 0 : manager.username,
167
- };
168
- }),
169
- });
170
- }
171
150
  if (project.data.inContext) {
172
151
  defaultSettings.push({
173
152
  key: 'inContext',
@@ -320,30 +299,3 @@ function getOAuthLoginFormId(clientId) {
320
299
  return `oauth_form_${clientId}`;
321
300
  }
322
301
  exports.getOAuthLoginFormId = getOAuthLoginFormId;
323
- function getManagers(client, projectId, ownerId) {
324
- return __awaiter(this, void 0, void 0, function* () {
325
- const managers = [];
326
- if (client.organization) {
327
- try {
328
- const admins = (yield client.usersApi.withFetchAll().listUsers()).data.filter((user) => user.data.isAdmin);
329
- managers.push(...admins.map((admin) => admin.data));
330
- }
331
- catch (e) {
332
- console.error('Failed to get organization users', e);
333
- }
334
- }
335
- const projectMembers = (yield client.usersApi.withFetchAll().listProjectMembers(projectId)).data.filter((user) => 'role' in user.data ? ['owner', 'manager'].includes(user.data.role) : user.data.isManager);
336
- managers.push(...projectMembers.map((members) => members.data));
337
- const uniqueManagers = new Map();
338
- managers.forEach((manager) => {
339
- if (manager.id !== ownerId && !uniqueManagers.has(manager.id)) {
340
- uniqueManagers.set(manager.id, manager);
341
- }
342
- });
343
- return Array.from(uniqueManagers.values()).sort((a, b) => {
344
- const aValue = a.firstName || a.username || '';
345
- const bValue = b.firstName || b.username || '';
346
- return aValue.localeCompare(bValue);
347
- });
348
- });
349
- }
@@ -275,7 +275,7 @@ function handle(config) {
275
275
  workflowStep.key = config.identifier + '-' + (0, util_3.getWorkflowStepKey)(workflowStep);
276
276
  }
277
277
  const uiModule = ((_a = workflowStep === null || workflowStep === void 0 ? void 0 : workflowStep.settingsUiModule) === null || _a === void 0 ? void 0 : _a.formSchema) || ((_b = workflowStep === null || workflowStep === void 0 ? void 0 : workflowStep.settingsUiModule) === null || _b === void 0 ? void 0 : _b.fileName);
278
- modules['workflow-step-type'].push(Object.assign({ key: workflowStep.key, name: workflowStep.name || config.name, description: workflowStep.description || config.description, boundaries: workflowStep.boundaries, updateSettingsUrl: (0, util_3.getWorkflowStepUrl)('/settings', workflowStep), deleteSettingsUrl: (0, util_3.getWorkflowStepUrl)('/delete', workflowStep) }, (uiModule ? { url: (0, util_3.getWorkflowStepUrl)('/workflow-step', workflowStep) } : {})));
278
+ modules['workflow-step-type'].push(Object.assign(Object.assign(Object.assign({ key: workflowStep.key, name: workflowStep.name || config.name, description: workflowStep.description || config.description, boundaries: workflowStep.boundaries }, (workflowStep.editorMode ? { editorMode: workflowStep.editorMode } : {})), { updateSettingsUrl: (0, util_3.getWorkflowStepUrl)('/settings', workflowStep), deleteSettingsUrl: (0, util_3.getWorkflowStepUrl)('/delete', workflowStep) }), (uiModule ? { url: (0, util_3.getWorkflowStepUrl)('/workflow-step', workflowStep) } : {})));
279
279
  }
280
280
  }
281
281
  const events = {
@@ -285,11 +285,14 @@ function handle(config) {
285
285
  if (!(0, subscription_1.isAppFree)(config)) {
286
286
  events['subscription_paid'] = '/subscription-paid';
287
287
  }
288
+ const defaultScopes = config.projectIntegration
289
+ ? [types_1.Scope.USERS, types_1.Scope.PROJECTS, types_1.Scope.APPLICATIONS]
290
+ : [types_1.Scope.PROJECTS];
288
291
  return (_req, res) => {
289
292
  const manifest = Object.assign(Object.assign(Object.assign(Object.assign({ identifier: config.identifier, name: config.name, logo: (0, util_1.getLogoUrl)(), baseUrl: config.baseUrl, authentication: {
290
293
  type: config.authenticationType || types_1.AuthenticationType.APP,
291
294
  clientId: config.clientId,
292
- } }, (config.agent && { agent: config.agent })), { events, scopes: config.scopes ? config.scopes : [types_1.Scope.PROJECTS] }), (config.defaultPermissions && { default_permissions: config.defaultPermissions })), { modules });
295
+ } }, (config.agent && { agent: config.agent })), { events, scopes: config.scopes ? config.scopes : defaultScopes }), (config.defaultPermissions && { default_permissions: config.defaultPermissions })), { modules });
293
296
  res.send(manifest);
294
297
  };
295
298
  }
@@ -68,9 +68,7 @@ function webhookHandler(config, webhooks) {
68
68
  const json = JSON.parse(req.body.toString());
69
69
  for (const webhook of webhooks) {
70
70
  if (webhook.key === moduleKey) {
71
- for (const event of json.events) {
72
- yield webhook.callback({ credentials, event, client });
73
- }
71
+ yield webhook.callback({ credentials, events: json.events, client });
74
72
  }
75
73
  }
76
74
  }));
@@ -10,7 +10,7 @@ export interface Webhook extends ModuleKey {
10
10
  */
11
11
  callback: (data: {
12
12
  credentials: CrowdinCredentials;
13
- event: Event;
13
+ events: Event[];
14
14
  client: Crowdin;
15
15
  }) => Promise<void>;
16
16
  }
@@ -26,6 +26,10 @@ export interface WorkflowStepTypeModule extends ModuleKey {
26
26
  * settings UI module
27
27
  */
28
28
  settingsUiModule?: UiModule;
29
+ /**
30
+ * Editor mode that will be used in the editor for this module
31
+ */
32
+ editorMode?: EditorMode;
29
33
  /**
30
34
  * input and output boundaries for the module
31
35
  */
@@ -54,4 +58,9 @@ export interface WorkflowStepTypeModule extends ModuleKey {
54
58
  client: Crowdin;
55
59
  }) => Promise<void>;
56
60
  }
61
+ export declare enum EditorMode {
62
+ comfortable = "comfortable",
63
+ sideBySide = "side-by-side",
64
+ multilingual = "multilingual"
65
+ }
57
66
  export {};
@@ -1,2 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EditorMode = void 0;
4
+ var EditorMode;
5
+ (function (EditorMode) {
6
+ EditorMode["comfortable"] = "comfortable";
7
+ EditorMode["sideBySide"] = "side-by-side";
8
+ EditorMode["multilingual"] = "multilingual";
9
+ })(EditorMode = exports.EditorMode || (exports.EditorMode = {}));