@crowdin/app-project-module 0.91.0 → 0.92.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.
@@ -10,270 +10,21 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  const util_1 = require("../../../util");
13
- const types_1 = require("../../../types");
14
13
  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
14
  function handle() {
22
15
  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 inviteRestricted = !isAdmin &&
51
- 'invite_restrict_enabled' in req.crowdinContext.jwtPayload.context &&
52
- !!req.crowdinContext.jwtPayload.context.invite_restrict_enabled;
53
- const usersWhoWillBeInvitedToOrganization = filterOrganizationUsers(usersToInvite);
54
- const usersWhoWillBeInvitedToProject = filterProjectUsers({
55
- inviteRestricted,
56
- usersToInvite,
57
- projectMembers,
58
- organizationMembers,
59
- });
60
- const usersWhoNotIssetInApplicationInstallation = filterNotIssetApplicationUsers({
61
- inviteRestricted,
62
- usersToInvite,
63
- applicationInstallation,
64
- projectMembers,
65
- organizationMembers,
66
- });
67
- const usersWhoNotIssetInIntegration = filterNotIssetIntegrationUsers({
68
- inviteRestricted,
69
- usersToInvite,
70
- integrationCredentials,
71
- projectMembers,
72
- organizationMembers,
73
- });
74
- if (onlyCheck) {
16
+ const clientId = req.crowdinContext.clientId;
17
+ const users = req.body.users;
18
+ if (!users) {
75
19
  return res.send({
76
- data: {
77
- isAdmin,
78
- inviteRestricted,
79
- editApplicationAvailable: applicationInstallation !== null,
80
- usersWhoWillBeInvitedToOrganization,
81
- usersWhoWillBeInvitedToProject,
82
- usersWhoNotIssetInApplicationInstallation,
83
- usersWhoNotIssetInIntegration,
84
- },
20
+ success: false,
21
+ message: 'No users provided',
85
22
  });
86
23
  }
87
- const response = yield inviteUsers({
88
- req,
89
- projectId,
90
- isAdmin,
91
- inviteRestricted,
92
- usersToInvite,
93
- applicationInstallation,
94
- usersWhoWillBeInvitedToOrganization,
95
- usersWhoWillBeInvitedToProject,
96
- usersWhoNotIssetInApplicationInstallation,
24
+ yield (0, storage_1.getStorage)().updateIntegrationManagers(clientId, JSON.stringify(users.map((user) => `${user.id}`)));
25
+ return res.send({
26
+ success: true,
97
27
  });
98
- return res.send(response);
99
28
  }));
100
29
  }
101
30
  exports.default = handle;
102
- function filterOrganizationUsers(usersToInvite) {
103
- return usersToInvite.filter((identifier) => (0, util_1.validateEmail)(identifier)).map((name) => ({ name: `${name}` }));
104
- }
105
- function filterProjectUsers({ inviteRestricted, usersToInvite, projectMembers, organizationMembers, }) {
106
- return usersToInvite
107
- .map((identifier) => {
108
- if ((0, util_1.validateEmail)(identifier) && !inviteRestricted) {
109
- return { name: `${identifier}` };
110
- }
111
- const user = projectMembers.find((member) => member.id === +identifier);
112
- if (!user) {
113
- const organizationUser = organizationMembers.find((member) => member.id === +identifier);
114
- return organizationUser && !organizationUser.isAdmin
115
- ? { id: organizationUser.id, name: (0, users_1.getUserFullName)(organizationUser) }
116
- : null;
117
- }
118
- return ('role' in user ? !CONSTANTS.MANAGER_ROLES.includes(user.role) : !user.isManager)
119
- ? { id: user.id, name: (0, users_1.getUserFullName)(user) }
120
- : null;
121
- })
122
- .filter(Boolean);
123
- }
124
- function filterNotIssetApplicationUsers({ inviteRestricted, usersToInvite, applicationInstallation, projectMembers, organizationMembers, }) {
125
- let userIdentifiers = [];
126
- if (!applicationInstallation) {
127
- return [];
128
- }
129
- const projectIntegrationModules = applicationInstallation.data.modules.filter((module) => module.type === CONSTANTS.PROJECT_INTEGRATIONS_MODULE_TYPE);
130
- if (!projectIntegrationModules.length) {
131
- return [];
132
- }
133
- if (projectIntegrationModules.some((module) => module.permissions.user.value === types_1.UserPermissions.ALL_MEMBERS)) {
134
- return [];
135
- }
136
- else {
137
- userIdentifiers = usersToInvite.filter((userId) => projectIntegrationModules.every((module) => !module.permissions.user.ids.includes(+userId)));
138
- }
139
- return userIdentifiers
140
- .map((identifier) => {
141
- if ((0, util_1.validateEmail)(identifier) && !inviteRestricted) {
142
- return { name: `${identifier}` };
143
- }
144
- let user;
145
- user = projectMembers.find((member) => member.id === +identifier);
146
- if (!user) {
147
- user = organizationMembers.find((member) => member.id === +identifier);
148
- }
149
- return user ? { id: user.id, name: (0, users_1.getUserFullName)(user) } : null;
150
- })
151
- .filter(Boolean);
152
- }
153
- function filterNotIssetIntegrationUsers({ inviteRestricted, usersToInvite, integrationCredentials, projectMembers, organizationMembers, }) {
154
- let integrationManagers = [];
155
- if (integrationCredentials === null || integrationCredentials === void 0 ? void 0 : integrationCredentials.managers) {
156
- integrationManagers = JSON.parse(integrationCredentials.managers);
157
- }
158
- return usersToInvite
159
- .map((identifier) => {
160
- if (integrationManagers.includes(`${identifier}`)) {
161
- return null;
162
- }
163
- if ((0, util_1.validateEmail)(identifier) && !inviteRestricted) {
164
- return { name: `${identifier}` };
165
- }
166
- let user;
167
- user = projectMembers.find((member) => member.id === +identifier);
168
- if (!user) {
169
- user = organizationMembers.find((member) => member.id === +identifier);
170
- }
171
- return user ? { id: user.id, name: (0, users_1.getUserFullName)(user) } : null;
172
- })
173
- .filter(Boolean);
174
- }
175
- function inviteUsers({ req, projectId, isAdmin, inviteRestricted, usersToInvite, applicationInstallation, usersWhoWillBeInvitedToOrganization, usersWhoWillBeInvitedToProject, usersWhoNotIssetInApplicationInstallation, }) {
176
- return __awaiter(this, void 0, void 0, function* () {
177
- const client = req.crowdinApiClient;
178
- if (inviteRestricted) {
179
- usersWhoWillBeInvitedToOrganization = [];
180
- }
181
- const alreadyAddedUserIds = yield inviteUsersToProject({
182
- client,
183
- projectId,
184
- usersWhoWillBeInvitedToOrganization,
185
- usersWhoWillBeInvitedToProject,
186
- });
187
- if (isAdmin) {
188
- yield addUsersToApplicationInstallation({
189
- client,
190
- ownerId: req.integrationCredentials.ownerId,
191
- applicationInstallation,
192
- alreadyAddedUserIds,
193
- usersWhoNotIssetInApplicationInstallation,
194
- });
195
- }
196
- yield addUsersToIntegration({
197
- clientId: req.crowdinContext.clientId,
198
- alreadyAddedUserIds,
199
- usersToInvite,
200
- });
201
- return { success: true };
202
- });
203
- }
204
- function inviteUsersToProject({ client, projectId, usersWhoWillBeInvitedToOrganization, usersWhoWillBeInvitedToProject, }) {
205
- return __awaiter(this, void 0, void 0, function* () {
206
- let addedIds = [];
207
- const emailInvites = usersWhoWillBeInvitedToOrganization.map((user) => user.name);
208
- const userIdInvites = usersWhoWillBeInvitedToProject.map((user) => user.id).filter(Boolean);
209
- try {
210
- if (emailInvites.length) {
211
- const inviteResponse = (yield client.usersApi.addProjectMember(projectId, {
212
- managerAccess: true,
213
- emails: emailInvites,
214
- })); // TODO: fix typings in the @crowdin/crowdin-api-client
215
- addedIds = inviteResponse.data.added.map((member) => member.id);
216
- }
217
- }
218
- catch (error) {
219
- console.error('Failed to invite users', error);
220
- }
221
- try {
222
- if (userIdInvites.length) {
223
- yield client.usersApi.addProjectMember(projectId, {
224
- managerAccess: true,
225
- userIds: userIdInvites,
226
- }); // TODO: fix typings in the @crowdin/crowdin-api-client
227
- }
228
- }
229
- catch (error) {
230
- console.error('Failed to grant project manager access', error);
231
- }
232
- return addedIds;
233
- });
234
- }
235
- function addUsersToApplicationInstallation({ client, ownerId, applicationInstallation, alreadyAddedUserIds, usersWhoNotIssetInApplicationInstallation, }) {
236
- return __awaiter(this, void 0, void 0, function* () {
237
- if (!applicationInstallation || !usersWhoNotIssetInApplicationInstallation.length) {
238
- return;
239
- }
240
- const applicationInvites = [
241
- ownerId,
242
- ...alreadyAddedUserIds,
243
- ...usersWhoNotIssetInApplicationInstallation.map((user) => user.id).filter(Boolean),
244
- ];
245
- const permissions = applicationInstallation.data.modules.filter((module) => module.type === CONSTANTS.PROJECT_INTEGRATIONS_MODULE_TYPE);
246
- if (!permissions.length) {
247
- return;
248
- }
249
- try {
250
- for (const module of permissions) {
251
- if (module.permissions.user.value === types_1.UserPermissions.ALL_MEMBERS) {
252
- continue;
253
- }
254
- const userIds = [...new Set([...module.permissions.user.ids, ...applicationInvites])];
255
- client.applicationsApi.editApplicationInstallation(applicationInstallation.data.identifier, [
256
- {
257
- op: 'replace',
258
- path: `/modules/${module.key}/permissions`,
259
- value: {
260
- user: {
261
- value: types_1.UserPermissions.RESTRICTED,
262
- ids: userIds,
263
- },
264
- },
265
- },
266
- ]);
267
- }
268
- }
269
- catch (error) {
270
- console.error('Failed to update application permissions', error);
271
- }
272
- });
273
- }
274
- function addUsersToIntegration({ clientId, alreadyAddedUserIds, usersToInvite, }) {
275
- return __awaiter(this, void 0, void 0, function* () {
276
- const integrationInvites = [...alreadyAddedUserIds, ...usersToInvite.filter((identifier) => +identifier)];
277
- yield (0, storage_1.getStorage)().updateIntegrationManagers(clientId, JSON.stringify(integrationInvites.map((id) => `${id}`)));
278
- });
279
- }
@@ -1,27 +1,4 @@
1
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
2
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
3
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
4
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -36,7 +13,7 @@ const util_1 = require("../../../util");
36
13
  const logger_1 = require("../../../util/logger");
37
14
  const subscription_1 = require("../../../util/subscription");
38
15
  const defaults_1 = require("../util/defaults");
39
- const crowdinAppFunctions = __importStar(require("@crowdin/crowdin-apps-functions"));
16
+ const users_1 = require("./users");
40
17
  function handle(config, integration) {
41
18
  return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
42
19
  var _a, _b, _c, _d;
@@ -64,21 +41,29 @@ function handle(config, integration) {
64
41
  options.oauthMode = integration.oauthLogin.mode;
65
42
  }
66
43
  }
67
- else if (integration.getConfiguration) {
68
- const { userId } = crowdinAppFunctions.parseCrowdinId(req.crowdinContext.clientId);
44
+ else {
45
+ if (integration.getConfiguration) {
46
+ options.config = JSON.stringify(req.integrationSettings || {});
47
+ options.reloadOnConfigSave = !!integration.reloadOnConfigSave;
48
+ options.integrationPagination = integration.integrationPagination;
49
+ if ((_b = req.query) === null || _b === void 0 ? void 0 : _b.parentUrl) {
50
+ const parentUrl = new URL(req.query.parentUrl);
51
+ parentUrl.searchParams.set('zen-mode', 'true');
52
+ options.zenModeUrl = parentUrl.toString();
53
+ }
54
+ const configurationFields = yield integration.getConfiguration(req.crowdinContext.jwtPayload.context.project_id, req.crowdinApiClient, req.integrationCredentials);
55
+ options.configurationFields = (0, defaults_1.groupFieldsByCategory)(configurationFields);
56
+ logger(`Adding configuration fields ${JSON.stringify(options.configurationFields, null, 2)}`);
57
+ }
69
58
  options.hasOrganization = !!req.crowdinContext.jwtPayload.domain;
59
+ const userId = req.crowdinContext.jwtPayload.context.user_id;
70
60
  options.isOwner = req.integrationCredentials.ownerId === userId;
71
- options.config = JSON.stringify(req.integrationSettings || {});
72
- options.reloadOnConfigSave = !!integration.reloadOnConfigSave;
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 = (0, defaults_1.groupFieldsByCategory)(configurationFields);
81
- logger(`Adding configuration fields ${JSON.stringify(options.configurationFields, null, 2)}`);
61
+ options.userId = userId;
62
+ options.isManager = yield (0, users_1.isManager)({
63
+ client: req.crowdinApiClient,
64
+ projectId: req.crowdinContext.jwtPayload.context.project_id,
65
+ memberId: userId,
66
+ });
82
67
  }
83
68
  options.infoModal = integration.infoModal;
84
69
  options.syncNewElements = integration.syncNewElements;
@@ -3,11 +3,8 @@ import Crowdin, { UsersModel } from '@crowdin/crowdin-api-client';
3
3
  import { Response } from 'express';
4
4
  export type ProjectMember = UsersModel.ProjectMember | UsersModel.EnterpriseProjectMember;
5
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 }: {
6
+ export declare function isManager({ client, projectId, memberId, }: {
7
7
  client: Crowdin;
8
8
  projectId: number;
9
- }): Promise<{
10
- projectMembers: ProjectMember[];
11
- organizationMembers: UsersModel.User[];
12
- }>;
13
- export declare function getUserFullName(user: UsersModel.User | ProjectMember): string;
9
+ memberId: number;
10
+ }): Promise<{}>;
@@ -1,27 +1,4 @@
1
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
2
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
3
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
4
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -32,32 +9,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
32
9
  });
33
10
  };
34
11
  Object.defineProperty(exports, "__esModule", { value: true });
35
- exports.getUserFullName = exports.getUsers = void 0;
12
+ exports.isManager = void 0;
36
13
  const util_1 = require("../../../util");
37
14
  const storage_1 = require("../../../storage");
38
- const crowdinAppFunctions = __importStar(require("@crowdin/crowdin-apps-functions"));
15
+ const MANAGER_ROLES = ['owner', 'manager'];
39
16
  function handle() {
40
17
  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
18
  let integrationManagers = [];
62
19
  const integrationCredentials = yield (0, storage_1.getStorage)().getIntegrationCredentials(req.crowdinContext.clientId);
63
20
  if (integrationCredentials === null || integrationCredentials === void 0 ? void 0 : integrationCredentials.managers) {
@@ -65,38 +22,18 @@ function handle() {
65
22
  }
66
23
  res.json({
67
24
  data: {
68
- users: sortedUsers,
69
25
  managers: integrationManagers,
70
26
  },
71
27
  });
72
28
  }));
73
29
  }
74
30
  exports.default = handle;
75
- function getUsers({ client, projectId }) {
31
+ function isManager({ client, projectId, memberId, }) {
76
32
  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;
33
+ const member = (yield client.usersApi.getProjectMemberPermissions(projectId, memberId)).data;
34
+ const isAdmin = 'isAdmin' in member ? member.isAdmin : 'role' in member && member.role === 'owner';
35
+ const isManager = ('role' in member && MANAGER_ROLES.includes(member.role)) || ('isManager' in member && member.isManager);
36
+ return isAdmin || isManager;
93
37
  });
94
38
  }
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;
39
+ exports.isManager = isManager;
@@ -362,9 +362,7 @@ function handle(config) {
362
362
  if (!(0, subscription_1.isAppFree)(config)) {
363
363
  events['subscription_paid'] = '/subscription-paid';
364
364
  }
365
- const defaultScopes = config.projectIntegration
366
- ? [types_1.Scope.USERS, types_1.Scope.PROJECTS, types_1.Scope.APPLICATIONS]
367
- : [types_1.Scope.PROJECTS];
365
+ const defaultScopes = config.projectIntegration ? [types_1.Scope.USERS, types_1.Scope.PROJECTS] : [types_1.Scope.PROJECTS];
368
366
  return (_req, res) => {
369
367
  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: {
370
368
  type: config.authenticationType || types_1.AuthenticationType.APP,
@@ -36,7 +36,9 @@
36
36
  <div id="buttons">
37
37
  <crowdin-button id="show-integration-btn" class="hidden" icon-before="arrow_back" onclick="showIntegration();">Integration</crowdin-button>
38
38
  <crowdin-button id="show-error-logs-btn" icon-before="list" onclick="showErrorLogs();">Error logs</crowdin-button>
39
- <crowdin-button icon-before="link" onclick="showPermissionsDialog()">Share</crowdin-button>
39
+ {{#if isManager}}
40
+ <crowdin-button icon-before="link" onclick="showPermissionsDialog()">Share</crowdin-button>
41
+ {{/if}}
40
42
  {{#if infoModal}}
41
43
  <crowdin-button icon-before="info" onclick="openModal(infoModal);">{{infoModal.title}}</crowdin-button>
42
44
  {{/if}}
@@ -154,7 +156,7 @@
154
156
  label="Users"
155
157
  help-text="Search for members by name, username, email, or invite new ones using their email."
156
158
  is-position-fixed
157
- onchange="inviteUsers()"
159
+ onchange="checkUsers()"
158
160
  >
159
161
  </crowdin-select>
160
162
  </div>
@@ -221,7 +223,7 @@
221
223
  </div>
222
224
 
223
225
  <div slot="footer">
224
- <crowdin-button id="confirm-users-btn" outlined onclick="inviteUsers(false)">Save</crowdin-button>
226
+ <crowdin-button id="confirm-users-btn" outlined onclick="inviteUsers()">Save</crowdin-button>
225
227
  </div>
226
228
  </crowdin-modal>
227
229
 
@@ -959,6 +961,18 @@
959
961
  permissionsModal.setAttribute('body-overflow-unset', true)
960
962
  }
961
963
 
964
+ function getUserFullName(user) {
965
+ if (user.full_name) {
966
+ return user.full_name;
967
+ }
968
+
969
+ const ownerFullName = user.name || ((user.first_name || '') + ' ' + (user.last_name || '')).trim();
970
+
971
+ return !!ownerFullName && user?.login !== ownerFullName
972
+ ? `${ownerFullName} (${user?.login})`
973
+ : user?.login;
974
+ }
975
+
962
976
  function showPermissionsDialog() {
963
977
  hideConfirmUsersBlock();
964
978
  openModal(permissions)
@@ -967,13 +981,26 @@
967
981
 
968
982
  select.value = '[]';
969
983
 
970
- checkOrigin()
971
- .then(restParams => fetch('api/users' + restParams))
972
- .then(checkResponse)
973
- .then((res) => {
974
- let userOptions = res.data.users.map(user => `<option value="${user.id}">${sanitizeHTML(user.name)}</option>`).join('');
975
- select.innerHTML = userOptions;
976
- select.value = JSON.stringify(res.data.managers);
984
+ Promise.all([
985
+ new Promise((resolve) => {
986
+ checkOrigin()
987
+ .then(restParams => fetch('api/users' + restParams))
988
+ .then(checkResponse)
989
+ .then((res) => {
990
+ resolve(JSON.stringify(res.data.managers));
991
+ })
992
+ }),
993
+ new Promise((resolve) => {
994
+ AP.getUsers(res => {
995
+ const users = Object.values(res).filter(user => user.id !== Number('{{userId}}'));
996
+
997
+ resolve(users.map(user => `<option value="${user.id}">${sanitizeHTML(getUserFullName(user))}</option>`).join(''));
998
+ })
999
+ })
1000
+ ])
1001
+ .then(([managers, users]) => {
1002
+ select.innerHTML = users;
1003
+ select.value = managers;
977
1004
  })
978
1005
  .catch(e => catchRejection(e, 'Can\'t fetch users'))
979
1006
  .finally(() => unsetLoader('#permissions-modal'));
@@ -987,43 +1014,116 @@
987
1014
  permissionsModal.removeAttribute('body-overflow-unset')
988
1015
  }
989
1016
 
990
- async function inviteUsers(onlyCheck = true) {
1017
+ function checkUsers() {
991
1018
  setLoader('#permissions-modal');
992
1019
 
993
1020
  const select = document.getElementById('users');
994
1021
 
995
- if (onlyCheck && select.value === '[]') {
1022
+ if (select.value === '[]') {
996
1023
  hideConfirmUsersBlock();
997
1024
  unsetLoader('#permissions-modal');
998
1025
  return;
999
1026
  }
1000
1027
 
1001
- const params = {
1002
- users: JSON.parse(select.value),
1003
- onlyCheck,
1004
- };
1028
+ const users = JSON.parse(select.value);
1029
+ const user_ids = users.filter(id => parseInt(id));
1030
+ const emails = users.filter(id => !parseInt(id));
1031
+
1032
+ const params = { user_ids, emails };
1033
+
1034
+ Promise.all([
1035
+ new Promise((resolve) => {
1036
+ checkOrigin()
1037
+ .then(restParams => fetch('api/users' + restParams))
1038
+ .then(checkResponse)
1039
+ .then((usersResponse) => {
1040
+ resolve(usersResponse.data.managers);
1041
+ });
1042
+ }),
1043
+ new Promise((resolve) => {
1044
+ AP.validateUsers(params, (res) => {
1045
+ resolve(res.data);
1046
+ });
1047
+ }),
1048
+ new Promise((resolve) => {
1049
+ AP.getUsers(res => {
1050
+ resolve(res);
1051
+ });
1052
+ })
1053
+ ])
1054
+ .then(([appManagers, checkUsersResponse, allUsers]) => {
1055
+ const newUserEmails = checkUsersResponse.newUserEmails.map(email => ({name: email}));
1056
+
1057
+ const newProjectManagers = [
1058
+ ...newUserEmails,
1059
+ ...checkUsersResponse.newProjectManagers.map(user => ({
1060
+ name: getUserFullName(user),
1061
+ })),
1062
+ ];
1063
+
1064
+ const newApplicationAccessUsers = [
1065
+ ...newUserEmails,
1066
+ ...checkUsersResponse.newApplicationAccessUsers.map(user => ({
1067
+ name: getUserFullName(user),
1068
+ })),
1069
+ ];
1070
+
1071
+ const newApplicationManagers = [
1072
+ ...newUserEmails,
1073
+ ...Object.values(allUsers)
1074
+ .filter(user => users.some(id => +id === user.id) && !appManagers.some(id => +id === user.id))
1075
+ .map(user => ({name: getUserFullName(user)})),
1076
+ ];
1077
+
1078
+ prepareUsersConfirmBlock({
1079
+ ...checkUsersResponse,
1080
+ newUserEmails,
1081
+ newProjectManagers,
1082
+ newApplicationAccessUsers,
1083
+ newApplicationManagers,
1084
+ });
1085
+ })
1086
+ .catch(e => catchRejection(e, e?.error || 'Can\'t invite users'))
1087
+ .finally(() => unsetLoader('#permissions-modal'));
1088
+ }
1089
+
1090
+ function inviteUsers() {
1091
+ setLoader('#permissions-modal');
1092
+
1093
+ const select = document.getElementById('users');
1094
+ const users = JSON.parse(select.value);
1095
+ const user_ids = users.filter(id => parseInt(id));
1096
+ const emails = users.filter(id => !parseInt(id));
1097
+
1098
+ const params = { user_ids, emails };
1099
+
1100
+ AP.inviteUsers(params, (res) => {
1101
+ if (!res.success) {
1102
+ catchRejection(res, res?.error?.message || 'Can\'t invite users')
1103
+ unsetLoader('#permissions-modal');
1104
+ return;
1105
+ }
1106
+
1107
+ checkOrigin()
1108
+ .then(restParams => fetch('api/invite-users' + restParams, {
1109
+ method: 'POST',
1110
+ headers: { 'Content-Type': 'application/json' },
1111
+ body: JSON.stringify(res.data)
1112
+ }))
1113
+ .then(checkResponse)
1114
+ .then((res) => {
1115
+ if (!res.success) {
1116
+ catchRejection(res, res?.message || 'Can\'t invite users');
1117
+ return;
1118
+ }
1005
1119
 
1006
- checkOrigin()
1007
- .then(restParams => fetch('api/invite-users' + restParams, {
1008
- method: 'POST',
1009
- headers: { 'Content-Type': 'application/json' },
1010
- body: JSON.stringify(params)
1011
- }))
1012
- .then(checkResponse)
1013
- .then((response) => {
1014
- if (!onlyCheck) {
1015
1120
  showToast('Users successfully updated');
1016
1121
  hideConfirmUsersBlock();
1017
1122
  closeModal(permissions);
1018
- return;
1019
- }
1020
-
1021
- prepareUsersConfirmBlock(response.data);
1022
- })
1023
- .catch(e => {
1024
- catchRejection(e, e?.error || 'Can\'t invite users')
1025
- })
1026
- .finally(() => unsetLoader('#permissions-modal'));
1123
+ })
1124
+ .catch((e) => catchRejection(e, e?.error || 'Can\'t invite users'))
1125
+ .finally(() => unsetLoader('#permissions-modal'));
1126
+ });
1027
1127
  }
1028
1128
 
1029
1129
  function prepareUsersConfirmBlock(usersData) {
@@ -1044,8 +1144,8 @@
1044
1144
  processUsersWhoWillBeInvitedToOrganization(organizationInvite, usersData, organizationWillGrantElement, grantedElement, notAvailableElement);
1045
1145
  }
1046
1146
 
1047
- processUsersWhoWillBeInvited(projectInvite, usersData.usersWhoWillBeInvitedToProject, willGrantedElement, grantedElement);
1048
- processUsersWhoWillBeInvited(applicationCredentialsInvite, usersData.usersWhoNotIssetInIntegration, willGrantedElement, grantedElement);
1147
+ processUsersWhoWillBeInvited(projectInvite, usersData.newProjectManagers, willGrantedElement, grantedElement);
1148
+ processUsersWhoWillBeInvited(applicationCredentialsInvite, usersData.newApplicationManagers, willGrantedElement, grantedElement);
1049
1149
 
1050
1150
  processUsersWhoWillBeInvitedApplicationSettings(usersData, willGrantedElement, grantedElement, notAvailableElement);
1051
1151
  }
@@ -1058,7 +1158,7 @@
1058
1158
  description.classList.remove('text-warning');
1059
1159
  description.innerText = '';
1060
1160
 
1061
- const users = usersData.usersWhoWillBeInvitedToOrganization;
1161
+ const users = usersData.newUserEmails;
1062
1162
 
1063
1163
  if (users.length) {
1064
1164
  if (usersData.inviteRestricted) {
@@ -1114,25 +1214,13 @@
1114
1214
 
1115
1215
  let affectedUsers = '<ul>';
1116
1216
 
1117
- if (!usersData.editApplicationAvailable) {
1118
- descriptionMessage += ' The application doesn\'t have permission to update this setting.';
1119
- descriptionMessage += usersData.isAdmin ? ' Please reinstall the app.' : ' Please ask the organization admin to reinstall the app.';
1120
-
1121
- description.classList.add('text-warning');
1122
- description.innerText = descriptionMessage;
1123
-
1124
- tooltip.innerHTML = notAvailableElement;
1125
- for (const user of usersData.usersWhoNotIssetInApplicationInstallation) {
1126
- affectedUsers += `<li><crowdin-p>${sanitizeHTML(user.name)}</crowdin-p></li>`;
1127
- }
1128
- userList.innerHTML = affectedUsers + '</ul>';
1129
- } else if (!usersData.usersWhoNotIssetInApplicationInstallation.length) {
1217
+ if (!usersData.newApplicationAccessUsers.length) {
1130
1218
  tooltip.innerHTML = grantedElement;
1131
1219
  userList.innerHTML = grantedElement;
1132
1220
  } else {
1133
1221
  if (usersData.isAdmin) {
1134
1222
  tooltip.innerHTML = willGrantedElement;
1135
- for (const user of usersData.usersWhoNotIssetInApplicationInstallation) {
1223
+ for (const user of usersData.newApplicationAccessUsers) {
1136
1224
  affectedUsers += `<li><crowdin-p>${sanitizeHTML(user.name)}</crowdin-p></li>`;
1137
1225
  }
1138
1226
  userList.innerHTML = affectedUsers + '</ul>';
@@ -1143,7 +1231,7 @@
1143
1231
  description.innerText = descriptionMessage;
1144
1232
 
1145
1233
  tooltip.innerHTML = notAvailableElement;
1146
- for (const user of usersData.usersWhoNotIssetInApplicationInstallation) {
1234
+ for (const user of usersData.newApplicationAccessUsers) {
1147
1235
  affectedUsers += `<li><crowdin-p>${sanitizeHTML(user.name)}</crowdin-p></li>`;
1148
1236
  }
1149
1237
  userList.innerHTML = affectedUsers + '</ul>';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crowdin/app-project-module",
3
- "version": "0.91.0",
3
+ "version": "0.92.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",
@@ -34,7 +34,7 @@
34
34
  "lodash.isstring": "^4.0.1",
35
35
  "lodash.snakecase": "^4.1.1",
36
36
  "lodash.uniqby": "^4.7.0",
37
- "minimatch": "^10.0.1",
37
+ "minimatch": "^10.0.3",
38
38
  "mysql2": "^3.12.0",
39
39
  "node-cron": "^3.0.3",
40
40
  "pg": "^8.13.3",