Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/protocols' into RocketChat#755
Browse files Browse the repository at this point in the history
# Conflicts:
#	packages/rocketchat-i18n/i18n/ru.i18n.json
  • Loading branch information
Scarvis committed Nov 9, 2020
2 parents 0919f43 + a7e578c commit 18f6313
Show file tree
Hide file tree
Showing 20 changed files with 862 additions and 46 deletions.
28 changes: 28 additions & 0 deletions app/api/server/v1/protocols.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { API } from '../api';
import { findProtocols, findProtocol } from '../lib/protocols';
import { hasPermission } from '../../../authorization';
import { Users } from '../../../models';

API.v1.addRoute('protocols.list', { authRequired: true }, {
get() {
Expand Down Expand Up @@ -28,3 +29,30 @@ API.v1.addRoute('protocols.findOne', { authRequired: true }, {
return API.v1.success(Promise.await(findProtocol(query._id)));
},
});

API.v1.addRoute('protocols.participants', { authRequired: true }, {
get() {
if (!hasPermission(this.userId, 'manage-protocols')) {
return API.v1.unauthorized();
}

const { offset, count } = this.getPaginationItems();
const { sort, fields, query } = this.parseJsonQuery();

const protocol = Promise.await(findProtocol(query._id));

const users = Users.find({ _id: { $in: protocol.participants } }, {
sort: sort || { username: 1 },
skip: offset,
limit: count,
fields,
}).fetch();

return API.v1.success({
users,
count: users.length,
offset,
total: Users.find({ _id: { $in: protocol.participants } }).count(),
});
},
});
22 changes: 22 additions & 0 deletions app/api/server/v1/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { getURL } from '../../../utils';
import {
validateCustomFields,
saveUser,
saveParticipant,
saveCustomFieldsWithoutValidation,
checkUsernameAvailability,
setUserAvatar,
Expand Down Expand Up @@ -115,6 +116,27 @@ API.v1.addRoute('users.deleteOwnAccount', { authRequired: true }, {
},
});

API.v1.addRoute('users.createParticipant', { authRequired: true }, {
post() {
check(this.bodyParams, {
email: String,
name: String,
surname: String,
patronymic: String,
organization: Match.Maybe(String),
position: Match.Maybe(String),
phone: Match.Maybe(String),
workingGroup: Match.Maybe(String),
});

const newUserId = saveParticipant(this.userId, this.bodyParams);

const { fields } = this.parseJsonQuery();

return API.v1.success({ user: Users.findOneById(newUserId, { fields }) });
},
});

API.v1.addRoute('users.getAvatar', { authRequired: false }, {
get() {
const user = this.getUserFromParams();
Expand Down
1 change: 1 addition & 0 deletions app/lib/server/functions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export { relinquishRoomOwnerships } from './relinquishRoomOwnerships';
export { saveCustomFields } from './saveCustomFields';
export { saveCustomFieldsWithoutValidation } from './saveCustomFieldsWithoutValidation';
export { saveUser } from './saveUser';
export { saveParticipant } from './saveUser';
export { sendMessage } from './sendMessage';
export { setEmail } from './setEmail';
export { setRealName, _setRealName } from './setRealName';
Expand Down
176 changes: 176 additions & 0 deletions app/lib/server/functions/saveUser.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,79 @@ function validateUserEditing(userId, userData) {
}
}

function validateParticipantData(userId, userData) {
if (userData._id && userId !== userData._id && !hasPermission(userId, 'edit-other-user-info')) {
throw new Meteor.Error('error-action-not-allowed', 'Editing user is not allowed', {
method: 'insertOrUpdateUser',
action: 'Editing_user',
});
}

if (!userData._id && !hasPermission(userId, 'create-user')) {
throw new Meteor.Error('error-action-not-allowed', 'Adding user is not allowed', {
method: 'insertOrUpdateUser',
action: 'Adding_user',
});
}

// if (userData.roles && _.indexOf(userData.roles, 'admin') >= 0 && !hasPermission(userId, 'assign-admin-role')) {
// throw new Meteor.Error('error-action-not-allowed', 'Assigning admin is not allowed', {
// method: 'insertOrUpdateUser',
// action: 'Assign_admin',
// });
// }

if (!userData._id && !s.trim(userData.username)) {
throw new Meteor.Error('error-the-field-is-required', 'The field Username is required', {
method: 'insertOrUpdateUser',
field: 'Username',
});
}

// if (userData.roles) {
// validateUserRoles(userId, userData);
// }

// let nameValidation;

// try {
// nameValidation = new RegExp(`^${ settings.get('UTF8_Names_Validation') }$`);
// } catch (e) {
// nameValidation = new RegExp('^[0-9a-zA-Z-_.]+$');
// }

// if (userData.username && !nameValidation.test(userData.username)) {
// throw new Meteor.Error('error-input-is-not-a-valid-field', `${ _.escape(userData.username) } is not a valid username`, {
// method: 'insertOrUpdateUser',
// input: userData.username,
// field: 'Username',
// });
// }

// if (!userData._id && !userData.password && !userData.setRandomPassword) {
// throw new Meteor.Error('error-the-field-is-required', 'The field Password is required', {
// method: 'insertOrUpdateUser',
// field: 'Password',
// });
// }

if (!userData._id) {
if (!checkUsernameAvailability(userData.username)) {
throw new Meteor.Error('error-field-unavailable', `${ _.escape(userData.username) } is already in use :(`, {
method: 'insertOrUpdateUser',
field: userData.username,
});
}

if (userData.email && !checkEmailAvailability(userData.email)) {
throw new Meteor.Error('error-field-unavailable', `${ _.escape(userData.email) } is already in use :(`, {
method: 'insertOrUpdateUser',
field: userData.email,
});
}
}
}

const handleOrganization = (updateUser, organization) => {
if (organization) {
if (organization.trim()) {
Expand Down Expand Up @@ -446,3 +519,106 @@ export const saveUser = function(userId, userData) {

return true;
};

const getLoginExample = (surname, name, patronymic) => {
if (!surname || !name) {
return '';
}

const translit = (str) => {
const L = {
'А':'A','а':'a','Б':'B','б':'b','В':'V','в':'v','Г':'G','г':'g',
'Д':'D','д':'d','Е':'E','е':'e','Ё':'Yo','ё':'yo','Ж':'Zh','ж':'zh',
'З':'Z','з':'z','И':'I','и':'i','Й':'Y','й':'y','К':'K','к':'k',
'Л':'L','л':'l','М':'M','м':'m','Н':'N','н':'n','О':'O','о':'o',
'П':'P','п':'p','Р':'R','р':'r','С':'S','с':'s','Т':'T','т':'t',
'У':'U','у':'u','Ф':'F','ф':'f','Х':'Kh','х':'kh','Ц':'Ts','ц':'ts',
'Ч':'Ch','ч':'ch','Ш':'Sh','ш':'sh','Щ':'Sch','щ':'sch','Ъ':'"','ъ':'"',
'Ы':'Y','ы':'y','Ь':"'",'ь':"'",'Э':'E','э':'e','Ю':'Yu','ю':'yu',
'Я':'Ya','я':'ya'
};
let reg = '';
for (const kr in L) {
reg += kr;
}
reg = new RegExp('[' + reg + ']', 'g');
const translate = function(a) {
return a in L ? L[a] : a;
};
return str.replace(reg, translate).toLowerCase();
};

const capitalizeFirstLetter = (translitString) => {
return translitString.charAt(0).toUpperCase() + translitString.slice(1);
};

const patron = patronymic ? patronymic[0] : '';
return capitalizeFirstLetter(translit(surname + name[0] + patron));
};

export const saveParticipant = function(userId, userData) {
userData.username = getLoginExample(userData.surname, userData.name, userData.patronymic);

validateParticipantData(userId, userData);

userData.password = passwordPolicy.generatePassword();
userData.requirePasswordChange = true;

delete userData.setRandomPassword;

if (!userData._id) {
validateEmailDomain(userData.email);

// insert user
const createUser = {
username: userData.username,
password: userData.password,
joinDefaultChannels: false,
};
if (userData.email) {
createUser.email = userData.email;
}

const _id = Accounts.createUser(createUser);

const updateUser = {
$set: {
// roles: userData.roles || ['user'],
surname: userData.surname,
...typeof userData.name !== 'undefined' && { name: userData.name },
...typeof userData.patronymic !== 'undefined' && { patronymic: userData.patronymic },
settings: userData.settings || {},
participant: true,
},
};

if (typeof userData.requirePasswordChange !== 'undefined') {
updateUser.$set.requirePasswordChange = userData.requirePasswordChange;
}

updateUser.$set['emails.0.verified'] = false;

handleOrganization(updateUser, userData.organization);
handlePosition(updateUser, userData.position);
handlePhone(updateUser, userData.phone);
handleWorkingGroup(updateUser, userData.workingGroup);

Meteor.users.update({ _id }, updateUser);

userData._id = _id;

if (settings.get('Accounts_SetDefaultAvatar') === true && userData.email) {
const gravatarUrl = Gravatar.imageUrl(userData.email, { default: '404', size: 200, secure: true });

try {
setUserAvatar(userData, gravatarUrl, '', 'url');
} catch (e) {
// Ignore this error for now, as it not being successful isn't bad
}
}

return _id;
}

return true;
};
16 changes: 16 additions & 0 deletions app/models/server/models/Protocols.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,22 @@ class Protocols extends Base {
return itemData._id;
}

addParticipant(protocolId, userId) {
return this.update({
_id: protocolId,
participants: { $ne: userId },
}, {
$addToSet: { participants: userId },
});
}

removeParticipantById(protocolId, userId) {
const data = this.findOne({ _id: protocolId });

if (data.participants) {
this.update({ _id: protocolId }, { $pull: { participants: userId }});
}
}
}

export default new Protocols();
17 changes: 15 additions & 2 deletions app/protocols/client/views/AddItem.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState, useCallback } from 'react';
import { Field, TextAreaInput, Button, InputBox, ButtonGroup, TextInput } from '@rocket.chat/fuselage';
import { Field, Button, InputBox, ButtonGroup, TextInput } from '@rocket.chat/fuselage';
import DatePicker, { registerLocale } from 'react-datepicker';
import ru from 'date-fns/locale/ru';
registerLocale('ru', ru);
Expand All @@ -10,6 +10,8 @@ import { useRouteParameter } from '../../../../client/contexts/RouterContext';
import { useMethod } from '../../../../client/contexts/ServerContext';
import { validateItemData, createItemData } from './lib';
import VerticalBar from '../../../../client/components/basic/VerticalBar';
import CKEditor from "@ckeditor/ckeditor5-react";
import ClassicEditor from "@ckeditor/ckeditor5-build-classic";

export function AddItem({ goToNew, close, onChange, ...props }) {
const t = useTranslation();
Expand Down Expand Up @@ -61,7 +63,18 @@ export function AddItem({ goToNew, close, onChange, ...props }) {
<Field>
<Field.Label>{t('Item_Name')}</Field.Label>
<Field.Row>
<TextAreaInput rows='10' multiple value={name} onChange={(e) => setName(e.currentTarget.value)} placeholder={t('Item_Name')} />
<CKEditor
editor={ ClassicEditor }
config={ {
language: 'ru',
toolbar: [ 'bold', 'italic', 'link' ]
} }
data={name}
onChange={ (event, editor) => {
const data = editor.getData();
setName(data);
} }
/>
</Field.Row>
</Field>
<Field>
Expand Down
17 changes: 15 additions & 2 deletions app/protocols/client/views/AddSection.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import React, { useState, useCallback } from 'react';
import { Field, TextAreaInput, Button, InputBox, ButtonGroup, TextInput } from '@rocket.chat/fuselage';
import { Field, Button, InputBox, ButtonGroup } from '@rocket.chat/fuselage';

import { useToastMessageDispatch } from '../../../../client/contexts/ToastMessagesContext';
import { useTranslation } from '../../../../client/contexts/TranslationContext';
import { useRouteParameter } from '../../../../client/contexts/RouterContext';
import { useMethod } from '../../../../client/contexts/ServerContext';
import { validateSectionData, createSectionData } from './lib';
import VerticalBar from '../../../../client/components/basic/VerticalBar';
import CKEditor from "@ckeditor/ckeditor5-react";
import ClassicEditor from "@ckeditor/ckeditor5-build-classic";

export function AddSection({ goToNew, close, onChange, ...props }) {
const t = useTranslation();
Expand Down Expand Up @@ -53,7 +55,18 @@ export function AddSection({ goToNew, close, onChange, ...props }) {
<Field>
<Field.Label>{t('Section_Name')}</Field.Label>
<Field.Row>
<TextAreaInput value={name} onChange={(e) => setName(e.currentTarget.value)} placeholder={t('Section_Name')} />
<CKEditor
editor={ ClassicEditor }
config={ {
language: 'ru',
toolbar: [ 'bold', 'italic', 'link' ]
} }
data={name}
onChange={ (event, editor) => {
const data = editor.getData();
setName(data);
} }
/>
</Field.Row>
</Field>
<Field>
Expand Down
Loading

0 comments on commit 18f6313

Please sign in to comment.