@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.
- package/out/modules/integration/handlers/invite-users.js +8 -257
- package/out/modules/integration/handlers/main.js +22 -37
- package/out/modules/integration/handlers/users.d.ts +3 -6
- package/out/modules/integration/handlers/users.js +8 -71
- package/out/modules/manifest.js +1 -3
- package/out/views/main.handlebars +140 -52
- package/package.json +2 -2
|
@@ -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
|
|
24
|
-
const
|
|
25
|
-
if (!
|
|
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
|
-
|
|
77
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
|
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
|
|
68
|
-
|
|
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.
|
|
72
|
-
options.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
|
6
|
+
export declare function isManager({ client, projectId, memberId, }: {
|
|
7
7
|
client: Crowdin;
|
|
8
8
|
projectId: number;
|
|
9
|
-
|
|
10
|
-
|
|
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.
|
|
12
|
+
exports.isManager = void 0;
|
|
36
13
|
const util_1 = require("../../../util");
|
|
37
14
|
const storage_1 = require("../../../storage");
|
|
38
|
-
const
|
|
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
|
|
31
|
+
function isManager({ client, projectId, memberId, }) {
|
|
76
32
|
return __awaiter(this, void 0, void 0, function* () {
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
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.
|
|
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;
|
package/out/modules/manifest.js
CHANGED
|
@@ -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
|
-
|
|
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="
|
|
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(
|
|
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
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
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
|
-
|
|
1017
|
+
function checkUsers() {
|
|
991
1018
|
setLoader('#permissions-modal');
|
|
992
1019
|
|
|
993
1020
|
const select = document.getElementById('users');
|
|
994
1021
|
|
|
995
|
-
if (
|
|
1022
|
+
if (select.value === '[]') {
|
|
996
1023
|
hideConfirmUsersBlock();
|
|
997
1024
|
unsetLoader('#permissions-modal');
|
|
998
1025
|
return;
|
|
999
1026
|
}
|
|
1000
1027
|
|
|
1001
|
-
const
|
|
1002
|
-
|
|
1003
|
-
|
|
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
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
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.
|
|
1048
|
-
processUsersWhoWillBeInvited(applicationCredentialsInvite, usersData.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
37
|
+
"minimatch": "^10.0.3",
|
|
38
38
|
"mysql2": "^3.12.0",
|
|
39
39
|
"node-cron": "^3.0.3",
|
|
40
40
|
"pg": "^8.13.3",
|