@crowdin/app-project-module 0.80.0 → 0.81.1
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/README.md +5 -7
- package/out/modules/api/api.js +2 -2
- package/out/modules/integration/handlers/invite-users.js +18 -7
- package/out/modules/integration/index.js +4 -2
- package/out/modules/integration/types.d.ts +2 -2
- package/out/util/logger.js +3 -1
- package/out/views/main.handlebars +35 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
# Crowdin
|
|
1
|
+
# Crowdin Apps SDK
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The Crowdin Apps SDK is a framework that allows you to build custom apps for Crowdin. It provides a set of tools and components that you can use to build your app and integrate it with Crowdin.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## Contributing
|
|
5
|
+
It is designed to help you create apps that can be easily installed and used by Crowdin users. It handles the communication between your app and Crowdin, so you can focus on building the functionality of your app.
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
:bookmark: See the [docs](https://crowdin.github.io/app-project-module/) for more information.
|
|
10
8
|
|
|
11
9
|
## Seeking Assistance
|
|
12
10
|
|
|
13
|
-
If you
|
|
11
|
+
If you are experiencing problems or would like to suggest a feature, [Contact Customer Success Service](https://crowdin.com/contacts).
|
package/out/modules/api/api.js
CHANGED
|
@@ -499,9 +499,9 @@ function addDefaultApiEndpoints(app, config) {
|
|
|
499
499
|
checkSubscriptionExpiration: true,
|
|
500
500
|
moduleKey: config.projectIntegration.key,
|
|
501
501
|
}), (req, res) => {
|
|
502
|
-
var _a;
|
|
502
|
+
var _a, _b;
|
|
503
503
|
let fields = [];
|
|
504
|
-
if ((_a = config.projectIntegration) === null || _a === void 0 ? void 0 : _a.loginForm.fields) {
|
|
504
|
+
if ((_b = (_a = config.projectIntegration) === null || _a === void 0 ? void 0 : _a.loginForm) === null || _b === void 0 ? void 0 : _b.fields) {
|
|
505
505
|
fields = getFormFields(config === null || config === void 0 ? void 0 : config.projectIntegration.loginForm.fields);
|
|
506
506
|
}
|
|
507
507
|
res.send({ fields });
|
|
@@ -47,19 +47,25 @@ function handle() {
|
|
|
47
47
|
if (!hasManagerAccess) {
|
|
48
48
|
return res.status(403).send({ error: 'Access denied' });
|
|
49
49
|
}
|
|
50
|
+
const inviteRestricted = !isAdmin &&
|
|
51
|
+
'invite_restrict_enabled' in req.crowdinContext.jwtPayload.context &&
|
|
52
|
+
!!req.crowdinContext.jwtPayload.context.invite_restrict_enabled;
|
|
50
53
|
const usersWhoWillBeInvitedToOrganization = filterOrganizationUsers(usersToInvite);
|
|
51
54
|
const usersWhoWillBeInvitedToProject = filterProjectUsers({
|
|
55
|
+
inviteRestricted,
|
|
52
56
|
usersToInvite,
|
|
53
57
|
projectMembers,
|
|
54
58
|
organizationMembers,
|
|
55
59
|
});
|
|
56
60
|
const usersWhoNotIssetInApplicationInstallation = filterNotIssetApplicationUsers({
|
|
61
|
+
inviteRestricted,
|
|
57
62
|
usersToInvite,
|
|
58
63
|
applicationInstallation,
|
|
59
64
|
projectMembers,
|
|
60
65
|
organizationMembers,
|
|
61
66
|
});
|
|
62
67
|
const usersWhoNotIssetInIntegration = filterNotIssetIntegrationUsers({
|
|
68
|
+
inviteRestricted,
|
|
63
69
|
usersToInvite,
|
|
64
70
|
integrationCredentials,
|
|
65
71
|
projectMembers,
|
|
@@ -69,6 +75,7 @@ function handle() {
|
|
|
69
75
|
return res.send({
|
|
70
76
|
data: {
|
|
71
77
|
isAdmin,
|
|
78
|
+
inviteRestricted,
|
|
72
79
|
editApplicationAvailable: applicationInstallation !== null,
|
|
73
80
|
usersWhoWillBeInvitedToOrganization,
|
|
74
81
|
usersWhoWillBeInvitedToProject,
|
|
@@ -81,6 +88,7 @@ function handle() {
|
|
|
81
88
|
req,
|
|
82
89
|
projectId,
|
|
83
90
|
isAdmin,
|
|
91
|
+
inviteRestricted,
|
|
84
92
|
usersToInvite,
|
|
85
93
|
applicationInstallation,
|
|
86
94
|
usersWhoWillBeInvitedToOrganization,
|
|
@@ -94,10 +102,10 @@ exports.default = handle;
|
|
|
94
102
|
function filterOrganizationUsers(usersToInvite) {
|
|
95
103
|
return usersToInvite.filter((identifier) => (0, util_1.validateEmail)(identifier)).map((name) => ({ name: `${name}` }));
|
|
96
104
|
}
|
|
97
|
-
function filterProjectUsers({ usersToInvite, projectMembers, organizationMembers, }) {
|
|
105
|
+
function filterProjectUsers({ inviteRestricted, usersToInvite, projectMembers, organizationMembers, }) {
|
|
98
106
|
return usersToInvite
|
|
99
107
|
.map((identifier) => {
|
|
100
|
-
if ((0, util_1.validateEmail)(identifier)) {
|
|
108
|
+
if ((0, util_1.validateEmail)(identifier) && !inviteRestricted) {
|
|
101
109
|
return { name: `${identifier}` };
|
|
102
110
|
}
|
|
103
111
|
const user = projectMembers.find((member) => member.id === +identifier);
|
|
@@ -113,7 +121,7 @@ function filterProjectUsers({ usersToInvite, projectMembers, organizationMembers
|
|
|
113
121
|
})
|
|
114
122
|
.filter(Boolean);
|
|
115
123
|
}
|
|
116
|
-
function filterNotIssetApplicationUsers({ usersToInvite, applicationInstallation, projectMembers, organizationMembers, }) {
|
|
124
|
+
function filterNotIssetApplicationUsers({ inviteRestricted, usersToInvite, applicationInstallation, projectMembers, organizationMembers, }) {
|
|
117
125
|
let userIdentifiers = [];
|
|
118
126
|
if (!applicationInstallation) {
|
|
119
127
|
return [];
|
|
@@ -130,7 +138,7 @@ function filterNotIssetApplicationUsers({ usersToInvite, applicationInstallation
|
|
|
130
138
|
}
|
|
131
139
|
return userIdentifiers
|
|
132
140
|
.map((identifier) => {
|
|
133
|
-
if ((0, util_1.validateEmail)(identifier)) {
|
|
141
|
+
if ((0, util_1.validateEmail)(identifier) && !inviteRestricted) {
|
|
134
142
|
return { name: `${identifier}` };
|
|
135
143
|
}
|
|
136
144
|
let user;
|
|
@@ -142,7 +150,7 @@ function filterNotIssetApplicationUsers({ usersToInvite, applicationInstallation
|
|
|
142
150
|
})
|
|
143
151
|
.filter(Boolean);
|
|
144
152
|
}
|
|
145
|
-
function filterNotIssetIntegrationUsers({ usersToInvite, integrationCredentials, projectMembers, organizationMembers, }) {
|
|
153
|
+
function filterNotIssetIntegrationUsers({ inviteRestricted, usersToInvite, integrationCredentials, projectMembers, organizationMembers, }) {
|
|
146
154
|
let integrationManagers = [];
|
|
147
155
|
if (integrationCredentials === null || integrationCredentials === void 0 ? void 0 : integrationCredentials.managers) {
|
|
148
156
|
integrationManagers = JSON.parse(integrationCredentials.managers);
|
|
@@ -152,7 +160,7 @@ function filterNotIssetIntegrationUsers({ usersToInvite, integrationCredentials,
|
|
|
152
160
|
if (integrationManagers.includes(`${identifier}`)) {
|
|
153
161
|
return null;
|
|
154
162
|
}
|
|
155
|
-
if ((0, util_1.validateEmail)(identifier)) {
|
|
163
|
+
if ((0, util_1.validateEmail)(identifier) && !inviteRestricted) {
|
|
156
164
|
return { name: `${identifier}` };
|
|
157
165
|
}
|
|
158
166
|
let user;
|
|
@@ -164,9 +172,12 @@ function filterNotIssetIntegrationUsers({ usersToInvite, integrationCredentials,
|
|
|
164
172
|
})
|
|
165
173
|
.filter(Boolean);
|
|
166
174
|
}
|
|
167
|
-
function inviteUsers({ req, projectId, isAdmin, usersToInvite, applicationInstallation, usersWhoWillBeInvitedToOrganization, usersWhoWillBeInvitedToProject, usersWhoNotIssetInApplicationInstallation, }) {
|
|
175
|
+
function inviteUsers({ req, projectId, isAdmin, inviteRestricted, usersToInvite, applicationInstallation, usersWhoWillBeInvitedToOrganization, usersWhoWillBeInvitedToProject, usersWhoNotIssetInApplicationInstallation, }) {
|
|
168
176
|
return __awaiter(this, void 0, void 0, function* () {
|
|
169
177
|
const client = req.crowdinApiClient;
|
|
178
|
+
if (inviteRestricted) {
|
|
179
|
+
usersWhoWillBeInvitedToOrganization = [];
|
|
180
|
+
}
|
|
170
181
|
const alreadyAddedUserIds = yield inviteUsersToProject({
|
|
171
182
|
client,
|
|
172
183
|
projectId,
|
|
@@ -197,8 +197,10 @@ function register({ config, app }) {
|
|
|
197
197
|
}
|
|
198
198
|
// remove user errors
|
|
199
199
|
cron.schedule('0 0 * * *', () => __awaiter(this, void 0, void 0, function* () {
|
|
200
|
-
|
|
201
|
-
|
|
200
|
+
if (integrationLogic.userErrorLifetimeDays) {
|
|
201
|
+
const date = (0, util_1.getPreviousDate)(integrationLogic.userErrorLifetimeDays);
|
|
202
|
+
yield (0, storage_1.getStorage)().deleteAllUsersErrorsOlderThan(`${date.getTime()}`);
|
|
203
|
+
}
|
|
202
204
|
}));
|
|
203
205
|
if (integrationLogic.webhooks) {
|
|
204
206
|
app.post(`${integrationLogic.webhooks.crowdinWebhookUrl
|
|
@@ -6,7 +6,7 @@ export interface IntegrationLogic extends ModuleKey {
|
|
|
6
6
|
/**
|
|
7
7
|
* Customize your app login form
|
|
8
8
|
*/
|
|
9
|
-
loginForm
|
|
9
|
+
loginForm?: LoginForm;
|
|
10
10
|
/**
|
|
11
11
|
* Define login process via OAuth2 protocol
|
|
12
12
|
*/
|
|
@@ -162,7 +162,7 @@ export interface IntegrationLogic extends ModuleKey {
|
|
|
162
162
|
/**
|
|
163
163
|
* The duration for storing user errors, default is 30 days.
|
|
164
164
|
*/
|
|
165
|
-
userErrorLifetimeDays
|
|
165
|
+
userErrorLifetimeDays?: number;
|
|
166
166
|
/**
|
|
167
167
|
* When true, folder filtering during automatic translation sync is bypassed, and the file tree is returned unchanged.
|
|
168
168
|
*/
|
package/out/util/logger.js
CHANGED
|
@@ -192,7 +192,9 @@ class AppModuleError extends Error {
|
|
|
192
192
|
super(typeof error === 'string' ? error : error.message);
|
|
193
193
|
this.isAxiosError = false;
|
|
194
194
|
this.name = 'AppModuleError';
|
|
195
|
-
|
|
195
|
+
if (typeof error !== 'string' && error.stack) {
|
|
196
|
+
this.stack = error.stack;
|
|
197
|
+
}
|
|
196
198
|
this.appData = data;
|
|
197
199
|
if (typeof error !== 'string') {
|
|
198
200
|
this.data = Object.assign({}, error);
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
integration-one-level-fetching="true"
|
|
72
72
|
{{/if}}
|
|
73
73
|
{{#if integrationSearchListener}}
|
|
74
|
-
allow-filter-change-listener="true"
|
|
74
|
+
allow-integration-filter-change-listener="true"
|
|
75
75
|
{{/if}}
|
|
76
76
|
{{#if integrationPagination}}
|
|
77
77
|
integration-load-more-files="true"
|
|
@@ -166,6 +166,7 @@
|
|
|
166
166
|
<tr class="organization-invite">
|
|
167
167
|
<td>
|
|
168
168
|
<crowdin-p>Registration in Organization</crowdin-p>
|
|
169
|
+
<div class="permission-description"></div>
|
|
169
170
|
</td>
|
|
170
171
|
<td class="affected-users"></td>
|
|
171
172
|
<td class="status"></td>
|
|
@@ -1009,7 +1010,7 @@
|
|
|
1009
1010
|
if (organizationInvite) {
|
|
1010
1011
|
const organizationWillGrantElement = `<span class="badge badge-will-be-granted">Will Be Registered</span>`;
|
|
1011
1012
|
|
|
1012
|
-
|
|
1013
|
+
processUsersWhoWillBeInvitedToOrganization(organizationInvite, usersData, organizationWillGrantElement, grantedElement, notAvailableElement);
|
|
1013
1014
|
}
|
|
1014
1015
|
|
|
1015
1016
|
processUsersWhoWillBeInvited(projectInvite, usersData.usersWhoWillBeInvitedToProject, willGrantedElement, grantedElement);
|
|
@@ -1018,6 +1019,38 @@
|
|
|
1018
1019
|
processUsersWhoWillBeInvitedApplicationSettings(usersData, willGrantedElement, grantedElement, notAvailableElement);
|
|
1019
1020
|
}
|
|
1020
1021
|
|
|
1022
|
+
function processUsersWhoWillBeInvitedToOrganization(element, usersData, willGrantedElement, alreadyGrantedMessage, notAvailableElement) {
|
|
1023
|
+
const tooltip = element.querySelector('.status');
|
|
1024
|
+
const description = element.querySelector('.permission-description');
|
|
1025
|
+
const userList = element.querySelector('.affected-users');
|
|
1026
|
+
|
|
1027
|
+
description.classList.remove('text-warning');
|
|
1028
|
+
description.innerText = '';
|
|
1029
|
+
|
|
1030
|
+
const users = usersData.usersWhoWillBeInvitedToOrganization;
|
|
1031
|
+
|
|
1032
|
+
if (users.length) {
|
|
1033
|
+
if (usersData.inviteRestricted) {
|
|
1034
|
+
description.classList.add('text-warning');
|
|
1035
|
+
description.innerText = 'New user invitations are restricted to administrators.';
|
|
1036
|
+
|
|
1037
|
+
tooltip.innerHTML = notAvailableElement;
|
|
1038
|
+
} else {
|
|
1039
|
+
tooltip.innerHTML = willGrantedElement;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
let affectedUsers = '<ul>';
|
|
1043
|
+
for (const user of users) {
|
|
1044
|
+
affectedUsers += `<li><crowdin-p>${sanitizeHTML(user.name)}</crowdin-p></li>`;
|
|
1045
|
+
}
|
|
1046
|
+
affectedUsers += '</ul>';
|
|
1047
|
+
userList.innerHTML = affectedUsers;
|
|
1048
|
+
} else {
|
|
1049
|
+
tooltip.innerHTML = alreadyGrantedMessage;
|
|
1050
|
+
userList.innerHTML = alreadyGrantedMessage;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1021
1054
|
function processUsersWhoWillBeInvited(element, users, grantMessage, alreadyGrantedMessage) {
|
|
1022
1055
|
const tooltip = element.querySelector('.status');
|
|
1023
1056
|
const userList = element.querySelector('.affected-users');
|
package/package.json
CHANGED